1 /** 2 * Copyright (C) 2012 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 core, ops, gui, runtime*/ 26 27 /** 28 * @class 29 * A cursor is a dom node that visually represents a cursor in a DOM tree. 30 * It should stay synchronized with the selection in the document. When 31 * there is only one collapsed selection range, a cursor should be shown at 32 * that point. 33 * 34 * Putting the cursor in the DOM tree modifies the DOM, so care should be taken 35 * to keep the selection consistent. If e.g. a selection is drawn over the 36 * cursor, and the cursor is updated to the selection, the cursor is removed 37 * from the DOM because the selection is not collapsed. This means that the 38 * offsets of the selection may have to be changed. 39 * 40 * When the selection is collapsed, the cursor is placed after the point of the 41 * selection and the selection will stay valid. However, if the cursor was 42 * placed in the DOM tree and was counted in the offset, the offset in the 43 * selection should be decreased by one. 44 * 45 * Even when the selection allows for a cursor, it might be desireable to hide 46 * the cursor by not letting it be part of the DOM. 47 * 48 * @constructor 49 * @param {!string} memberId The memberid this cursor is assigned to 50 * @param {!ops.Document} document The document in which the cursor is placed 51 */ 52 ops.OdtCursor = function OdtCursor(memberId, document) { 53 "use strict"; 54 var self = this, 55 validSelectionTypes = {}, 56 selectionType, 57 /**@type{!core.Cursor}*/ 58 cursor, 59 events = new core.EventNotifier([ops.OdtCursor.signalCursorUpdated]); 60 61 /** 62 * Remove the cursor from the document 63 * @return {undefined} 64 */ 65 this.removeFromDocument = function () { 66 // TODO: find out if nodeAfterCursor, textNodeIncrease need to be dealt with in any way 67 cursor.remove(); 68 }; 69 70 /** 71 * Subscribe to cursor update events. 72 * 73 * The update event called whenever the cursor is moved around manually. 74 * @param {!string} eventid 75 * @param {!Function} cb 76 */ 77 this.subscribe = function (eventid, cb) { 78 events.subscribe(eventid, cb); 79 }; 80 81 /** 82 * Unsubscribe from cursor events 83 * @param {!string} eventid 84 * @param {!Function} cb 85 */ 86 this.unsubscribe = function (eventid, cb) { 87 events.unsubscribe(eventid, cb); 88 }; 89 90 /** 91 * Obtain the memberid the cursor is assigned to. 92 * @return {string} 93 */ 94 this.getMemberId = function () { 95 return memberId; 96 }; 97 /** 98 * Obtain the node representing the cursor. 99 * @return {!Element} 100 */ 101 this.getNode = function () { 102 return cursor.getNode(); 103 }; 104 /** 105 * Obtain the node representing the selection start point. 106 * If a 0-length range is selected (e.g., by clicking without 107 * dragging),, this will return the exact same node as getNode 108 * @return {!Element} 109 */ 110 this.getAnchorNode = function () { 111 return cursor.getAnchorNode(); 112 }; 113 /** 114 * Obtain the currently selected range to which the cursor corresponds. 115 * @return {!Range} 116 */ 117 this.getSelectedRange = function () { 118 return cursor.getSelectedRange(); 119 }; 120 /** Set the given range as the selected range for this cursor 121 * @param {!Range} range, 122 * @param {boolean} isForwardSelection 123 * @return {undefined} 124 */ 125 this.setSelectedRange = function (range, isForwardSelection) { 126 cursor.setSelectedRange(range, isForwardSelection); 127 events.emit(ops.OdtCursor.signalCursorUpdated, self); 128 }; 129 /** 130 * Returns if the selection of this cursor has the 131 * same direction as the direction of the range 132 * @return {boolean} 133 */ 134 this.hasForwardSelection = function () { 135 return cursor.hasForwardSelection(); 136 }; 137 /** 138 * Obtain the document to which the cursor corresponds. 139 * @return {!ops.Document} 140 */ 141 this.getDocument = function () { 142 return document; 143 }; 144 145 /** 146 * Gets the current selection type. 147 * @return {!string} 148 */ 149 this.getSelectionType = function () { 150 return selectionType; 151 }; 152 153 /** 154 * Sets the current selection type to the given value. 155 * @param {!string} value 156 * @return {undefined} 157 */ 158 this.setSelectionType = function (value) { 159 if (validSelectionTypes.hasOwnProperty(value)) { 160 selectionType = value; 161 } else { 162 runtime.log("Invalid selection type: " + value); 163 } 164 }; 165 166 /** 167 * Reset selection type to default. 168 * @return {undefined} 169 */ 170 this.resetSelectionType = function () { 171 self.setSelectionType(ops.OdtCursor.RangeSelection); 172 }; 173 174 function init() { 175 cursor = new core.Cursor(document.getDOMDocument(), memberId); 176 177 validSelectionTypes[ops.OdtCursor.RangeSelection] = true; 178 validSelectionTypes[ops.OdtCursor.RegionSelection] = true; 179 self.resetSelectionType(); 180 } 181 182 init(); 183 }; 184 185 /**@const 186 @type {!string} */ 187 ops.OdtCursor.RangeSelection = 'Range'; 188 /**@const 189 @type {!string} */ 190 ops.OdtCursor.RegionSelection = 'Region'; 191 /**@const 192 @type {!string} */ 193 ops.OdtCursor.signalCursorUpdated = "cursorUpdated"; 194