1 /** 2 * Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global runtime, gui, odf, core */ 26 27 /** 28 * @constructor 29 * @implements {core.Destroyable} 30 * @param {!odf.OdfCanvas} odfCanvas 31 * @param {!function():!number} getActiveModifier Get the currently active hyperlink click handler modifier 32 */ 33 gui.HyperlinkTooltipView = function HyperlinkTooltipView(odfCanvas, getActiveModifier) { 34 "use strict"; 35 var domUtils = core.DomUtils, 36 odfUtils = odf.OdfUtils, 37 /**@type{!Window}*/ 38 window = /**@type{!Window}*/(runtime.getWindow()), 39 /**@type{!Element}*/ 40 linkSpan, 41 /**@type{!Element}*/ 42 textSpan, 43 /**@type{!HTMLElement}*/ 44 tooltipElement, 45 /** 46 * @const 47 * @type {!number} 48 */ 49 offsetXPx = 15, 50 /** 51 * @const 52 * @type {!number} 53 */ 54 offsetYPx = 10; // small adjustment to the final position so tooltip wouldn't sit right on top of caret 55 56 runtime.assert(window !== null, "Expected to be run in an environment which has a global window, like a browser."); 57 58 /** 59 * @param {?Node} node 60 * @return {?Element} 61 */ 62 function getHyperlinkElement(node) { 63 while (node) { 64 if (odfUtils.isHyperlink(node)) { 65 return /**@type{!Element}*/(node); 66 } 67 if (odfUtils.isParagraph(node) || odfUtils.isInlineRoot(node)) { 68 break; 69 } 70 node = node.parentNode; 71 } 72 return null; 73 } 74 75 /** 76 * @return {!string} 77 */ 78 function getHint() { 79 var modifierKey = getActiveModifier(), 80 hint; 81 switch (modifierKey) { 82 case gui.KeyboardHandler.Modifier.Ctrl: 83 hint = runtime.tr("Ctrl-click to follow link"); 84 break; 85 case gui.KeyboardHandler.Modifier.Meta: 86 hint = runtime.tr("⌘-click to follow link"); 87 break; 88 default: 89 hint = ""; 90 break; 91 } 92 return hint; 93 } 94 95 /** 96 * Show the tooltip 97 * @param {!Event} e 98 * @return {undefined} 99 */ 100 this.showTooltip = function (e) { 101 var target = e.target || e.srcElement, 102 sizerElement = /** @type{!Element}*/(odfCanvas.getSizer()), 103 zoomLevel = odfCanvas.getZoomLevel(), 104 referenceRect, 105 linkElement, 106 left, top, max; 107 108 linkElement = getHyperlinkElement(/**@type{?Node}*/(target)); 109 if (!linkElement) { 110 return; 111 } 112 113 if (!domUtils.containsNode(sizerElement, tooltipElement)) { 114 // TODO Remove when a proper undo manager arrives 115 // The undo manager can replace the root element, discarding the original. 116 // The tooltip element is still valid, and simply needs to be re-attached 117 // after this occurs. 118 sizerElement.appendChild(tooltipElement); 119 } 120 121 textSpan.textContent = getHint(); 122 linkSpan.textContent = odfUtils.getHyperlinkTarget(linkElement); 123 tooltipElement.style.display = "block"; 124 125 max = window.innerWidth - tooltipElement.offsetWidth - offsetXPx; 126 left = e.clientX > max ? max : e.clientX + offsetXPx; // coordinates relative to the viewport 127 max = window.innerHeight - tooltipElement.offsetHeight - offsetYPx; 128 top = e.clientY > max ? max : e.clientY + offsetYPx; // coordinates relative to the viewport 129 130 // converts the coordinates to relative to the sizer element 131 referenceRect = sizerElement.getBoundingClientRect(); 132 left = (left - referenceRect.left) / zoomLevel; 133 top = (top - referenceRect.top) / zoomLevel; 134 135 tooltipElement.style.left = left + "px"; 136 tooltipElement.style.top = top + "px"; 137 }; 138 139 /** 140 * Hide the tooltip 141 * @return {undefined} 142 */ 143 this.hideTooltip = function () { 144 tooltipElement.style.display = "none"; 145 }; 146 147 /** 148 * Destroy the object. 149 * Do not access any member of this object after this call. 150 * @param {function(!Error=):undefined} callback 151 * @return {undefined} 152 */ 153 this.destroy = function(callback) { 154 if (tooltipElement.parentNode) { 155 // The tool tip might not be present in the current DOM just after an undo is performed. 156 // The tooltip is re-added to the DOM the first time it is asked to be shown after an undo. 157 tooltipElement.parentNode.removeChild(tooltipElement); 158 } 159 callback(); 160 }; 161 162 /** 163 * @return {undefined} 164 */ 165 function init() { 166 var document = odfCanvas.getElement().ownerDocument; 167 linkSpan = document.createElement("span"); 168 textSpan = document.createElement("span"); 169 linkSpan.className = "webodf-hyperlinkTooltipLink"; 170 textSpan.className = "webodf-hyperlinkTooltipText"; 171 172 tooltipElement = /**@type{!HTMLElement}*/(document.createElement("div")); 173 tooltipElement.className = "webodf-hyperlinkTooltip"; 174 tooltipElement.appendChild(linkSpan); 175 tooltipElement.appendChild(textSpan); 176 odfCanvas.getElement().appendChild(tooltipElement); 177 } 178 179 init(); 180 }; 181