View Javadoc

1   /*
2    * Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the License); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.editor.textEditor.tools;
13  
14  import java.util.List;
15  import java.util.Map;
16  
17  import org.eclipse.draw2d.Cursors;
18  import org.eclipse.gef.DragTracker;
19  import org.eclipse.gef.EditPart;
20  import org.eclipse.gef.commands.Command;
21  import org.eclipse.gef.tools.SelectionTool;
22  import org.eclipse.swt.SWT;
23  import org.eclipse.swt.events.KeyEvent;
24  import org.eclipse.swt.widgets.MessageBox;
25  
26  import sk.baka.ikslibs.interval.DOMInterval;
27  import sk.baka.ikslibs.interval.DOMIntervalSet;
28  import sk.baka.ikslibs.ptr.DomPointer;
29  import sk.uniba.euromath.document.XMLAccess;
30  import sk.uniba.euromath.editor.textEditor.CaretManager;
31  import sk.uniba.euromath.editor.textEditor.Direction;
32  import sk.uniba.euromath.editor.textEditor.ITextPieceContainer;
33  import sk.uniba.euromath.editor.textEditor.ITextPieceKeeper;
34  import sk.uniba.euromath.editor.textEditor.requests.editTextRequests.DeleteTextRequest;
35  import sk.uniba.euromath.editor.textEditor.requests.editTextRequests.InsertTextRequest;
36  import sk.uniba.euromath.editor.xmlEditor.XMLEditDomain;
37  import sk.uniba.euromath.editor.xmlEditor.commands.DeleteSelectionCommand;
38  import sk.uniba.euromath.editor.xmlEditor.commands.DocumentModifyCompoundCommand;
39  import sk.uniba.euromath.editor.xmlEditor.viewers.IXMLGraphicalViewer;
40  
41  /***
42   * <B>TextTool</B> - Is responsible for editing of text. Switching between
43   * tools is performed by
44   * <OL>
45   * <LI>double-click in text. That switches StructureTool to TextTool(S -> T).
46   * <LI>Ctrl+T, that swithes these tools each other(T <-> S)
47   * <LI>Menu, [by icons].
48   * </OL>
49   * <H4> More deep in TextTool </H4>
50   * <P>
51   * <OL>
52   * 
53   * <LI>Supports caret, caret is always activated. Click of mouse in text
54   * retargets caret. By clicking not directly on text, but in position where is
55   * short line of text, caret is shown at the end of line. If there is not a
56   * line, nothing happens. Caret is moved also by
57   * keyboard(arrows,HOME,END,ctrl-a, ...).
58   * 
59   * <LI>Selections.
60   * <OL>
61   * <LI>Selections of text
62   * <UL>
63   * <LI>by holding <I>shift or ctrl or ...</I> and moving caret
64   * <LI>by draging of mouse [+ keys]
65   * </UL>
66   * <LI>Selections of structure - means elements, nodes
67   * <UL>
68   * <LI><I>alt + scroll = deep </I> by scrolling wheel is selected the bottom
69   * most element in doc containing caret or (parent or child towards caret) of
70   * selected element
71   * <LI><I>alt + right or left click = width</I>, selects brother of selected
72   * element
73   * <LI><I>alt + middleButtonClick = direct select</I>, selectes the bottom
74   * most element in doc containing cursor
75   * </UL>
76   * </OL>
77   * 
78   * <LI>Undo, Redo, clipboard. Standard.
79   * 
80   * <LI>Editing.
81   * <OL>
82   * <LI>Text
83   * <UL>
84   * <LI>Insert - direct keypressing in editor or by <B>InsetBox</B>
85   * <LI>delete - <I> del, backSpace</I> + [selection] or Menu
86   * </UL>
87   * <LI>Structure
88   * <UL>
89   * <LI>Insert,Modify - through Menu
90   * <LI>delete - <I> del, backSpace</I> + [selection] or Menu
91   * </UL>
92   * </OL>
93   * 
94   * </OL>
95   * </P>
96   * 
97   * @author Martin Kollar on 3.5.2005
98   */
99  
100 public class TextTool extends SelectionTool {
101 
102         /***
103          * Debug code.
104          */
105         private static final String CODE = "text editing"; //$NON-NLS-1$
106 
107         /***
108          * New line break.
109          */
110         private static final char NEWLINE = '\n';
111 
112         /***
113          * Standard space.
114          */
115         private static final char SPACE = '\u0020';
116 
117         /***
118          * Non breakable space.
119          */
120         private static final char NBSPACE = '\u00A0';
121 
122         /***
123          * Linebreak.
124          */
125         private static final char LINEBREAK = '\u2028';
126 
127         /***
128          * Zero width space.
129          */
130         private static final char ZERO_WIDTH_SPACE = '\u200B';
131 
132         /***
133          * Non breakable zero width space.
134          */
135         private static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF';
136 
137         /***
138          * Caret offset where keyboard selection starts.
139          */
140         private int startSelectionCaretOffset;
141 
142         /***
143          * Keeper where keyboard selection starts.
144          */
145         private ITextPieceKeeper startSelectionKeeperUnderCaret;
146 
147         /***
148          * Holds reference to caretManager.
149          */
150         private final CaretManager caretManager;
151 
152         private DOMIntervalSet localSelection;
153 
154         /***
155          * Check if <code>character</code> can be written to text
156          * 
157          * @param character
158          *                character to check
159          * @return <code>true</code> if character can by written to text, else
160          *         <code>false</code>
161          */
162         static public boolean acceptableText(char character) {
163                 return Character.isLetterOrDigit(character)
164                                 || Character.isSpaceChar(character)
165                                 || (Character.getType(character) == Character.DASH_PUNCTUATION)
166                                 || (Character.getType(character) == Character.START_PUNCTUATION)
167                                 || (Character.getType(character) == Character.END_PUNCTUATION)
168                                 || (Character.getType(character) == Character.CONNECTOR_PUNCTUATION)
169                                 || (Character.getType(character) == Character.OTHER_PUNCTUATION) // 24
170                                 || (Character.getType(character) == Character.MATH_SYMBOL) // 25
171                                 || (Character.getType(character) == Character.CURRENCY_SYMBOL)
172                                 || (Character.getType(character) == Character.MODIFIER_SYMBOL);
173         }
174 
175         /***
176          * Tests if character is whitespace.
177          * 
178          * @param c
179          *                character to test
180          * @return <code>true</code> if character is whitespace
181          */
182         static public boolean isWhiteSpace(char c) {
183                 return ((c == SPACE) || (c == NEWLINE) || (c == NBSPACE)
184                                 || (c == LINEBREAK) || (c == ZERO_WIDTH_SPACE) || (c == ZERO_WIDTH_NOBREAK_SPACE));
185         }
186 
187         /***
188          * Constructor.
189          * 
190          * @param source
191          *                focused EditPart
192          * @param caretOffset
193          *                caret offset from start of selectionStartPart EditPart
194          * @param cManager
195          *                CaretManager
196          */
197         public TextTool(CaretManager cManager) {
198                 this.caretManager = cManager;
199                 setDefaultCursor(Cursors.IBEAM);
200                 setUnloadWhenFinished(false);
201         }
202 
203         /***
204          * Constructor.
205          * 
206          * @param source
207          *                focused EditPart
208          * @param caretOffset
209          *                caret offset from start of selectionStartPart EditPart
210          * @param cManager
211          *                CaretManager
212          */
213         public TextTool(CaretManager cManager, ITextPieceKeeper keeper,
214                         int offset) {
215                 this(cManager);
216                 this.caretManager.activateCaret(keeper, offset);
217         }
218 
219         /***
220          * Activates tool. Caret is activated or not, depends on.
221          */
222         @Override
223         public void activate() {
224                 super.activate();
225         }
226 
227         /***
228          * Deactivates Tool and Caret
229          */
230         @Override
231         public void deactivate() {
232                 this.caretManager.deactivateCaret();
233                 super.deactivate();
234         }
235 
236         /*
237          * (non-Javadoc)
238          * 
239          * @see org.eclipse.gef.tools.AbstractTool#getDebugName()
240          */
241         @Override
242         protected String getDebugName() {
243                 return CODE;
244         }
245 
246         /***
247          * @param event
248          *                KeyEvent left,right,up,down
249          * 
250          * @return RequestConstants.LEFT[RIGHT,UP,DOWN]
251          */
252         private Direction KeyEventToDirection(KeyEvent event) {
253                 if (event.character == SWT.BS)
254                         return Direction.Left;
255                 if (event.keyCode == SWT.ARROW_LEFT)
256                         return Direction.Left;
257                 if (event.keyCode == SWT.ARROW_RIGHT)
258                         return Direction.Right;
259                 if (event.keyCode == SWT.ARROW_UP)
260                         return Direction.Up;
261                 if (event.keyCode == SWT.ARROW_DOWN)
262                         return Direction.Down;
263                 throw new IllegalArgumentException();
264         }
265 
266         /***
267          * @param e
268          *                Left,Right,Up,Down,Home,End KeyEvent
269          */
270         protected void performArrowMove(KeyEvent e) {
271                 this.caretManager.moveCaret(KeyEventToDirection(e), 1);
272         }
273 
274         /***
275          * Performs text input, including caret movement.
276          * 
277          * @param e
278          *                key event holding character to input
279          */
280         protected Command getTextInputCommand(KeyEvent e) {
281                 ITextPieceKeeper keeper = this.caretManager
282                                 .getActiveTextPieceKeeper();
283 
284                 InsertTextRequest textRequest = new InsertTextRequest(keeper,
285                                 String.valueOf(e.character), this.caretManager
286                                                 .getCaretOffset());
287 
288                 // TODO Studva maybe space insertion deny at some places
289                 return keeper.getCommand(textRequest);
290         }
291 
292         protected Command getDeleteSelectionCommand() {
293                 // if selection is not empty, then delete it
294                 if (!isSelectionEmpty()) {
295                         Command command = new DeleteSelectionCommand(
296                                         getXMLAccess(), this.caretManager,
297                                         getXMLGraphicalViewer());
298                         return command;
299                 }
300                 throw new IllegalArgumentException();
301         }
302 
303         /***
304          * Performs text delete, including caret movement.
305          * 
306          * @param e
307          *                key event holding delete order
308          */
309         protected Command getDeleteCommand(KeyEvent e) {
310                 // if selection is not empty, then delete it
311                 if (!isSelectionEmpty())
312                         return getDeleteSelectionCommand();
313 
314                 // if selection is empty, then delete char at caret
315                 if (this.caretManager.getActiveTextPieceKeeper() == null)
316                         return null;
317                 Direction direction;
318                 if (e.character == SWT.BS)
319                         direction = Direction.Left;
320                 else if (e.keyCode == 127)
321                         direction = Direction.Right;
322                 else
323                         throw new IllegalArgumentException();
324 
325                 DeleteTextRequest deleteRequest = new DeleteTextRequest(
326                                 this.caretManager.getActiveTextPieceKeeper(),
327                                 this.caretManager.getCaretOffset(), 1,
328                                 direction);
329 
330                 return this.caretManager.getActiveTextPieceKeeper().getCommand(
331                                 deleteRequest);
332         }
333 
334         /***
335          * Tests if key of event is navigation key.
336          * 
337          * @param e
338          *                event to test
339          * @return true if is navigation key
340          */
341         protected boolean acceptableArrow(KeyEvent e) {
342                 int code = e.keyCode;
343                 return (code == SWT.ARROW_LEFT) || (code == SWT.ARROW_RIGHT)
344                                 || (code == SWT.ARROW_DOWN)
345                                 || (code == SWT.ARROW_UP) || (code == SWT.HOME)
346                                 || (code == SWT.END);
347         }
348 
349         /***
350          * Tests if key of event is deleting key.
351          * 
352          * @param e
353          *                event to test
354          * @return true if is deleting key
355          */
356         protected boolean isDeletingAction(KeyEvent e) {
357                 return (e.keyCode == SWT.BS) || (e.keyCode == SWT.DEL);
358         }
359 
360         /***
361          * Process only left mouse clicks (as selection or anything).
362          */
363         @Override
364         protected boolean handleButtonDown(int button) {
365                 if (button != 1)
366                         return false;
367                 return super.handleButtonDown(button);
368         }
369 
370         @Override
371         public void setDragTracker(DragTracker newDragTracker) {
372                 /*TODO Studva konzultovat s Motom ci takto alebo nie
373                 if ((newDragTracker != null)
374                                 && !(newDragTracker instanceof TextDragTracker))
375                         return;
376                 */
377                 super.setDragTracker(newDragTracker);
378         }
379 
380         /***
381          * Handles key downs to move caret and make key controlled selections.
382          */
383         @Override
384         protected boolean handleKeyDown(KeyEvent e) {
385                 if (this.caretManager.getActiveTextPieceKeeper() == null)
386                         return false;
387 
388                 if (e.keyCode == SWT.SHIFT) {
389                         // by pressing shift or shift + ctrl we are ready to
390                         // keyboard
391                         // drag
392                         stateTransition(STATE_INITIAL, STATE_ACCESSIBLE_DRAG);
393                         // remember start selection point
394                         this.startSelectionKeeperUnderCaret = this.caretManager
395                                         .getActiveTextPieceKeeper();
396                         this.startSelectionCaretOffset = this.caretManager
397                                         .getCaretOffset();
398                 }
399 
400                 if (acceptableArrow(e)) {
401                         performArrowMove(e);
402                         // if ACCESSIBLE_DRAG, keyboard drag started
403                         stateTransition(STATE_ACCESSIBLE_DRAG,
404                                         STATE_ACCESSIBLE_DRAG_IN_PROGRESS);
405 
406                         if (isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
407                                 // drag has started now or is in progress
408                                 getXMLGraphicalViewer().setSelection(
409                                                 computeGlobalSelection());
410                         }
411                         if (isInState(STATE_INITIAL)) {
412                                 // not dragging - caret movement cancels
413                                 // selection
414                                 getCurrentViewer().deselectAll();
415                         }
416                         return true;
417                 }
418 
419                 if (acceptableText(e.character)) {
420 
421                         // by inputing char, we set initial state
422                         setState(STATE_INITIAL);
423 
424                         // delete selected text and input new text
425                         if (!isSelectionEmpty()) {
426                                 Command command = new DocumentModifyCompoundCommand(
427                                                 getXMLAccess());
428                                 command.chain(getDeleteSelectionCommand());
429                                 command.chain(getTextInputCommand(e));
430                                 executeCommand(command);
431                         } else {
432                                 executeCommand(getTextInputCommand(e));
433                         }
434                         return true;
435                 }
436 
437                 if (isDeletingAction(e)) {
438                         executeCommand(getDeleteCommand(e));
439                         return true;
440                 }
441 
442                 return false;
443         }
444 
445         @Override
446         protected boolean handleKeyUp(KeyEvent e) {
447                 // 166 = t/T - switching tools
448                 if ((e.keyCode == 116)
449                                 && (getCurrentInput().isControlKeyDown())) {
450                         this.caretManager.deactivateCaret();
451                         getDomain().setActiveTool(new TextStructureTool());
452                         return true;
453                 }
454 
455                 if (e.keyCode == SWT.SHIFT) {
456                         if (isInState(STATE_ACCESSIBLE_DRAG
457                                         | STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
458                                 getXMLGraphicalViewer().setSelection(
459                                                 computeGlobalSelection());
460                         }
461                         setState(STATE_INITIAL);
462                         return true;
463                 }
464 
465                 return false;
466         }
467 
468         /***
469          * Updates local selection object corresponding to keyboard drag.
470          * Selection object is made current for state of this tool after method
471          * execution.
472          */
473         protected void updateLocalSelection() {
474                 DomPointer pointer1 = this.startSelectionKeeperUnderCaret
475                                 .getTextPieceInfo().getDomPointer(
476                                                 this.startSelectionCaretOffset,
477                                                 getXMLAccess());
478                 DomPointer pointer2 = this.caretManager
479                                 .getActiveTextPieceKeeper()
480                                 .getTextPieceInfo()
481                                 .getDomPointer(
482                                                 this.caretManager
483                                                                 .getCaretOffset(),
484                                                 getXMLAccess());
485                 // compare pointers to find they ordering
486                 if (pointer1.equals(pointer2)) {
487                         // pointers point to same place = nothing selected by
488                         // this drag
489                         this.localSelection = new DOMIntervalSet();
490                         return;
491                 }
492 
493                 if (pointer1.compareTo(pointer2) < 0) {
494                         this.localSelection = new DOMIntervalSet(
495                                         new DOMInterval(pointer1, pointer2));
496 
497                 } else {
498                         this.localSelection = new DOMIntervalSet(
499                                         new DOMInterval(pointer2, pointer1));
500                 }
501         }
502 
503         /***
504          * Computes actual selection made by keyboard(arrows) to set to viewer.
505          * Pressed ctrl and shift keys influence how selection is computed. The
506          * user needs 2 styles of selecting: simple interval (shift pressed),
507          * simple interval with caret movement by words(shift + ctrl pressed).
508          * 
509          * @return actual selection of viewer
510          */
511         protected DOMIntervalSet computeGlobalSelection() {
512                 updateLocalSelection();
513 
514                 // shift pressed or shift + ctrl pressed
515                 if (getCurrentInput().isShiftKeyDown()) {
516                         // simple interval
517                         return this.localSelection;
518                 }
519                 // keys are illegaly pressed, don't change viewer's selection
520                 return getXMLGraphicalViewer().getDOMSelection();
521         }
522 
523         /***
524          * Method to corect state of this tool after transformation of document.
525          * That means to set correctly caret exactly to the position in document
526          * where was before transformation.
527          * 
528          * @param map
529          */
530         public void reinit(Map<String, List<ITextPieceContainer>> map) {
531                 if (!this.caretManager.isCaretActive())
532                         return;
533                 String nodeId = this.caretManager.getActiveTextPieceKeeper()
534                                 .getTextPieceInfo().getNodeID();
535                 int nodeOffset = this.caretManager
536                                 .getActiveTextPieceKeeper()
537                                 .getTextPieceInfo()
538                                 .resolveRenderedIndex(
539                                                 this.caretManager
540                                                                 .getCaretOffset(),
541                                                 getXMLAccess());
542                 // TODO Studva LOW PRIORITY - not functional when not injective
543                 // XSLT
544                 ITextPieceContainer container = map.get(nodeId).get(0);
545                 int renderedContainerOffset = container.resolveOriginalIndex(
546                                 nodeOffset, getXMLAccess());
547                 ITextPieceKeeper keeper = container
548                                 .getKeeperByRenderedOffset(renderedContainerOffset);
549                 this.caretManager
550                                 .activateCaret(
551                                                 keeper,
552                                                 renderedContainerOffset
553                                                                 - keeper
554                                                                                 .getTextPieceInfo()
555                                                                                 .getRenderedOffset());
556         }
557 
558         /***
559          * Getter.
560          * 
561          * @return IXMLGraphicalViewer
562          */
563         protected IXMLGraphicalViewer getXMLGraphicalViewer() {
564                 return (IXMLGraphicalViewer) getCurrentViewer();
565         }
566 
567         /***
568          * Getter.
569          * 
570          * @return xmlAccess instance
571          */
572         protected XMLAccess getXMLAccess() {
573                 return ((XMLEditDomain) getDomain()).getXMLEditor()
574                                 .getXMLAccess();
575         }
576 
577         @Override
578         protected String getCommandName() {
579                 return CODE;
580         }
581 
582         /***
583          * Simpler implementation than super, as target is directly taken
584          * editpart at mouse cursor location.
585          */
586         @Override
587         protected boolean updateTargetUnderMouse() {
588                 if (!isTargetLocked()) {
589                         EditPart editPart = getCurrentViewer().findObjectAt(
590                                         getLocation());
591                         boolean changed = getTargetEditPart() != editPart;
592                         setTargetEditPart(editPart);
593                         return changed;
594                 }
595                 return false;
596         }
597 
598         protected boolean isSelectionEmpty() {
599                 return getXMLGraphicalViewer().getDOMSelection().isEmpty();
600         }
601 
602         protected void executeCommand(Command command) {
603                 if (command.canExecute()) {
604                         getDomain().getCommandStack().execute(command);
605                 } else {
606                         MessageBox messageBox = new MessageBox(
607                                         ((XMLEditDomain) getDomain())
608                                                         .getXMLEditor()
609                                                         .getEditorSite()
610                                                         .getSite().getShell());
611                         messageBox
612                                         .setText("Document doesn't allows this operation.");
613                 }
614         }
615 }