1 /** 2 * Copyright (C) 2013 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, core, gui, odf, ops, Node */ 26 27 /** 28 * @constructor 29 * @param {!ops.Session} session 30 * @param {!string} inputMemberId 31 */ 32 gui.SelectionController = function SelectionController(session, inputMemberId) { 33 "use strict"; 34 var odtDocument = session.getOdtDocument(), 35 domUtils = new core.DomUtils(), 36 odfUtils = new odf.OdfUtils(), 37 baseFilter = odtDocument.getPositionFilter(), 38 keyboardMovementsFilter = new core.PositionFilterChain(), 39 rootFilter = odtDocument.createRootFilter(inputMemberId), 40 TRAILING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, 41 LEADING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.LEADING, 42 PREVIOUS = core.StepDirection.PREVIOUS, 43 NEXT = core.StepDirection.NEXT; 44 45 /** 46 * Create a new step iterator with the base Odt filter, and a root filter for the current input member. 47 * The step iterator subtree is set to the root of the current cursor node 48 * @return {!core.StepIterator} 49 */ 50 function createKeyboardStepIterator() { 51 var cursor = odtDocument.getCursor(inputMemberId), 52 node = cursor.getNode(); 53 54 return odtDocument.createStepIterator(node, 0, [baseFilter, rootFilter], odtDocument.getRootElement(node)); 55 } 56 57 /** 58 * Create a new step iterator that will iterate by word boundaries 59 * @param {!Node} node 60 * @param {!number} offset 61 * @param {!odf.WordBoundaryFilter.IncludeWhitespace} includeWhitespace 62 * @return {!core.StepIterator} 63 */ 64 function createWordBoundaryStepIterator(node, offset, includeWhitespace) { 65 var wordBoundaryFilter = new odf.WordBoundaryFilter(odtDocument, includeWhitespace), 66 nodeRoot = odtDocument.getRootElement(node) || odtDocument.getRootNode(), 67 nodeRootFilter = odtDocument.createRootFilter(nodeRoot); 68 return odtDocument.createStepIterator(node, offset, [baseFilter, nodeRootFilter, wordBoundaryFilter], nodeRoot); 69 } 70 71 /** 72 * Derive a selection-type object from the provided cursor 73 * @param {!{anchorNode: Node, anchorOffset: !number, focusNode: Node, focusOffset: !number}} selection 74 * @return {{range: !Range, hasForwardSelection: !boolean}} 75 */ 76 function selectionToRange(selection) { 77 var hasForwardSelection = domUtils.comparePoints(/**@type{!Node}*/(selection.anchorNode), selection.anchorOffset, 78 /**@type{!Node}*/(selection.focusNode), selection.focusOffset) >= 0, 79 range = selection.focusNode.ownerDocument.createRange(); 80 if (hasForwardSelection) { 81 range.setStart(selection.anchorNode, selection.anchorOffset); 82 range.setEnd(selection.focusNode, selection.focusOffset); 83 } else { 84 range.setStart(selection.focusNode, selection.focusOffset); 85 range.setEnd(selection.anchorNode, selection.anchorOffset); 86 } 87 return { 88 range: range, 89 hasForwardSelection: hasForwardSelection 90 }; 91 } 92 this.selectionToRange = selectionToRange; 93 94 /** 95 * Derive a selection-type object from the provided cursor 96 * @param {!Range} range 97 * @param {!boolean} hasForwardSelection 98 * @return {!{anchorNode: !Node, anchorOffset: !number, focusNode: !Node, focusOffset: !number}} 99 */ 100 function rangeToSelection(range, hasForwardSelection) { 101 if (hasForwardSelection) { 102 return { 103 anchorNode: /**@type{!Node}*/(range.startContainer), 104 anchorOffset: range.startOffset, 105 focusNode: /**@type{!Node}*/(range.endContainer), 106 focusOffset: range.endOffset 107 }; 108 } 109 return { 110 anchorNode: /**@type{!Node}*/(range.endContainer), 111 anchorOffset: range.endOffset, 112 focusNode: /**@type{!Node}*/(range.startContainer), 113 focusOffset: range.startOffset 114 }; 115 } 116 this.rangeToSelection = rangeToSelection; 117 118 /** 119 * @param {!number} position 120 * @param {!number} length 121 * @param {string=} selectionType 122 * @return {!ops.Operation} 123 */ 124 function createOpMoveCursor(position, length, selectionType) { 125 var op = new ops.OpMoveCursor(); 126 op.init({ 127 memberid: inputMemberId, 128 position: position, 129 length: length || 0, 130 selectionType: selectionType 131 }); 132 return op; 133 } 134 135 /** 136 * Move or extend the local member's selection to the specified focus point. 137 * 138 * @param {!Node} focusNode 139 * @param {!number} focusOffset 140 * @param {!boolean} extend Set to true to extend the selection (i.e., the current selection anchor 141 * will remain unchanged) 142 * @return {undefined} 143 */ 144 function moveCursorFocusPoint(focusNode, focusOffset, extend) { 145 var cursor, 146 newSelection, 147 newCursorSelection; 148 149 cursor = odtDocument.getCursor(inputMemberId); 150 newSelection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()); 151 newSelection.focusNode = focusNode; 152 newSelection.focusOffset = focusOffset; 153 154 if (!extend) { 155 newSelection.anchorNode = newSelection.focusNode; 156 newSelection.anchorOffset = newSelection.focusOffset; 157 } 158 newCursorSelection = odtDocument.convertDomToCursorRange(newSelection); 159 session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)]); 160 } 161 162 /** 163 * @param {!Node} frameNode 164 */ 165 function selectImage(frameNode) { 166 var frameRoot = odtDocument.getRootElement(frameNode), 167 frameRootFilter = odtDocument.createRootFilter(frameRoot), 168 stepIterator = odtDocument.createStepIterator(frameNode, 0, [frameRootFilter, odtDocument.getPositionFilter()], frameRoot), 169 anchorNode, 170 anchorOffset, 171 newSelection, 172 op; 173 174 if (!stepIterator.roundToPreviousStep()) { 175 runtime.assert(false, "No walkable position before frame"); 176 } 177 anchorNode = stepIterator.container(); 178 anchorOffset = stepIterator.offset(); 179 180 stepIterator.setPosition(frameNode, frameNode.childNodes.length); 181 if (!stepIterator.roundToNextStep()) { 182 runtime.assert(false, "No walkable position after frame"); 183 } 184 185 newSelection = odtDocument.convertDomToCursorRange({ 186 anchorNode: anchorNode, 187 anchorOffset: anchorOffset, 188 focusNode: stepIterator.container(), 189 focusOffset: stepIterator.offset() 190 }); 191 op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RegionSelection); 192 session.enqueue([op]); 193 } 194 this.selectImage = selectImage; 195 196 /** 197 * Expands the supplied selection to the nearest word boundaries 198 * @param {!Range} range 199 */ 200 function expandToWordBoundaries(range) { 201 var stepIterator; 202 203 stepIterator = createWordBoundaryStepIterator(/**@type{!Node}*/(range.startContainer), range.startOffset, TRAILING_SPACE); 204 if (stepIterator.roundToPreviousStep()) { 205 range.setStart(stepIterator.container(), stepIterator.offset()); 206 } 207 208 stepIterator = createWordBoundaryStepIterator(/**@type{!Node}*/(range.endContainer), range.endOffset, LEADING_SPACE); 209 if (stepIterator.roundToNextStep()) { 210 range.setEnd(stepIterator.container(), stepIterator.offset()); 211 } 212 } 213 this.expandToWordBoundaries = expandToWordBoundaries; 214 215 /** 216 * Expands the supplied selection to the nearest paragraph boundaries 217 * @param {!Range} range 218 */ 219 function expandToParagraphBoundaries(range) { 220 var paragraphs = odfUtils.getParagraphElements(range), 221 startParagraph = paragraphs[0], 222 endParagraph = paragraphs[paragraphs.length - 1]; 223 224 if (startParagraph) { 225 range.setStart(startParagraph, 0); 226 } 227 228 if (endParagraph) { 229 if (odfUtils.isParagraph(range.endContainer) && range.endOffset === 0) { 230 // Chrome's built-in paragraph expansion will put the end of the selection 231 // at (p,0) of the FOLLOWING paragraph. Round this back down to ensure 232 // the next paragraph doesn't get incorrectly selected 233 range.setEndBefore(endParagraph); 234 } else { 235 range.setEnd(endParagraph, endParagraph.childNodes.length); 236 } 237 } 238 } 239 this.expandToParagraphBoundaries = expandToParagraphBoundaries; 240 241 /** 242 * Rounds to the closest available step inside the supplied root, and preferably 243 * inside the original paragraph the node and offset are within. If (node, offset) is 244 * outside the root, the closest root boundary is used instead. 245 * This function will assert if no valid step is found within the supplied root. 246 * 247 * @param {!Node} root Root to contain iteration within 248 * @param {!Array.<!core.PositionFilter>} filters Position filters 249 * @param {!Range} range Range to modify 250 * @param {!boolean} modifyStart Set to true to modify the start container & offset. If false, the end 251 * container and offset will be modified instead. 252 * 253 * @return {undefined} 254 */ 255 function roundToClosestStep(root, filters, range, modifyStart) { 256 var stepIterator, 257 node, 258 offset; 259 260 if (modifyStart) { 261 node = /**@type{!Node}*/(range.startContainer); 262 offset = range.startOffset; 263 } else { 264 node = /**@type{!Node}*/(range.endContainer); 265 offset = range.endOffset; 266 } 267 268 if (!domUtils.containsNode(root, node)) { 269 if (domUtils.comparePoints(root, 0, node, offset) < 0) { 270 offset = 0; 271 } else { 272 offset = root.childNodes.length; 273 } 274 node = root; 275 } 276 stepIterator = odtDocument.createStepIterator(node, offset, filters, odfUtils.getParagraphElement(node) || root); 277 if (!stepIterator.roundToClosestStep()) { 278 runtime.assert(false, "No step found in requested range"); 279 } 280 if (modifyStart) { 281 range.setStart(stepIterator.container(), stepIterator.offset()); 282 } else { 283 range.setEnd(stepIterator.container(), stepIterator.offset()); 284 } 285 } 286 287 /** 288 * Set the user's cursor to the specified selection. If the start and end containers are in different roots, 289 * the anchor's root constraint is used (the anchor is the startContainer for a forward selection, or the 290 * endContainer for a reverse selection). 291 * 292 * If both the range start and range end are outside of the canvas element, no operations are generated. 293 * 294 * @param {!Range} range 295 * @param {!boolean} hasForwardSelection Set to true to indicate the range is from anchor (startContainer) to focus 296 * (endContainer) 297 * @param {number=} clickCount A value of 2 denotes expandToWordBoundaries, while a value of 3 and above will expand 298 * to paragraph boundaries. 299 * @return {undefined} 300 */ 301 function selectRange(range, hasForwardSelection, clickCount) { 302 var canvasElement = odtDocument.getOdfCanvas().getElement(), 303 validSelection, 304 startInsideCanvas, 305 endInsideCanvas, 306 existingSelection, 307 newSelection, 308 anchorRoot, 309 filters = [baseFilter], 310 op; 311 312 startInsideCanvas = domUtils.containsNode(canvasElement, range.startContainer); 313 endInsideCanvas = domUtils.containsNode(canvasElement, range.endContainer); 314 if (!startInsideCanvas && !endInsideCanvas) { 315 return; 316 } 317 318 if (startInsideCanvas && endInsideCanvas) { 319 // Expansion behaviour should only occur when double & triple clicking is inside the canvas 320 if (clickCount === 2) { 321 expandToWordBoundaries(range); 322 } else if (clickCount >= 3) { 323 expandToParagraphBoundaries(range); 324 } 325 } 326 327 if (hasForwardSelection) { 328 anchorRoot = odtDocument.getRootElement(/**@type{!Node}*/(range.startContainer)); 329 } else { 330 anchorRoot = odtDocument.getRootElement(/**@type{!Node}*/(range.endContainer)); 331 } 332 if (!anchorRoot) { 333 // If the range end is not within a root element, use the document root instead 334 anchorRoot = odtDocument.getRootNode(); 335 } 336 filters.push(odtDocument.createRootFilter(anchorRoot)); 337 roundToClosestStep(anchorRoot, filters, range, true); 338 roundToClosestStep(anchorRoot, filters, range, false); 339 validSelection = rangeToSelection(range, hasForwardSelection); 340 newSelection = odtDocument.convertDomToCursorRange(validSelection); 341 existingSelection = odtDocument.getCursorSelection(inputMemberId); 342 if (newSelection.position !== existingSelection.position || newSelection.length !== existingSelection.length) { 343 op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RangeSelection); 344 session.enqueue([op]); 345 } 346 } 347 this.selectRange = selectRange; 348 349 /** 350 * @param {!number} lengthAdjust length adjustment 351 * @return {undefined} 352 */ 353 function extendCursorByAdjustment(lengthAdjust) { 354 var selection = odtDocument.getCursorSelection(inputMemberId), 355 stepCounter = odtDocument.getCursor(inputMemberId).getStepCounter(), 356 newLength; 357 if (lengthAdjust !== 0) { 358 if (lengthAdjust > 0) { 359 lengthAdjust = stepCounter.convertForwardStepsBetweenFilters(lengthAdjust, keyboardMovementsFilter, baseFilter); 360 } else { 361 lengthAdjust = -stepCounter.convertBackwardStepsBetweenFilters(-lengthAdjust, keyboardMovementsFilter, baseFilter); 362 } 363 364 newLength = selection.length + lengthAdjust; 365 session.enqueue([createOpMoveCursor(selection.position, newLength)]); 366 } 367 } 368 369 /** 370 * @param {!number} positionAdjust position adjustment 371 * @return {undefined} 372 */ 373 function moveCursorByAdjustment(positionAdjust) { 374 var position = odtDocument.getCursorPosition(inputMemberId), 375 stepCounter = odtDocument.getCursor(inputMemberId).getStepCounter(); 376 if (positionAdjust !== 0) { 377 positionAdjust = (positionAdjust > 0) 378 ? stepCounter.convertForwardStepsBetweenFilters(positionAdjust, keyboardMovementsFilter, baseFilter) 379 : -stepCounter.convertBackwardStepsBetweenFilters(-positionAdjust, keyboardMovementsFilter, baseFilter); 380 381 position = position + positionAdjust; 382 session.enqueue([createOpMoveCursor(position, 0)]); 383 } 384 } 385 386 /** 387 * @param {!core.StepDirection} direction 388 * @param {!boolean} extend 389 * @return {undefined} 390 */ 391 function moveCursor(direction, extend) { 392 var stepIterator = createKeyboardStepIterator(); 393 394 if (stepIterator.advanceStep(direction)) { 395 moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend); 396 } 397 } 398 399 /** 400 * @return {!boolean} 401 */ 402 function moveCursorToLeft() { 403 moveCursor(PREVIOUS, false); 404 return true; 405 } 406 this.moveCursorToLeft = moveCursorToLeft; 407 408 /** 409 * @return {!boolean} 410 */ 411 function moveCursorToRight() { 412 moveCursor(NEXT, false); 413 return true; 414 } 415 this.moveCursorToRight = moveCursorToRight; 416 417 /** 418 * @return {!boolean} 419 */ 420 function extendSelectionToLeft() { 421 moveCursor(PREVIOUS, true); 422 return true; 423 } 424 this.extendSelectionToLeft = extendSelectionToLeft; 425 426 /** 427 * @return {!boolean} 428 */ 429 function extendSelectionToRight() { 430 moveCursor(NEXT, true); 431 return true; 432 } 433 this.extendSelectionToRight = extendSelectionToRight; 434 435 /** 436 * @param {!core.StepDirection} direction PREVIOUS for upwards NEXT for downwards 437 * @param {!boolean} extend 438 * @return {undefined} 439 */ 440 function moveCursorByLine(direction, extend) { 441 var paragraphNode = odtDocument.getParagraphElement(odtDocument.getCursor(inputMemberId).getNode()), 442 steps, 443 lineDirection = direction === NEXT ? 1 : -1; 444 445 runtime.assert(Boolean(paragraphNode), "SelectionController: Cursor outside paragraph"); 446 steps = odtDocument.getCursor(inputMemberId).getStepCounter().countLinesSteps(lineDirection, keyboardMovementsFilter); 447 if (extend) { 448 extendCursorByAdjustment(steps); 449 } else { 450 moveCursorByAdjustment(steps); 451 } 452 } 453 454 /** 455 * @return {!boolean} 456 */ 457 function moveCursorUp() { 458 moveCursorByLine(PREVIOUS, false); 459 return true; 460 } 461 this.moveCursorUp = moveCursorUp; 462 463 /** 464 * @return {!boolean} 465 */ 466 function moveCursorDown() { 467 moveCursorByLine(NEXT, false); 468 return true; 469 } 470 this.moveCursorDown = moveCursorDown; 471 472 /** 473 * @return {!boolean} 474 */ 475 function extendSelectionUp() { 476 moveCursorByLine(PREVIOUS, true); 477 return true; 478 } 479 this.extendSelectionUp = extendSelectionUp; 480 481 /** 482 * @return {!boolean} 483 */ 484 function extendSelectionDown() { 485 moveCursorByLine(NEXT, true); 486 return true; 487 } 488 this.extendSelectionDown = extendSelectionDown; 489 490 /** 491 * @param {!core.StepDirection} direction 492 * @param {!boolean} extend 493 * @return {undefined} 494 */ 495 function moveCursorToLineBoundary(direction, extend) { 496 var lineDirection = direction === core.StepDirection.NEXT ? 1 : -1, 497 steps = odtDocument.getCursor(inputMemberId).getStepCounter().countStepsToLineBoundary( 498 lineDirection, 499 keyboardMovementsFilter 500 ); 501 if (extend) { 502 extendCursorByAdjustment(steps); 503 } else { 504 moveCursorByAdjustment(steps); 505 } 506 } 507 508 /** 509 * @param {!core.StepDirection} direction 510 * @param {!boolean} extend whether extend the selection instead of moving the cursor 511 * @return {undefined} 512 */ 513 function moveCursorByWord(direction, extend) { 514 var cursor = odtDocument.getCursor(inputMemberId), 515 newSelection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), 516 stepIterator = createWordBoundaryStepIterator(newSelection.focusNode, newSelection.focusOffset, TRAILING_SPACE); 517 518 if (stepIterator.advanceStep(direction)) { 519 moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend); 520 } 521 } 522 523 /** 524 * @return {!boolean} 525 */ 526 function moveCursorBeforeWord() { 527 moveCursorByWord(PREVIOUS, false); 528 return true; 529 } 530 this.moveCursorBeforeWord = moveCursorBeforeWord; 531 532 /** 533 * @return {!boolean} 534 */ 535 function moveCursorPastWord() { 536 moveCursorByWord(NEXT, false); 537 return true; 538 } 539 this.moveCursorPastWord = moveCursorPastWord; 540 541 /** 542 * @return {!boolean} 543 */ 544 function extendSelectionBeforeWord() { 545 moveCursorByWord(PREVIOUS, true); 546 return true; 547 } 548 this.extendSelectionBeforeWord = extendSelectionBeforeWord; 549 550 /** 551 * @return {!boolean} 552 */ 553 function extendSelectionPastWord() { 554 moveCursorByWord(NEXT, true); 555 return true; 556 } 557 this.extendSelectionPastWord = extendSelectionPastWord; 558 559 /** 560 * @return {!boolean} 561 */ 562 function moveCursorToLineStart() { 563 moveCursorToLineBoundary(PREVIOUS, false); 564 return true; 565 } 566 this.moveCursorToLineStart = moveCursorToLineStart; 567 568 /** 569 * @return {!boolean} 570 */ 571 function moveCursorToLineEnd() { 572 moveCursorToLineBoundary(NEXT, false); 573 return true; 574 } 575 this.moveCursorToLineEnd = moveCursorToLineEnd; 576 577 /** 578 * @return {!boolean} 579 */ 580 function extendSelectionToLineStart() { 581 moveCursorToLineBoundary(PREVIOUS, true); 582 return true; 583 } 584 this.extendSelectionToLineStart = extendSelectionToLineStart; 585 586 /** 587 * @return {!boolean} 588 */ 589 function extendSelectionToLineEnd() { 590 moveCursorToLineBoundary(NEXT, true); 591 return true; 592 } 593 this.extendSelectionToLineEnd = extendSelectionToLineEnd; 594 595 /** 596 * @param {!core.StepDirection} direction 597 * @param {!boolean} extend True to extend the selection 598 * @param {!function(!Node):Node} getContainmentNode Returns a node container for the supplied node. 599 * Usually this will be something like the parent paragraph or root the supplied node is within 600 * @return {undefined} 601 */ 602 function adjustSelectionByNode(direction, extend, getContainmentNode) { 603 var validStepFound = false, 604 cursor = odtDocument.getCursor(inputMemberId), 605 containmentNode, 606 selection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), 607 rootElement = odtDocument.getRootElement(selection.focusNode), 608 stepIterator; 609 610 runtime.assert(Boolean(rootElement), "SelectionController: Cursor outside root"); 611 stepIterator = odtDocument.createStepIterator(selection.focusNode, selection.focusOffset, [baseFilter, rootFilter], rootElement); 612 stepIterator.roundToClosestStep(); 613 614 if (!stepIterator.advanceStep(direction)) { 615 return; 616 } 617 618 containmentNode = getContainmentNode(stepIterator.container()); 619 if (!containmentNode) { 620 return; 621 } 622 623 if (direction === PREVIOUS) { 624 stepIterator.setPosition(/**@type{!Node}*/(containmentNode), 0); 625 // Round up to the first walkable step in the containment node 626 validStepFound = stepIterator.roundToNextStep(); 627 } else { 628 stepIterator.setPosition(/**@type{!Node}*/(containmentNode), containmentNode.childNodes.length); 629 // Round down to the last walkable step in the containment node 630 validStepFound = stepIterator.roundToPreviousStep(); 631 } 632 633 if (validStepFound) { 634 moveCursorFocusPoint(stepIterator.container(), stepIterator.offset(), extend); 635 } 636 } 637 638 /** 639 * @return {!boolean} 640 */ 641 this.extendSelectionToParagraphStart = function() { 642 adjustSelectionByNode(PREVIOUS, true, odtDocument.getParagraphElement); 643 return true; 644 }; 645 646 /** 647 * @return {!boolean} 648 */ 649 this.extendSelectionToParagraphEnd = function () { 650 adjustSelectionByNode(NEXT, true, odtDocument.getParagraphElement); 651 return true; 652 }; 653 654 /** 655 * @return {!boolean} 656 */ 657 this.moveCursorToParagraphStart = function () { 658 adjustSelectionByNode(PREVIOUS, false, odtDocument.getParagraphElement); 659 return true; 660 }; 661 662 /** 663 * @return {!boolean} 664 */ 665 this.moveCursorToParagraphEnd = function () { 666 adjustSelectionByNode(NEXT, false, odtDocument.getParagraphElement); 667 return true; 668 }; 669 670 /** 671 * @return {!boolean} 672 */ 673 this.moveCursorToDocumentStart = function () { 674 adjustSelectionByNode(PREVIOUS, false, odtDocument.getRootElement); 675 return true; 676 }; 677 678 /** 679 * @return {!boolean} 680 */ 681 this.moveCursorToDocumentEnd = function () { 682 adjustSelectionByNode(NEXT, false, odtDocument.getRootElement); 683 return true; 684 }; 685 686 /** 687 * @return {!boolean} 688 */ 689 this.extendSelectionToDocumentStart = function () { 690 adjustSelectionByNode(PREVIOUS, true, odtDocument.getRootElement); 691 return true; 692 }; 693 694 /** 695 * @return {!boolean} 696 */ 697 this.extendSelectionToDocumentEnd = function () { 698 adjustSelectionByNode(NEXT, true, odtDocument.getRootElement); 699 return true; 700 }; 701 702 /** 703 * @return {!boolean} 704 */ 705 function extendSelectionToEntireDocument() { 706 var cursor = odtDocument.getCursor(inputMemberId), 707 rootElement = odtDocument.getRootElement(cursor.getNode()), 708 anchorNode, 709 anchorOffset, 710 stepIterator, 711 newCursorSelection; 712 713 runtime.assert(Boolean(rootElement), "SelectionController: Cursor outside root"); 714 stepIterator = odtDocument.createStepIterator(rootElement, 0, [baseFilter, rootFilter], rootElement); 715 stepIterator.roundToClosestStep(); 716 anchorNode = stepIterator.container(); 717 anchorOffset = stepIterator.offset(); 718 719 stepIterator.setPosition(rootElement, rootElement.childNodes.length); 720 stepIterator.roundToClosestStep(); 721 newCursorSelection = odtDocument.convertDomToCursorRange({ 722 anchorNode: anchorNode, 723 anchorOffset: anchorOffset, 724 focusNode: stepIterator.container(), 725 focusOffset: stepIterator.offset() 726 }); 727 session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)]); 728 return true; 729 } 730 this.extendSelectionToEntireDocument = extendSelectionToEntireDocument; 731 732 /** 733 * @return {undefined} 734 */ 735 function init() { 736 keyboardMovementsFilter.addFilter(baseFilter); 737 keyboardMovementsFilter.addFilter(odtDocument.createRootFilter(inputMemberId)); 738 } 739 init(); 740 }; 741