// ==UserScript==
// @name		Ultimate Search Highlighter
// @version		1.44
// @description	Highlights search terms on page, with summaries of results, and search engine integration.
// @include		*
// ==/UserScript==

/* USAGE
 * As a button (to be used in conjuction with an existing field):
 *	1)	Enter the following lines into the address bar
 *			opera:/button/Search,"javascript:opera.USH.run(opera.USH.check='%s','newSearch');",,"Highlight" + Show hidden popup menu & Search,"javascript:opera.USH.run(opera.USH.check='USHRegExp %s','newSearch');",,"Highlight"
 *	2)	Drag the tab onto a toolbar and answer yes when asked if you want to add the button
 *
 * As a search field:
 *	1)	Enter the following lines into the address bar
 *			opera:/Edit/Search,"javascript:opera.USH.run(opera.USH.check='%s','newSearch');",,"Highlight"
 *	2)	Drag the tab onto a toolbar and answer yes when asked if you want to add the field
 *
 * From the context menu (see http://operawiki.info/EditingINIFiles for details on how to edit the menu file)
 *	1)	Add the following line to the [Hotclick Popup Menu] section
 *			Item, "Highlight"=Go to page,"javascript:opera.USH.run(window.getSelection().toString(),'new');",1,,"Blank"
 *
 * As a keyboard shortcut (best method)
 *	1)	Add the following as an action
 *			Go to page,"javascript:opera.USH.run(null,'newBlank');",1
 *
 * Visit the following thread if you have any questions/feedback
 * http://my.opera.com/community/forums/findpost.pl?id=1648805
 */

(opera.USH = new function() {
	var preferences = {
		runOnLoad: true,						// Run script on page load based on search engine referrer
		autoHideDelay: 2000,				// Time in ms after which the toolbar will hide if not in use
		noAutoHide: false,					// Disables toolbar auto-hiding
		highlightOnLoad: true,			// Highlight page automatically or just show toolbar
		toolbarHiddenOnLoad: false,	// Toolbar is initially hidden
		toolbarAtBottom: false,			// Show the toolbar at the bottom of the screen
		stripPluses: false,					// Strip pluses from search string. For use when using Opera's search field
		useStopwords: false,				// Highlight words ignored by search engines. Only used for search engine highlights
		useCookies: true,						// Use cookies to store searches
		searchHiddenText: true,			// Searches within text that is not visible to the user
		embedStyle: true,						// Embed CSS via javascript. False if using external CSS
		keyShortcuts: ['0)',				// The key for the 'Options' button
			'-_',												// The key for the 'New' button
			'=+',												// The key for the 'Toggle' button
			'\\|',											// The key for the 'Close' button
			'9(',												// The key that toggles the visibility of the toolbar
			'`'],												// The toggle key. Enables or disables the other shortcuts. Search terms are 1-8
		usePunctuation: false,			// Allow matching of search terms regardless of punctuation
		wholeWordsOnly: true,				// Only highlight whole words
		matchCase: false						// Highlights are case sensitive
	},
	colours = ['#ffff66','#A0FFFF','#99ff99','#ff9999','#ff66ff','#FF7F50','#00FF00','#7FFF00','#00BFFF','#FF00FF','#FFD700','#CD5C5C','#C0C0C0','#B0C4DE','#808000','#FFA500','#ADD8E6'],
	searchEngines = [null
		,[1,,,'##(.+)$']
		,[2,'\\w+\\.+google\\.(?:[^\\.]+|[^\\.]{2,3}\\.[^\\.]{2})',,'[&?]q=([^&]+)']
		,[2,'search\\.live\\.com',,'[&?]q=([^&]+)']
		,[2,'search\\.yahoo\\.com',,'[&?]p=([^&]+)']
		,[2,'(?:\\w+\\.)?a9\\.com',,'(?:\\w+\\.)?a9\\.com/([^&]+)']
		,[2,'en\\.wikipedia\\.org/wiki/Special:Search','','[&?]search=([^&]+)']
		,[2,'http://my\\.opera\\.com/community/forums/search\\.dml.*?',true,'[&?]term=([^&]+)']
		,[4,,,'UserJS-USH=(.*?)(?:;|(%3B)|$)']
	],
	strings = {
		_prompt: 'Highlight keywords:',
		_opts: 'Options',
		_new: 'New Highlight',
		_hide: 'Toggle Highlights',
		_close: 'Close',
		_goto: 'Goto next instance of',
		_nfound: 'not found',
		_error: 'Ultimate Search Highlighter:\nFailed to create RegExp. Check syntax\n',
		_usePunctuation: 'Match punctuation',
		_wholeWordsOnly: 'Match whole words only',
		_matchCase: 'Match case',
		_search: 'Searching: '
	},
	enabled = false,
	query = '',
	regExp = null,
	frameIndex = 0,
	frames = [null],
	merlin = opera.version() < 9.5,
	getDim = function(el) {
		var t = 0, l = 0, h = el.offsetHeight, w = el.offsetWidth;
		if( el.getBoundingClientRect ) { var d = el.getBoundingClientRect(); d.height = h; d.width = w; return d; }
		while( el ) { t += el.offsetTop; l += el.offsetLeft; el = el.offsetParent; }
		t = (t-=document.documentElement.scrollTop)<0?0:t;
		l = (l-=document.documentElement.scrollLeft)<0?0:l;
		return {top:t, left:l, bottom:h+t, right:w+l, height:h, width:w};
	},
	highlighter = {
		add: function() {
			if( !regExp ) { return; }

			var excElems = ['UserJS-USH-toolbar','UserJS-USH-highlight','head','applet','object','embed','param','script','style','frameset','frame','iframe','textarea','input','option','select','img','map'],
					textNodes = document.selectNodes('//*[text()][not(ancestor-or-self::*[local-name()="'+excElems.join('" or local-name()="')+'"])]/text()');
			if( !(toolbar.progress.control.max = textNodes.length) ) { return false; }

			toolbar.tBar.className = 'UserJS-USH-'+(preferences.toolbarAtBottom?'bottom':'top')+' UserJS-USH-progress';

			var hFind, hElem = document.createElementNS(resolver.xhtmlNS,'UserJS-USH-highlight'); hElem.tabIndex = 0; hElem.setAttribute('iID',iID);
			for( var i = 0, k, textNode, term; textNode = textNodes[i]; i++) {
				if( textNode.nodeType != 3 || (!preferences.searchHiddenText && (!textNode.parentNode || !textNode.parentNode.offsetHeight)) ) { continue; }
				toolbar.progress.control.value = i;
				while(hFind = regExp.exec(textNode.data)) {
					if( !hFind[0] ) { break; }
					for( k = 1; !hFind[k++]; );
					term = results.terms[k-=2];

					(hElem = hElem.cloneNode(false)).style.background = term[0].colour;
					textNode = textNode.splitText(hFind.index+hFind[0].length-hFind[k+1].length);
					textNode.deleteData(0,(hElem.text=hFind[k+1]).length);
					textNode.parentNode.insertBefore(hElem,textNode);

					hElem.setAttribute('term',k);
					hElem.setAttribute('count',term[1]);
					term[1] = ++term[0].total;
				}
			}
		},
		remove: function(toggle,idx) {
			var hNodes = document.selectNodes('//xhtml:UserJS-USH-highlight[@iID="'+iID+'"]'+(idx!==undefined?'[@term="'+idx+'"]':''),resolver);

			for (var i = 0, hNode, hPar; hNode = hNodes[i]; i++) {
				if(toggle) { hNode[(hNode.hasAttribute('hide')?'remove':'set')+'Attribute']('hide',''); }
				else { hPar = hNode.parentNode; hNode.removeNode(false); hPar.normalize(); }
			}
		},
		run: function() {
			USH.check = true;
			if( query ) {
				query = query.replace(/(^\s*)|(\s*$)/g,'');
				if( searchEngines[0]&2 || preferences.stripPluses ) {
					query = query.replace(/[+]/g,' ');
				}
			}

			if( enabled ) { this.remove(); }
			toolbar.create();
			if( !searchEngines[0] || preferences.highlightOnLoad ) {
				results.createRegExp();
				this.add();
				searchEngines[0] = null;
			}
			toolbar.update();
			preferences.highlightOnLoad = enabled = true;
			toolbar.resize();
			if( self == top ) { toolbar.tBar.focus(); }
			USH.check = false;
		}
	},
	resolver = {
		xhtmlNS: 'http://www.w3.org/1999/xhtml',
		lookupNamespaceURI: function() { return this.xhtmlNS; }
	},
	results = {
		terms: [],
		createRegExp: function () {
			if( !(regExp = query) ) { return; }
			saveVal('query',query);
			this.terms = [];

			var terms = [], termsArray = [], term, quote, tsplit, doRegExp = false, sText, punc = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g,
			wb = (preferences.wholeWordsOnly&&/[^\x20-\x7e]/.test(query))?/[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xa9\xab-\xb4\xb6-\xb9\xbb-\xbf\xd7\xf7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u02FF\u0374\u0375\u037E-\u0385\u0387\u03F6\u0482\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06E9\u06FD\u06FE\u0700-\u070F\u07F6-\u07F9\u0964\u0965\u0970\u09F2-\u09FA\u0AF1\u0B70\u0BF0-\u0BFA\u0CF1\u0CF2\u0DF4\u0E3F\u0E4F\u0E5A\u0E5B\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F85\u0FBE-\u0FC5\u0FC7-\u0FD1\u104A-\u104F\u10FB\u1360-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1735\u1736\u17B4\u17B5\u17D4-\u17D6\u17D8-\u17DB\u17F0-\u180A\u180E\u1940-\u1945\u19DE-\u19FF\u1A1E\u1A1F\u1B5A-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u2153-\u2182\u2190-\u2B23\u2CE5-\u2CFF\u2E00-\u3004\u3007-\u3029\u3030\u3036-\u303A\u303D-\u303F\u309B\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31CF\u3200-\u33FF\u4DC0-\u4DFF\uA490-\uA716\uA720\uA721\uA828-\uA82B\uA874-\uA877\uD800-\uF8FF\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE10-\uFE19\uFE30-\uFE6B\uFEFF-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFFD]/.source:'\\b';

			if( (term = query.indexOf('USHRegExp ')) == 0 ) {
				doRegExp = true;
				terms[0] = [query.substring(10),1];
			}

			if( !doRegExp ) {
				sText = query.split(/([\s+\-\|]*)"([^"]*)"/);
				for( var i = 0, k, j; (term = sText[i]) != null; i++ ) {
					k = i%3;
					if( !k && term ) {
						tsplit = ((searchEngines[0]&2 && !preferences.useStopwords)?
											term.replace(/(?:^|\s)(?:I|a|about|an|are|as|at|be|by|com|for|from|how|in|is|it|of|on|or|that|the|this|to|was|what|when|where|who|will|with|and|the|www|(?:(?:filetype|inurl|site|related)(?::.*?)))(?:$|\s)/gi,' '):
											term).split(/([^\s+\-\|]+)/);
						tsplit.pop();
						for( j = 0; (term = tsplit[j]) != null; j++ ) {
							if( !(j%2) ) {
								if( !term || !(/\s+.*-+$/).test(term) ) { terms[terms.length] = [tsplit[j+1],0]; } }
								else if( (/^-+$/).test(term) ) { terms[terms.length] = [terms[terms.length][0]+'-'+tsplit[j+1],0]; }
						}
					}
					else if( k==1 ) {
						if( !term || term[term.length-1] != '-' ) { terms[terms.length] = [sText[i+1],1]; }
					}
				}
			}

			for( i = 0; term = terms[i]; i++ ) {
				quote = term[1]; term = term[0];
				terms[i] = doRegExp?term:term.replace(punc,(quote||preferences.usePunctuation||term.length<2)?'\\s*\\$&\\s*':punc.source+'?');
				if( terms[' '+term] ) { terms.splice(i,1); i--; continue; }
				terms[' '+term] = true;
				this.terms[i] = [{ text: term, colour: colours[i%colours.length], total: 0 }, 0, -1];
			}

			term = (!doRegExp && preferences.wholeWordsOnly?'(?:^|'+wb+')(?:('+terms.join(')|(')+'))(?='+wb+'|$)':'('+terms.join(')|(')+')');
			try { regExp = new RegExp(term,preferences.matchCase?'':'i'); }
			catch(e) { alert(strings._error+term); regExp = null; }
		},
		handleEvent: function(e) {
			var el = e.target, idx = el.count, term = this.terms[idx];
			if( !term || !term[0].total ) { return }
			getSelection().removeAllRanges();
			clearTimeout(this.timer);

			if( e.ctrlKey ) { (--term[2] < 0)&&(term[2] = term[1]-1); }
			else if( ++term[2] >= term[1] ) { term[2] = 0; }
			el.lastChild.data = term[0].text+' ['+(term[2]+1)+'/'+term[0].total+']';

			if( this.activeElem ) { this.activeElem.removeAttribute('active'); }
			if( !(el = document.selectSingleNode('//xhtml:UserJS-USH-highlight[@iID="'+iID+'"][@term="'+idx+'"][@count="'+term[2]+'"]',resolver)) ) { return; }

			el.scrollIntoView(!preferences.toolbarAtBottom);
			var dim = getDim(el), tDim = toolbar.dim, y = dim.bottom+15, xT = tDim.left, x = dim.left-15;
			scrollBy((x<xT || (x=dim.right+15)>(xT=tDim.right))?x-xT:0,
			preferences.toolbarAtBottom?(y>tDim.top?tDim.height-tDim.bottom+y:0):((y=dim.top-15)<tDim.bottom?-tDim.height-tDim.top+y:0));

			(this.activeElem = el).setAttribute('active','');
			this.timer = setTimeout(function(el) { var r = document.createRange(); r.selectNodeContents(el); getSelection().addRange(r); results.timer = null; },500,el);
		},
		activeElem: null,
		timer: null
	},
	searchData = {
		hash: location.hash,
		referrer: document.referrer,
		cookie: document.cookie,
		assign: function(data) {
			if( data && (data = data.split('|')) ) {
				for( var i = 0, key, keys = ['hash','referrer','cookie']; key = keys[i]; i++ ) {
					this[key] = unescape(data[i])||this[key];
				}
			}
			for( var i = 1, sE, data, rE = new RegExp(); sE = searchEngines[i]; i++ ) {
				if( sE[1] && rE.compile('^'+sE[1]+'$').test(sE[2]?location:location.hostname) ) {
					this.referrer = this.cookie = '';
					if( searchEngines[0] = sE[0]&8 ) { query = sE[3]; return; }
					break;
				}
				switch( searchEngines[0] = sE[0] ) {
					case 1:
						data = this.hash;
						break;
					case 2:
						if( !preferences.runOnLoad || !(data = this.referrer) ||
						!(rE.compile('^'+(sE[2]?'':'https?:\\/\\/')+sE[1]).test(data)) ) { continue; }
						break;
					case 4:
						data = this.cookie;
				}

				rE.compile(sE[3]);
				if( data && (data = rE.exec(data)) ) {
					query = unescape(decodeURIComponent(data[1]));
					break;
				}
			}
		},
		toString: function() {
			return 'USH|searchData|'+escape(this.hash)+'|'+escape(this.referrer)+'|'+escape(this.cookie)+'|';
		}
	},
	toolbar = {
		mOver: null,
		styles: null,
		buttons: [],
		tBar: null,
		progress: null,
		optsBttn: null,
		optsMenu: null,
		input: null,
		newBttn: null,
		hideBttn: null,
		closeBttn: null,
		dim: {},
		hideTimer: null,
		handleEvent: function(e,state) {
			clearTimeout(this.hideTimer);
			if( e ) { switch( e.type ) {
				case 'click': this.tBar.focus(); break;
				case 'blur': if( results.timer ) { return; }
				case 'mouseout':
				return this.hideTimer = setTimeout(function(t,s){t.handleEvent(null,s);},preferences.autoHideDelay,this,false);
				default: state = true;
			}}
			this.tBar.style.visibility = ((state===undefined)?!!this.tBar.style.visibility:
			(this.optsBttn.hasAttribute('active')||document.activeElement==this.input||state))?'':'hidden';
		},
		close: function() {
			highlighter.remove();
			saveVal('query',query='');
			results.terms = [];
			this.update();
			if( this.styles ) { this.styles.removeNode(true); }
			this.tBar.removeNode(true);
			this.mOver.removeNode(true);
			enabled = false;
		},
		create: function() {
			var divEl = this.tBar, d = document, xhtmlNS = resolver.xhtmlNS;

			if( !divEl ) {
				(this.tBar = divEl = d.createElementNS(xhtmlNS,'UserJS-USH-toolbar')).tabIndex = 0;

				var i, term, terms, menuEl = divEl.appendChild(d.createElementNS(xhtmlNS,'UserJS-USH-menu'));

				function createButton(id,fn) {
					var el = d.createElementNS(xhtmlNS,'input'); el.type = 'button';
					el.className = 'UserJS-USH-'+id+'Bttn'; el.title = strings['_'+id]; el.setAttribute('form','');
					el.onclick = fn||function(e){ USH.run(e,this.className); }
					return toolbar[id+'Bttn'] = el;
				}

				this.progress = divEl.appendChild(d.createElementNS(xhtmlNS,'label'));
				this.progress.text = strings._search;
				this.progress.appendChild(d.createElementNS(xhtmlNS,'input')).type = 'range';

				var ul = this.optsMenu = d.createElementNS(xhtmlNS,'ul'), li = d.createElementNS(xhtmlNS,'li'),
				elem = li.appendChild(d.createElementNS(xhtmlNS,'label'));
				elem.appendChild(d.createElementNS(xhtmlNS,'input')).type = 'checkbox';
				elem.appendChild(d.createTextNode(''));

				for( i = 0, terms = ['usePunctuation','wholeWordsOnly','matchCase']; term = terms[i++]; ) {
					li = ul.appendChild(li.cloneNode(true));
					(elem = li.firstChild).lastChild.data = strings['_'+term];
					(elem = elem.firstChild).prefIdx = term; elem.checked = preferences[term];
					elem.onchange = function() { saveVal(this.prefIdx,this.checked); }
				}

				menuEl.appendChild(createButton('opts',function(e) {
					var active = this.hasAttribute('active');
					this[active?'removeAttribute':'setAttribute']('active','');
					toolbar.optsMenu.className = (!active&&!e.ctrlKey)?'UserJS-USH-selected':'';
				}));
				menuEl.appendChild(ul);

				(this.input = i = menuEl.appendChild(d.createElementNS(xhtmlNS,'input'))).className = 'UserJS-USH-input';
				i.setAttribute('form','');

				(this.mOver = d.createElementNS(xhtmlNS,'UserJS-USH-mouseover')).onclick = this;
				if( !preferences.noAutoHide ) { this.mOver.onmouseover = i.onfocus = i.onblur = divEl.onblur = divEl.onmouseout = divEl.onmouseover = this; }

				for( i = 0, terms = ['new','hide','close']; term = terms[i++]; ) { menuEl.appendChild(createButton(term)); }

				if( preferences.embedStyle || preferences.noAutoHide ) {
					(this.styles = d.createElementNS(xhtmlNS,"style")).text = (preferences.noAutoHide?'html::before { } html::after { }':'')+
					(preferences.embedStyle?'\
					UserJS-USH-toolbar, UserJS-USH-mouseover { position: fixed !important; left: 0 !important; right: 0 !important; min-width: 100% !important; font: 12px/18px normal Arial, sans-serif !important; }\
					UserJS-USH-toolbar { height: auto !important; color: #000 !important; background: -o-skin("Browser Window Skin") #f2f2ee !important; border: solid #000 !important; border-width: 0 0 1px 0 !important; z-index: 9999 !important; }\
					UserJS-USH-mouseover { height: 10px !important; z-index: 9998 !important; }\
					.UserJS-USH-top { top: 0 !important; }\
					.UserJS-USH-bottom { bottom: 0 !important; border-width: 1px 0 0 0 !important; }\
					.UserJS-USH-empty { border-right-width: 1px !important; right: auto !important; min-width: 20em !important; }\
					UserJS-USH-toolbar, UserJS-USH-toolbar *, UserJS-USH-highlight { display: inline-block !important; text-align: left !important; text-indent: 0 !important; margin: 0 !important; padding: 0 !important; height: auto !important; width: auto !important; float: none !important; clear: none !important; }\
					UserJS-USH-toolbar *, UserJS-USH-highlight { font: inherit !important; color: inherit !important; }\
					UserJS-USH-highlight { display: inline !important; color: #000 !important; font-weight: bold !important; }\
					UserJS-USH-highlight[hide] { color: inherit !important; font-weight: inherit !important; background: inherit !important; }\
					UserJS-USH-highlight[active] { outline: solid 3px invert !important; }\
					UserJS-USH-menu { float: right !important; min-width: 20em !important; }\
					UserJS-USH-menu>* { vertical-align: middle !important; }\
					UserJS-USH-result { padding: 1px 5px !important; }\
					UserJS-USH-result[disabled] { opacity: 0.5 !important; }\
					UserJS-USH-result[disabled] *, .UserJS-USH-progress>*, UserJS-USH-toolbar>label { display: none !important; }\
					.UserJS-USH-progress>label { display: block !important; }\
					UserJS-USH-result, UserJS-USH-toolbar input { background: #FFF !important; color: #000 !important; margin: 2px !important; border: none !important }\
					UserJS-USH-menu>input[type] { content: "" !important; padding: 2px !important; }\
					UserJS-USH-menu>input[type]::after { display: block !important; content: "" !important; height: 1.3em !important; width: 1.3em !important; }\
					UserJS-USH-menu>input[type], UserJS-USH-result { background: -o-skin("Push Button Skin") !important; font-weight: bold !important; }\
					UserJS-USH-menu>input[type]:hover, UserJS-USH-result[enabled]:hover { background: -o-skin("Push Default Button Skin") !important; }\
					UserJS-USH-menu>input[type]:active, UserJS-USH-menu>input[type][active], UserJS-USH-result[enabled]:active { background: -o-skin("Push Button Skin.pressed") !important; }\
					UserJS-USH-rIcon { width: 8px !important; height: 8px !important; border: 1px #666 solid !important; margin: 0 2px 0 0 !important; }\
					.UserJS-USH-input { border: 1px solid #000 !important; min-width: 11em !important; padding: 2px 2px 1px !important; }\
					.UserJS-USH-optsBttn::after { background: -o-skin("Edit Properties") !important; }\
					.UserJS-USH-newBttn::after { background: -o-skin("Search") !important; }\
					.UserJS-USH-hideBttn::after { background: -o-skin("Caption Restore") !important; }\
					.UserJS-USH-closeBttn::after { background: -o-skin("Caption Close") !important; }\
					UserJS-USH-toolbar ul { display: none !important; position: absolute !important; background: #f2f2ee !important; border: 1px solid #000 !important; padding: 0 3px !important; list-style: none !important; }\
					.UserJS-USH-top ul { top: 1.5em !important; margin-top: 6px !important; border-top-width: 0 !important; }\
					.UserJS-USH-bottom ul { bottom: 1.5em !important; margin-bottom: 6px !important; border-bottom-width: 0 !important; }\
					UserJS-USH-toolbar ul.UserJS-USH-selected, UserJS-USH-toolbar ul>li { display: block !important; }\
					UserJS-USH-toolbar ul input { padding: 1px !important; margin: 0 2px 0 0 !important; }':'');
				}
			}

			if( query ) { this.input.value = query; }
			if( !enabled ) {
				if( this.styles ) { d.documentElement.appendChild(this.styles); }
				d.documentElement.appendChild(this.tBar);
				d.documentElement.appendChild(this.mOver);
			}
			this.handleEvent(null,!searchEngines[0]||!preferences.toolbarHiddenOnLoad);
		},
		resize: function() {
			if( !enabled ) { return; }
			this.dim = getDim(this.tBar);
			if( preferences.noAutoHide && this.styles ) {
				this.styles.sheet.cssRules[preferences.toolbarAtBottom?1:0].style.cssText = 'display: block !important; content: "" !important; height: '+this.dim.height+'px !important';
			}
		},
		update: function() {
			var divEl = this.tBar;
			divEl.className = this.mOver.className = 'UserJS-USH-'+(preferences.toolbarAtBottom?'bottom':'top')+(results.terms.length?'':' UserJS-USH-empty');

			if( !regExp ) { return; }

			var menuEl = divEl.firstChild.removeNode(true); this.progress.removeNode(true);
			divEl.text = '';
			divEl.appendChild(menuEl); divEl.appendChild(this.progress);

			if( results.terms.length ) {
				var rBttn = document.createElementNS(resolver.xhtmlNS,'UserJS-USH-result');
				rBttn.unselectable = 'on';
				rBttn.appendChild(document.createElementNS(resolver.xhtmlNS,'UserJS-USH-rIcon'));
				rBttn.appendChild(document.createTextNode(''));

				for( var i = 0, term, total, text; (term = results.terms[i]) && (term = term[0]); i++ ) {
					(((rBttn = rBttn.cloneNode(true)).count = i)<8)&&(this.buttons[i] = rBttn);
					text = rBttn.lastChild.data = term.text;
					rBttn.title = (total = term.total)?strings._goto+' "'+text+'"':'"'+text+'" '+strings._nfound;
					rBttn.title = strings._goto+' "'+text+'"';
					if( total ) {
						rBttn.onclick = results;
						rBttn.lastChild.data += ' ['+total+']';
						rBttn.firstChild.onclick = function() { highlighter.remove(true,this.parentNode.count); }
						rBttn.firstChild.style.background = term.colour+' !important';
					}
					rBttn.setAttribute(total?'enabled':'disabled','');
					rBttn.removeAttribute(total?'disabled':'enabled');
					divEl.appendChild(rBttn);
				}
			}
		}
	};

	this.check = this.query = null;

	this.init = function() {
		delete this.init;
		if(!(document.documentElement instanceof HTMLHtmlElement)) { return; }

		if( opera.USHprefs instanceof Function ) {
			preferences = opera.USHprefs('preferences');
			colours = opera.USHprefs('colours');
			searchEngines = opera.USHprefs('searchEngines');
			strings = opera.USHprefs('strings');
			delete opera.USHprefs;
		}

		if( self==top ) { searchData.assign(); }

		opera.addEventListener('BeforeEvent.message',function(ujsEv) {
			var ev = ujsEv.event, msg = ev.data, data;
			if( !!msg.indexOf('USH|') ) { return; }
			ujsEv.preventDefault(); ev.preventDefault();

			switch( msg = msg.substr(4) )	{
				case 'frameLoaded':
					ev.source.postMessage('USH|frameIndex|'+frames.length);
					ev.source.postMessage(searchData);
					frames[frames.length] = ev.source;
					break;
				case 'frameIndex|'+(data=msg.substr(11)):
					frameIndex = data;
					break;
				case 'searchData|'+(data=msg.substr(11)):
					searchData.assign(data);
					if( query && searchEngines[0] ) { highlighter.run(); }
					break;
				case 'run|'+(data=msg.substr(4)):
					data = data.split('|');
					USH.run(unescape(data[0]),data[1],data[2]^0);
					break;
			}
		},false);

		opera.addEventListener('BeforeEvent.keypress',({
			enabled: false,
			keys: {},
			event: document.createEvent('UIEvents'),
			init: function() {
				var i, j, keys, key, bttns = ['optsBttn','newBttn','hideBttn','closeBttn','mOver'], bttn;

				for( i = 0, j = 0; (keys = preferences.keyShortcuts[i]) && (bttn = bttns[i]); i++, j = 0 ) {
					while( key = keys.charCodeAt(j) ) { this.keys[(j?'__':'_')+key] = bttn; j++ }
				}
				this.keys.toggle = preferences.keyShortcuts[i].charCodeAt(0);

				for( i = 0, keys = [33,64,35,36,37,94,38,42], key; key = keys[i]; i++ ) {
					this.keys['_'+(49+i)] = this.keys['__'+key] = i;
				}

				this.event.initEvent('click',true,true);

				return this;
			},
			handleEvent: function(ujsEv) {
				var e = ujsEv.event, el = e.target, key = e.which;
				if( !enabled || (!toolbar.tBar.contains(el) && el.forms instanceof NodeList) ) { return; }

				if( el == toolbar.input ) {
					if( key == 13 && el.value ) { USH.run(e,'UserJS-USH-newBttn'); }
					return;
				}

				var bttn = this.keys[(e.shiftKey?'__':'_')+key];
				bttn = (typeof bttn == 'number')?toolbar.buttons[bttn]:toolbar[bttn];

				if( key == this.keys.toggle ) { this.enabled = !this.enabled; }
				if( this.enabled && bttn ) {
					ujsEv.preventDefault(); e.preventDefault();
					this.event.shiftKey = e.shiftKey; this.event.ctrlKey = e.ctrlKey;
					bttn.dispatchEvent(this.event);
				}
			}
		}).init(),false);

		opera.addEventListener('AfterEvent.resize',function(){toolbar.resize()},false);

		opera.addEventListener('AfterEvent.DOMContentLoaded',function(ujsEv) {
			opera.removeEventListener(ujsEv.type,arguments.callee,false);
			if( self!=top ) { return (merlin?top.document:top).postMessage('USH|frameLoaded'); }
			if( query && searchEngines[0] || (query = USH.query) ) { highlighter.run(); }
		},false);
	}

	this.run = function(e,action,frame) {
		frame = frame||(e&&e.shiftKey);
		switch( action ) {
			case 'UserJS-USH-closeBttn':
				toolbar.close();
				break;
			case 'UserJS-USH-hideBttn':
				highlighter.remove(true);
				break;
			case 'UserJS-USH-newBttn':
				query = toolbar.input.value.replace(/^\s*(USHRegExp\s)?\s*/,e&&e.ctrlKey?'USHRegExp ':'$1');
				action = 'new';
				highlighter.run();
				break;
			case 'newSearch':
				if( !this.check || this.check == location ) { return; }
				action = 'new';
				frame = true;
				this.check = null;
			case 'new':
				query = e;
				highlighter.run();
				break;
			case 'newBlank':
				frame = document.body instanceof HTMLFrameSetElement;
				enabled = !!(query = '');
				highlighter.run();
				toolbar.input.focus(); toolbar.input.select();
		}
		if( !frame ) { return; }
		if( self!=top ) { return (merlin?top.document:top).postMessage('USH|run|'+escape(query)+'|'+action+'|'+frameIndex); }
		for( var i = 1, f; f = frames[i]; i++ ) {
			if( i !== frame ) { (merlin?f.document:f).postMessage('USH|run|'+escape(query)+'|'+action); }
		}
	}

	function saveVal(key,val) {
		preferences[key] = val;
		if( preferences.useCookies && key == 'query' ) {
			document.cookie = 'UserJS-USH='+encodeURIComponent(val)+';path=/;'+(val?'':'expires='+new Date(0).toGMTString());
		}
	}
	var USH = this, iID = setTimeout(function(){},0); clearTimeout(iID);
}).init();