1/*
2 * qTip2 - Pretty powerful tooltips - v2.2.0
3 * http://qtip2.com
4 *
5 * Copyright (c) 2013 Craig Michael Thompson
6 * Released under the MIT, GPL licenses
7 * http://jquery.org/license
8 *
9 * Date: Thu Nov 21 2013 08:34 GMT+0000
10 * Plugins: tips modal viewport svg imagemap ie6
11 * Styles: basic css3
12 */
13/*global window: false, jQuery: false, console: false, define: false */
14
15/* Cache window, document, undefined */
16(function( window, document, undefined ) {
17
18// Uses AMD or browser globals to create a jQuery plugin.
19(function( factory ) {
20	"use strict";
21	if(typeof define === 'function' && define.amd) {
22		define(['jquery'], factory);
23	}
24	else if(jQuery && !jQuery.fn.qtip) {
25		factory(jQuery);
26	}
27}
28(function($) {
29	"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
30
31;// Munge the primitives - Paul Irish tip
32var TRUE = true,
33FALSE = false,
34NULL = null,
35
36// Common variables
37X = 'x', Y = 'y',
38WIDTH = 'width',
39HEIGHT = 'height',
40
41// Positioning sides
42TOP = 'top',
43LEFT = 'left',
44BOTTOM = 'bottom',
45RIGHT = 'right',
46CENTER = 'center',
47
48// Position adjustment types
49FLIP = 'flip',
50FLIPINVERT = 'flipinvert',
51SHIFT = 'shift',
52
53// Shortcut vars
54QTIP, PROTOTYPE, CORNER, CHECKS,
55PLUGINS = {},
56NAMESPACE = 'qtip',
57ATTR_HAS = 'data-hasqtip',
58ATTR_ID = 'data-qtip-id',
59WIDGET = ['ui-widget', 'ui-tooltip'],
60SELECTOR = '.'+NAMESPACE,
61INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),
62
63CLASS_FIXED = NAMESPACE+'-fixed',
64CLASS_DEFAULT = NAMESPACE + '-default',
65CLASS_FOCUS = NAMESPACE + '-focus',
66CLASS_HOVER = NAMESPACE + '-hover',
67CLASS_DISABLED = NAMESPACE+'-disabled',
68
69replaceSuffix = '_replacedByqTip',
70oldtitle = 'oldtitle',
71trackingBound,
72
73// Browser detection
74BROWSER = {
75	/*
76	 * IE version detection
77	 *
78	 * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
79	 * Credit to James Padolsey for the original implemntation!
80	 */
81	ie: (function(){
82		var v = 3, div = document.createElement('div');
83		while ((div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->')) {
84			if(!div.getElementsByTagName('i')[0]) { break; }
85		}
86		return v > 4 ? v : NaN;
87	}()),
88
89	/*
90	 * iOS version detection
91	 */
92	iOS: parseFloat(
93		('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
94		.replace('undefined', '3_2').replace('_', '.').replace('_', '')
95	) || FALSE
96};
97
98;function QTip(target, options, id, attr) {
99	// Elements and ID
100	this.id = id;
101	this.target = target;
102	this.tooltip = NULL;
103	this.elements = { target: target };
104
105	// Internal constructs
106	this._id = NAMESPACE + '-' + id;
107	this.timers = { img: {} };
108	this.options = options;
109	this.plugins = {};
110
111	// Cache object
112	this.cache = {
113		event: {},
114		target: $(),
115		disabled: FALSE,
116		attr: attr,
117		onTooltip: FALSE,
118		lastClass: ''
119	};
120
121	// Set the initial flags
122	this.rendered = this.destroyed = this.disabled = this.waiting =
123		this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
124}
125PROTOTYPE = QTip.prototype;
126
127PROTOTYPE._when = function(deferreds) {
128	return $.when.apply($, deferreds);
129};
130
131PROTOTYPE.render = function(show) {
132	if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
133
134	var self = this,
135		options = this.options,
136		cache = this.cache,
137		elements = this.elements,
138		text = options.content.text,
139		title = options.content.title,
140		button = options.content.button,
141		posOptions = options.position,
142		namespace = '.'+this._id+' ',
143		deferreds = [],
144		tooltip;
145
146	// Add ARIA attributes to target
147	$.attr(this.target[0], 'aria-describedby', this._id);
148
149	// Create tooltip element
150	this.tooltip = elements.tooltip = tooltip = $('<div/>', {
151		'id': this._id,
152		'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, NAMESPACE + '-pos-' + options.position.my.abbrev() ].join(' '),
153		'width': options.style.width || '',
154		'height': options.style.height || '',
155		'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
156
157		/* ARIA specific attributes */
158		'role': 'alert',
159		'aria-live': 'polite',
160		'aria-atomic': FALSE,
161		'aria-describedby': this._id + '-content',
162		'aria-hidden': TRUE
163	})
164	.toggleClass(CLASS_DISABLED, this.disabled)
165	.attr(ATTR_ID, this.id)
166	.data(NAMESPACE, this)
167	.appendTo(posOptions.container)
168	.append(
169		// Create content element
170		elements.content = $('<div />', {
171			'class': NAMESPACE + '-content',
172			'id': this._id + '-content',
173			'aria-atomic': TRUE
174		})
175	);
176
177	// Set rendered flag and prevent redundant reposition calls for now
178	this.rendered = -1;
179	this.positioning = TRUE;
180
181	// Create title...
182	if(title) {
183		this._createTitle();
184
185		// Update title only if its not a callback (called in toggle if so)
186		if(!$.isFunction(title)) {
187			deferreds.push( this._updateTitle(title, FALSE) );
188		}
189	}
190
191	// Create button
192	if(button) { this._createButton(); }
193
194	// Set proper rendered flag and update content if not a callback function (called in toggle)
195	if(!$.isFunction(text)) {
196		deferreds.push( this._updateContent(text, FALSE) );
197	}
198	this.rendered = TRUE;
199
200	// Setup widget classes
201	this._setWidget();
202
203	// Initialize 'render' plugins
204	$.each(PLUGINS, function(name) {
205		var instance;
206		if(this.initialize === 'render' && (instance = this(self))) {
207			self.plugins[name] = instance;
208		}
209	});
210
211	// Unassign initial events and assign proper events
212	this._unassignEvents();
213	this._assignEvents();
214
215	// When deferreds have completed
216	this._when(deferreds).then(function() {
217		// tooltiprender event
218		self._trigger('render');
219
220		// Reset flags
221		self.positioning = FALSE;
222
223		// Show tooltip if not hidden during wait period
224		if(!self.hiddenDuringWait && (options.show.ready || show)) {
225			self.toggle(TRUE, cache.event, FALSE);
226		}
227		self.hiddenDuringWait = FALSE;
228	});
229
230	// Expose API
231	QTIP.api[this.id] = this;
232
233	return this;
234};
235
236PROTOTYPE.destroy = function(immediate) {
237	// Set flag the signify destroy is taking place to plugins
238	// and ensure it only gets destroyed once!
239	if(this.destroyed) { return this.target; }
240
241	function process() {
242		if(this.destroyed) { return; }
243		this.destroyed = TRUE;
244
245		var target = this.target,
246			title = target.attr(oldtitle);
247
248		// Destroy tooltip if rendered
249		if(this.rendered) {
250			this.tooltip.stop(1,0).find('*').remove().end().remove();
251		}
252
253		// Destroy all plugins
254		$.each(this.plugins, function(name) {
255			this.destroy && this.destroy();
256		});
257
258		// Clear timers and remove bound events
259		clearTimeout(this.timers.show);
260		clearTimeout(this.timers.hide);
261		this._unassignEvents();
262
263		// Remove api object and ARIA attributes
264		target.removeData(NAMESPACE)
265			.removeAttr(ATTR_ID)
266			.removeAttr(ATTR_HAS)
267			.removeAttr('aria-describedby');
268
269		// Reset old title attribute if removed
270		if(this.options.suppress && title) {
271			target.attr('title', title).removeAttr(oldtitle);
272		}
273
274		// Remove qTip events associated with this API
275		this._unbind(target);
276
277		// Remove ID from used id objects, and delete object references
278		// for better garbage collection and leak protection
279		this.options = this.elements = this.cache = this.timers =
280			this.plugins = this.mouse = NULL;
281
282		// Delete epoxsed API object
283		delete QTIP.api[this.id];
284	}
285
286	// If an immediate destory is needed
287	if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
288		this.tooltip.one('tooltiphidden', $.proxy(process, this));
289		!this.triggering && this.hide();
290	}
291
292	// If we're not in the process of hiding... process
293	else { process.call(this); }
294
295	return this.target;
296};
297
298;function invalidOpt(a) {
299	return a === NULL || $.type(a) !== 'object';
300}
301
302function invalidContent(c) {
303	return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) ));
304}
305
306// Option object sanitizer
307function sanitizeOptions(opts) {
308	var content, text, ajax, once;
309
310	if(invalidOpt(opts)) { return FALSE; }
311
312	if(invalidOpt(opts.metadata)) {
313		opts.metadata = { type: opts.metadata };
314	}
315
316	if('content' in opts) {
317		content = opts.content;
318
319		if(invalidOpt(content) || content.jquery || content.done) {
320			content = opts.content = {
321				text: (text = invalidContent(content) ? FALSE : content)
322			};
323		}
324		else { text = content.text; }
325
326		// DEPRECATED - Old content.ajax plugin functionality
327		// Converts it into the proper Deferred syntax
328		if('ajax' in content) {
329			ajax = content.ajax;
330			once = ajax && ajax.once !== FALSE;
331			delete content.ajax;
332
333			content.text = function(event, api) {
334				var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',
335
336				deferred = $.ajax(
337					$.extend({}, ajax, { context: api })
338				)
339				.then(ajax.success, NULL, ajax.error)
340				.then(function(content) {
341					if(content && once) { api.set('content.text', content); }
342					return content;
343				},
344				function(xhr, status, error) {
345					if(api.destroyed || xhr.status === 0) { return; }
346					api.set('content.text', status + ': ' + error);
347				});
348
349				return !once ? (api.set('content.text', loading), deferred) : loading;
350			};
351		}
352
353		if('title' in content) {
354			if(!invalidOpt(content.title)) {
355				content.button = content.title.button;
356				content.title = content.title.text;
357			}
358
359			if(invalidContent(content.title || FALSE)) {
360				content.title = FALSE;
361			}
362		}
363	}
364
365	if('position' in opts && invalidOpt(opts.position)) {
366		opts.position = { my: opts.position, at: opts.position };
367	}
368
369	if('show' in opts && invalidOpt(opts.show)) {
370		opts.show = opts.show.jquery ? { target: opts.show } :
371			opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
372	}
373
374	if('hide' in opts && invalidOpt(opts.hide)) {
375		opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
376	}
377
378	if('style' in opts && invalidOpt(opts.style)) {
379		opts.style = { classes: opts.style };
380	}
381
382	// Sanitize plugin options
383	$.each(PLUGINS, function() {
384		this.sanitize && this.sanitize(opts);
385	});
386
387	return opts;
388}
389
390// Setup builtin .set() option checks
391CHECKS = PROTOTYPE.checks = {
392	builtin: {
393		// Core checks
394		'^id$': function(obj, o, v, prev) {
395			var id = v === TRUE ? QTIP.nextid : v,
396				new_id = NAMESPACE + '-' + id;
397
398			if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {
399				this._id = new_id;
400
401				if(this.rendered) {
402					this.tooltip[0].id = this._id;
403					this.elements.content[0].id = this._id + '-content';
404					this.elements.title[0].id = this._id + '-title';
405				}
406			}
407			else { obj[o] = prev; }
408		},
409		'^prerender': function(obj, o, v) {
410			v && !this.rendered && this.render(this.options.show.ready);
411		},
412
413		// Content checks
414		'^content.text$': function(obj, o, v) {
415			this._updateContent(v);
416		},
417		'^content.attr$': function(obj, o, v, prev) {
418			if(this.options.content.text === this.target.attr(prev)) {
419				this._updateContent( this.target.attr(v) );
420			}
421		},
422		'^content.title$': function(obj, o, v) {
423			// Remove title if content is null
424			if(!v) { return this._removeTitle(); }
425
426			// If title isn't already created, create it now and update
427			v && !this.elements.title && this._createTitle();
428			this._updateTitle(v);
429		},
430		'^content.button$': function(obj, o, v) {
431			this._updateButton(v);
432		},
433		'^content.title.(text|button)$': function(obj, o, v) {
434			this.set('content.'+o, v); // Backwards title.text/button compat
435		},
436
437		// Position checks
438		'^position.(my|at)$': function(obj, o, v){
439			'string' === typeof v && (obj[o] = new CORNER(v, o === 'at'));
440		},
441		'^position.container$': function(obj, o, v){
442			this.rendered && this.tooltip.appendTo(v);
443		},
444
445		// Show checks
446		'^show.ready$': function(obj, o, v) {
447			v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
448		},
449
450		// Style checks
451		'^style.classes$': function(obj, o, v, p) {
452			this.rendered && this.tooltip.removeClass(p).addClass(v);
453		},
454		'^style.(width|height)': function(obj, o, v) {
455			this.rendered && this.tooltip.css(o, v);
456		},
457		'^style.widget|content.title': function() {
458			this.rendered && this._setWidget();
459		},
460		'^style.def': function(obj, o, v) {
461			this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
462		},
463
464		// Events check
465		'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
466			this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
467		},
468
469		// Properties which require event reassignment
470		'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
471			if(!this.rendered) { return; }
472
473			// Set tracking flag
474			var posOptions = this.options.position;
475			this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
476
477			// Reassign events
478			this._unassignEvents();
479			this._assignEvents();
480		}
481	}
482};
483
484// Dot notation converter
485function convertNotation(options, notation) {
486	var i = 0, obj, option = options,
487
488	// Split notation into array
489	levels = notation.split('.');
490
491	// Loop through
492	while( option = option[ levels[i++] ] ) {
493		if(i < levels.length) { obj = option; }
494	}
495
496	return [obj || options, levels.pop()];
497}
498
499PROTOTYPE.get = function(notation) {
500	if(this.destroyed) { return this; }
501
502	var o = convertNotation(this.options, notation.toLowerCase()),
503		result = o[0][ o[1] ];
504
505	return result.precedance ? result.string() : result;
506};
507
508function setCallback(notation, args) {
509	var category, rule, match;
510
511	for(category in this.checks) {
512		for(rule in this.checks[category]) {
513			if(match = (new RegExp(rule, 'i')).exec(notation)) {
514				args.push(match);
515
516				if(category === 'builtin' || this.plugins[category]) {
517					this.checks[category][rule].apply(
518						this.plugins[category] || this, args
519					);
520				}
521			}
522		}
523	}
524}
525
526var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
527	rrender = /^prerender|show\.ready/i;
528
529PROTOTYPE.set = function(option, value) {
530	if(this.destroyed) { return this; }
531
532	var rendered = this.rendered,
533		reposition = FALSE,
534		options = this.options,
535		checks = this.checks,
536		name;
537
538	// Convert singular option/value pair into object form
539	if('string' === typeof option) {
540		name = option; option = {}; option[name] = value;
541	}
542	else { option = $.extend({}, option); }
543
544	// Set all of the defined options to their new values
545	$.each(option, function(notation, value) {
546		if(rendered && rrender.test(notation)) {
547			delete option[notation]; return;
548		}
549
550		// Set new obj value
551		var obj = convertNotation(options, notation.toLowerCase()), previous;
552		previous = obj[0][ obj[1] ];
553		obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value;
554
555		// Also check if we need to reposition
556		reposition = rmove.test(notation) || reposition;
557
558		// Set the new params for the callback
559		option[notation] = [obj[0], obj[1], value, previous];
560	});
561
562	// Re-sanitize options
563	sanitizeOptions(options);
564
565	/*
566	 * Execute any valid callbacks for the set options
567	 * Also set positioning flag so we don't get loads of redundant repositioning calls.
568	 */
569	this.positioning = TRUE;
570	$.each(option, $.proxy(setCallback, this));
571	this.positioning = FALSE;
572
573	// Update position if needed
574	if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
575		this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
576	}
577
578	return this;
579};
580
581;PROTOTYPE._update = function(content, element, reposition) {
582	var self = this,
583		cache = this.cache;
584
585	// Make sure tooltip is rendered and content is defined. If not return
586	if(!this.rendered || !content) { return FALSE; }
587
588	// Use function to parse content
589	if($.isFunction(content)) {
590		content = content.call(this.elements.target, cache.event, this) || '';
591	}
592
593	// Handle deferred content
594	if($.isFunction(content.then)) {
595		cache.waiting = TRUE;
596		return content.then(function(c) {
597			cache.waiting = FALSE;
598			return self._update(c, element);
599		}, NULL, function(e) {
600			return self._update(e, element);
601		});
602	}
603
604	// If content is null... return false
605	if(content === FALSE || (!content && content !== '')) { return FALSE; }
606
607	// Append new content if its a DOM array and show it if hidden
608	if(content.jquery && content.length > 0) {
609		element.empty().append(
610			content.css({ display: 'block', visibility: 'visible' })
611		);
612	}
613
614	// Content is a regular string, insert the new content
615	else { element.html(content); }
616
617	// Wait for content to be loaded, and reposition
618	return this._waitForContent(element).then(function(images) {
619		if(images.images && images.images.length && self.rendered && self.tooltip[0].offsetWidth > 0) {
620			self.reposition(cache.event, !images.length);
621		}
622	});
623};
624
625PROTOTYPE._waitForContent = function(element) {
626	var cache = this.cache;
627
628	// Set flag
629	cache.waiting = TRUE;
630
631	// If imagesLoaded is included, ensure images have loaded and return promise
632	return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([]) )
633		.done(function() { cache.waiting = FALSE; })
634		.promise();
635};
636
637PROTOTYPE._updateContent = function(content, reposition) {
638	this._update(content, this.elements.content, reposition);
639};
640
641PROTOTYPE._updateTitle = function(content, reposition) {
642	if(this._update(content, this.elements.title, reposition) === FALSE) {
643		this._removeTitle(FALSE);
644	}
645};
646
647PROTOTYPE._createTitle = function()
648{
649	var elements = this.elements,
650		id = this._id+'-title';
651
652	// Destroy previous title element, if present
653	if(elements.titlebar) { this._removeTitle(); }
654
655	// Create title bar and title elements
656	elements.titlebar = $('<div />', {
657		'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
658	})
659	.append(
660		elements.title = $('<div />', {
661			'id': id,
662			'class': NAMESPACE + '-title',
663			'aria-atomic': TRUE
664		})
665	)
666	.insertBefore(elements.content)
667
668	// Button-specific events
669	.delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
670		$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
671	})
672	.delegate('.qtip-close', 'mouseover mouseout', function(event){
673		$(this).toggleClass('ui-state-hover', event.type === 'mouseover');
674	});
675
676	// Create button if enabled
677	if(this.options.content.button) { this._createButton(); }
678};
679
680PROTOTYPE._removeTitle = function(reposition)
681{
682	var elements = this.elements;
683
684	if(elements.title) {
685		elements.titlebar.remove();
686		elements.titlebar = elements.title = elements.button = NULL;
687
688		// Reposition if enabled
689		if(reposition !== FALSE) { this.reposition(); }
690	}
691};
692
693;PROTOTYPE.reposition = function(event, effect) {
694	if(!this.rendered || this.positioning || this.destroyed) { return this; }
695
696	// Set positioning flag
697	this.positioning = TRUE;
698
699	var cache = this.cache,
700		tooltip = this.tooltip,
701		posOptions = this.options.position,
702		target = posOptions.target,
703		my = posOptions.my,
704		at = posOptions.at,
705		viewport = posOptions.viewport,
706		container = posOptions.container,
707		adjust = posOptions.adjust,
708		method = adjust.method.split(' '),
709		tooltipWidth = tooltip.outerWidth(FALSE),
710		tooltipHeight = tooltip.outerHeight(FALSE),
711		targetWidth = 0,
712		targetHeight = 0,
713		type = tooltip.css('position'),
714		position = { left: 0, top: 0 },
715		visible = tooltip[0].offsetWidth > 0,
716		isScroll = event && event.type === 'scroll',
717		win = $(window),
718		doc = container[0].ownerDocument,
719		mouse = this.mouse,
720		pluginCalculations, offset;
721
722	// Check if absolute position was passed
723	if($.isArray(target) && target.length === 2) {
724		// Force left top and set position
725		at = { x: LEFT, y: TOP };
726		position = { left: target[0], top: target[1] };
727	}
728
729	// Check if mouse was the target
730	else if(target === 'mouse') {
731		// Force left top to allow flipping
732		at = { x: LEFT, y: TOP };
733
734		// Use the cached mouse coordinates if available, or passed event has no coordinates
735		if(mouse && mouse.pageX && (adjust.mouse || !event || !event.pageX) ) {
736			event = mouse;
737		}
738
739		// If the passed event has no coordinates (such as a scroll event)
740		else if(!event || !event.pageX) {
741			// Use the mouse origin that caused the show event, if distance hiding is enabled
742			if((!adjust.mouse || this.options.show.distance) && cache.origin && cache.origin.pageX) {
743				event =  cache.origin;
744			}
745
746			// Use cached event for resize/scroll events
747			else if(!event || (event && (event.type === 'resize' || event.type === 'scroll'))) {
748				event = cache.event;
749			}
750		}
751
752		// Calculate body and container offset and take them into account below
753		if(type !== 'static') { position = container.offset(); }
754		if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
755			offset = $(document.body).offset();
756		}
757
758		// Use event coordinates for position
759		position = {
760			left: event.pageX - position.left + (offset && offset.left || 0),
761			top: event.pageY - position.top + (offset && offset.top || 0)
762		};
763
764		// Scroll events are a pain, some browsers
765		if(adjust.mouse && isScroll && mouse) {
766			position.left -= (mouse.scrollX || 0) - win.scrollLeft();
767			position.top -= (mouse.scrollY || 0) - win.scrollTop();
768		}
769	}
770
771	// Target wasn't mouse or absolute...
772	else {
773		// Check if event targetting is being used
774		if(target === 'event') {
775			if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
776				cache.target = $(event.target);
777			}
778			else if(!event.target) {
779				cache.target = this.elements.target;
780			}
781		}
782		else if(target !== 'event'){
783			cache.target = $(target.jquery ? target : this.elements.target);
784		}
785		target = cache.target;
786
787		// Parse the target into a jQuery object and make sure there's an element present
788		target = $(target).eq(0);
789		if(target.length === 0) { return this; }
790
791		// Check if window or document is the target
792		else if(target[0] === document || target[0] === window) {
793			targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
794			targetHeight = BROWSER.iOS ? window.innerHeight : target.height();
795
796			if(target[0] === window) {
797				position = {
798					top: (viewport || target).scrollTop(),
799					left: (viewport || target).scrollLeft()
800				};
801			}
802		}
803
804		// Check if the target is an <AREA> element
805		else if(PLUGINS.imagemap && target.is('area')) {
806			pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
807		}
808
809		// Check if the target is an SVG element
810		else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
811			pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
812		}
813
814		// Otherwise use regular jQuery methods
815		else {
816			targetWidth = target.outerWidth(FALSE);
817			targetHeight = target.outerHeight(FALSE);
818			position = target.offset();
819		}
820
821		// Parse returned plugin values into proper variables
822		if(pluginCalculations) {
823			targetWidth = pluginCalculations.width;
824			targetHeight = pluginCalculations.height;
825			offset = pluginCalculations.offset;
826			position = pluginCalculations.position;
827		}
828
829		// Adjust position to take into account offset parents
830		position = this.reposition.offset(target, position, container);
831
832		// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
833		if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
834			(BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
835			(!BROWSER.iOS && type === 'fixed')
836		){
837			position.left -= win.scrollLeft();
838			position.top -= win.scrollTop();
839		}
840
841		// Adjust position relative to target
842		if(!pluginCalculations || (pluginCalculations && pluginCalculations.adjustable !== FALSE)) {
843			position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
844			position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
845		}
846	}
847
848	// Adjust position relative to tooltip
849	position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
850	position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);
851
852	// Use viewport adjustment plugin if enabled
853	if(PLUGINS.viewport) {
854		position.adjusted = PLUGINS.viewport(
855			this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
856		);
857
858		// Apply offsets supplied by positioning plugin (if used)
859		if(offset && position.adjusted.left) { position.left += offset.left; }
860		if(offset && position.adjusted.top) {  position.top += offset.top; }
861	}
862
863	// Viewport adjustment is disabled, set values to zero
864	else { position.adjusted = { left: 0, top: 0 }; }
865
866	// tooltipmove event
867	if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
868	delete position.adjusted;
869
870	// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
871	if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
872		tooltip.css(position);
873	}
874
875	// Use custom function if provided
876	else if($.isFunction(posOptions.effect)) {
877		posOptions.effect.call(tooltip, this, $.extend({}, position));
878		tooltip.queue(function(next) {
879			// Reset attributes to avoid cross-browser rendering bugs
880			$(this).css({ opacity: '', height: '' });
881			if(BROWSER.ie) { this.style.removeAttribute('filter'); }
882
883			next();
884		});
885	}
886
887	// Set positioning flag
888	this.positioning = FALSE;
889
890	return this;
891};
892
893// Custom (more correct for qTip!) offset calculator
894PROTOTYPE.reposition.offset = function(elem, pos, container) {
895	if(!container[0]) { return pos; }
896
897	var ownerDocument = $(elem[0].ownerDocument),
898		quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
899		parent = container[0],
900		scrolled, position, parentOffset, overflow;
901
902	function scroll(e, i) {
903		pos.left += i * e.scrollLeft();
904		pos.top += i * e.scrollTop();
905	}
906
907	// Compensate for non-static containers offset
908	do {
909		if((position = $.css(parent, 'position')) !== 'static') {
910			if(position === 'fixed') {
911				parentOffset = parent.getBoundingClientRect();
912				scroll(ownerDocument, -1);
913			}
914			else {
915				parentOffset = $(parent).position();
916				parentOffset.left += (parseFloat($.css(parent, 'borderLeftWidth')) || 0);
917				parentOffset.top += (parseFloat($.css(parent, 'borderTopWidth')) || 0);
918			}
919
920			pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
921			pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);
922
923			// If this is the first parent element with an overflow of "scroll" or "auto", store it
924			if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
925		}
926	}
927	while((parent = parent.offsetParent));
928
929	// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
930	if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
931		scroll(scrolled, 1);
932	}
933
934	return pos;
935};
936
937// Corner class
938var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
939	corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
940	this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
941	this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
942	this.forceY = !!forceY;
943
944	var f = corner.charAt(0);
945	this.precedance = (f === 't' || f === 'b' ? Y : X);
946}).prototype;
947
948C.invert = function(z, center) {
949	this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
950};
951
952C.string = function() {
953	var x = this.x, y = this.y;
954	return x === y ? x : this.precedance === Y || (this.forceY && y !== 'center') ? y+' '+x : x+' '+y;
955};
956
957C.abbrev = function() {
958	var result = this.string().split(' ');
959	return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
960};
961
962C.clone = function() {
963	return new CORNER( this.string(), this.forceY );
964};;
965PROTOTYPE.toggle = function(state, event) {
966	var cache = this.cache,
967		options = this.options,
968		tooltip = this.tooltip;
969
970	// Try to prevent flickering when tooltip overlaps show element
971	if(event) {
972		if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
973			options.show.target.add(event.target).length === options.show.target.length &&
974			tooltip.has(event.relatedTarget).length) {
975			return this;
976		}
977
978		// Cache event
979		cache.event = cloneEvent(event);
980	}
981
982	// If we're currently waiting and we've just hidden... stop it
983	this.waiting && !state && (this.hiddenDuringWait = TRUE);
984
985	// Render the tooltip if showing and it isn't already
986	if(!this.rendered) { return state ? this.render(1) : this; }
987	else if(this.destroyed || this.disabled) { return this; }
988
989	var type = state ? 'show' : 'hide',
990		opts = this.options[type],
991		otherOpts = this.options[ !state ? 'show' : 'hide' ],
992		posOptions = this.options.position,
993		contentOptions = this.options.content,
994		width = this.tooltip.css('width'),
995		visible = this.tooltip.is(':visible'),
996		animate = state || opts.target.length === 1,
997		sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
998		identicalState, allow, showEvent, delay, after;
999
1000	// Detect state if valid one isn't provided
1001	if((typeof state).search('boolean|number')) { state = !visible; }
1002
1003	// Check if the tooltip is in an identical state to the new would-be state
1004	identicalState = !tooltip.is(':animated') && visible === state && sameTarget;
1005
1006	// Fire tooltip(show/hide) event and check if destroyed
1007	allow = !identicalState ? !!this._trigger(type, [90]) : NULL;
1008
1009	// Check to make sure the tooltip wasn't destroyed in the callback
1010	if(this.destroyed) { return this; }
1011
1012	// If the user didn't stop the method prematurely and we're showing the tooltip, focus it
1013	if(allow !== FALSE && state) { this.focus(event); }
1014
1015	// If the state hasn't changed or the user stopped it, return early
1016	if(!allow || identicalState) { return this; }
1017
1018	// Set ARIA hidden attribute
1019	$.attr(tooltip[0], 'aria-hidden', !!!state);
1020
1021	// Execute state specific properties
1022	if(state) {
1023		// Store show origin coordinates
1024		cache.origin = cloneEvent(this.mouse);
1025
1026		// Update tooltip content & title if it's a dynamic function
1027		if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
1028		if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }
1029
1030		// Cache mousemove events for positioning purposes (if not already tracking)
1031		if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
1032			$(document).bind('mousemove.'+NAMESPACE, this._storeMouse);
1033			trackingBound = TRUE;
1034		}
1035
1036		// Update the tooltip position (set width first to prevent viewport/max-width issues)
1037		if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
1038		this.reposition(event, arguments[2]);
1039		if(!width) { tooltip.css('width', ''); }
1040
1041		// Hide other tooltips if tooltip is solo
1042		if(!!opts.solo) {
1043			(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
1044				.not(tooltip).not(opts.target).qtip('hide', $.Event('tooltipsolo'));
1045		}
1046	}
1047	else {
1048		// Clear show timer if we're hiding
1049		clearTimeout(this.timers.show);
1050
1051		// Remove cached origin on hide
1052		delete cache.origin;
1053
1054		// Remove mouse tracking event if not needed (all tracking qTips are hidden)
1055		if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
1056			$(document).unbind('mousemove.'+NAMESPACE);
1057			trackingBound = FALSE;
1058		}
1059
1060		// Blur the tooltip
1061		this.blur(event);
1062	}
1063
1064	// Define post-animation, state specific properties
1065	after = $.proxy(function() {
1066		if(state) {
1067			// Prevent antialias from disappearing in IE by removing filter
1068			if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }
1069
1070			// Remove overflow setting to prevent tip bugs
1071			tooltip.css('overflow', '');
1072
1073			// Autofocus elements if enabled
1074			if('string' === typeof opts.autofocus) {
1075				$(this.options.show.autofocus, tooltip).focus();
1076			}
1077
1078			// If set, hide tooltip when inactive for delay period
1079			this.options.show.target.trigger('qtip-'+this.id+'-inactive');
1080		}
1081		else {
1082			// Reset CSS states
1083			tooltip.css({
1084				display: '',
1085				visibility: '',
1086				opacity: '',
1087				left: '',
1088				top: ''
1089			});
1090		}
1091
1092		// tooltipvisible/tooltiphidden events
1093		this._trigger(state ? 'visible' : 'hidden');
1094	}, this);
1095
1096	// If no effect type is supplied, use a simple toggle
1097	if(opts.effect === FALSE || animate === FALSE) {
1098		tooltip[ type ]();
1099		after();
1100	}
1101
1102	// Use custom function if provided
1103	else if($.isFunction(opts.effect)) {
1104		tooltip.stop(1, 1);
1105		opts.effect.call(tooltip, this);
1106		tooltip.queue('fx', function(n) {
1107			after(); n();
1108		});
1109	}
1110
1111	// Use basic fade function by default
1112	else { tooltip.fadeTo(90, state ? 1 : 0, after); }
1113
1114	// If inactive hide method is set, active it
1115	if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }
1116
1117	return this;
1118};
1119
1120PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };
1121
1122PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
1123
1124;PROTOTYPE.focus = function(event) {
1125	if(!this.rendered || this.destroyed) { return this; }
1126
1127	var qtips = $(SELECTOR),
1128		tooltip = this.tooltip,
1129		curIndex = parseInt(tooltip[0].style.zIndex, 10),
1130		newIndex = QTIP.zindex + qtips.length,
1131		focusedElem;
1132
1133	// Only update the z-index if it has changed and tooltip is not already focused
1134	if(!tooltip.hasClass(CLASS_FOCUS)) {
1135		// tooltipfocus event
1136		if(this._trigger('focus', [newIndex], event)) {
1137			// Only update z-index's if they've changed
1138			if(curIndex !== newIndex) {
1139				// Reduce our z-index's and keep them properly ordered
1140				qtips.each(function() {
1141					if(this.style.zIndex > curIndex) {
1142						this.style.zIndex = this.style.zIndex - 1;
1143					}
1144				});
1145
1146				// Fire blur event for focused tooltip
1147				qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
1148			}
1149
1150			// Set the new z-index
1151			tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
1152		}
1153	}
1154
1155	return this;
1156};
1157
1158PROTOTYPE.blur = function(event) {
1159	if(!this.rendered || this.destroyed) { return this; }
1160
1161	// Set focused status to FALSE
1162	this.tooltip.removeClass(CLASS_FOCUS);
1163
1164	// tooltipblur event
1165	this._trigger('blur', [ this.tooltip.css('zIndex') ], event);
1166
1167	return this;
1168};
1169
1170;PROTOTYPE.disable = function(state) {
1171	if(this.destroyed) { return this; }
1172
1173	// If 'toggle' is passed, toggle the current state
1174	if(state === 'toggle') {
1175		state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
1176	}
1177
1178	// Disable if no state passed
1179	else if('boolean' !== typeof state) {
1180		state = TRUE;
1181	}
1182
1183	if(this.rendered) {
1184		this.tooltip.toggleClass(CLASS_DISABLED, state)
1185			.attr('aria-disabled', state);
1186	}
1187
1188	this.disabled = !!state;
1189
1190	return this;
1191};
1192
1193PROTOTYPE.enable = function() { return this.disable(FALSE); };
1194
1195;PROTOTYPE._createButton = function()
1196{
1197	var self = this,
1198		elements = this.elements,
1199		tooltip = elements.tooltip,
1200		button = this.options.content.button,
1201		isString = typeof button === 'string',
1202		close = isString ? button : 'Close tooltip';
1203
1204	if(elements.button) { elements.button.remove(); }
1205
1206	// Use custom button if one was supplied by user, else use default
1207	if(button.jquery) {
1208		elements.button = button;
1209	}
1210	else {
1211		elements.button = $('<a />', {
1212			'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
1213			'title': close,
1214			'aria-label': close
1215		})
1216		.prepend(
1217			$('<span />', {
1218				'class': 'ui-icon ui-icon-close',
1219				'html': '&times;'
1220			})
1221		);
1222	}
1223
1224	// Create button and setup attributes
1225	elements.button.appendTo(elements.titlebar || tooltip)
1226		.attr('role', 'button')
1227		.click(function(event) {
1228			if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
1229			return FALSE;
1230		});
1231};
1232
1233PROTOTYPE._updateButton = function(button)
1234{
1235	// Make sure tooltip is rendered and if not, return
1236	if(!this.rendered) { return FALSE; }
1237
1238	var elem = this.elements.button;
1239	if(button) { this._createButton(); }
1240	else { elem.remove(); }
1241};
1242
1243;// Widget class creator
1244function createWidgetClass(cls) {
1245	return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
1246}
1247
1248// Widget class setter method
1249PROTOTYPE._setWidget = function()
1250{
1251	var on = this.options.style.widget,
1252		elements = this.elements,
1253		tooltip = elements.tooltip,
1254		disabled = tooltip.hasClass(CLASS_DISABLED);
1255
1256	tooltip.removeClass(CLASS_DISABLED);
1257	CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
1258	tooltip.toggleClass(CLASS_DISABLED, disabled);
1259
1260	tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);
1261
1262	if(elements.content) {
1263		elements.content.toggleClass( createWidgetClass('content'), on);
1264	}
1265	if(elements.titlebar) {
1266		elements.titlebar.toggleClass( createWidgetClass('header'), on);
1267	}
1268	if(elements.button) {
1269		elements.button.toggleClass(NAMESPACE+'-icon', !on);
1270	}
1271};;function cloneEvent(event) {
1272	return event && {
1273		type: event.type,
1274		pageX: event.pageX,
1275		pageY: event.pageY,
1276		target: event.target,
1277		relatedTarget: event.relatedTarget,
1278		scrollX: event.scrollX || window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft,
1279		scrollY: event.scrollY || window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop
1280	} || {};
1281}
1282
1283function delay(callback, duration) {
1284	// If tooltip has displayed, start hide timer
1285	if(duration > 0) {
1286		return setTimeout(
1287			$.proxy(callback, this), duration
1288		);
1289	}
1290	else{ callback.call(this); }
1291}
1292
1293function showMethod(event) {
1294	if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }
1295
1296	// Clear hide timers
1297	clearTimeout(this.timers.show);
1298	clearTimeout(this.timers.hide);
1299
1300	// Start show timer
1301	this.timers.show = delay.call(this,
1302		function() { this.toggle(TRUE, event); },
1303		this.options.show.delay
1304	);
1305}
1306
1307function hideMethod(event) {
1308	if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }
1309
1310	// Check if new target was actually the tooltip element
1311	var relatedTarget = $(event.relatedTarget),
1312		ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
1313		ontoTarget = relatedTarget[0] === this.options.show.target[0];
1314
1315	// Clear timers and stop animation queue
1316	clearTimeout(this.timers.show);
1317	clearTimeout(this.timers.hide);
1318
1319	// Prevent hiding if tooltip is fixed and event target is the tooltip.
1320	// Or if mouse positioning is enabled and cursor momentarily overlaps
1321	if(this !== relatedTarget[0] &&
1322		(this.options.position.target === 'mouse' && ontoTooltip) ||
1323		(this.options.hide.fixed && (
1324			(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
1325		))
1326	{
1327		try {
1328			event.preventDefault();
1329			event.stopImmediatePropagation();
1330		} catch(e) {}
1331
1332		return;
1333	}
1334
1335	// If tooltip has displayed, start hide timer
1336	this.timers.hide = delay.call(this,
1337		function() { this.toggle(FALSE, event); },
1338		this.options.hide.delay,
1339		this
1340	);
1341}
1342
1343function inactiveMethod(event) {
1344	if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return FALSE; }
1345
1346	// Clear timer
1347	clearTimeout(this.timers.inactive);
1348
1349	this.timers.inactive = delay.call(this,
1350		function(){ this.hide(event); },
1351		this.options.hide.inactive
1352	);
1353}
1354
1355function repositionMethod(event) {
1356	if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
1357}
1358
1359// Store mouse coordinates
1360PROTOTYPE._storeMouse = function(event) {
1361	(this.mouse = cloneEvent(event)).type = 'mousemove';
1362};
1363
1364// Bind events
1365PROTOTYPE._bind = function(targets, events, method, suffix, context) {
1366	var ns = '.' + this._id + (suffix ? '-'+suffix : '');
1367	events.length && $(targets).bind(
1368		(events.split ? events : events.join(ns + ' ')) + ns,
1369		$.proxy(method, context || this)
1370	);
1371};
1372PROTOTYPE._unbind = function(targets, suffix) {
1373	$(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
1374};
1375
1376// Apply common event handlers using delegate (avoids excessive .bind calls!)
1377var ns = '.'+NAMESPACE;
1378function delegate(selector, events, method) {
1379	$(document.body).delegate(selector,
1380		(events.split ? events : events.join(ns + ' ')) + ns,
1381		function() {
1382			var api = QTIP.api[ $.attr(this, ATTR_ID) ];
1383			api && !api.disabled && method.apply(api, arguments);
1384		}
1385	);
1386}
1387
1388$(function() {
1389	delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
1390		var state = event.type === 'mouseenter',
1391			tooltip = $(event.currentTarget),
1392			target = $(event.relatedTarget || event.target),
1393			options = this.options;
1394
1395		// On mouseenter...
1396		if(state) {
1397			// Focus the tooltip on mouseenter (z-index stacking)
1398			this.focus(event);
1399
1400			// Clear hide timer on tooltip hover to prevent it from closing
1401			tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
1402		}
1403
1404		// On mouseleave...
1405		else {
1406			// Hide when we leave the tooltip and not onto the show target (if a hide event is set)
1407			if(options.position.target === 'mouse' && options.hide.event &&
1408				options.show.target && !target.closest(options.show.target[0]).length) {
1409				this.hide(event);
1410			}
1411		}
1412
1413		// Add hover class
1414		tooltip.toggleClass(CLASS_HOVER, state);
1415	});
1416
1417	// Define events which reset the 'inactive' event handler
1418	delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
1419});
1420
1421// Event trigger
1422PROTOTYPE._trigger = function(type, args, event) {
1423	var callback = $.Event('tooltip'+type);
1424	callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;
1425
1426	this.triggering = type;
1427	this.tooltip.trigger(callback, [this].concat(args || []));
1428	this.triggering = FALSE;
1429
1430	return !callback.isDefaultPrevented();
1431};
1432
1433PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod) {
1434	// If hide and show targets are the same...
1435	if(hideTarget.add(showTarget).length === hideTarget.length) {
1436		var toggleEvents = [];
1437
1438		// Filter identical show/hide events
1439		hideEvents = $.map(hideEvents, function(type) {
1440			var showIndex = $.inArray(type, showEvents);
1441
1442			// Both events are identical, remove from both hide and show events
1443			// and append to toggleEvents
1444			if(showIndex > -1) {
1445				toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
1446				return;
1447			}
1448
1449			return type;
1450		});
1451
1452		// Toggle events are special case of identical show/hide events, which happen in sequence
1453		toggleEvents.length && this._bind(showTarget, toggleEvents, function(event) {
1454			var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
1455			(state ? hideMethod : showMethod).call(this, event);
1456		});
1457	}
1458
1459	// Apply show/hide/toggle events
1460	this._bind(showTarget, showEvents, showMethod);
1461	this._bind(hideTarget, hideEvents, hideMethod);
1462};
1463
1464PROTOTYPE._assignInitialEvents = function(event) {
1465	var options = this.options,
1466		showTarget = options.show.target,
1467		hideTarget = options.hide.target,
1468		showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1469		hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1470
1471	/*
1472	 * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1473	 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1474	 */
1475	if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
1476		hideEvents.push('mouseleave');
1477	}
1478
1479	/*
1480	 * Also make sure initial mouse targetting works correctly by caching mousemove coords
1481	 * on show targets before the tooltip has rendered. Also set onTarget when triggered to
1482	 * keep mouse tracking working.
1483	 */
1484	this._bind(showTarget, 'mousemove', function(event) {
1485		this._storeMouse(event);
1486		this.cache.onTarget = TRUE;
1487	});
1488
1489	// Define hoverIntent function
1490	function hoverIntent(event) {
1491		// Only continue if tooltip isn't disabled
1492		if(this.disabled || this.destroyed) { return FALSE; }
1493
1494		// Cache the event data
1495		this.cache.event = cloneEvent(event);
1496		this.cache.target = event ? $(event.target) : [undefined];
1497
1498		// Start the event sequence
1499		clearTimeout(this.timers.show);
1500		this.timers.show = delay.call(this,
1501			function() { this.render(typeof event === 'object' || options.show.ready); },
1502			options.show.delay
1503		);
1504	}
1505
1506	// Filter and bind events
1507	this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
1508		clearTimeout(this.timers.show);
1509	});
1510
1511	// Prerendering is enabled, create tooltip now
1512	if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
1513};
1514
1515// Event assignment method
1516PROTOTYPE._assignEvents = function() {
1517	var self = this,
1518		options = this.options,
1519		posOptions = options.position,
1520
1521		tooltip = this.tooltip,
1522		showTarget = options.show.target,
1523		hideTarget = options.hide.target,
1524		containerTarget = posOptions.container,
1525		viewportTarget = posOptions.viewport,
1526		documentTarget = $(document),
1527		bodyTarget = $(document.body),
1528		windowTarget = $(window),
1529
1530		showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1531		hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1532
1533
1534	// Assign passed event callbacks
1535	$.each(options.events, function(name, callback) {
1536		self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
1537	});
1538
1539	// Hide tooltips when leaving current window/frame (but not select/option elements)
1540	if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
1541		this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
1542			if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
1543				this.hide(event);
1544			}
1545		});
1546	}
1547
1548	// Enable hide.fixed by adding appropriate class
1549	if(options.hide.fixed) {
1550		hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
1551	}
1552
1553	/*
1554	 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
1555	 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1556	 */
1557	else if(/mouse(over|enter)/i.test(options.show.event)) {
1558		this._bind(hideTarget, 'mouseleave', function() {
1559			clearTimeout(this.timers.show);
1560		});
1561	}
1562
1563	// Hide tooltip on document mousedown if unfocus events are enabled
1564	if(('' + options.hide.event).indexOf('unfocus') > -1) {
1565		this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
1566			var elem = $(event.target),
1567				enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
1568				isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;
1569
1570			if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
1571				!this.target.has(elem[0]).length && enabled
1572			) {
1573				this.hide(event);
1574			}
1575		});
1576	}
1577
1578	// Check if the tooltip hides when inactive
1579	if('number' === typeof options.hide.inactive) {
1580		// Bind inactive method to show target(s) as a custom event
1581		this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod);
1582
1583		// Define events which reset the 'inactive' event handler
1584		this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod, '-inactive');
1585	}
1586
1587	// Filter and bind events
1588	this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);
1589
1590	// Mouse movement bindings
1591	this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
1592		// Check if the tooltip hides when mouse is moved a certain distance
1593		if('number' === typeof options.hide.distance) {
1594			var origin = this.cache.origin || {},
1595				limit = this.options.hide.distance,
1596				abs = Math.abs;
1597
1598			// Check if the movement has gone beyond the limit, and hide it if so
1599			if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
1600				this.hide(event);
1601			}
1602		}
1603
1604		// Cache mousemove coords on show targets
1605		this._storeMouse(event);
1606	});
1607
1608	// Mouse positioning events
1609	if(posOptions.target === 'mouse') {
1610		// If mouse adjustment is on...
1611		if(posOptions.adjust.mouse) {
1612			// Apply a mouseleave event so we don't get problems with overlapping
1613			if(options.hide.event) {
1614				// Track if we're on the target or not
1615				this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
1616					this.cache.onTarget = event.type === 'mouseenter';
1617				});
1618			}
1619
1620			// Update tooltip position on mousemove
1621			this._bind(documentTarget, 'mousemove', function(event) {
1622				// Update the tooltip position only if the tooltip is visible and adjustment is enabled
1623				if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
1624					this.reposition(event);
1625				}
1626			});
1627		}
1628	}
1629
1630	// Adjust positions of the tooltip on window resize if enabled
1631	if(posOptions.adjust.resize || viewportTarget.length) {
1632		this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
1633	}
1634
1635	// Adjust tooltip position on scroll of the window or viewport element if present
1636	if(posOptions.adjust.scroll) {
1637		this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
1638	}
1639};
1640
1641// Un-assignment method
1642PROTOTYPE._unassignEvents = function() {
1643	var targets = [
1644		this.options.show.target[0],
1645		this.options.hide.target[0],
1646		this.rendered && this.tooltip[0],
1647		this.options.position.container[0],
1648		this.options.position.viewport[0],
1649		this.options.position.container.closest('html')[0], // unfocus
1650		window,
1651		document
1652	];
1653
1654	this._unbind($([]).pushStack( $.grep(targets, function(i) {
1655		return typeof i === 'object';
1656	})));
1657};
1658
1659;// Initialization method
1660function init(elem, id, opts) {
1661	var obj, posOptions, attr, config, title,
1662
1663	// Setup element references
1664	docBody = $(document.body),
1665
1666	// Use document body instead of document element if needed
1667	newTarget = elem[0] === document ? docBody : elem,
1668
1669	// Grab metadata from element if plugin is present
1670	metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
1671
1672	// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
1673	metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
1674
1675	// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
1676	html5 = elem.data(opts.metadata.name || 'qtipopts');
1677
1678	// If we don't get an object returned attempt to parse it manualyl without parseJSON
1679	try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}
1680
1681	// Merge in and sanitize metadata
1682	config = $.extend(TRUE, {}, QTIP.defaults, opts,
1683		typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
1684		sanitizeOptions(metadata5 || metadata));
1685
1686	// Re-grab our positioning options now we've merged our metadata and set id to passed value
1687	posOptions = config.position;
1688	config.id = id;
1689
1690	// Setup missing content if none is detected
1691	if('boolean' === typeof config.content.text) {
1692		attr = elem.attr(config.content.attr);
1693
1694		// Grab from supplied attribute if available
1695		if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
1696
1697		// No valid content was found, abort render
1698		else { return FALSE; }
1699	}
1700
1701	// Setup target options
1702	if(!posOptions.container.length) { posOptions.container = docBody; }
1703	if(posOptions.target === FALSE) { posOptions.target = newTarget; }
1704	if(config.show.target === FALSE) { config.show.target = newTarget; }
1705	if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
1706	if(config.hide.target === FALSE) { config.hide.target = newTarget; }
1707	if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
1708
1709	// Ensure we only use a single container
1710	posOptions.container = posOptions.container.eq(0);
1711
1712	// Convert position corner values into x and y strings
1713	posOptions.at = new CORNER(posOptions.at, TRUE);
1714	posOptions.my = new CORNER(posOptions.my);
1715
1716	// Destroy previous tooltip if overwrite is enabled, or skip element if not
1717	if(elem.data(NAMESPACE)) {
1718		if(config.overwrite) {
1719			elem.qtip('destroy', true);
1720		}
1721		else if(config.overwrite === FALSE) {
1722			return FALSE;
1723		}
1724	}
1725
1726	// Add has-qtip attribute
1727	elem.attr(ATTR_HAS, id);
1728
1729	// Remove title attribute and store it if present
1730	if(config.suppress && (title = elem.attr('title'))) {
1731		// Final attr call fixes event delegatiom and IE default tooltip showing problem
1732		elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
1733	}
1734
1735	// Initialize the tooltip and add API reference
1736	obj = new QTip(elem, config, id, !!attr);
1737	elem.data(NAMESPACE, obj);
1738
1739	// Catch remove/removeqtip events on target element to destroy redundant tooltip
1740	elem.one('remove.qtip-'+id+' removeqtip.qtip-'+id, function() {
1741		var api; if((api = $(this).data(NAMESPACE))) { api.destroy(true); }
1742	});
1743
1744	return obj;
1745}
1746
1747// jQuery $.fn extension method
1748QTIP = $.fn.qtip = function(options, notation, newValue)
1749{
1750	var command = ('' + options).toLowerCase(), // Parse command
1751		returned = NULL,
1752		args = $.makeArray(arguments).slice(1),
1753		event = args[args.length - 1],
1754		opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;
1755
1756	// Check for API request
1757	if((!arguments.length && opts) || command === 'api') {
1758		return opts;
1759	}
1760
1761	// Execute API command if present
1762	else if('string' === typeof options) {
1763		this.each(function() {
1764			var api = $.data(this, NAMESPACE);
1765			if(!api) { return TRUE; }
1766
1767			// Cache the event if possible
1768			if(event && event.timeStamp) { api.cache.event = event; }
1769
1770			// Check for specific API commands
1771			if(notation && (command === 'option' || command === 'options')) {
1772				if(newValue !== undefined || $.isPlainObject(notation)) {
1773					api.set(notation, newValue);
1774				}
1775				else {
1776					returned = api.get(notation);
1777					return FALSE;
1778				}
1779			}
1780
1781			// Execute API command
1782			else if(api[command]) {
1783				api[command].apply(api, args);
1784			}
1785		});
1786
1787		return returned !== NULL ? returned : this;
1788	}
1789
1790	// No API commands. validate provided options and setup qTips
1791	else if('object' === typeof options || !arguments.length) {
1792		// Sanitize options first
1793		opts = sanitizeOptions($.extend(TRUE, {}, options));
1794
1795		return this.each(function(i) {
1796			var api, id;
1797
1798			// Find next available ID, or use custom ID if provided
1799			id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1800			id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
1801
1802			// Initialize the qTip and re-grab newly sanitized options
1803			api = init($(this), id, opts);
1804			if(api === FALSE) { return TRUE; }
1805			else { QTIP.api[id] = api; }
1806
1807			// Initialize plugins
1808			$.each(PLUGINS, function() {
1809				if(this.initialize === 'initialize') { this(api); }
1810			});
1811
1812			// Assign initial pre-render events
1813			api._assignInitialEvents(event);
1814		});
1815	}
1816};
1817
1818// Expose class
1819$.qtip = QTip;
1820
1821// Populated in render method
1822QTIP.api = {};
1823;$.each({
1824	/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1825	attr: function(attr, val) {
1826		if(this.length) {
1827			var self = this[0],
1828				title = 'title',
1829				api = $.data(self, 'qtip');
1830
1831			if(attr === title && api && 'object' === typeof api && api.options.suppress) {
1832				if(arguments.length < 2) {
1833					return $.attr(self, oldtitle);
1834				}
1835
1836				// If qTip is rendered and title was originally used as content, update it
1837				if(api && api.options.content.attr === title && api.cache.attr) {
1838					api.set('content.text', val);
1839				}
1840
1841				// Use the regular attr method to set, then cache the result
1842				return this.attr(oldtitle, val);
1843			}
1844		}
1845
1846		return $.fn['attr'+replaceSuffix].apply(this, arguments);
1847	},
1848
1849	/* Allow clone to correctly retrieve cached title attributes */
1850	clone: function(keepData) {
1851		var titles = $([]), title = 'title',
1852
1853		// Clone our element using the real clone method
1854		elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
1855
1856		// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
1857		if(!keepData) {
1858			elems.filter('['+oldtitle+']').attr('title', function() {
1859				return $.attr(this, oldtitle);
1860			})
1861			.removeAttr(oldtitle);
1862		}
1863
1864		return elems;
1865	}
1866}, function(name, func) {
1867	if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
1868
1869	var old = $.fn[name+replaceSuffix] = $.fn[name];
1870	$.fn[name] = function() {
1871		return func.apply(this, arguments) || old.apply(this, arguments);
1872	};
1873});
1874
1875/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
1876 * This snippet is taken directly from jQuery UI source code found here:
1877 *     http://code.jquery.com/ui/jquery-ui-git.js
1878 */
1879if(!$.ui) {
1880	$['cleanData'+replaceSuffix] = $.cleanData;
1881	$.cleanData = function( elems ) {
1882		for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
1883			if(elem.attr(ATTR_HAS)) {
1884				try { elem.triggerHandler('removeqtip'); }
1885				catch( e ) {}
1886			}
1887		}
1888		$['cleanData'+replaceSuffix].apply(this, arguments);
1889	};
1890}
1891
1892;// qTip version
1893QTIP.version = '2.2.0';
1894
1895// Base ID for all qTips
1896QTIP.nextid = 0;
1897
1898// Inactive events array
1899QTIP.inactiveEvents = INACTIVE_EVENTS;
1900
1901// Base z-index for all qTips
1902QTIP.zindex = 15000;
1903
1904// Define configuration defaults
1905QTIP.defaults = {
1906	prerender: FALSE,
1907	id: FALSE,
1908	overwrite: TRUE,
1909	suppress: TRUE,
1910	content: {
1911		text: TRUE,
1912		attr: 'title',
1913		title: FALSE,
1914		button: FALSE
1915	},
1916	position: {
1917		my: 'top left',
1918		at: 'bottom right',
1919		target: FALSE,
1920		container: FALSE,
1921		viewport: FALSE,
1922		adjust: {
1923			x: 0, y: 0,
1924			mouse: TRUE,
1925			scroll: TRUE,
1926			resize: TRUE,
1927			method: 'flipinvert flipinvert'
1928		},
1929		effect: function(api, pos, viewport) {
1930			$(this).animate(pos, {
1931				duration: 200,
1932				queue: FALSE
1933			});
1934		}
1935	},
1936	show: {
1937		target: FALSE,
1938		event: 'mouseenter',
1939		effect: TRUE,
1940		delay: 90,
1941		solo: FALSE,
1942		ready: FALSE,
1943		autofocus: FALSE
1944	},
1945	hide: {
1946		target: FALSE,
1947		event: 'mouseleave',
1948		effect: TRUE,
1949		delay: 0,
1950		fixed: FALSE,
1951		inactive: FALSE,
1952		leave: 'window',
1953		distance: FALSE
1954	},
1955	style: {
1956		classes: '',
1957		widget: FALSE,
1958		width: FALSE,
1959		height: FALSE,
1960		def: TRUE
1961	},
1962	events: {
1963		render: NULL,
1964		move: NULL,
1965		show: NULL,
1966		hide: NULL,
1967		toggle: NULL,
1968		visible: NULL,
1969		hidden: NULL,
1970		focus: NULL,
1971		blur: NULL
1972	}
1973};
1974
1975;var TIP,
1976
1977// .bind()/.on() namespace
1978TIPNS = '.qtip-tip',
1979
1980// Common CSS strings
1981MARGIN = 'margin',
1982BORDER = 'border',
1983COLOR = 'color',
1984BG_COLOR = 'background-color',
1985TRANSPARENT = 'transparent',
1986IMPORTANT = ' !important',
1987
1988// Check if the browser supports <canvas/> elements
1989HASCANVAS = !!document.createElement('canvas').getContext,
1990
1991// Invalid colour values used in parseColours()
1992INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;
1993
1994// Camel-case method, taken from jQuery source
1995// http://code.jquery.com/jquery-1.8.0.js
1996function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
1997
1998/*
1999 * Modified from Modernizr's testPropsAll()
2000 * http://modernizr.com/downloads/modernizr-latest.js
2001 */
2002var cssProps = {}, cssPrefixes = ["Webkit", "O", "Moz", "ms"];
2003function vendorCss(elem, prop) {
2004	var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
2005		props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '),
2006		cur, val, i = 0;
2007
2008	// If the property has already been mapped...
2009	if(cssProps[prop]) { return elem.css(cssProps[prop]); }
2010
2011	while((cur = props[i++])) {
2012		if((val = elem.css(cur)) !== undefined) {
2013			return cssProps[prop] = cur, val;
2014		}
2015	}
2016}
2017
2018// Parse a given elements CSS property into an int
2019function intCss(elem, prop) {
2020	return Math.ceil(parseFloat(vendorCss(elem, prop)));
2021}
2022
2023
2024// VML creation (for IE only)
2025if(!HASCANVAS) {
2026	var createVML = function(tag, props, style) {
2027		return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
2028			' style="behavior: url(#default#VML); '+(style||'')+ '" />';
2029	};
2030}
2031
2032// Canvas only definitions
2033else {
2034	var PIXEL_RATIO = window.devicePixelRatio || 1,
2035		BACKING_STORE_RATIO = (function() {
2036			var context = document.createElement('canvas').getContext('2d');
2037			return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio ||
2038					context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1;
2039		}()),
2040		SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
2041}
2042
2043
2044function Tip(qtip, options) {
2045	this._ns = 'tip';
2046	this.options = options;
2047	this.offset = options.offset;
2048	this.size = [ options.width, options.height ];
2049
2050	// Initialize
2051	this.init( (this.qtip = qtip) );
2052}
2053
2054$.extend(Tip.prototype, {
2055	init: function(qtip) {
2056		var context, tip;
2057
2058		// Create tip element and prepend to the tooltip
2059		tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip);
2060
2061		// Create tip drawing element(s)
2062		if(HASCANVAS) {
2063			// save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
2064			context = $('<canvas />').appendTo(this.element)[0].getContext('2d');
2065
2066			// Setup constant parameters
2067			context.lineJoin = 'miter';
2068			context.miterLimit = 100000;
2069			context.save();
2070		}
2071		else {
2072			context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
2073			this.element.html(context + context);
2074
2075			// Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
2076			qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns);
2077		}
2078
2079		// Bind update events
2080		qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this);
2081
2082		// Create it
2083		this.create();
2084	},
2085
2086	_swapDimensions: function() {
2087		this.size[0] = this.options.height;
2088		this.size[1] = this.options.width;
2089	},
2090	_resetDimensions: function() {
2091		this.size[0] = this.options.width;
2092		this.size[1] = this.options.height;
2093	},
2094
2095	_useTitle: function(corner) {
2096		var titlebar = this.qtip.elements.titlebar;
2097		return titlebar && (
2098			corner.y === TOP || (corner.y === CENTER && this.element.position().top + (this.size[1] / 2) + this.options.offset < titlebar.outerHeight(TRUE))
2099		);
2100	},
2101
2102	_parseCorner: function(corner) {
2103		var my = this.qtip.options.position.my;
2104
2105		// Detect corner and mimic properties
2106		if(corner === FALSE || my === FALSE) {
2107			corner = FALSE;
2108		}
2109		else if(corner === TRUE) {
2110			corner = new CORNER( my.string() );
2111		}
2112		else if(!corner.string) {
2113			corner = new CORNER(corner);
2114			corner.fixed = TRUE;
2115		}
2116
2117		return corner;
2118	},
2119
2120	_parseWidth: function(corner, side, use) {
2121		var elements = this.qtip.elements,
2122			prop = BORDER + camel(side) + 'Width';
2123
2124		return (use ? intCss(use, prop) : (
2125			intCss(elements.content, prop) ||
2126			intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2127			intCss(elements.tooltip, prop)
2128		)) || 0;
2129	},
2130
2131	_parseRadius: function(corner) {
2132		var elements = this.qtip.elements,
2133			prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';
2134
2135		return BROWSER.ie < 9 ? 0 :
2136			intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2137			intCss(elements.tooltip, prop) || 0;
2138	},
2139
2140	_invalidColour: function(elem, prop, compare) {
2141		var val = elem.css(prop);
2142		return !val || (compare && val === elem.css(compare)) || INVALID.test(val) ? FALSE : val;
2143	},
2144
2145	_parseColours: function(corner) {
2146		var elements = this.qtip.elements,
2147			tip = this.element.css('cssText', ''),
2148			borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR),
2149			colorElem = this._useTitle(corner) && elements.titlebar || elements.content,
2150			css = this._invalidColour, color = [];
2151
2152		// Attempt to detect the background colour from various elements, left-to-right precedance
2153		color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
2154			css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR);
2155
2156		// Attempt to detect the correct border side colour from various elements, left-to-right precedance
2157		color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
2158			css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide);
2159
2160		// Reset background and border colours
2161		$('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');
2162
2163		return color;
2164	},
2165
2166	_calculateSize: function(corner) {
2167		var y = corner.precedance === Y,
2168			width = this.options['width'],
2169			height = this.options['height'],
2170			isCenter = corner.abbrev() === 'c',
2171			base = (y ? width: height) * (isCenter ? 0.5 : 1),
2172			pow = Math.pow,
2173			round = Math.round,
2174			bigHyp, ratio, result,
2175
2176		smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
2177		hyp = [ (this.border / base) * smallHyp, (this.border / height) * smallHyp ];
2178
2179		hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) );
2180		hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) );
2181
2182		bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
2183		ratio = bigHyp / smallHyp;
2184
2185		result = [ round(ratio * width), round(ratio * height) ];
2186		return y ? result : result.reverse();
2187	},
2188
2189	// Tip coordinates calculator
2190	_calculateTip: function(corner, size, scale) {
2191		scale = scale || 1;
2192		size = size || this.size;
2193
2194		var width = size[0] * scale,
2195			height = size[1] * scale,
2196			width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
2197
2198		// Define tip coordinates in terms of height and width values
2199		tips = {
2200			br:	[0,0,		width,height,	width,0],
2201			bl:	[0,0,		width,0,		0,height],
2202			tr:	[0,height,	width,0,		width,height],
2203			tl:	[0,0,		0,height,		width,height],
2204			tc:	[0,height,	width2,0,		width,height],
2205			bc:	[0,0,		width,0,		width2,height],
2206			rc:	[0,0,		width,height2,	0,height],
2207			lc:	[width,0,	width,height,	0,height2]
2208		};
2209
2210		// Set common side shapes
2211		tips.lt = tips.br; tips.rt = tips.bl;
2212		tips.lb = tips.tr; tips.rb = tips.tl;
2213
2214		return tips[ corner.abbrev() ];
2215	},
2216
2217	// Tip coordinates drawer (canvas)
2218	_drawCoords: function(context, coords) {
2219		context.beginPath();
2220		context.moveTo(coords[0], coords[1]);
2221		context.lineTo(coords[2], coords[3]);
2222		context.lineTo(coords[4], coords[5]);
2223		context.closePath();
2224	},
2225
2226	create: function() {
2227		// Determine tip corner
2228		var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);
2229
2230		// If we have a tip corner...
2231		if( (this.enabled = !!this.corner && this.corner.abbrev() !== 'c') ) {
2232			// Cache it
2233			this.qtip.cache.corner = c.clone();
2234
2235			// Create it
2236			this.update();
2237		}
2238
2239		// Toggle tip element
2240		this.element.toggle(this.enabled);
2241
2242		return this.corner;
2243	},
2244
2245	update: function(corner, position) {
2246		if(!this.enabled) { return this; }
2247
2248		var elements = this.qtip.elements,
2249			tip = this.element,
2250			inner = tip.children(),
2251			options = this.options,
2252			curSize = this.size,
2253			mimic = options.mimic,
2254			round = Math.round,
2255			color, precedance, context,
2256			coords, bigCoords, translate, newSize, border, BACKING_STORE_RATIO;
2257
2258		// Re-determine tip if not already set
2259		if(!corner) { corner = this.qtip.cache.corner || this.corner; }
2260
2261		// Use corner property if we detect an invalid mimic value
2262		if(mimic === FALSE) { mimic = corner; }
2263
2264		// Otherwise inherit mimic properties from the corner object as necessary
2265		else {
2266			mimic = new CORNER(mimic);
2267			mimic.precedance = corner.precedance;
2268
2269			if(mimic.x === 'inherit') { mimic.x = corner.x; }
2270			else if(mimic.y === 'inherit') { mimic.y = corner.y; }
2271			else if(mimic.x === mimic.y) {
2272				mimic[ corner.precedance ] = corner[ corner.precedance ];
2273			}
2274		}
2275		precedance = mimic.precedance;
2276
2277		// Ensure the tip width.height are relative to the tip position
2278		if(corner.precedance === X) { this._swapDimensions(); }
2279		else { this._resetDimensions(); }
2280
2281		// Update our colours
2282		color = this.color = this._parseColours(corner);
2283
2284		// Detect border width, taking into account colours
2285		if(color[1] !== TRANSPARENT) {
2286			// Grab border width
2287			border = this.border = this._parseWidth(corner, corner[corner.precedance]);
2288
2289			// If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
2290			if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; }
2291
2292			// Set border width (use detected border width if options.border is true)
2293			this.border = border = options.border !== TRUE ? options.border : border;
2294		}
2295
2296		// Border colour was invalid, set border to zero
2297		else { this.border = border = 0; }
2298
2299		// Determine tip size
2300		newSize = this.size = this._calculateSize(corner);
2301		tip.css({
2302			width: newSize[0],
2303			height: newSize[1],
2304			lineHeight: newSize[1]+'px'
2305		});
2306
2307		// Calculate tip translation
2308		if(corner.precedance === Y) {
2309			translate = [
2310				round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2),
2311				round(mimic.y === TOP ? newSize[1] - curSize[1] : 0)
2312			];
2313		}
2314		else {
2315			translate = [
2316				round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
2317				round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2)
2318			];
2319		}
2320
2321		// Canvas drawing implementation
2322		if(HASCANVAS) {
2323			// Grab canvas context and clear/save it
2324			context = inner[0].getContext('2d');
2325			context.restore(); context.save();
2326			context.clearRect(0,0,6000,6000);
2327
2328			// Calculate coordinates
2329			coords = this._calculateTip(mimic, curSize, SCALE);
2330			bigCoords = this._calculateTip(mimic, this.size, SCALE);
2331
2332			// Set the canvas size using calculated size
2333			inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE);
2334			inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);
2335
2336			// Draw the outer-stroke tip
2337			this._drawCoords(context, bigCoords);
2338			context.fillStyle = color[1];
2339			context.fill();
2340
2341			// Draw the actual tip
2342			context.translate(translate[0] * SCALE, translate[1] * SCALE);
2343			this._drawCoords(context, coords);
2344			context.fillStyle = color[0];
2345			context.fill();
2346		}
2347
2348		// VML (IE Proprietary implementation)
2349		else {
2350			// Calculate coordinates
2351			coords = this._calculateTip(mimic);
2352
2353			// Setup coordinates string
2354			coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +
2355				',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';
2356
2357			// Setup VML-specific offset for pixel-perfection
2358			translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2359				BROWSER.ie === 8 ? 2 : 1 : 0;
2360
2361			// Set initial CSS
2362			inner.css({
2363				coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2364				antialias: ''+(mimic.string().indexOf(CENTER) > -1),
2365				left: translate[0] - (translate[2] * Number(precedance === X)),
2366				top: translate[1] - (translate[2] * Number(precedance === Y)),
2367				width: newSize[0] + border,
2368				height: newSize[1] + border
2369			})
2370			.each(function(i) {
2371				var $this = $(this);
2372
2373				// Set shape specific attributes
2374				$this[ $this.prop ? 'prop' : 'attr' ]({
2375					coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2376					path: coords,
2377					fillcolor: color[0],
2378					filled: !!i,
2379					stroked: !i
2380				})
2381				.toggle(!!(border || i));
2382
2383				// Check if border is enabled and add stroke element
2384				!i && $this.html( createVML(
2385					'stroke', 'weight="'+(border*2)+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"'
2386				) );
2387			});
2388		}
2389
2390		// Opera bug #357 - Incorrect tip position
2391		// https://github.com/Craga89/qTip2/issues/367
2392		window.opera && setTimeout(function() {
2393			elements.tip.css({
2394				display: 'inline-block',
2395				visibility: 'visible'
2396			});
2397		}, 1);
2398
2399		// Position if needed
2400		if(position !== FALSE) { this.calculate(corner, newSize); }
2401	},
2402
2403	calculate: function(corner, size) {
2404		if(!this.enabled) { return FALSE; }
2405
2406		var self = this,
2407			elements = this.qtip.elements,
2408			tip = this.element,
2409			userOffset = this.options.offset,
2410			isWidget = elements.tooltip.hasClass('ui-widget'),
2411			position = {  },
2412			precedance, corners;
2413
2414		// Inherit corner if not provided
2415		corner = corner || this.corner;
2416		precedance = corner.precedance;
2417
2418		// Determine which tip dimension to use for adjustment
2419		size = size || this._calculateSize(corner);
2420
2421		// Setup corners and offset array
2422		corners = [ corner.x, corner.y ];
2423		if(precedance === X) { corners.reverse(); }
2424
2425		// Calculate tip position
2426		$.each(corners, function(i, side) {
2427			var b, bc, br;
2428
2429			if(side === CENTER) {
2430				b = precedance === Y ? LEFT : TOP;
2431				position[ b ] = '50%';
2432				position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset;
2433			}
2434			else {
2435				b = self._parseWidth(corner, side, elements.tooltip);
2436				bc = self._parseWidth(corner, side, elements.content);
2437				br = self._parseRadius(corner);
2438
2439				position[ side ] = Math.max(-self.border, i ? bc : (userOffset + (br > b ? br : -b)));
2440			}
2441		});
2442
2443		// Adjust for tip size
2444		position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ];
2445
2446		// Set and return new position
2447		tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position);
2448		return position;
2449	},
2450
2451	reposition: function(event, api, pos, viewport) {
2452		if(!this.enabled) { return; }
2453
2454		var cache = api.cache,
2455			newCorner = this.corner.clone(),
2456			adjust = pos.adjusted,
2457			method = api.options.position.adjust.method.split(' '),
2458			horizontal = method[0],
2459			vertical = method[1] || method[0],
2460			shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
2461			offset, css = {}, props;
2462
2463		function shiftflip(direction, precedance, popposite, side, opposite) {
2464			// Horizontal - Shift or flip method
2465			if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) {
2466				newCorner.precedance = newCorner.precedance === X ? Y : X;
2467			}
2468			else if(direction !== SHIFT && adjust[side]){
2469				newCorner[precedance] = newCorner[precedance] === CENTER ?
2470					(adjust[side] > 0 ? side : opposite) : (newCorner[precedance] === side ? opposite : side);
2471			}
2472		}
2473
2474		function shiftonly(xy, side, opposite) {
2475			if(newCorner[xy] === CENTER) {
2476				css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side];
2477			}
2478			else {
2479				props = offset[opposite] !== undefined ?
2480					[ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ];
2481
2482				if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) {
2483					pos[side] -= adjust[side];
2484					shift[side] = FALSE;
2485				}
2486
2487				css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy];
2488			}
2489		}
2490
2491		// If our tip position isn't fixed e.g. doesn't adjust with viewport...
2492		if(this.corner.fixed !== TRUE) {
2493			// Perform shift/flip adjustments
2494			shiftflip(horizontal, X, Y, LEFT, RIGHT);
2495			shiftflip(vertical, Y, X, TOP, BOTTOM);
2496
2497			// Update and redraw the tip if needed (check cached details of last drawn tip)
2498			if(newCorner.string() !== cache.corner.string() && (cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left)) {
2499				this.update(newCorner, FALSE);
2500			}
2501		}
2502
2503		// Setup tip offset properties
2504		offset = this.calculate(newCorner);
2505
2506		// Readjust offset object to make it left/top
2507		if(offset.right !== undefined) { offset.left = -offset.right; }
2508		if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
2509		offset.user = this.offset;
2510
2511		// Perform shift adjustments
2512		if(shift.left = (horizontal === SHIFT && !!adjust.left)) { shiftonly(X, LEFT, RIGHT); }
2513		if(shift.top = (vertical === SHIFT && !!adjust.top)) { shiftonly(Y, TOP, BOTTOM); }
2514
2515		/*
2516		* If the tip is adjusted in both dimensions, or in a
2517		* direction that would cause it to be anywhere but the
2518		* outer border, hide it!
2519		*/
2520		this.element.css(css).toggle(
2521			!((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))
2522		);
2523
2524		// Adjust position to accomodate tip dimensions
2525		pos.left -= offset.left.charAt ? offset.user :
2526			horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0;
2527		pos.top -= offset.top.charAt ? offset.user :
2528			vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0;
2529
2530		// Cache details
2531		cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;
2532		cache.corner = newCorner.clone();
2533	},
2534
2535	destroy: function() {
2536		// Unbind events
2537		this.qtip._unbind(this.qtip.tooltip, this._ns);
2538
2539		// Remove the tip element(s)
2540		if(this.qtip.elements.tip) {
2541			this.qtip.elements.tip.find('*')
2542				.remove().end().remove();
2543		}
2544	}
2545});
2546
2547TIP = PLUGINS.tip = function(api) {
2548	return new Tip(api, api.options.style.tip);
2549};
2550
2551// Initialize tip on render
2552TIP.initialize = 'render';
2553
2554// Setup plugin sanitization options
2555TIP.sanitize = function(options) {
2556	if(options.style && 'tip' in options.style) {
2557		var opts = options.style.tip;
2558		if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }
2559		if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
2560	}
2561};
2562
2563// Add new option checks for the plugin
2564CHECKS.tip = {
2565	'^position.my|style.tip.(corner|mimic|border)$': function() {
2566		// Make sure a tip can be drawn
2567		this.create();
2568
2569		// Reposition the tooltip
2570		this.qtip.reposition();
2571	},
2572	'^style.tip.(height|width)$': function(obj) {
2573		// Re-set dimensions and redraw the tip
2574		this.size = [ obj.width, obj.height ];
2575		this.update();
2576
2577		// Reposition the tooltip
2578		this.qtip.reposition();
2579	},
2580	'^content.title|style.(classes|widget)$': function() {
2581		this.update();
2582	}
2583};
2584
2585// Extend original qTip defaults
2586$.extend(TRUE, QTIP.defaults, {
2587	style: {
2588		tip: {
2589			corner: TRUE,
2590			mimic: FALSE,
2591			width: 6,
2592			height: 6,
2593			border: TRUE,
2594			offset: 0
2595		}
2596	}
2597});
2598
2599;var MODAL, OVERLAY,
2600	MODALCLASS = 'qtip-modal',
2601	MODALSELECTOR = '.'+MODALCLASS;
2602
2603OVERLAY = function()
2604{
2605	var self = this,
2606		focusableElems = {},
2607		current, onLast,
2608		prevState, elem;
2609
2610	// Modified code from jQuery UI 1.10.0 source
2611	// http://code.jquery.com/ui/1.10.0/jquery-ui.js
2612	function focusable(element) {
2613		// Use the defined focusable checker when possible
2614		if($.expr[':'].focusable) { return $.expr[':'].focusable; }
2615
2616		var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
2617			nodeName = element.nodeName && element.nodeName.toLowerCase(),
2618			map, mapName, img;
2619
2620		if('area' === nodeName) {
2621			map = element.parentNode;
2622			mapName = map.name;
2623			if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
2624				return false;
2625			}
2626			img = $('img[usemap=#' + mapName + ']')[0];
2627			return !!img && img.is(':visible');
2628		}
2629		return (/input|select|textarea|button|object/.test( nodeName ) ?
2630				!element.disabled :
2631				'a' === nodeName ?
2632					element.href || isTabIndexNotNaN :
2633					isTabIndexNotNaN
2634			);
2635	}
2636
2637	// Focus inputs using cached focusable elements (see update())
2638	function focusInputs(blurElems) {
2639		// Blurring body element in IE causes window.open windows to unfocus!
2640		if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
2641
2642		// Focus the inputs
2643		else { focusableElems.first().focus(); }
2644	}
2645
2646	// Steal focus from elements outside tooltip
2647	function stealFocus(event) {
2648		if(!elem.is(':visible')) { return; }
2649
2650		var target = $(event.target),
2651			tooltip = current.tooltip,
2652			container = target.closest(SELECTOR),
2653			targetOnTop;
2654
2655		// Determine if input container target is above this
2656		targetOnTop = container.length < 1 ? FALSE :
2657			(parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
2658
2659		// If we're showing a modal, but focus has landed on an input below
2660		// this modal, divert focus to the first visible input in this modal
2661		// or if we can't find one... the tooltip itself
2662		if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
2663			focusInputs(target);
2664		}
2665
2666		// Detect when we leave the last focusable element...
2667		onLast = event.target === focusableElems[focusableElems.length - 1];
2668	}
2669
2670	$.extend(self, {
2671		init: function() {
2672			// Create document overlay
2673			elem = self.elem = $('<div />', {
2674				id: 'qtip-overlay',
2675				html: '<div></div>',
2676				mousedown: function() { return FALSE; }
2677			})
2678			.hide();
2679
2680			// Make sure we can't focus anything outside the tooltip
2681			$(document.body).bind('focusin'+MODALSELECTOR, stealFocus);
2682
2683			// Apply keyboard "Escape key" close handler
2684			$(document).bind('keydown'+MODALSELECTOR, function(event) {
2685				if(current && current.options.show.modal.escape && event.keyCode === 27) {
2686					current.hide(event);
2687				}
2688			});
2689
2690			// Apply click handler for blur option
2691			elem.bind('click'+MODALSELECTOR, function(event) {
2692				if(current && current.options.show.modal.blur) {
2693					current.hide(event);
2694				}
2695			});
2696
2697			return self;
2698		},
2699
2700		update: function(api) {
2701			// Update current API reference
2702			current = api;
2703
2704			// Update focusable elements if enabled
2705			if(api.options.show.modal.stealfocus !== FALSE) {
2706				focusableElems = api.tooltip.find('*').filter(function() {
2707					return focusable(this);
2708				});
2709			}
2710			else { focusableElems = []; }
2711		},
2712
2713		toggle: function(api, state, duration) {
2714			var docBody = $(document.body),
2715				tooltip = api.tooltip,
2716				options = api.options.show.modal,
2717				effect = options.effect,
2718				type = state ? 'show': 'hide',
2719				visible = elem.is(':visible'),
2720				visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip),
2721				zindex;
2722
2723			// Set active tooltip API reference
2724			self.update(api);
2725
2726			// If the modal can steal the focus...
2727			// Blur the current item and focus anything in the modal we an
2728			if(state && options.stealfocus !== FALSE) {
2729				focusInputs( $(':focus') );
2730			}
2731
2732			// Toggle backdrop cursor style on show
2733			elem.toggleClass('blurs', options.blur);
2734
2735			// Append to body on show
2736			if(state) {
2737				elem.appendTo(document.body);
2738			}
2739
2740			// Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
2741			if((elem.is(':animated') && visible === state && prevState !== FALSE) || (!state && visibleModals.length)) {
2742				return self;
2743			}
2744
2745			// Stop all animations
2746			elem.stop(TRUE, FALSE);
2747
2748			// Use custom function if provided
2749			if($.isFunction(effect)) {
2750				effect.call(elem, state);
2751			}
2752
2753			// If no effect type is supplied, use a simple toggle
2754			else if(effect === FALSE) {
2755				elem[ type ]();
2756			}
2757
2758			// Use basic fade function
2759			else {
2760				elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
2761					if(!state) { elem.hide(); }
2762				});
2763			}
2764
2765			// Reset position and detach from body on hide
2766			if(!state) {
2767				elem.queue(function(next) {
2768					elem.css({ left: '', top: '' });
2769					if(!$(MODALSELECTOR).length) { elem.detach(); }
2770					next();
2771				});
2772			}
2773
2774			// Cache the state
2775			prevState = state;
2776
2777			// If the tooltip is destroyed, set reference to null
2778			if(current.destroyed) { current = NULL; }
2779
2780			return self;
2781		}
2782	});
2783
2784	self.init();
2785};
2786OVERLAY = new OVERLAY();
2787
2788function Modal(api, options) {
2789	this.options = options;
2790	this._ns = '-modal';
2791
2792	this.init( (this.qtip = api) );
2793}
2794
2795$.extend(Modal.prototype, {
2796	init: function(qtip) {
2797		var tooltip = qtip.tooltip;
2798
2799		// If modal is disabled... return
2800		if(!this.options.on) { return this; }
2801
2802		// Set overlay reference
2803		qtip.elements.overlay = OVERLAY.elem;
2804
2805		// Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
2806		tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length);
2807
2808		// Apply our show/hide/focus modal events
2809		qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
2810			var oEvent = event.originalEvent;
2811
2812			// Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
2813			if(event.target === tooltip[0]) {
2814				if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) {
2815					try { event.preventDefault(); } catch(e) {}
2816				}
2817				else if(!oEvent || (oEvent && oEvent.type !== 'tooltipsolo')) {
2818					this.toggle(event, event.type === 'tooltipshow', duration);
2819				}
2820			}
2821		}, this._ns, this);
2822
2823		// Adjust modal z-index on tooltip focus
2824		qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
2825			// If focus was cancelled before it reached us, don't do anything
2826			if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
2827
2828			var qtips = $(MODALSELECTOR),
2829
2830			// Keep the modal's lower than other, regular qtips
2831			newIndex = QTIP.modal_zindex + qtips.length,
2832			curIndex = parseInt(tooltip[0].style.zIndex, 10);
2833
2834			// Set overlay z-index
2835			OVERLAY.elem[0].style.zIndex = newIndex - 1;
2836
2837			// Reduce modal z-index's and keep them properly ordered
2838			qtips.each(function() {
2839				if(this.style.zIndex > curIndex) {
2840					this.style.zIndex -= 1;
2841				}
2842			});
2843
2844			// Fire blur event for focused tooltip
2845			qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);
2846
2847			// Set the new z-index
2848			tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
2849
2850			// Set current
2851			OVERLAY.update(api);
2852
2853			// Prevent default handling
2854			try { event.preventDefault(); } catch(e) {}
2855		}, this._ns, this);
2856
2857		// Focus any other visible modals when this one hides
2858		qtip._bind(tooltip, 'tooltiphide', function(event) {
2859			if(event.target === tooltip[0]) {
2860				$(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
2861			}
2862		}, this._ns, this);
2863	},
2864
2865	toggle: function(event, state, duration) {
2866		// Make sure default event hasn't been prevented
2867		if(event && event.isDefaultPrevented()) { return this; }
2868
2869		// Toggle it
2870		OVERLAY.toggle(this.qtip, !!state, duration);
2871	},
2872
2873	destroy: function() {
2874		// Remove modal class
2875		this.qtip.tooltip.removeClass(MODALCLASS);
2876
2877		// Remove bound events
2878		this.qtip._unbind(this.qtip.tooltip, this._ns);
2879
2880		// Delete element reference
2881		OVERLAY.toggle(this.qtip, FALSE);
2882		delete this.qtip.elements.overlay;
2883	}
2884});
2885
2886
2887MODAL = PLUGINS.modal = function(api) {
2888	return new Modal(api, api.options.show.modal);
2889};
2890
2891// Setup sanitiztion rules
2892MODAL.sanitize = function(opts) {
2893	if(opts.show) {
2894		if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
2895		else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
2896	}
2897};
2898
2899// Base z-index for all modal tooltips (use qTip core z-index as a base)
2900QTIP.modal_zindex = QTIP.zindex - 200;
2901
2902// Plugin needs to be initialized on render
2903MODAL.initialize = 'render';
2904
2905// Setup option set checks
2906CHECKS.modal = {
2907	'^show.modal.(on|blur)$': function() {
2908		// Initialise
2909		this.destroy();
2910		this.init();
2911
2912		// Show the modal if not visible already and tooltip is visible
2913		this.qtip.elems.overlay.toggle(
2914			this.qtip.tooltip[0].offsetWidth > 0
2915		);
2916	}
2917};
2918
2919// Extend original api defaults
2920$.extend(TRUE, QTIP.defaults, {
2921	show: {
2922		modal: {
2923			on: FALSE,
2924			effect: TRUE,
2925			blur: TRUE,
2926			stealfocus: TRUE,
2927			escape: TRUE
2928		}
2929	}
2930});
2931;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
2932{
2933	var target = posOptions.target,
2934		tooltip = api.elements.tooltip,
2935		my = posOptions.my,
2936		at = posOptions.at,
2937		adjust = posOptions.adjust,
2938		method = adjust.method.split(' '),
2939		methodX = method[0],
2940		methodY = method[1] || method[0],
2941		viewport = posOptions.viewport,
2942		container = posOptions.container,
2943		cache = api.cache,
2944		adjusted = { left: 0, top: 0 },
2945		fixed, newMy, newClass, containerOffset, containerStatic,
2946		viewportWidth, viewportHeight, viewportScroll, viewportOffset;
2947
2948	// If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
2949	if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
2950		return adjusted;
2951	}
2952
2953	// Cach container details
2954	containerOffset = container.offset() || adjusted;
2955	containerStatic = container.css('position') === 'static';
2956
2957	// Cache our viewport details
2958	fixed = tooltip.css('position') === 'fixed';
2959	viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
2960	viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
2961	viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
2962	viewportOffset = viewport.offset() || adjusted;
2963
2964	// Generic calculation method
2965	function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
2966		var initialPos = position[side1],
2967			mySide = my[side],
2968			atSide = at[side],
2969			isShift = type === SHIFT,
2970			myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
2971			atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
2972			sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
2973			overflow1 = sideOffset - initialPos,
2974			overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
2975			offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
2976
2977		// shift
2978		if(isShift) {
2979			offset = (mySide === side1 ? 1 : -1) * myLength;
2980
2981			// Adjust position but keep it within viewport dimensions
2982			position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
2983			position[side1] = Math.max(
2984				-containerOffset[side1] + viewportOffset[side1],
2985				initialPos - offset,
2986				Math.min(
2987					Math.max(
2988						-containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
2989						initialPos + offset
2990					),
2991					position[side1],
2992
2993					// Make sure we don't adjust complete off the element when using 'center'
2994					mySide === 'center' ? initialPos - myLength : 1E9
2995				)
2996			);
2997
2998		}
2999
3000		// flip/flipinvert
3001		else {
3002			// Update adjustment amount depending on if using flipinvert or flip
3003			adjust *= (type === FLIPINVERT ? 2 : 0);
3004
3005			// Check for overflow on the left/top
3006			if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
3007				position[side1] -= offset + adjust;
3008				newMy.invert(side, side1);
3009			}
3010
3011			// Check for overflow on the bottom/right
3012			else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0)  ) {
3013				position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
3014				newMy.invert(side, side2);
3015			}
3016
3017			// Make sure we haven't made things worse with the adjustment and reset if so
3018			if(position[side1] < viewportScroll && -position[side1] > overflow2) {
3019				position[side1] = initialPos; newMy = my.clone();
3020			}
3021		}
3022
3023		return position[side1] - initialPos;
3024	}
3025
3026	// Set newMy if using flip or flipinvert methods
3027	if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
3028
3029	// Adjust position based onviewport and adjustment options
3030	adjusted = {
3031		left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
3032		top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0
3033	};
3034
3035	// Set tooltip position class if it's changed
3036	if(newMy && cache.lastClass !== (newClass = NAMESPACE + '-pos-' + newMy.abbrev())) {
3037		tooltip.removeClass(api.cache.lastClass).addClass( (api.cache.lastClass = newClass) );
3038	}
3039
3040	return adjusted;
3041};
3042;PLUGINS.polys = {
3043	// POLY area coordinate calculator
3044	//	Special thanks to Ed Cradock for helping out with this.
3045	//	Uses a binary search algorithm to find suitable coordinates.
3046	polygon: function(baseCoords, corner) {
3047		var result = {
3048			width: 0, height: 0,
3049			position: {
3050				top: 1e10, right: 0,
3051				bottom: 0, left: 1e10
3052			},
3053			adjustable: FALSE
3054		},
3055		i = 0, next,
3056		coords = [],
3057		compareX = 1, compareY = 1,
3058		realX = 0, realY = 0,
3059		newWidth, newHeight;
3060
3061		// First pass, sanitize coords and determine outer edges
3062		i = baseCoords.length; while(i--) {
3063			next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
3064
3065			if(next[0] > result.position.right){ result.position.right = next[0]; }
3066			if(next[0] < result.position.left){ result.position.left = next[0]; }
3067			if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
3068			if(next[1] < result.position.top){ result.position.top = next[1]; }
3069
3070			coords.push(next);
3071		}
3072
3073		// Calculate height and width from outer edges
3074		newWidth = result.width = Math.abs(result.position.right - result.position.left);
3075		newHeight = result.height = Math.abs(result.position.bottom - result.position.top);
3076
3077		// If it's the center corner...
3078		if(corner.abbrev() === 'c') {
3079			result.position = {
3080				left: result.position.left + (result.width / 2),
3081				top: result.position.top + (result.height / 2)
3082			};
3083		}
3084		else {
3085			// Second pass, use a binary search algorithm to locate most suitable coordinate
3086			while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
3087			{
3088				newWidth = Math.floor(newWidth / 2);
3089				newHeight = Math.floor(newHeight / 2);
3090
3091				if(corner.x === LEFT){ compareX = newWidth; }
3092				else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
3093				else{ compareX += Math.floor(newWidth / 2); }
3094
3095				if(corner.y === TOP){ compareY = newHeight; }
3096				else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
3097				else{ compareY += Math.floor(newHeight / 2); }
3098
3099				i = coords.length; while(i--)
3100				{
3101					if(coords.length < 2){ break; }
3102
3103					realX = coords[i][0] - result.position.left;
3104					realY = coords[i][1] - result.position.top;
3105
3106					if((corner.x === LEFT && realX >= compareX) ||
3107					(corner.x === RIGHT && realX <= compareX) ||
3108					(corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
3109					(corner.y === TOP && realY >= compareY) ||
3110					(corner.y === BOTTOM && realY <= compareY) ||
3111					(corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
3112						coords.splice(i, 1);
3113					}
3114				}
3115			}
3116			result.position = { left: coords[0][0], top: coords[0][1] };
3117		}
3118
3119		return result;
3120	},
3121
3122	rect: function(ax, ay, bx, by) {
3123		return {
3124			width: Math.abs(bx - ax),
3125			height: Math.abs(by - ay),
3126			position: {
3127				left: Math.min(ax, bx),
3128				top: Math.min(ay, by)
3129			}
3130		};
3131	},
3132
3133	_angles: {
3134		tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
3135		bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
3136		rc: 2, lc: 1, c: 0
3137	},
3138	ellipse: function(cx, cy, rx, ry, corner) {
3139		var c = PLUGINS.polys._angles[ corner.abbrev() ],
3140			rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
3141			rys = ry * Math.sin( c * Math.PI );
3142
3143		return {
3144			width: (rx * 2) - Math.abs(rxc),
3145			height: (ry * 2) - Math.abs(rys),
3146			position: {
3147				left: cx + rxc,
3148				top: cy + rys
3149			},
3150			adjustable: FALSE
3151		};
3152	},
3153	circle: function(cx, cy, r, corner) {
3154		return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
3155	}
3156};;PLUGINS.svg = function(api, svg, corner)
3157{
3158	var doc = $(document),
3159		elem = svg[0],
3160		root = $(elem.ownerSVGElement),
3161		xScale = 1, yScale = 1,
3162		complex = true,
3163		rootWidth, rootHeight,
3164		mtx, transformed, viewBox,
3165		len, next, i, points,
3166		result, position, dimensions;
3167
3168	// Ascend the parentNode chain until we find an element with getBBox()
3169	while(!elem.getBBox) { elem = elem.parentNode; }
3170	if(!elem.getBBox || !elem.parentNode) { return FALSE; }
3171
3172	// Determine dimensions where possible
3173	rootWidth = root.attr('width') || root.width() || parseInt(root.css('width'), 10);
3174	rootHeight = root.attr('height') || root.height() || parseInt(root.css('height'), 10);
3175
3176	// Add stroke characteristics to scaling
3177	var strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2;
3178	if(strokeWidth2) {
3179		xScale += strokeWidth2 / rootWidth;
3180		yScale += strokeWidth2 / rootHeight;
3181	}
3182
3183	// Determine which shape calculation to use
3184	switch(elem.nodeName) {
3185		case 'ellipse':
3186		case 'circle':
3187			result = PLUGINS.polys.ellipse(
3188				elem.cx.baseVal.value,
3189				elem.cy.baseVal.value,
3190				(elem.rx || elem.r).baseVal.value + strokeWidth2,
3191				(elem.ry || elem.r).baseVal.value + strokeWidth2,
3192				corner
3193			);
3194		break;
3195
3196		case 'line':
3197		case 'polygon':
3198		case 'polyline':
3199			// Determine points object (line has none, so mimic using array)
3200			points = elem.points || [
3201				{ x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
3202				{ x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
3203			];
3204
3205			for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
3206				next = points.getItem ? points.getItem(i) : points[i];
3207				result.push.apply(result, [next.x, next.y]);
3208			}
3209
3210			result = PLUGINS.polys.polygon(result, corner);
3211		break;
3212
3213		// Unknown shape or rectangle? Use bounding box
3214		default:
3215			result = elem.getBoundingClientRect();
3216			result = {
3217				width: result.width, height: result.height,
3218				position: {
3219					left: result.left,
3220					top: result.top
3221				}
3222			};
3223			complex = false;
3224		break;
3225	}
3226
3227	// Shortcut assignments
3228	position = result.position;
3229	root = root[0];
3230
3231	// If the shape was complex (i.e. not using bounding box calculations)
3232	if(complex) {
3233		// Convert position into a pixel value
3234		if(root.createSVGPoint) {
3235			mtx = elem.getScreenCTM();
3236			points = root.createSVGPoint();
3237
3238			points.x = position.left;
3239			points.y = position.top;
3240			transformed = points.matrixTransform( mtx );
3241			position.left = transformed.x;
3242			position.top = transformed.y;
3243		}
3244
3245		// Calculate viewBox characteristics
3246		if(root.viewBox && (viewBox = root.viewBox.baseVal) && viewBox.width && viewBox.height) {
3247			xScale *= rootWidth / viewBox.width;
3248			yScale *= rootHeight / viewBox.height;
3249		}
3250	}
3251
3252	// Adjust by scroll offset
3253	position.left += doc.scrollLeft();
3254	position.top += doc.scrollTop();
3255
3256	return result;
3257};;PLUGINS.imagemap = function(api, area, corner, adjustMethod)
3258{
3259	if(!area.jquery) { area = $(area); }
3260
3261	var shape = area.attr('shape').toLowerCase().replace('poly', 'polygon'),
3262		image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
3263		coordsString = $.trim(area.attr('coords')),
3264		coordsArray = coordsString.replace(/,$/, '').split(','),
3265		imageOffset, coords, i, next, result, len;
3266
3267	// If we can't find the image using the map...
3268	if(!image.length) { return FALSE; }
3269
3270	// Pass coordinates string if polygon
3271	if(shape === 'polygon') {
3272		result = PLUGINS.polys.polygon(coordsArray, corner);
3273	}
3274
3275	// Otherwise parse the coordinates and pass them as arguments
3276	else if(PLUGINS.polys[shape]) {
3277		for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
3278			coords.push( parseInt(coordsArray[i], 10) );
3279		}
3280
3281		result = PLUGINS.polys[shape].apply(
3282			this, coords.concat(corner)
3283		);
3284	}
3285
3286	// If no shapre calculation method was found, return false
3287	else { return FALSE; }
3288
3289	// Make sure we account for padding and borders on the image
3290	imageOffset = image.offset();
3291	imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
3292	imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);
3293
3294	// Add image position to offset coordinates
3295	result.position.left += imageOffset.left;
3296	result.position.top += imageOffset.top;
3297
3298	return result;
3299};;var IE6,
3300
3301/*
3302 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3303 * Special thanks to Brandon Aaron
3304 */
3305BGIFRAME = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
3306	' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
3307		'-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>';
3308
3309function Ie6(api, qtip) {
3310	this._ns = 'ie6';
3311	this.init( (this.qtip = api) );
3312}
3313
3314$.extend(Ie6.prototype, {
3315	_scroll : function() {
3316		var overlay = this.qtip.elements.overlay;
3317		overlay && (overlay[0].style.top = $(window).scrollTop() + 'px');
3318	},
3319
3320	init: function(qtip) {
3321		var tooltip = qtip.tooltip,
3322			scroll;
3323
3324		// Create the BGIFrame element if needed
3325		if($('select, object').length < 1) {
3326			this.bgiframe = qtip.elements.bgiframe = $(BGIFRAME).appendTo(tooltip);
3327
3328			// Update BGIFrame on tooltip move
3329			qtip._bind(tooltip, 'tooltipmove', this.adjustBGIFrame, this._ns, this);
3330		}
3331
3332		// redraw() container for width/height calculations
3333		this.redrawContainer = $('<div/>', { id: NAMESPACE+'-rcontainer' })
3334			.appendTo(document.body);
3335
3336		// Fixup modal plugin if present too
3337		if( qtip.elements.overlay && qtip.elements.overlay.addClass('qtipmodal-ie6fix') ) {
3338			qtip._bind(window, ['scroll', 'resize'], this._scroll, this._ns, this);
3339			qtip._bind(tooltip, ['tooltipshow'], this._scroll, this._ns, this);
3340		}
3341
3342		// Set dimensions
3343		this.redraw();
3344	},
3345
3346	adjustBGIFrame: function() {
3347		var tooltip = this.qtip.tooltip,
3348			dimensions = {
3349				height: tooltip.outerHeight(FALSE),
3350				width: tooltip.outerWidth(FALSE)
3351			},
3352			plugin = this.qtip.plugins.tip,
3353			tip = this.qtip.elements.tip,
3354			tipAdjust, offset;
3355
3356		// Adjust border offset
3357		offset = parseInt(tooltip.css('borderLeftWidth'), 10) || 0;
3358		offset = { left: -offset, top: -offset };
3359
3360		// Adjust for tips plugin
3361		if(plugin && tip) {
3362			tipAdjust = (plugin.corner.precedance === 'x') ? [WIDTH, LEFT] : [HEIGHT, TOP];
3363			offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
3364		}
3365
3366		// Update bgiframe
3367		this.bgiframe.css(offset).css(dimensions);
3368	},
3369
3370	// Max/min width simulator function
3371	redraw: function() {
3372		if(this.qtip.rendered < 1 || this.drawing) { return this; }
3373
3374		var tooltip = this.qtip.tooltip,
3375			style = this.qtip.options.style,
3376			container = this.qtip.options.position.container,
3377			perc, width, max, min;
3378
3379		// Set drawing flag
3380		this.qtip.drawing = 1;
3381
3382		// If tooltip has a set height/width, just set it... like a boss!
3383		if(style.height) { tooltip.css(HEIGHT, style.height); }
3384		if(style.width) { tooltip.css(WIDTH, style.width); }
3385
3386		// Simulate max/min width if not set width present...
3387		else {
3388			// Reset width and add fluid class
3389			tooltip.css(WIDTH, '').appendTo(this.redrawContainer);
3390
3391			// Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
3392			width = tooltip.width();
3393			if(width % 2 < 1) { width += 1; }
3394
3395			// Grab our max/min properties
3396			max = tooltip.css('maxWidth') || '';
3397			min = tooltip.css('minWidth') || '';
3398
3399			// Parse into proper pixel values
3400			perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
3401		max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
3402			min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
3403
3404			// Determine new dimension size based on max/min/current values
3405			width = max + min ? Math.min(Math.max(width, min), max) : width;
3406
3407			// Set the newly calculated width and remvoe fluid class
3408			tooltip.css(WIDTH, Math.round(width)).appendTo(container);
3409		}
3410
3411		// Set drawing flag
3412		this.drawing = 0;
3413
3414		return this;
3415	},
3416
3417	destroy: function() {
3418		// Remove iframe
3419		this.bgiframe && this.bgiframe.remove();
3420
3421		// Remove bound events
3422		this.qtip._unbind([window, this.qtip.tooltip], this._ns);
3423	}
3424});
3425
3426IE6 = PLUGINS.ie6 = function(api) {
3427	// Proceed only if the browser is IE6
3428	return BROWSER.ie === 6 ? new Ie6(api) : FALSE;
3429};
3430
3431IE6.initialize = 'render';
3432
3433CHECKS.ie6 = {
3434	'^content|style$': function() {
3435		this.redraw();
3436	}
3437};;}));
3438}( window, document ));
3439
3440
3441