diff --git a/addons/autolink/autolink.js b/addons/autolink/autolink.js index 5d32aadb2..f3068b9f7 100644 --- a/addons/autolink/autolink.js +++ b/addons/autolink/autolink.js @@ -31,6 +31,7 @@ }, API_AUTOLINK : function(oSender, params) { var textNode = params[0]; + if(!$(textNode).parent().length || $(textNode).parent().get(0).nodeName.toLowerCase() == 'a') return; var content = textNode.nodeValue; var dummy = $(''); @@ -44,20 +45,31 @@ }, extractTargets : function(obj) { var thisPlugin = this; + var wrap = $('.xe_content', obj); + if(wrap.length) { + this.extractTargets(wrap); + return; + } $(obj) .contents() .each(function(){ + var node_name = this.nodeName.toLowerCase(); + if($.inArray(node_name, ['a', 'pre', 'xml', 'textarea', 'input', 'select', 'option', 'code', 'script', 'style', 'iframe', 'button', 'img', 'embed', 'object', 'ins']) != -1) return; + // FIX ME : When this meanless code wasn't executed, url_regex do not run correctly. why? url_regex.exec(''); - if (!$(this).is('a,pre,xml,code,script,style,:input')) { - if (this.nodeType == 3 && url_regex.test(this.nodeValue)) { // text node - thisPlugin.targets.push(this); - } else { - - thisPlugin.extractTargets(this); - } + if(this.nodeType == 3) { // text node + var content = this.nodeValue; + + if(content.length < 5) return; + + if(!/(http|https|ftp|news|telnet|irc):\/\//i.test(content)) return; + + thisPlugin.targets.push(this); + } else { + thisPlugin.extractTargets(this); } }); } diff --git a/addons/captcha/captcha.addon.php b/addons/captcha/captcha.addon.php index 551aaa074..06d2f9816 100644 --- a/addons/captcha/captcha.addon.php +++ b/addons/captcha/captcha.addon.php @@ -18,7 +18,7 @@ if($logged_info->is_admin == 'Y' || $logged_info->is_site_admin) return; if($addon_info->target != 'all' && Context::get('is_logged')) return; - $target_acts = array('procBoardInsertDocument','procBoardInsertComment','procIssuetrackerInsertIssue','procIssuetrackerInsertHistory'); + $target_acts = array('procBoardInsertDocument','procBoardInsertComment','procIssuetrackerInsertIssue','procIssuetrackerInsertHistory','procTextyleInsertComment'); if($addon_info->apply_find_account=='apply') $target_acts[] = 'procMemberFindAccount'; if($addon_info->apply_resend_auth_mail=='apply') $target_acts[] = 'procMemberResendAuthMail'; @@ -102,7 +102,6 @@ exit(); } - Context::addJsFile('./addons/captcha/captcha.js',false); // 게시판/ 이슈트래커의 글쓰기/댓글쓰기 액션 호출시 세션 비교 diff --git a/addons/captcha/captcha.js b/addons/captcha/captcha.js index 0a3f6b018..7c60c8253 100644 --- a/addons/captcha/captcha.js +++ b/addons/captcha/captcha.js @@ -25,7 +25,7 @@ var calledArgs = null; }); $('').appendTo(captchaXE); diff --git a/addons/captcha/conf/info.xml b/addons/captcha/conf/info.xml index 3ece27bcf..71a3409ef 100644 --- a/addons/captcha/conf/info.xml +++ b/addons/captcha/conf/info.xml @@ -34,130 +34,130 @@ zero - zero - zero + zero + zero zero - zero + zero zero Captcha 표시 대상 - 应用对象 - Captchaを表示する対象 - 選擇目標 + 应用对象 + Captchaを表示する対象 + 選擇目標 Captcha Target Mục tiêu Captcha hiển thị 글/댓글 등록시 captcha가 동작할 대상을 정할 수 있습니다. 관리자는 무조건 제외됩니다 - 可以指定验证码应用对象(管理员除外)。 - 管理者を除き、書き込み・コメントを入力する際にcaptchaイメージを見せる対象を設定します。 - 除了管理員,在發表主題或評論時,設定圖形驗證應用的對象。 + 可以指定验证码应用对象(管理员除外)。 + 管理者を除き、書き込み・コメントを入力する際にcaptchaイメージを見せる対象を設定します。 + 除了管理員,在發表主題或評論時,設定圖形驗證應用的對象。 You may specify targets CAPTCHA work. It's not applied when administrator writes. Khi gửi bài, bình luận, Capcha sẽ hiển thị để xác nhận hành động của người sử dụng. Chức năng này không hoạt động với người quản lý. 로그인하지 않은 사용자 - 非登录用户 - ログインしてないユーザー - 非用戶 + 非登录用户 + ログインしてないユーザー + 非用戶 Not logged-in users Người dùng chưa đăng nhập 모든 사용자 - 所有用户 - すべてのユーザー - 所有用戶 + 所有用户 + すべてのユーザー + 所有用戶 All users Tất cả mọi người 동작 방식 - 验证方式 - 動作方式 - 行為模式 + 验证方式 + 動作方式 + 行為模式 How it works Sử dụng "1번만 동작"을 선택하시면 1번만 동작후 상태를 저장해서 다음부터 물어보지 않고 그렇지 않으면 매번 물어보게 됩니다 - "一次"就是每个IP只出现一次验证。 - 「1回だけ表示」を選択すると、最初だけ動作した後、その情報を記憶して次回からはCaptchaを見せないようにします。また、もう一つのオプションは毎回Captchaを表示します。 - 選擇"單次",產生第一次動作後,下次不會再顯示;選擇"每次"則會一直顯示。 + "一次"就是每个IP只出现一次验证。 + 「1回だけ表示」を選択すると、最初だけ動作した後、その情報を記憶して次回からはCaptchaを見せないようにします。また、もう一つのオプションは毎回Captchaを表示します。 + 選擇"單次",產生第一次動作後,下次不會再顯示;選擇"每次"則會一直顯示。 If you choose "Once", CAPTCHA works only once for the user by storing status. Otherwise, this addon would show an image every time the user writes. Nếu chọn "Chỉ một lần" thì sau lần hiển thị đó Capcha sẽ không hiển thị với người sử dụng đó nữa. 1번만 동작 - 一次 - 1回だけ表示 - 單次 + 一次 + 1回だけ表示 + 單次 Chỉ một lần once 매번 동작 - 每次 - 毎回表示 - 每次 + 每次 + 毎回表示 + 每次 every time Luôn sử dụng - 비밀번호 찾기 적용 - 应用到查找密码功能 - 비밀번호 찾기 적용 - 비밀번호 찾기 적용 - applying to an action finding account - Khi lấy lại mật khẩu - 적용으로 하시면 비밀번호 찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. - 启用此项功能可以有效地拦截以查找密码名义发送的垃圾邮件。 - 적용으로 하시면 비밀번호찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. - 적용으로 하시면 비밀번호찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. - If you set this option as apply, CAPTCHA will work for finding account action, too. - Nếu áp dụng, khi thành viên cần lấy lại mật khẩu khi lỡ quên, Capcha sẽ hiện thị để xác nhận việc này. + 비밀번호 찾기 적용 + 应用到查找密码功能 + 비밀번호 찾기 적용 + 비밀번호 찾기 적용 + applying to an action finding account + Khi lấy lại mật khẩu + 적용으로 하시면 비밀번호 찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. + 启用此项功能可以有效地拦截以查找密码名义发送的垃圾邮件。 + 적용으로 하시면 비밀번호찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. + 적용으로 하시면 비밀번호찾기 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. + If you set this option as apply, CAPTCHA will work for finding account action, too. + Nếu áp dụng, khi thành viên cần lấy lại mật khẩu khi lỡ quên, Capcha sẽ hiện thị để xác nhận việc này. 적용하지 않음 - 不启用 - 적용하지 않음 - 적용하지 않음 + 不启用 + 적용하지 않음 + 적용하지 않음 Not apply Không áp dụng 적용 - 启用 - 적용 - 적용 + 启用 + 적용 + 적용 Apply Áp dụng 인증 메일 재발송 적용 - 应用到认证邮件重新发送功能 - 인증 메일 재발송 적용 - 인증 메일 재발송 적용 + 应用到认证邮件重新发送功能 + 인증 메일 재발송 적용 + 인증 메일 재발송 적용 apply to an action resending authmail Khi lấy lại mã kích hoạt 적용으로 하시면 인증 메일 재발송 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. - 启用此项功能可以有效地拦截以重新发送认证邮件名义发送的垃圾邮件。 - 적용으로 하시면 인증 메일 재발송 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. - 적용으로 하시면 인증 메일 재발송 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. + 启用此项功能可以有效地拦截以重新发送认证邮件名义发送的垃圾邮件。 + 적용으로 하시면 인증 메일 재발송 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. + 적용으로 하시면 인증 메일 재발송 기능에도 적용되어 악의적인 봇(또는 프로그램)에 의한 메일 발송을 막을 수 있습니다. If you set this option as apply, CAPTCHA will work for resending authmail action, too. Nếu áp dụng, khi thành viên cần lấy lại mã kích hoạt thành viên, Capcha sẽ hiện thị để xác nhận việc này. 적용하지 않음 - 不启用 - 적용하지 않음 - 적용하지 않음 + 不启用 + 적용하지 않음 + 적용하지 않음 Not apply Không áp dụng 적용 - 启用 - 적용 - 적용 + 启用 + 적용 + 적용 Apply Áp dụng diff --git a/addons/resize_image/js/resize_image.js b/addons/resize_image/js/resize_image.js index b0fe66bc8..70c3d02ed 100644 --- a/addons/resize_image/js/resize_image.js +++ b/addons/resize_image/js/resize_image.js @@ -121,11 +121,14 @@ function getScreen() { prevbtn.css("visibility", (this.index>0)?"visible":"hidden"); nextbtn.css("visibility", (this.index - 以 IPhone (touch) 和 smartphone 瀏覽時會以最適當的畫面顯示。 + 用iPhone(touch)和智慧型手機瀏覽時會以最適當的畫面顯示。 IPhone(touch)など、スマートフォンからアクセスした時、最適化されたインターフェースで表示させます。 @@ -29,7 +29,7 @@ haneul haneul - haneul + haneul haneul haneul haneul diff --git a/addons/smartphone/smartphone.addon.php b/addons/smartphone/smartphone.addon.php index f4d9a3190..b95a9bb8b 100644 --- a/addons/smartphone/smartphone.addon.php +++ b/addons/smartphone/smartphone.addon.php @@ -3,7 +3,16 @@ if(Context::get('module')=='admin') return; - require_once(_XE_PATH_.'addons/smartphone/classes/smartphone.class.php'); + if($called_position == "before_module_init") + { + require(_XE_PATH_.'addons/smartphone/classes/smartphone.class.php'); + + if(Context::get('full_browse')) + { + setcookie("FullBrowse", 1); + } + } + if(!smartphoneXE::isFromSmartPhone()) return; if($called_position == 'after_module_proc' ) { diff --git a/addons/smartphone/tpl/layout.html b/addons/smartphone/tpl/layout.html index bf8cec8c0..93d7e5845 100644 --- a/addons/smartphone/tpl/layout.html +++ b/addons/smartphone/tpl/layout.html @@ -10,6 +10,7 @@
+
Top @@ -18,4 +19,8 @@ Next Menu +
+
diff --git a/addons/smartphone/tpl/smartphone.css b/addons/smartphone/tpl/smartphone.css index 3a356caf8..ea9a39ccf 100644 --- a/addons/smartphone/tpl/smartphone.css +++ b/addons/smartphone/tpl/smartphone.css @@ -22,6 +22,16 @@ body { -webkit-box-sizing: border-box; } +.smartPhoneToolBar .buttons +{ + float: left; +} + +.smartPhoneToolBar .rightbuttons +{ + float: right; margin-right:5px; margin-top:5px; +} + .smartPhoneTitleBar h1 { white-space:nowrap; overflow: hidden; @@ -187,3 +197,4 @@ body { letter-spacing:0px; } + diff --git a/addons/smartphone/tpl/smartphone.js b/addons/smartphone/tpl/smartphone.js index d0b4a13ca..e27634d03 100644 --- a/addons/smartphone/tpl/smartphone.js +++ b/addons/smartphone/tpl/smartphone.js @@ -1,5 +1,9 @@ var xeSmartMenu = null; var xeSmartUpperMenu = null; +function setFullBrowse() { + location.href = current_url.setQuery('full_browse',1); +} + function showXEMenu() { if(!xeSmartMenu) { diff --git a/classes/db/DBCubrid.class.php b/classes/db/DBCubrid.class.php index 803f7195c..41eb81beb 100644 --- a/classes/db/DBCubrid.class.php +++ b/classes/db/DBCubrid.class.php @@ -584,28 +584,28 @@ foreach($output->columns as $key => $val) { $name = $val['name']; - $click_count = '%s'; - if($val['click_count'] && count($output->conditions)>0){ - $click_count = 'incr(%s)'; - } + $click_count = '%s'; + if($val['click_count'] && count($output->conditions)>0) { + $click_count = 'incr(%s)'; + } $alias = $val['alias'] ? sprintf('"%s"',$val['alias']) : null; if(substr($name,-1) == '*') { $column_list[] = $name; } elseif(strpos($name,'.')===false && strpos($name,'(')===false) { - $name = sprintf($click_count,$name); - if($alias) $column_list[] = sprintf('%s as %s', $name, $alias); - else $column_list[] = sprintf('%s',$name); + $name = sprintf($click_count,$name); + if($alias) $column_list[] = sprintf('"%s" as %s', $name, $alias); + else $column_list[] = sprintf('"%s"',$name); } else { if(strpos($name,'.')!=false) { list($prefix, $name) = explode('.',$name); $prefix = sprintf('"%s"',$prefix); $name = ($name == '*') ? $name : sprintf('"%s"',$name); - $column_list[] = sprintf($click_count,sprintf('%s.%s', $prefix, $name)) . ($alias ? sprintf(' as %s',$alias) : ''); + $column_list[] = sprintf($click_count,sprintf('%s.%s', $prefix, $name)) . ($alias ? sprintf(' as %s',$alias) : ''); } else { - $column_list[] = sprintf($click_count,$name) . ($alias ? sprintf(' as %s',$alias) : ''); + $column_list[] = sprintf($click_count,$name) . ($alias ? sprintf(' as %s',$alias) : ''); } } } diff --git a/classes/db/DBMysqli.class.php b/classes/db/DBMysqli.class.php index 826ddd103..6145de187 100644 --- a/classes/db/DBMysqli.class.php +++ b/classes/db/DBMysqli.class.php @@ -73,15 +73,15 @@ // db 정보가 없으면 무시 if(!$this->hostname || !$this->userid || !$this->password || !$this->database) return; - // 접속시도 + // 접속시도 if($this->port){ - $this->fd = mysqli_connect($this->hostname, $this->userid, $this->password, $this->database, $this->port); + $this->fd = @mysqli_connect($this->hostname, $this->userid, $this->password, $this->database, $this->port); }else{ - $this->fd = mysqli_connect($this->hostname, $this->userid, $this->password, $this->database); + $this->fd = @mysqli_connect($this->hostname, $this->userid, $this->password, $this->database); } $error = mysqli_connect_errno(); if($error) { - $this->setError($error); + $this->setError($error,mysqli_connect_error()); return; } mysqli_set_charset($this->fd,'utf8'); @@ -421,22 +421,20 @@ $table_list[] = '`'.$this->prefix.$val.'`'; } - // 컬럼 정리 + // 컬럼 정리 foreach($output->columns as $key => $val) { $name = $val['name']; $value = $val['value']; - $value_list = array(); if($output->column_type[$name]!='number') { $value = "'".$this->addQuotes($value)."'"; if(!$value) $value = 'null'; } elseif(!$value || is_numeric($value)) $value = (int)$value; - $column_list[] = '`'.$name.'`'; $value_list[] = $value; } - $query = sprintf("insert into %s (%s) values (%s);", implode(',',$table_list), implode(',',$column_list), implode(',', $value_list)); + $query = sprintf("insert into %s (%s) values (%s)", implode(',',$table_list), implode(',',$column_list), implode(',', $value_list)); return $this->_query($query); } @@ -450,7 +448,7 @@ $table_list[] = '`'.$this->prefix.$val.'` as '.$key; } - // 컬럼 정리 + // 컬럼 정리 foreach($output->columns as $key => $val) { if(!isset($val['value'])) continue; $name = $val['name']; diff --git a/classes/file/FileHandler.class.php b/classes/file/FileHandler.class.php index c8a94369f..e292cf59d 100644 --- a/classes/file/FileHandler.class.php +++ b/classes/file/FileHandler.class.php @@ -62,6 +62,7 @@ * @return none **/ function copyFile($source, $target, $force='Y'){ + setlocale(LC_CTYPE, 'en_US.UTF8', 'ko_KR.UTF8'); $source = FileHandler::getRealPath($source); $target_dir = FileHandler::getRealPath(dirname($target)); $target = basename($target); diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 890abc6d7..c637016e5 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -51,7 +51,7 @@ if(!$module_srl) $this->module_srl = (int)Context::get('module_srl'); else $this->module_srl = (int)$module_srl; - $this->entry = Context::get('entry'); + $this->entry = Context::convertEncodingStr(Context::get('entry')); // Validate variables to prevent XSS if($this->module && !preg_match("/^([a-z0-9\_\-]+)$/i",$this->module)) die(Context::getLang("msg_invalid_request")); diff --git a/common/js/plugins/qtip/jquery.qtip.debug.js b/common/js/plugins/qtip/jquery.qtip.debug.js new file mode 100644 index 000000000..5b9801140 --- /dev/null +++ b/common/js/plugins/qtip/jquery.qtip.debug.js @@ -0,0 +1,142 @@ +/** + * jquery.qtip.debug. The jQuery tooltip plugin debugger + * + * Debug mode logs all tooltip events and error messages to the + * global $.fn.qtip.log object, as well as creates an array of + * references to all tooltips on the page. + * + * If a console is present, such as firebug (http://getfirebug.com), + * Opera's inbuilt Drangonfly, or IE8's internal Web Developer tool, + * errors will be reported to the console based on your set report + * mask, which is explained in details below. + * + * + * Copyright (c) 2009 Craig Thompson + * http://craigsworks.com + * + * Licensed under MIT + * http://www.opensource.org/licenses/mit-license.php + * + * Launch : February 2009 + * Version : 1.0.0-rc3 + * Released: Friday 27th April, 2009 - 23:30 + */ + +/* DEBUG MODE - OFF BY DEFAULT - set to true to enable debugging */ +$.fn.qtip.debug = true; + + +if($.fn.qtip.debug) +{ + // Create new debug constants + $.fn.qtip.constants = + { + /* Error messages */ + NO_TOOLTIP_PRESENT: 'No tooltip is present on the selected element. Cannot proceed with API call.', + TOOLTIP_NOT_RENDERED: 'Tooltip has not yet been rendered! Cannot proceed with API call.', + NO_VALID_CONTENT: 'No valid content was provided in the options. Using blank content.', + TOOLTIP_IS_DISABLED: 'Tooltip is disabled, cannot proceed with API call.', + TOOLTIP_ALREADY_DISABLED: 'Tooltip is already disabled.', + TOOLTIP_ALREADY_ENABLED: 'Tooltip is already enabled.', + CANVAS_VML_NOT_SUPPORTED: 'Niether canvas or VML are supported, corner and tips will not be drawn.', + STYLE_NOT_DEFINED: 'No such style is defined in the global styles object.', + INVALID_AREA_SHAPE: 'The AREA element used as your target has an invalid area shape. Possible values are: rect, circle, poly.', + CANNOT_FOCUS_STATIC: 'Focusing static elements is not possible as they don\'t have z-index properties.', + CANNOT_POSITION_STATIC: 'Positioning static elements is not possible as they don\'t have left or top properties.', + NO_CONTENT_PROVIDED: 'You must specify some content with which to update.', + + /* Event messages */ + EVENT_RENDERED: '(Event) Tooltip has been rendered.', + EVENT_SHOWN: '(Event) Tooltip has been shown.', + EVENT_HIDDEN: '(Event) Tooltip has been hidden.', + EVENT_FOCUSED: '(Event) Tooltip has been focused.', + EVENT_DISABLED: '(Event) Tooltip has been disabled.', + EVENT_ENABLED: '(Event) Tooltip has been enabled.', + EVENT_DESTROYED: '(Event) Tooltip has been destroyed.', + EVENT_POSITION_UPDATED: '(Event) Tooltip position has been updated.', + EVENT_CONTENT_UPDATED: '(Event) Tooltip contents have been updated.', + EVENT_CONTENT_LOADED: '(Event) Tooltip contents have been loaded from specified URL.', + EVENT_TITLE_UPDATED: '(Event) Tooltip title has been updated.', + EVENT_STYLE_UPDATED: '(Event) Tooltip style has been updated.', + EVENT_WIDTH_UPDATED: '(Event) Tooltip width has been updated.' + }; + + // Define qTip log object + $.fn.qtip.log = { + /* CONSOLE REPORTING MASK + To use this functionality you need a console installed, such as firebug: + http://getfirebug.com + + This mask determines what errors are reported to the console. Possible values are: + 7 = Errors, warnings and messages + 6 = Errors and warnings + 5 = Errors and messages + 4 = Errors only + 3 = Warnings and messages + 2 = Warnings only + 1 = Messages only + */ + report: 7, + + /* DO NOT ALTER ANYTHING BELOW HERE! */ + qtips: [], + messages: [], + errors: [], + + /* Error handler function */ + error: function(type, message, method) + { + var self = this; + + // Format type string + switch(type) + { + case 1: + var typeString = 'info'; + var addTo = 'messages'; + break; + + case 2: + var typeString = 'warn'; + var addTo = 'messages'; + break; + + default: case 4: + var typeString = 'error'; + var addTo = 'errors'; + break; + } + + // Format time + var DateObj = new Date(); + var time = DateObj.getHours() + ':' + + DateObj.getMinutes() + ':' + + DateObj.getSeconds(); + + // Log the error into the log array + $.fn.qtip.log[addTo].push({ + time: time, + message: message, + + api: self, + callee: self[method] || method || null + }); + + // If debug mode is enabled, display the error + if($.fn.qtip.log.report & type > 0) + { + var logMessage = 'qTip ' + + ((typeof self.id !== 'undefined') ? '['+self.id+']' : '') + + ': ' + message; + + // Check console is present + if(window.console) window.console[typeString](logMessage); + + // Check for Opera Dragonfly + else if($.browser.opera && window.opera.postError) window.opera.postError(logMessage); + } + + return self; + } + }; +}; diff --git a/common/js/plugins/qtip/jquery.qtip.js b/common/js/plugins/qtip/jquery.qtip.js new file mode 100644 index 000000000..405424f2d --- /dev/null +++ b/common/js/plugins/qtip/jquery.qtip.js @@ -0,0 +1,2149 @@ +/*! + * jquery.qtip. The jQuery tooltip plugin + * + * Copyright (c) 2009 Craig Thompson + * http://craigsworks.com + * + * Licensed under MIT + * http://www.opensource.org/licenses/mit-license.php + * + * Launch : February 2009 + * Version : 1.0.0-rc3 + * Released: Tuesday 12th May, 2009 - 00:00 + * Debug: jquery.qtip.debug.js + */ +(function($) +{ + // Implementation + $.fn.qtip = function(options, blanket) + { + var i, id, interfaces, opts, obj, command, config, api; + + // Return API / Interfaces if requested + if(typeof options == 'string') + { + // Make sure API data exists if requested + if(typeof $(this).data('qtip') !== 'object') + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_TOOLTIP_PRESENT, false); + + // Return requested object + if(options == 'api') + return $(this).data('qtip').interfaces[ $(this).data('qtip').current ]; + else if(options == 'interfaces') + return $(this).data('qtip').interfaces; + } + + // Validate provided options + else + { + // Set null options object if no options are provided + if(!options) options = {}; + + // Sanitize option data + if(typeof options.content !== 'object' || (options.content.jquery && options.content.length > 0)) options.content = { text: options.content }; + if(typeof options.content.title !== 'object') options.content.title = { text: options.content.title }; + if(typeof options.position !== 'object') options.position = { corner: options.position }; + if(typeof options.position.corner !== 'object') options.position.corner = { target: options.position.corner, tooltip: options.position.corner }; + if(typeof options.show !== 'object') options.show = { when: options.show }; + if(typeof options.show.when !== 'object') options.show.when = { event: options.show.when }; + if(typeof options.show.effect !== 'object') options.show.effect = { type: options.show.effect }; + if(typeof options.hide !== 'object') options.hide = { when: options.hide }; + if(typeof options.hide.when !== 'object') options.hide.when = { event: options.hide.when }; + if(typeof options.hide.effect !== 'object') options.hide.effect = { type: options.hide.effect }; + if(typeof options.style !== 'object') options.style = { name: options.style }; + options.style = sanitizeStyle(options.style); + + // Build main options object + opts = $.extend(true, {}, $.fn.qtip.defaults, options); + + // Inherit all style properties into one syle object and include original options + opts.style = buildStyle.call({ options: opts }, opts.style); + opts.user = $.extend(true, {}, options); + }; + + // Iterate each matched element + return $(this).each(function() // Return original elements as per jQuery guidelines + { + // Check for API commands + if(typeof options == 'string') + { + command = options.toLowerCase(); + interfaces = $(this).qtip('interfaces'); + + // Make sure API data exists$('.qtip').qtip('destroy') + if(typeof interfaces == 'object') + { + // Check if API call is a BLANKET DESTROY command + if(blanket === true && command == 'destroy') + while(interfaces.length > 0) interfaces[interfaces.length-1].destroy(); + + // API call is not a BLANKET DESTROY command + else + { + // Check if supplied command effects this tooltip only (NOT BLANKET) + if(blanket !== true) interfaces = [ $(this).qtip('api') ]; + + // Execute command on chosen qTips + for(i = 0; i < interfaces.length; i++) + { + // Destroy command doesn't require tooltip to be rendered + if(command == 'destroy') interfaces[i].destroy(); + + // Only call API if tooltip is rendered and it wasn't a destroy call + else if(interfaces[i].status.rendered === true) + { + if(command == 'show') interfaces[i].show(); + else if(command == 'hide') interfaces[i].hide(); + else if(command == 'focus') interfaces[i].focus(); + else if(command == 'disable') interfaces[i].disable(true); + else if(command == 'enable') interfaces[i].disable(false); + }; + }; + }; + }; + } + + // No API commands, continue with qTip creation + else + { + // Create unique configuration object + config = $.extend(true, {}, opts); + config.hide.effect.length = opts.hide.effect.length; + config.show.effect.length = opts.show.effect.length; + + // Sanitize target options + if(config.position.container === false) config.position.container = $(document.body); + if(config.position.target === false) config.position.target = $(this); + if(config.show.when.target === false) config.show.when.target = $(this); + if(config.hide.when.target === false) config.hide.when.target = $(this); + + // Determine tooltip ID (Reuse array slots if possible) + id = $.fn.qtip.interfaces.length; + for(i = 0; i < id; i++) + { + if(typeof $.fn.qtip.interfaces[i] == 'undefined'){ id = i; break; }; + }; + + // Instantiate the tooltip + obj = new qTip($(this), config, id); + + // Add API references + $.fn.qtip.interfaces[id] = obj; + + // Check if element already has qTip data assigned + if(typeof $(this).data('qtip') == 'object') + { + // Set new current interface id + if(typeof $(this).attr('qtip') === 'undefined') + $(this).data('qtip').current = $(this).data('qtip').interfaces.length; + + // Push new API interface onto interfaces array + $(this).data('qtip').interfaces.push(obj); + } + + // No qTip data is present, create now + else $(this).data('qtip', { current: 0, interfaces: [obj] }); + + // If prerendering is disabled, create tooltip on showEvent + if(config.content.prerender === false && config.show.when.event !== false && config.show.ready !== true) + { + config.show.when.target.bind(config.show.when.event+'.qtip-'+id+'-create', { qtip: id }, function(event) + { + // Retrieve API interface via passed qTip Id + api = $.fn.qtip.interfaces[ event.data.qtip ]; + + // Unbind show event and cache mouse coords + api.options.show.when.target.unbind(api.options.show.when.event+'.qtip-'+event.data.qtip+'-create'); + api.cache.mouse = { x: event.pageX, y: event.pageY }; + + // Render tooltip and start the event sequence + construct.call( api ); + api.options.show.when.target.trigger(api.options.show.when.event); + }); + } + + // Prerendering is enabled, create tooltip now + else + { + // Set mouse position cache to top left of the element + obj.cache.mouse = { + x: config.show.when.target.offset().left, + y: config.show.when.target.offset().top + }; + + // Construct the tooltip + construct.call(obj); + } + }; + }); + }; + + // Instantiator + function qTip(target, options, id) + { + // Declare this reference + var self = this; + + // Setup class attributes + self.id = id; + self.options = options; + self.status = { + animated: false, + rendered: false, + disabled: false, + focused: false + }; + self.elements = { + target: target.addClass(self.options.style.classes.target), + tooltip: null, + wrapper: null, + content: null, + contentWrapper: null, + title: null, + button: null, + tip: null, + bgiframe: null + }; + self.cache = { + mouse: {}, + position: {}, + toggle: 0 + }; + self.timers = {}; + + // Define exposed API methods + $.extend(self, self.options.api, + { + show: function(event) + { + var returned, solo; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'show'); + + // Only continue if element is visible + if(self.elements.tooltip.css('display') !== 'none') return self; + + // Clear animation queue + self.elements.tooltip.stop(true, false); + + // Call API method and if return value is false, halt + returned = self.beforeShow.call(self, event); + if(returned === false) return self; + + // Define afterShow callback method + function afterShow() + { + // Call API method and focus if it isn't static + if(self.options.position.type !== 'static') self.focus(); + self.onShow.call(self, event); + + // Prevent antialias from disappearing in IE7 by removing filter attribute + if($.browser.msie) self.elements.tooltip.get(0).style.removeAttribute('filter'); + }; + + // Maintain toggle functionality if enabled + self.cache.toggle = 1; + + // Update tooltip position if it isn't static + if(self.options.position.type !== 'static') + self.updatePosition(event, (self.options.show.effect.length > 0)); + + // Hide other tooltips if tooltip is solo + if(typeof self.options.show.solo == 'object') solo = $(self.options.show.solo); + else if(self.options.show.solo === true) solo = $('div.qtip').not(self.elements.tooltip); + if(solo) solo.each(function(){ if($(this).qtip('api').status.rendered === true) $(this).qtip('api').hide(); }); + + // Show tooltip + if(typeof self.options.show.effect.type == 'function') + { + self.options.show.effect.type.call(self.elements.tooltip, self.options.show.effect.length); + self.elements.tooltip.queue(function(){ afterShow(); $(this).dequeue(); }); + } + else + { + switch(self.options.show.effect.type.toLowerCase()) + { + case 'fade': + self.elements.tooltip.fadeIn(self.options.show.effect.length, afterShow); + break; + case 'slide': + self.elements.tooltip.slideDown(self.options.show.effect.length, function() + { + afterShow(); + if(self.options.position.type !== 'static') self.updatePosition(event, true); + }); + break; + case 'grow': + self.elements.tooltip.show(self.options.show.effect.length, afterShow); + break; + default: + self.elements.tooltip.show(null, afterShow); + break; + }; + + // Add active class to tooltip + self.elements.tooltip.addClass(self.options.style.classes.active); + }; + + // Log event and return + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_SHOWN, 'show'); + }, + + hide: function(event) + { + var returned; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'hide'); + + // Only continue if element is visible + else if(self.elements.tooltip.css('display') === 'none') return self; + + // Stop show timer and animation queue + clearTimeout(self.timers.show); + self.elements.tooltip.stop(true, false); + + // Call API method and if return value is false, halt + returned = self.beforeHide.call(self, event); + if(returned === false) return self; + + // Define afterHide callback method + function afterHide(){ self.onHide.call(self, event); }; + + // Maintain toggle functionality if enabled + self.cache.toggle = 0; + + // Hide tooltip + if(typeof self.options.hide.effect.type == 'function') + { + self.options.hide.effect.type.call(self.elements.tooltip, self.options.hide.effect.length); + self.elements.tooltip.queue(function(){ afterHide(); $(this).dequeue(); }); + } + else + { + switch(self.options.hide.effect.type.toLowerCase()) + { + case 'fade': + self.elements.tooltip.fadeOut(self.options.hide.effect.length, afterHide); + break; + case 'slide': + self.elements.tooltip.slideUp(self.options.hide.effect.length, afterHide); + break; + case 'grow': + self.elements.tooltip.hide(self.options.hide.effect.length, afterHide); + break; + default: + self.elements.tooltip.hide(null, afterHide); + break; + }; + + // Remove active class to tooltip + self.elements.tooltip.removeClass(self.options.style.classes.active); + }; + + // Log event and return + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_HIDDEN, 'hide'); + }, + + updatePosition: function(event, animate) + { + var i, target, tooltip, coords, mapName, imagePos, newPosition, ieAdjust, ie6Adjust, borderAdjust, mouseAdjust, offset, curPosition, returned + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updatePosition'); + + // If tooltip is static, return + else if(self.options.position.type == 'static') + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_POSITION_STATIC, 'updatePosition'); + + // Define property objects + target = { + position: { left: 0, top: 0 }, + dimensions: { height: 0, width: 0 }, + corner: self.options.position.corner.target + }; + tooltip = { + position: self.getPosition(), + dimensions: self.getDimensions(), + corner: self.options.position.corner.tooltip + }; + + // Target is an HTML element + if(self.options.position.target !== 'mouse') + { + // If the HTML element is AREA, calculate position manually + if(self.options.position.target.get(0).nodeName.toLowerCase() == 'area') + { + // Retrieve coordinates from coords attribute and parse into integers + coords = self.options.position.target.attr('coords').split(','); + for(i = 0; i < coords.length; i++) coords[i] = parseInt(coords[i]); + + // Setup target position object + mapName = self.options.position.target.parent('map').attr('name'); + imagePos = $('img[usemap="#'+mapName+'"]:first').offset(); + target.position = { + left: Math.floor(imagePos.left + coords[0]), + top: Math.floor(imagePos.top + coords[1]) + }; + + // Determine width and height of the area + switch(self.options.position.target.attr('shape').toLowerCase()) + { + case 'rect': + target.dimensions = { + width: Math.ceil(Math.abs(coords[2] - coords[0])), + height: Math.ceil(Math.abs(coords[3] - coords[1])) + }; + break; + + case 'circle': + target.dimensions = { + width: coords[2] + 1, + height: coords[2] + 1 + }; + break; + + case 'poly': + target.dimensions = { + width: coords[0], + height: coords[1] + }; + + for(i = 0; i < coords.length; i++) + { + if(i % 2 == 0) + { + if(coords[i] > target.dimensions.width) + target.dimensions.width = coords[i]; + if(coords[i] < coords[0]) + target.position.left = Math.floor(imagePos.left + coords[i]); + } + else + { + if(coords[i] > target.dimensions.height) + target.dimensions.height = coords[i]; + if(coords[i] < coords[1]) + target.position.top = Math.floor(imagePos.top + coords[i]); + }; + }; + + target.dimensions.width = target.dimensions.width - (target.position.left - imagePos.left); + target.dimensions.height = target.dimensions.height - (target.position.top - imagePos.top); + break; + + default: + return $.fn.qtip.log.error.call(self, 4, $.fn.qtip.constants.INVALID_AREA_SHAPE, 'updatePosition'); + break; + }; + + // Adjust position by 2 pixels (Positioning bug?) + target.dimensions.width -= 2; target.dimensions.height -= 2; + } + + // Target is the document + else if(self.options.position.target.add(document.body).length === 1) + { + target.position = { left: $(document).scrollLeft(), top: $(document).scrollTop() }; + target.dimensions = { height: $(window).height(), width: $(window).width() }; + } + + // Target is a regular HTML element, find position normally + else + { + // Check if the target is another tooltip. If its animated, retrieve position from newPosition data + if(typeof self.options.position.target.attr('qtip') !== 'undefined') + target.position = self.options.position.target.qtip('api').cache.position; + else + target.position = self.options.position.target.offset(); + + // Setup dimensions objects + target.dimensions = { + height: self.options.position.target.outerHeight(), + width: self.options.position.target.outerWidth() + }; + }; + + // Calculate correct target corner position + newPosition = $.extend({}, target.position); + if(target.corner.search(/right/i) !== -1) + newPosition.left += target.dimensions.width; + + if(target.corner.search(/bottom/i) !== -1) + newPosition.top += target.dimensions.height; + + if(target.corner.search(/((top|bottom)Middle)|center/) !== -1) + newPosition.left += (target.dimensions.width / 2); + + if(target.corner.search(/((left|right)Middle)|center/) !== -1) + newPosition.top += (target.dimensions.height / 2); + } + + // Mouse is the target, set position to current mouse coordinates + else + { + // Setup target position and dimensions objects + target.position = newPosition = { left: self.cache.mouse.x, top: self.cache.mouse.y }; + target.dimensions = { height: 1, width: 1 }; + }; + + // Calculate correct target corner position + if(tooltip.corner.search(/right/i) !== -1) + newPosition.left -= tooltip.dimensions.width; + + if(tooltip.corner.search(/bottom/i) !== -1) + newPosition.top -= tooltip.dimensions.height; + + if(tooltip.corner.search(/((top|bottom)Middle)|center/) !== -1) + newPosition.left -= (tooltip.dimensions.width / 2); + + if(tooltip.corner.search(/((left|right)Middle)|center/) !== -1) + newPosition.top -= (tooltip.dimensions.height / 2); + + // Setup IE adjustment variables (Pixel gap bugs) + ieAdjust = ($.browser.msie) ? 1 : 0; // And this is why I hate IE... + ie6Adjust = ($.browser.msie && parseInt($.browser.version.charAt(0)) === 6) ? 1 : 0; // ...and even more so IE6! + + // Adjust for border radius + if(self.options.style.border.radius > 0) + { + if(tooltip.corner.search(/Left/) !== -1) + newPosition.left -= self.options.style.border.radius; + else if(tooltip.corner.search(/Right/) !== -1) + newPosition.left += self.options.style.border.radius; + + if(tooltip.corner.search(/Top/) !== -1) + newPosition.top -= self.options.style.border.radius; + else if(tooltip.corner.search(/Bottom/) !== -1) + newPosition.top += self.options.style.border.radius; + }; + + // IE only adjustments (Pixel perfect!) + if(ieAdjust) + { + if(tooltip.corner.search(/top/) !== -1) + newPosition.top -= ieAdjust + else if(tooltip.corner.search(/bottom/) !== -1) + newPosition.top += ieAdjust + + if(tooltip.corner.search(/left/) !== -1) + newPosition.left -= ieAdjust + else if(tooltip.corner.search(/right/) !== -1) + newPosition.left += ieAdjust + + if(tooltip.corner.search(/leftMiddle|rightMiddle/) !== -1) + newPosition.top -= 1 + }; + + // If screen adjustment is enabled, apply adjustments + if(self.options.position.adjust.screen === true) + newPosition = screenAdjust.call(self, newPosition, target, tooltip); + + // If mouse is the target, prevent tooltip appearing directly under the mouse + if(self.options.position.target === 'mouse' && self.options.position.adjust.mouse === true) + { + if(self.options.position.adjust.screen === true && self.elements.tip) + mouseAdjust = self.elements.tip.attr('rel'); + else + mouseAdjust = self.options.position.corner.tooltip; + + newPosition.left += (mouseAdjust.search(/right/i) !== -1) ? -6 : 6; + newPosition.top += (mouseAdjust.search(/bottom/i) !== -1) ? -6 : 6; + } + + // Initiate bgiframe plugin in IE6 if tooltip overlaps a select box or object element + if(!self.elements.bgiframe && $.browser.msie && parseInt($.browser.version.charAt(0)) == 6) + { + $('select, object').each(function() + { + offset = $(this).offset(); + offset.bottom = offset.top + $(this).height(); + offset.right = offset.left + $(this).width(); + + if(newPosition.top + tooltip.dimensions.height >= offset.top + && newPosition.left + tooltip.dimensions.width >= offset.left) + bgiframe.call(self); + }); + }; + + // Add user xy adjustments + newPosition.left += self.options.position.adjust.x; + newPosition.top += self.options.position.adjust.y; + + // Set new tooltip position if its moved, animate if enabled + curPosition = self.getPosition(); + if(newPosition.left != curPosition.left || newPosition.top != curPosition.top) + { + // Call API method and if return value is false, halt + returned = self.beforePositionUpdate.call(self, event); + if(returned === false) return self; + + // Cache new position + self.cache.position = newPosition; + + // Check if animation is enabled + if(animate === true) + { + // Set animated status + self.status.animated = true; + + // Animate and reset animated status on animation end + self.elements.tooltip.animate(newPosition, 200, 'swing', function(){ self.status.animated = false }); + } + + // Set new position via CSS + else self.elements.tooltip.css(newPosition); + + // Call API method and log event if its not a mouse move + self.onPositionUpdate.call(self, event); + if(typeof event !== 'undefined' && event.type && event.type !== 'mousemove') + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_POSITION_UPDATED, 'updatePosition'); + }; + + return self; + }, + + updateWidth: function(newWidth) + { + var hidden; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateWidth'); + + // Make sure supplied width is a number and if not, return + else if(newWidth && typeof newWidth !== 'number') + return $.fn.qtip.log.error.call(self, 2, 'newWidth must be of type number', 'updateWidth'); + + // Setup elements which must be hidden during width update + hidden = self.elements.contentWrapper.siblings().add(self.elements.tip).add(self.elements.button); + + // Calculate the new width if one is not supplied + if(!newWidth) + { + // Explicit width is set + if(typeof self.options.style.width.value == 'number') + newWidth = self.options.style.width.value; + + // No width is set, proceed with auto detection + else + { + // Set width to auto initally to determine new width and hide other elements + self.elements.tooltip.css({ width: 'auto' }); + hidden.hide(); + + // Set position and zoom to defaults to prevent IE hasLayout bug + if($.browser.msie) + self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: 'normal' }); + + // Set the new width + newWidth = self.getDimensions().width + 1; + + // Make sure its within the maximum and minimum width boundries + if(!self.options.style.width.value) + { + if(newWidth > self.options.style.width.max) newWidth = self.options.style.width.max + if(newWidth < self.options.style.width.min) newWidth = self.options.style.width.min + }; + }; + }; + + // Adjust newWidth by 1px if width is odd (IE6 rounding bug fix) + if(newWidth % 2 !== 0) newWidth -= 1; + + // Set the new calculated width and unhide other elements + self.elements.tooltip.width(newWidth); + hidden.show(); + + // Set the border width, if enabled + if(self.options.style.border.radius) + { + self.elements.tooltip.find('.qtip-betweenCorners').each(function(i) + { + $(this).width(newWidth - (self.options.style.border.radius * 2)); + }) + }; + + // IE only adjustments + if($.browser.msie) + { + // Reset position and zoom to give the wrapper layout (IE hasLayout bug) + self.elements.wrapper.add(self.elements.contentWrapper.children()).css({ zoom: '1' }); + + // Set the new width + self.elements.wrapper.width(newWidth); + + // Adjust BGIframe height and width if enabled + if(self.elements.bgiframe) self.elements.bgiframe.width(newWidth).height(self.getDimensions.height); + }; + + // Log event and return + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_WIDTH_UPDATED, 'updateWidth'); + }, + + updateStyle: function(name) + { + var tip, borders, context, corner, coordinates; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateStyle'); + + // Return if style is not defined or name is not a string + else if(typeof name !== 'string' || !$.fn.qtip.styles[name]) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.STYLE_NOT_DEFINED, 'updateStyle'); + + // Set the new style object + self.options.style = buildStyle.call(self, $.fn.qtip.styles[name], self.options.user.style); + + // Update initial styles of content and title elements + self.elements.content.css( jQueryStyle(self.options.style) ); + if(self.options.content.title.text !== false) + self.elements.title.css( jQueryStyle(self.options.style.title, true) ); + + // Update CSS border colour + self.elements.contentWrapper.css({ borderColor: self.options.style.border.color }); + + // Update tip color if enabled + if(self.options.style.tip.corner !== false) + { + if($('').get(0).getContext) + { + // Retrieve canvas context and clear + tip = self.elements.tooltip.find('.qtip-tip canvas:first'); + context = tip.get(0).getContext('2d'); + context.clearRect(0,0,300,300); + + // Draw new tip + corner = tip.parent('div[rel]:first').attr('rel'); + coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height); + drawTip.call(self, tip, coordinates, self.options.style.tip.color || self.options.style.border.color); + } + else if($.browser.msie) + { + // Set new fillcolor attribute + tip = self.elements.tooltip.find('.qtip-tip [nodeName="shape"]'); + tip.attr('fillcolor', self.options.style.tip.color || self.options.style.border.color); + }; + }; + + // Update border colors if enabled + if(self.options.style.border.radius > 0) + { + self.elements.tooltip.find('.qtip-betweenCorners').css({ backgroundColor: self.options.style.border.color }); + + if($('').get(0).getContext) + { + borders = calculateBorders(self.options.style.border.radius) + self.elements.tooltip.find('.qtip-wrapper canvas').each(function() + { + // Retrieve canvas context and clear + context = $(this).get(0).getContext('2d'); + context.clearRect(0,0,300,300); + + // Draw new border + corner = $(this).parent('div[rel]:first').attr('rel') + drawBorder.call(self, $(this), borders[corner], + self.options.style.border.radius, self.options.style.border.color); + }); + } + else if($.browser.msie) + { + // Set new fillcolor attribute on each border corner + self.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function() + { + $(this).attr('fillcolor', self.options.style.border.color) + }); + }; + }; + + // Log event and return + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_STYLE_UPDATED, 'updateStyle'); + }, + + updateContent: function(content, reposition) + { + var parsedContent, images, loadedImages; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateContent'); + + // Make sure content is defined before update + else if(!content) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateContent'); + + // Call API method and set new content if a string is returned + parsedContent = self.beforeContentUpdate.call(self, content); + if(typeof parsedContent == 'string') content = parsedContent; + else if(parsedContent === false) return; + + // Set position and zoom to defaults to prevent IE hasLayout bug + if($.browser.msie) self.elements.contentWrapper.children().css({ zoom: 'normal' }); + + // Append new content if its a DOM array and show it if hidden + if(content.jquery && content.length > 0) + content.clone(true).appendTo(self.elements.content).show(); + + // Content is a regular string, insert the new content + else self.elements.content.html(content); + + // Check if images need to be loaded before position is updated to prevent mis-positioning + images = self.elements.content.find('img[complete=false]'); + if(images.length > 0) + { + loadedImages = 0; + images.each(function(i) + { + $('') + .load(function(){ if(++loadedImages == images.length) afterLoad(); }); + }); + } + else afterLoad(); + + function afterLoad() + { + // Update the tooltip width + self.updateWidth(); + + // If repositioning is enabled, update positions + if(reposition !== false) + { + // Update position if tooltip isn't static + if(self.options.position.type !== 'static') + self.updatePosition(self.elements.tooltip.is(':visible'), true); + + // Reposition the tip if enabled + if(self.options.style.tip.corner !== false) + positionTip.call(self); + }; + }; + + // Call API method and log event + self.onContentUpdate.call(self); + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_UPDATED, 'loadContent'); + }, + + loadContent: function(url, data, method) + { + var returned; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'loadContent'); + + // Call API method and if return value is false, halt + returned = self.beforeContentLoad.call(self); + if(returned === false) return self; + + // Load content using specified request type + if(method == 'post') + $.post(url, data, setupContent); + else + $.get(url, data, setupContent); + + function setupContent(content) + { + // Call API method and log event + self.onContentLoad.call(self); + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_CONTENT_LOADED, 'loadContent'); + + // Update the content + self.updateContent(content); + }; + + return self; + }, + + updateTitle: function(content) + { + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'updateTitle'); + + // Make sure content is defined before update + else if(!content) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.NO_CONTENT_PROVIDED, 'updateTitle'); + + // Call API method and if return value is false, halt + returned = self.beforeTitleUpdate.call(self); + if(returned === false) return self; + + // Set the new content and reappend the button if enabled + if(self.elements.button) self.elements.button = self.elements.button.clone(true); + self.elements.title.html(content) + if(self.elements.button) self.elements.title.prepend(self.elements.button); + + // Call API method and log event + self.onTitleUpdate.call(self); + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_TITLE_UPDATED, 'updateTitle'); + }, + + focus: function(event) + { + var curIndex, newIndex, elemIndex, returned; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'focus'); + + else if(self.options.position.type == 'static') + return $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.CANNOT_FOCUS_STATIC, 'focus'); + + // Set z-index variables + curIndex = parseInt( self.elements.tooltip.css('z-index') ); + newIndex = 6000 + $('div.qtip[qtip]').length - 1; + + // Only update the z-index if it has changed and tooltip is not already focused + if(!self.status.focused && curIndex !== newIndex) + { + // Call API method and if return value is false, halt + returned = self.beforeFocus.call(self, event); + if(returned === false) return self; + + // Loop through all other tooltips + $('div.qtip[qtip]').not(self.elements.tooltip).each(function() + { + if($(this).qtip('api').status.rendered === true) + { + elemIndex = parseInt($(this).css('z-index')); + + // Reduce all other tooltip z-index by 1 + if(typeof elemIndex == 'number' && elemIndex > -1) + $(this).css({ zIndex: parseInt( $(this).css('z-index') ) - 1 }); + + // Set focused status to false + $(this).qtip('api').status.focused = false; + } + }) + + // Set the new z-index and set focus status to true + self.elements.tooltip.css({ zIndex: newIndex }); + self.status.focused = true; + + // Call API method and log event + self.onFocus.call(self, event); + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_FOCUSED, 'focus'); + }; + + return self; + }, + + disable: function(state) + { + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'disable'); + + if(state) + { + // Tooltip is not already disabled, proceed + if(!self.status.disabled) + { + // Set the disabled flag and log event + self.status.disabled = true; + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DISABLED, 'disable'); + } + + // Tooltip is already disabled, inform user via log + else $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED, 'disable'); + } + else + { + // Tooltip is not already enabled, proceed + if(self.status.disabled) + { + // Reassign events, set disable status and log + self.status.disabled = false; + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_ENABLED, 'disable'); + } + + // Tooltip is already enabled, inform the user via log + else $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED, 'disable'); + }; + + return self; + }, + + destroy: function() + { + var i, returned, interfaces; + + // Call API method and if return value is false, halt + returned = self.beforeDestroy.call(self); + if(returned === false) return self; + + // Check if tooltip is rendered + if(self.status.rendered) + { + // Remove event handlers and remove element + self.options.show.when.target.unbind('mousemove.qtip', self.updatePosition); + self.options.show.when.target.unbind('mouseout.qtip', self.hide); + self.options.show.when.target.unbind(self.options.show.when.event + '.qtip'); + self.options.hide.when.target.unbind(self.options.hide.when.event + '.qtip'); + self.elements.tooltip.unbind(self.options.hide.when.event + '.qtip'); + self.elements.tooltip.unbind('mouseover.qtip', self.focus); + self.elements.tooltip.remove(); + } + + // Tooltip isn't yet rendered, remove render event + else self.options.show.when.target.unbind(self.options.show.when.event+'.qtip-create'); + + // Check to make sure qTip data is present on target element + if(typeof self.elements.target.data('qtip') == 'object') + { + // Remove API references from interfaces object + interfaces = self.elements.target.data('qtip').interfaces; + if(typeof interfaces == 'object' && interfaces.length > 0) + { + // Remove API from interfaces array + for(i = 0; i < interfaces.length - 1; i++) + if(interfaces[i].id == self.id) interfaces.splice(i, 1) + } + } + delete $.fn.qtip.interfaces[self.id]; + + // Set qTip current id to previous tooltips API if available + if(typeof interfaces == 'object' && interfaces.length > 0) + self.elements.target.data('qtip').current = interfaces.length -1; + else + self.elements.target.removeData('qtip'); + + // Call API method and log destroy + self.onDestroy.call(self); + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_DESTROYED, 'destroy'); + + return self.elements.target + }, + + getPosition: function() + { + var show, offset; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getPosition'); + + show = (self.elements.tooltip.css('display') !== 'none') ? false : true; + + // Show and hide tooltip to make sure coordinates are returned + if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show(); + offset = self.elements.tooltip.offset(); + if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide(); + + return offset; + }, + + getDimensions: function() + { + var show, dimensions; + + // Make sure tooltip is rendered and if not, return + if(!self.status.rendered) + return $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.TOOLTIP_NOT_RENDERED, 'getDimensions'); + + show = (!self.elements.tooltip.is(':visible')) ? true : false; + + // Show and hide tooltip to make sure dimensions are returned + if(show) self.elements.tooltip.css({ visiblity: 'hidden' }).show(); + dimensions = { + height: self.elements.tooltip.outerHeight(), + width: self.elements.tooltip.outerWidth() + }; + if(show) self.elements.tooltip.css({ visiblity: 'visible' }).hide(); + + return dimensions; + } + }); + }; + + // Define priamry construct function + function construct() + { + var self, adjust, content, url, data, method, tempLength; + self = this; + + // Call API method + self.beforeRender.call(self); + + // Set rendered status to true + self.status.rendered = true; + + // Create initial tooltip elements + self.elements.tooltip = '
' + + '
' + + '
' + + '
' + + '
'; + + // Append to container element + self.elements.tooltip = $(self.elements.tooltip); + self.elements.tooltip.appendTo(self.options.position.container) + + // Setup tooltip qTip data + self.elements.tooltip.data('qtip', { current: 0, interfaces: [self] }); + + // Setup element references + self.elements.wrapper = self.elements.tooltip.children('div:first'); + self.elements.contentWrapper = self.elements.wrapper.children('div:first').css({ background: self.options.style.background }); + self.elements.content = self.elements.contentWrapper.children('div:first').css( jQueryStyle(self.options.style) ); + + // Apply IE hasLayout fix to wrapper and content elements + if($.browser.msie) self.elements.wrapper.add(self.elements.content).css({ zoom: 1 }); + + // Setup tooltip attributes + if(self.options.hide.when.event == 'unfocus') self.elements.tooltip.attr('unfocus', true); + + // If an explicit width is set, updateWidth prior to setting content to prevent dirty rendering + if(typeof self.options.style.width.value == 'number') self.updateWidth(); + + // Create borders and tips if supported by the browser + if($('').get(0).getContext || $.browser.msie) + { + // Create border + if(self.options.style.border.radius > 0) + createBorder.call(self); + else + self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color }); + + // Create tip if enabled + if(self.options.style.tip.corner !== false) + createTip.call(self); + } + + // Neither canvas or VML is supported, tips and borders cannot be drawn! + else + { + // Set defined border width + self.elements.contentWrapper.css({ border: self.options.style.border.width+'px solid '+self.options.style.border.color }); + + // Reset border radius and tip + self.options.style.border.radius = 0; + self.options.style.tip.corner = false; + + // Inform via log + $.fn.qtip.log.error.call(self, 2, $.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED, 'render'); + }; + + // Use the provided content string or DOM array + if((typeof self.options.content.text == 'string' && self.options.content.text.length > 0) + || (self.options.content.text.jquery && self.options.content.text.length > 0)) + content = self.options.content.text; + + // Use title string for content if present + else if(typeof self.elements.target.attr('title') == 'string' && self.elements.target.attr('title').length > 0) + { + content = self.elements.target.attr('title').replace("\\n", '
'); + self.elements.target.attr('title', ''); // Remove title attribute to prevent default tooltip showing + } + + // No title is present, use alt attribute instead + else if(typeof self.elements.target.attr('alt') == 'string' && self.elements.target.attr('alt').length > 0) + { + content = self.elements.target.attr('alt').replace("\\n", '
'); + self.elements.target.attr('alt', ''); // Remove alt attribute to prevent default tooltip showing + } + + // No valid content was provided, inform via log + else + { + content = ' '; + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.NO_VALID_CONTENT, 'render'); + }; + + // Set the tooltips content and create title if enabled + if(self.options.content.title.text !== false) createTitle.call(self); + self.updateContent(content); + + // Assign events and toggle tooltip with focus + assignEvents.call(self); + if(self.options.show.ready === true) self.show(); + + // Retrieve ajax content if provided + if(self.options.content.url !== false) + { + url = self.options.content.url; + data = self.options.content.data; + method = self.options.content.method || 'get'; + self.loadContent(url, data, method); + }; + + // Call API method and log event + self.onRender.call(self); + $.fn.qtip.log.error.call(self, 1, $.fn.qtip.constants.EVENT_RENDERED, 'render'); + }; + + // Create borders using canvas and VML + function createBorder() + { + var self, i, width, radius, color, coordinates, containers, size, betweenWidth, betweenCorners, borderTop, borderBottom, borderCoord, sideWidth, vertWidth; + self = this; + + // Destroy previous border elements, if present + self.elements.wrapper.find('.qtip-borderBottom, .qtip-borderTop').remove(); + + // Setup local variables + width = self.options.style.border.width; + radius = self.options.style.border.radius; + color = self.options.style.border.color || self.options.style.tip.color; + + // Calculate border coordinates + coordinates = calculateBorders(radius); + + // Create containers for the border shapes + containers = {}; + for(i in coordinates) + { + // Create shape container + containers[i] = '
'; + + // Canvas is supported + if($('').get(0).getContext) + containers[i] += ''; + + // No canvas, but if it's IE use VML + else if($.browser.msie) + { + size = radius * 2 + 3; + containers[i] += ''; + + }; + + containers[i] += '
'; + }; + + // Create between corners elements + betweenWidth = self.getDimensions().width - (Math.max(width, radius) * 2); + betweenCorners = '
'; + + // Create top border container + borderTop = '
' + + containers['topLeft'] + containers['topRight'] + betweenCorners; + self.elements.wrapper.prepend(borderTop); + + // Create bottom border container + borderBottom = '
' + + containers['bottomLeft'] + containers['bottomRight'] + betweenCorners; + self.elements.wrapper.append(borderBottom); + + // Draw the borders if canvas were used (Delayed til after DOM creation) + if($('').get(0).getContext) + { + self.elements.wrapper.find('canvas').each(function() + { + borderCoord = coordinates[ $(this).parent('[rel]:first').attr('rel') ]; + drawBorder.call(self, $(this), borderCoord, radius, color); + }) + } + + // Create a phantom VML element (IE won't show the last created VML element otherwise) + else if($.browser.msie) self.elements.tooltip.append(''); + + // Setup contentWrapper border + sideWidth = Math.max(radius, (radius + (width - radius)) ) + vertWidth = Math.max(width - radius, 0); + self.elements.contentWrapper.css({ + border: '0px solid ' + color, + borderWidth: vertWidth + 'px ' + sideWidth + 'px' + }) + }; + + // Border canvas draw method + function drawBorder(canvas, coordinates, radius, color) + { + // Create corner + var context = canvas.get(0).getContext('2d'); + context.fillStyle = color; + context.beginPath(); + context.arc(coordinates[0], coordinates[1], radius, 0, Math.PI * 2, false); + context.fill(); + }; + + // Create tip using canvas and VML + function createTip(corner) + { + var self, color, coordinates, coordsize, path; + self = this; + + // Destroy previous tip, if there is one + if(self.elements.tip !== null) self.elements.tip.remove(); + + // Setup color and corner values + color = self.options.style.tip.color || self.options.style.border.color; + if(self.options.style.tip.corner === false) return; + else if(!corner) corner = self.options.style.tip.corner; + + // Calculate tip coordinates + coordinates = calculateTip(corner, self.options.style.tip.size.width, self.options.style.tip.size.height); + + // Create tip element + self.elements.tip = '
'; + + // Use canvas element if supported + if($('').get(0).getContext) + self.elements.tip += ''; + + // Canvas not supported - Use VML (IE) + else if($.browser.msie) + { + // Create coordize and tip path using tip coordinates + coordsize = self.options.style.tip.size.width + ',' + self.options.style.tip.size.height; + path = 'm' + coordinates[0][0] + ',' + coordinates[0][1]; + path += ' l' + coordinates[1][0] + ',' + coordinates[1][1]; + path += ' ' + coordinates[2][0] + ',' + coordinates[2][1]; + path += ' xe'; + + // Create VML element + self.elements.tip += ''; + + // Create a phantom VML element (IE won't show the last created VML element otherwise) + self.elements.tip += ''; + + // Prevent tooltip appearing above the content (IE z-index bug) + self.elements.contentWrapper.css('position', 'relative'); + }; + + // Attach new tip to tooltip element + self.elements.tooltip.prepend(self.elements.tip + '
'); + + // Create element reference and draw the canvas tip (Delayed til after DOM creation) + self.elements.tip = self.elements.tooltip.find('.'+self.options.style.classes.tip).eq(0); + if($('').get(0).getContext) + drawTip.call(self, self.elements.tip.find('canvas:first'), coordinates, color); + + // Fix IE small tip bug + if(corner.search(/top/) !== -1 && $.browser.msie && parseInt($.browser.version.charAt(0)) === 6) + self.elements.tip.css({ marginTop: -4 }); + + // Set the tip position + positionTip.call(self, corner); + }; + + // Canvas tip drawing method + function drawTip(canvas, coordinates, color) + { + // Setup properties + var context = canvas.get(0).getContext('2d'); + context.fillStyle = color; + + // Create tip + context.beginPath(); + context.moveTo(coordinates[0][0], coordinates[0][1]); + context.lineTo(coordinates[1][0], coordinates[1][1]); + context.lineTo(coordinates[2][0], coordinates[2][1]); + context.fill(); + }; + + function positionTip(corner) + { + var self, ieAdjust, paddingCorner, paddingSize, newMargin; + self = this; + + // Return if tips are disabled or tip is not yet rendered + if(self.options.style.tip.corner === false || !self.elements.tip) return; + if(!corner) corner = self.elements.tip.attr('rel'); + + // Setup adjustment variables + ieAdjust = positionAdjust = ($.browser.msie) ? 1 : 0; + + // Set initial position + self.elements.tip.css(corner.match(/left|right|top|bottom/)[0], 0); + + // Set position of tip to correct side + if(corner.search(/top|bottom/) !== -1) + { + // Adjustments for IE6 - 0.5px border gap bug + if($.browser.msie) + { + if(parseInt($.browser.version.charAt(0)) === 6) + positionAdjust = (corner.search(/top/) !== -1) ? -3 : 1; + else + positionAdjust = (corner.search(/top/) !== -1) ? 1 : 2; + }; + + if(corner.search(/Middle/) !== -1) + self.elements.tip.css({ left: '50%', marginLeft: -(self.options.style.tip.size.width / 2) }); + + else if(corner.search(/Left/) !== -1) + self.elements.tip.css({ left: self.options.style.border.radius - ieAdjust }); + + else if(corner.search(/Right/) !== -1) + self.elements.tip.css({ right: self.options.style.border.radius + ieAdjust }); + + if(corner.search(/top/) !== -1) + self.elements.tip.css({ top: -positionAdjust }); + else + self.elements.tip.css({ bottom: positionAdjust }); + + } + else if(corner.search(/left|right/) !== -1) + { + // Adjustments for IE6 - 0.5px border gap bug + if($.browser.msie) + positionAdjust = (parseInt($.browser.version.charAt(0)) === 6) ? 1 : ((corner.search(/left/) !== -1) ? 1 : 2); + + if(corner.search(/Middle/) !== -1) + self.elements.tip.css({ top: '50%', marginTop: -(self.options.style.tip.size.height / 2) }); + + else if(corner.search(/Top/) !== -1) + self.elements.tip.css({ top: self.options.style.border.radius - ieAdjust }); + + else if(corner.search(/Bottom/) !== -1) + self.elements.tip.css({ bottom: self.options.style.border.radius + ieAdjust }); + + if(corner.search(/left/) !== -1) + self.elements.tip.css({ left: -positionAdjust }); + else + self.elements.tip.css({ right: positionAdjust }); + }; + + // Adjust tooltip padding to compensate for tip + paddingCorner = 'padding-' + corner.match(/left|right|top|bottom/)[0]; + paddingSize = self.options.style.tip.size[ (paddingCorner.search(/left|right/) !== -1) ? 'width' : 'height' ]; + self.elements.tooltip.css('padding', 0); + self.elements.tooltip.css(paddingCorner, paddingSize); + + // Match content margin to prevent gap bug in IE6 ONLY + if($.browser.msie && parseInt($.browser.version.charAt(0)) == 6) + { + newMargin = parseInt(self.elements.tip.css('margin-top')) || 0; + newMargin += parseInt(self.elements.content.css('margin-top')) || 0; + + self.elements.tip.css({ marginTop: newMargin }); + }; + }; + + // Create title bar for content + function createTitle() + { + var self = this; + + // Destroy previous title element, if present + if(self.elements.title !== null) self.elements.title.remove(); + + // Create title element + self.elements.title = $('
') + .css( jQueryStyle(self.options.style.title, true) ) + .css({ zoom: ($.browser.msie) ? 1 : 0 }) + .prependTo(self.elements.contentWrapper); + + // Update title with contents if enabled + if(self.options.content.title.text) self.updateTitle.call(self, self.options.content.title.text); + + // Create title close buttons if enabled + if(self.options.content.title.button !== false + && typeof self.options.content.title.button == 'string') + { + self.elements.button = $('') + .css( jQueryStyle(self.options.style.button, true) ) + .html(self.options.content.title.button) + .prependTo(self.elements.title) + .click(function(event){ if(!self.status.disabled) self.hide(event) }); + }; + }; + + // Assign hide and show events + function assignEvents() + { + var self, showTarget, hideTarget, inactiveEvents; + self = this; + + // Setup event target variables + showTarget = self.options.show.when.target; + hideTarget = self.options.hide.when.target; + + // Add tooltip as a hideTarget is its fixed + if(self.options.hide.fixed) hideTarget = hideTarget.add(self.elements.tooltip); + + // Check if the hide event is special 'inactive' type + if(self.options.hide.when.event == 'inactive') + { + // Define events which reset the 'inactive' event handler + inactiveEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', + 'mouseout', 'mouseenter', 'mouseleave', 'mouseover' ]; + + // Define 'inactive' event timer method + function inactiveMethod(event) + { + if(self.status.disabled === true) return; + + //Clear and reset the timer + clearTimeout(self.timers.inactive); + self.timers.inactive = setTimeout(function() + { + // Unassign 'inactive' events + $(inactiveEvents).each(function() + { + hideTarget.unbind(this+'.qtip-inactive'); + self.elements.content.unbind(this+'.qtip-inactive'); + }); + + // Hide the tooltip + self.hide(event); + } + , self.options.hide.delay); + }; + } + + // Check if the tooltip is 'fixed' + else if(self.options.hide.fixed === true) + { + self.elements.tooltip.bind('mouseover.qtip', function() + { + if(self.status.disabled === true) return; + + // Reset the hide timer + clearTimeout(self.timers.hide); + }); + }; + + // Define show event method + function showMethod(event) + { + if(self.status.disabled === true) return; + + // If set, hide tooltip when inactive for delay period + if(self.options.hide.when.event == 'inactive') + { + // Assign each reset event + $(inactiveEvents).each(function() + { + hideTarget.bind(this+'.qtip-inactive', inactiveMethod); + self.elements.content.bind(this+'.qtip-inactive', inactiveMethod); + }); + + // Start the inactive timer + inactiveMethod(); + }; + + // Clear hide timers + clearTimeout(self.timers.show); + clearTimeout(self.timers.hide); + + // Start show timer + self.timers.show = setTimeout(function(){ self.show(event); }, self.options.show.delay); + }; + + // Define hide event method + function hideMethod(event) + { + if(self.status.disabled === true) return; + + // Prevent hiding if tooltip is fixed and event target is the tooltip + if(self.options.hide.fixed === true + && self.options.hide.when.event.search(/mouse(out|leave)/i) !== -1 + && $(event.relatedTarget).parents('div.qtip[qtip]').length > 0) + { + // Prevent default and popagation + event.stopPropagation(); + event.preventDefault(); + + // Reset the hide timer + clearTimeout(self.timers.hide); + return false; + }; + + // Clear timers and stop animation queue + clearTimeout(self.timers.show); + clearTimeout(self.timers.hide); + self.elements.tooltip.stop(true, true); + + // If tooltip has displayed, start hide timer + self.timers.hide = setTimeout(function(){ self.hide(event); }, self.options.hide.delay); + }; + + // Both events and targets are identical, apply events using a toggle + if((self.options.show.when.target.add(self.options.hide.when.target).length === 1 + && self.options.show.when.event == self.options.hide.when.event + && self.options.hide.when.event !== 'inactive') + || self.options.hide.when.event == 'unfocus') + { + self.cache.toggle = 0; + // Use a toggle to prevent hide/show conflicts + showTarget.bind(self.options.show.when.event + '.qtip', function(event) + { + if(self.cache.toggle == 0) showMethod(event); + else hideMethod(event); + }); + } + + // Events are not identical, bind normally + else + { + showTarget.bind(self.options.show.when.event + '.qtip', showMethod); + + // If the hide event is not 'inactive', bind the hide method + if(self.options.hide.when.event !== 'inactive') + hideTarget.bind(self.options.hide.when.event + '.qtip', hideMethod); + }; + + // Focus the tooltip on mouseover + if(self.options.position.type.search(/(fixed|absolute)/) !== -1) + self.elements.tooltip.bind('mouseover.qtip', self.focus); + + // If mouse is the target, update tooltip position on mousemove + if(self.options.position.target === 'mouse' && self.options.position.type !== 'static') + { + showTarget.bind('mousemove.qtip', function(event) + { + // Set the new mouse positions if adjustment is enabled + self.cache.mouse = { x: event.pageX, y: event.pageY }; + + // Update the tooltip position only if the tooltip is visible and adjustment is enabled + if(self.status.disabled === false + && self.options.position.adjust.mouse === true + && self.options.position.type !== 'static' + && self.elements.tooltip.css('display') !== 'none') + self.updatePosition(event); + }); + }; + }; + + // Screen position adjustment + function screenAdjust(position, target, tooltip) + { + var self, adjustedPosition, adjust, newCorner, overflow, corner; + self = this; + + // Setup corner and adjustment variable + if(tooltip.corner == 'center') return target.position // TODO: 'center' corner adjustment + adjustedPosition = $.extend({}, position); + newCorner = { x: false, y: false }; + + // Define overflow properties + overflow = { + left: (adjustedPosition.left < $.fn.qtip.cache.screen.scroll.left), + right: (adjustedPosition.left + tooltip.dimensions.width + 2 >= $.fn.qtip.cache.screen.width + $.fn.qtip.cache.screen.scroll.left), + top: (adjustedPosition.top < $.fn.qtip.cache.screen.scroll.top), + bottom: (adjustedPosition.top + tooltip.dimensions.height + 2 >= $.fn.qtip.cache.screen.height + $.fn.qtip.cache.screen.scroll.top) + }; + + // Determine new positioning properties + adjust = { + left: (overflow.left && (tooltip.corner.search(/right/i) != -1 || (tooltip.corner.search(/right/i) == -1 && !overflow.right))), + right: (overflow.right && (tooltip.corner.search(/left/i) != -1 || (tooltip.corner.search(/left/i) == -1 && !overflow.left))), + top: (overflow.top && tooltip.corner.search(/top/i) == -1), + bottom: (overflow.bottom && tooltip.corner.search(/bottom/i) == -1) + }; + + // Tooltip overflows off the left side of the screen + if(adjust.left) + { + if(self.options.position.target !== 'mouse') + adjustedPosition.left = target.position.left + target.dimensions.width; + else + adjustedPosition.left = self.cache.mouse.x + + newCorner.x = 'Left'; + } + + // Tooltip overflows off the right side of the screen + else if(adjust.right) + { + if(self.options.position.target !== 'mouse') + adjustedPosition.left = target.position.left - tooltip.dimensions.width; + else + adjustedPosition.left = self.cache.mouse.x - tooltip.dimensions.width; + + newCorner.x = 'Right'; + }; + + // Tooltip overflows off the top of the screen + if(adjust.top) + { + if(self.options.position.target !== 'mouse') + adjustedPosition.top = target.position.top + target.dimensions.height; + else + adjustedPosition.top = self.cache.mouse.y + + newCorner.y = 'top'; + } + + // Tooltip overflows off the bottom of the screen + else if(adjust.bottom) + { + if(self.options.position.target !== 'mouse') + adjustedPosition.top = target.position.top - tooltip.dimensions.height; + else + adjustedPosition.top = self.cache.mouse.y - tooltip.dimensions.height; + + newCorner.y = 'bottom'; + }; + + // Don't adjust if resulting position is negative + if(adjustedPosition.left < 0) + { + adjustedPosition.left = position.left; + newCorner.x = false; + }; + if(adjustedPosition.top < 0) + { + adjustedPosition.top = position.top; + newCorner.y = false; + }; + + // Change tip corner if positioning has changed and tips are enabled + if(self.options.style.tip.corner !== false) + { + // Determine new corner properties + adjustedPosition.corner = new String(tooltip.corner); + if(newCorner.x !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/Left|Right|Middle/, newCorner.x); + if(newCorner.y !== false) adjustedPosition.corner = adjustedPosition.corner.replace(/top|bottom/, newCorner.y); + + // Adjust tip if position has changed and tips are enabled + if(adjustedPosition.corner !== self.elements.tip.attr('rel')) + createTip.call(self, adjustedPosition.corner); + }; + + return adjustedPosition; + }; + + // Build a jQuery style object from supplied style object + function jQueryStyle(style, sub) + { + var styleObj, i; + + styleObj = $.extend(true, {}, style); + for(i in styleObj) + { + if(sub === true && i.search(/(tip|classes)/i) !== -1) + delete styleObj[i]; + else if(!sub && i.search(/(width|border|tip|title|classes|user)/i) !== -1) + delete styleObj[i]; + }; + + return styleObj; + }; + + // Sanitize styles + function sanitizeStyle(style) + { + if(typeof style.tip !== 'object') style.tip = { corner: style.tip }; + if(typeof style.tip.size !== 'object') style.tip.size = { width: style.tip.size, height: style.tip.size }; + if(typeof style.border !== 'object') style.border = { width: style.border }; + if(typeof style.width !== 'object') style.width = { value: style.width }; + if(typeof style.width.max == 'string') style.width.max = parseInt(style.width.max.replace(/([0-9]+)/i, "$1")); + if(typeof style.width.min == 'string') style.width.min = parseInt(style.width.min.replace(/([0-9]+)/i, "$1")); + + // Convert deprecated x and y tip values to width/height + if(typeof style.tip.size.x == 'number') + { + style.tip.size.width = style.tip.size.x; + delete style.tip.size.x; + }; + if(typeof style.tip.size.y == 'number') + { + style.tip.size.height = style.tip.size.y; + delete style.tip.size.y; + }; + + return style; + }; + + // Build styles recursively with inheritance + function buildStyle() + { + var self, i, styleArray, styleExtend, finalStyle, ieAdjust; + self = this; + + // Build style options from supplied arguments + styleArray = [true, {}]; + for(i = 0; i < arguments.length; i++) + styleArray.push(arguments[i]); + styleExtend = [ $.extend.apply($, styleArray) ]; + + // Loop through each named style inheritance + while(typeof styleExtend[0].name == 'string') + { + // Sanitize style data and append to extend array + styleExtend.unshift( sanitizeStyle($.fn.qtip.styles[ styleExtend[0].name ]) ); + }; + + // Make sure resulting tooltip className represents final style + styleExtend.unshift(true, {classes:{ tooltip: 'qtip-' + (arguments[0].name || 'defaults') }}, $.fn.qtip.styles.defaults); + + // Extend into a single style object + finalStyle = $.extend.apply($, styleExtend); + + // Adjust tip size if needed (IE 1px adjustment bug fix) + ieAdjust = ($.browser.msie) ? 1 : 0; + finalStyle.tip.size.width += ieAdjust; + finalStyle.tip.size.height += ieAdjust; + + // Force even numbers for pixel precision + if(finalStyle.tip.size.width % 2 > 0) finalStyle.tip.size.width += 1; + if(finalStyle.tip.size.height % 2 > 0) finalStyle.tip.size.height += 1; + + // Sanitize final styles tip corner value + if(finalStyle.tip.corner === true) + finalStyle.tip.corner = (self.options.position.corner.tooltip === 'center') ? false : self.options.position.corner.tooltip; + + return finalStyle; + }; + + // Tip coordinates calculator + function calculateTip(corner, width, height) + { + // Define tip coordinates in terms of height and width values + var tips = { + bottomRight: [[0,0], [width,height], [width,0]], + bottomLeft: [[0,0], [width,0], [0,height]], + topRight: [[0,height], [width,0], [width,height]], + topLeft: [[0,0], [0,height], [width,height]], + topMiddle: [[0,height], [width / 2,0], [width,height]], + bottomMiddle: [[0,0], [width,0], [width / 2,height]], + rightMiddle: [[0,0], [width,height / 2], [0,height]], + leftMiddle: [[width,0], [width,height], [0,height / 2]] + }; + tips.leftTop = tips.bottomRight; + tips.rightTop = tips.bottomLeft; + tips.leftBottom = tips.topRight; + tips.rightBottom = tips.topLeft; + + return tips[corner]; + }; + + // Border coordinates calculator + function calculateBorders(radius) + { + var borders; + + // Use canvas element if supported + if($('').get(0).getContext) + { + borders = { + topLeft: [radius,radius], topRight: [0,radius], + bottomLeft: [radius,0], bottomRight: [0,0] + }; + } + + // Canvas not supported - Use VML (IE) + else if($.browser.msie) + { + borders = { + topLeft: [-90,90,0], topRight: [-90,90,-radius], + bottomLeft: [90,270,0], bottomRight: [90, 270,-radius] + }; + }; + + return borders; + }; + + // BGIFRAME JQUERY PLUGIN ADAPTION + // Special thanks to Brandon Aaron for this plugin + // http://plugins.jquery.com/project/bgiframe + function bgiframe() + { + var self, html, dimensions; + self = this; + dimensions = self.getDimensions(); + + // Setup iframe HTML string + html = '