
/*
====================================================

** ZippyFindReplace **
Shipscript's Find/Replace Module
for Gecko and Explorer input fields. 
12 Aug 2008
copyright 2008 www.isdntek.com

====================================================
*/


/*
Opera issues:
-Cursor to bottom affects scrollers - scrolls back up about 11% - is not related to any scripts...
-appears to be associated with cursor location and file size fields. Lost focus? Need scrollto?
-   was a problem with CSS location of elements written above...moved elements out of td
-Find next doesn't appear to affect scrollers, but can't tell due to other issues.
-ScrollTop doesn't work in Opera.
-enter-key on palette color activates first button instead, and enters results in text. 
-     resolved with new key detect and dummy first "button".
*/


/* ======================== */
/*   Replace Selection      */
/* ======================== */

var editorUndocode;
var editorUndoMethod;
var editorIEmarker;
var rng_sel, rng_vscroll, rng_startPos, rng_endPos, rng_selSaver;



function insertAtCursor(editorField, startValue, endValue, method) {   // called from app
	// method= "replace" the selection else wrap the selection 
	var innerValue, seltext;
	editorUndocode=editorField.value;
	editorUndoMethod="";

	seltext=getEditorSelection(editorField)
	if (seltext=="errorinselection"){return}
	//if (seltext.length==0){editorUndoMethod="all"} // temporary solution to cursor management

	if (editorField.selectionStart >= 0) {//MOZILLA/NETSCAPE/GECKO/Opera support 
		if (startValue.indexOf("CMD**")==0) {
			eval(startValue.split("**")[1]);
			if(innerValue=='abortsearch'){return};
			}
		else if (method=="replace"){innerValue=startValue + endValue}
		else {innerValue=startValue +seltext+ endValue}
	} 

	else if (document.selection) {//IE support
		if (startValue.indexOf("CMD**")==0) {
			eval(startValue.split("**")[1]);
			if(innerValue=='abortsearch'){return};
			}
		else if (method=="replace"){innerValue=startValue + endValue;}
		else {innerValue=startValue +seltext+ endValue;}
	}
	else 	{
		if (method=="replace"){editorField.value=startValue + endValue;}
		else {editorField.value=editorField.value + startValue + endValue;}
	}
	setEditorSelection(editorField,innerValue);
	setTimeout('setEditorUndoState(document.getElementById("'+editorField.id+'"),true)',100); //opera needs a moment to fire onchange event
}

function getEditorSelection(editorField){ // internal function
	if (editorField.selectionStart >= 0) {	//MOZILLA/NETSCAPE/GECKO/Opera support
		rng_vscroll=editorField.scrollTop;
		rng_startPos = editorField.selectionStart;
		rng_endPos = editorField.selectionEnd;
		seltext=editorField.value.substring(rng_startPos,rng_endPos);
		rng_selSaver=seltext;
		return seltext;
	} 
	else if (document.selection) {	//IE support
		editorField.focus();

		/* *****take this out for TagBot which calls from outside **** */
		//restoreIEselection(editorField) // this is here to allow color palette and other non-button clicks

		rng_sel = document.selection.createRange();
		/* *****take out alert for TagBot which calls from outside**** */
		if (rng_sel.parentElement() != editorField){ /*alert('outside text field');*/ return ("errorinselection") }
		seltext=rng_sel.text;
		rng_selSaver=seltext
		return seltext;
	}
}

function setEditorSelection(editorField,innerValue){  //internal function
	if (editorField.selectionStart >= 0) {	//MOZILLA/NETSCAPE/GECKO/Opera support
		var endSegment=editorField.value.substring(rng_endPos, editorField.value.length)
		editorField.value = editorField.value.substring(0, rng_startPos)
		  + innerValue
		  + endSegment;
		editorField.focus();  // reacquire focus
		  //editorField.selectionEnd=rng_startPos+innerValue.length; 
		  //opera length is CRLF rather than just LF, so innerValue changes size after insertion.
		  //opera doesn't seem to update selectionEnd (or start) when shrinking a selection to zero.
		  //opera needs to encompass at least one character in order to undo properly.
		editorField.selectionStart=rng_startPos;
		editorField.selectionEnd=editorField.selectionStart+1; // this is a gimick to help opera a little bit.
		editorField.selectionEnd=editorField.value.length-endSegment.length;
   		//editorField.setSelectionRange(start,length) // sample
		editorField.scrollTop = rng_vscroll;
	} 
	else if (document.selection) {		//IE support
	  	rng_sel.text = innerValue;
	  	editorField.blur(); 		// lose IE focus to trigger onchange event for undo
	  	editorField.focus();  		// reset selection - cursor is at end of substitution
		rng_sel.moveStart("character", -lengthIE(innerValue))
		rng_sel.select()
		editorIEmarker = rng_sel.getBookmark()
	}

}


/* ======================== */
/*    Find/Replace 	    */
/* ======================== */

function editorFindReplace(editorField, findtext, reptext, which, action, selCase){  // called from app
	// seqential find/replace starting at cursor
	// which is "find" or "replace"
	// action can be "more" or "stop"
	// selCase is true/false
	

	if (editorField.selectionStart >= 0){
		switch (which){
		case "find"   : editorFindNextGecko(   editorField, findtext, reptext, action, selCase); break;
		case "replace": editorReplaceNextGecko(editorField, findtext, reptext, action, selCase); break;
		}
	}
	else if (document.selection){
		switch (which){
		case "find"   : editorFindNextIE(   editorField, findtext, reptext, action, selCase); break;
		case "replace": editorReplaceNextIE(editorField, findtext, reptext, action, selCase); break;
		}
	}
}


function editorFindNextGecko(editorField, findtext, reptext, action, selCase){  // internal function
	//--get current location or selection
	rng_startPos = editorField.selectionStart;
	rng_endPos = editorField.selectionEnd;
	seltext=editorField.value.substring(rng_startPos,rng_endPos);

	rng_endPos=editorField.value.length;
	//--if we are already sitting on a find/replace text, then move on to next;
	if (selCase==true){ if ( seltext==findtext || seltext==reptext ){rng_startPos=rng_startPos+seltext.length} }
	else {if ( seltext.toLowerCase()==findtext.toLowerCase() || seltext.toLowerCase()==reptext.toLowerCase() ){rng_startPos=rng_startPos+seltext.length}}

	//--start from the cursor location or after the last found text and search to end
	seltext=editorField.value.substring(rng_startPos,rng_endPos);

	if (selCase==true){var a=seltext.indexOf(findtext)}	
	else { var a=seltext.toLowerCase().indexOf(findtext.toLowerCase()) };	// find the start text 
	if (a<0){scrollGeckoWindow(editorField); alert ('finished'); return};

	var b=findtext.length;
	rng_startPos= rng_startPos+a
	rng_endPos  = rng_startPos+b

	editorField.selectionEnd=rng_endPos;
	editorField.selectionStart=rng_startPos;
	editorField.focus();
	scrollGeckoWindow(editorField)
}

function scrollGeckoWindow(editorField){   // internal function
	// get scroller size
	editorField.scrollTop=editorField.value.length
	var maxScroll=editorField.scrollTop  
	var scrollView=editorField.offsetHeight

	// scroll the window to display selected text
	relPos=rng_startPos/(editorField.value.length+1)
	os=scrollView*.20
	if (relPos<.5) {offset=-(os-relPos*2*os) }
	else if (relPos>=.5){offset=+(relPos*2*os-os)}
	editorField.scrollTop = (maxScroll* rng_startPos/(editorField.value.length+1))+offset

}


function editorReplaceNextGecko(editorField, findtext, reptext, action, selCase){  // internal function
	seltext=getEditorSelection(editorField)
	if (seltext.length>0){ // skips cursor if no text selected, as when starting from cursor. 
	    editorField.value = editorField.value.substring(0, rng_startPos)
		  + reptext
		  + editorField.value.substring(rng_endPos, editorField.value.length);
	    editorField.focus();  // reacquire focus
	    editorField.selectionStart=rng_startPos;
	    editorField.selectionEnd=rng_startPos+reptext.length;
	    setEditorUndoState(editorField,false);
		// alternatively, use insertAtCursor to make undo available, 
		// but we would need to back up position and length in the undo
	}
	if (action=='more'){ editorFindNextGecko(editorField, findtext, reptext,'', selCase) }
}


function editorFindNextIE(editorField, findtext, reptext, action, selCase){  // internal function
	// decide how to advance the cursor.
	// if there is no replacement, add the size of the findtext
	// if there is to be a replacement, then add the size of the replacement text instead.
	// but in all cases, start again at the cursor, in case the user clicked into the field.
	// therefore, detect the cursor before the next find.
	editorField.focus();
    	var rng  = document.selection.createRange(); 	// find the cursor and highlighted text in the text box  
	if (rng.parentElement()!= editorField){return}

  	//--if we are already sitting on a find/replace text, then move on to next;
	if (selCase==true){ if ( rng.text==findtext || rng.text==reptext ){var increment=true} else {var increment=false}}
	else{ if ( rng.text.toLowerCase()==findtext.toLowerCase() || rng.text.toLowerCase()==reptext.toLowerCase() ){var increment=true} else {var increment=false} }

	// carriage returns in the field present a counting problem for this method 
	// the rng.text will include CRLF as two characters, while the rng.move will count them as one character
	var rngsize = lengthIE(rng.text) ;

	rng=editorField.createTextRange();  // encompass all text

	// start from the cursor location or after the last found text
	var start = increment==true ? rngsize+Math.max(getIEcursor(editorField),0) : Math.max(getIEcursor(editorField),0);
	var o=rng.moveStart("character",start);				// begin looking from the cursor 

	if (selCase==true){var a=rng.text.replace(/\r\n/gi,"\r").indexOf(findtext) }	
	else {var a=rng.text.toLowerCase().replace(/\r\n/gi,"\r").indexOf(findtext.toLowerCase()) } ;	// find the start text 
	if (a<0){alert ('finished'); return};

	var b=findtext.length;
	var d=reptext.length;

	var s=rng.moveStart("character",a);	// these moves are relative to prior position, and count CRLF as one 
	var e=rng.moveEnd("character", (b) - lengthIE(rng.text));
	rng.select();
	editorIEmarker = rng.getBookmark();	// save this for lost focus when editing find/replace values 
}


function editorReplaceNextIE(editorField, findtext, reptext, action, selCase){  // internal function
	editorField.focus();
	var rng=document.selection.createRange();
	if (rng.parentElement() != editorField){return} // keep the replacement within the text box 
	
	if (rng.text.length>0){ // skips cursor if no text selected 
	  rng.text=reptext
	  var o=rng.moveStart("character",-reptext.length)
	  rng.select()
	  setEditorUndoState(editorField,false);
	}

	editorIEmarker = rng.getBookmark()  // save this for lost focus when editing find/replace values 

	// call the next find after replacement
	if (action=='more'){ editorFindNextIE(editorField, findtext, reptext,'',selCase) }
}




/* ======================== */
/*    Cursor Management     */
/* ======================== */

function restoreIEselection(editorField){  // internal function
	editorField.focus();  //at startup, this may select all
	if(editorIEmarker){
		var rng=editorField.createTextRange();
		u=rng.moveToBookmark(editorIEmarker)
		rng.select();
	}
}

function setIEBookmark(editorField){  // have allowed direct access to get around color replacement issue
	editorField.focus();  
	if (editorField.selectionStart){return}
	// gecko could enter if above test fails on fresh page load
	try { 
	var rng  = document.selection.createRange();
	editorIEmarker = rng.getBookmark();
	} catch(e) {return}
}

function getIEcursor(editorField){  // internal function
	//IE only
	// Clicking a button to invoke this routine automatically loses focus,  
	// and without any text hightlighted, the cursor can't be located. Thus, set focus.

	var savedSel;
	var loc;
	editorField.focus();
    	var rng  = document.selection.createRange(); 		// find the cursor and highlighted text in the text box 
	if (rng.parentElement() == editorField){
	//alert([rng.offsetLeft,rng.text])

	    textSize = lengthIE(rng.text);			// measure size of selection
	    savedSel = rng.getBookmark(); 			// bookmark the selection and initial cursor location
	    rng=editorField.createTextRange();			// select the entire textbox contents 
	//alert([rng.parentElement().tagName,rng.text])

	    rng.moveToBookmark(savedSel);			// apply the saved selection to the textbox - this can be outside textbox
	// if parent is no longer editorField then add a character to editorField and try again.
	//    if (rng.parentElement() != editorField){
	//	editorField.value=editorField.value+" ";	// will the bookmark hold up?
	//    	rng=editorField.createTextRange();		// select the entire textbox contents 
	//    	rng.moveToBookmark(savedSel);		
	//    } 	
	//alert(rng.parentElement().tagName)
	    a=rng.moveStart("character", -lengthIE(editorField.value) )  // expand range back to origin
	//alert(a)
	    expandedSize=lengthIE(rng.text);			// measure the expanded range
	    loc=expandedSize-textSize;				// calculate the cursor location
 	    rng.moveToBookmark(savedSel);			// reapply the initial selection after measurements
	    rng.select(); //removing this helps and hurts	// highlight the initial selection

	    editorIEmarker = rng.getBookmark();  		// save for lost focus when editing find/replace values 
	    return loc;

	} 

	return -1;

	// IE: a trailing return at the end of the field isn't captured. Acts strange. 
	// Can't keyboard at end of field. Mouseup/keyup triggers this routine and loses cursor. 
	// Replacement functions thereafter post outside textarea.
	// Drop list items won't paste to empty page.
	// Is it related to cursor management to get location?
	// This routine is called by cursor locator and by FindNext. Because FindNext hightlights an item, only CurLoc is a problem.

	//http://msdn.microsoft.com/en-us/library/ms536422(VS.85).aspx 	bFound = TextRange.findText(sText [, iSearchScope] [, iFlags])
	//http://msdn.microsoft.com/en-us/library/ms536630(VS.85).aspx 	TextRange.moveToElementText(oElement)
	//http://msdn.microsoft.com/en-us/library/ms535872.aspx		TextRange Object (read the methods, like setEndPoint Method)
	// look at collapse and expand in lieu of moveStart or End
}



function editorSelectAll(editorField){  // called from app
	editorField.select();
	if (editorField.selectionStart >= 0) { }//GECKO and trap Opera too
	else if (document.selection){ //IE
		var rng_sel = editorField.createTextRange();
	}
}

function lengthIE(term){ // internal function
	// reduce IE lengths to match gecko lengths
	return term.replace(/\r\n/gi,'\r').length 
	// why did we choose \r instead of \n ?
	// originally only for IE functions but had to add to gecko function for opera
} 

function fieldCursorLoc(editorField){   // called from app
	if (editorField.selectionStart >= 0) { //GECKO
		tloc=editorField.selectionStart;
		tloc=lengthIE(editorField.value.substring(0,editorField.selectionStart)); //for Opera
	} 
	else if (document.selection) {  //IE
		return "" ; // we have disabled this to prevent some usability and editor issues in IE.
		tloc=Math.max(getIEcursor(editorField),0)
	}
	return tloc;
}

function fieldSelectionSize(editorField){   // called from app
	var tsize=0;
	if (editorField.selectionStart >= 0) { //GECKO and Opera
		//tsize=editorField.selectionEnd-editorField.selectionStart);
		tsize=lengthIE(editorField.value.substring(editorField.selectionStart,editorField.selectionEnd));
	} 
	else if (document.selection) {  //IE
	   editorIEmarker=null; // test this on mousedown
		editorField.focus();
		var sel = document.selection.createRange();
		tsize=lengthIE(sel.text);
	}	
	return tsize;
}

function fieldCodeSize(editorField){   // called from app
        //IE & Opera count CRLF (as 2) and Gecko sees only LF (as 1), so textarea counts will differ in browsers
	return lengthIE(editorField.value)
}


/* ======================== */
/*    Undo		    */
/* ======================== */

function editorUndo(editorField){    // called from app
	// if no text was selected and instead inserted at cursor
	// then undo has nothing to work with.
	// so changed undo mode if no selection.
	if (editorUndoMethod=="all"){ // this isn't used
	  // for find/replace. The selected text loses focus
	  // cursor goes to end of file FF or selects all IE
	  editorField.value=editorUndocode; 
	  editorIEmarker=null
	}
	else { //undo the html selections only		
	  insertAtCursor(editorField, rng_selSaver, "", "replace")
	}
	setEditorUndoState(editorField,false);
}

function setEditorUndoState(editorField,tf){ 
	// sends a boolean value to application
	// stating whether an undo is available.
	// Application should set a flag or button state for user.
	try {editorUndoState(editorField,tf) } catch(e) {return false}
}





/*
====================================================
functions to add to application:

function editorUndoState(editorField,tf){ 
	// receives a boolean value from find/replace module
	// stating whether an undo is available
	// Application should set a flag or button state for user.
}

====================================================

*/

/* Notes for scrolling browser window...
    * window.pageYOffset is used by Firefox and other Mozilla browsers, Safari, Konqueror, and Opera.
    * document.documentElement.scrollTop is used by IE 6 in standards-compliant mode.
    * document.body.scrollTop is used by IE 5, and IE 6 in "Quirks" mode. 
*/
/* ---- end --- */