1
2
3
4
5
6
7
8
9
10
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";
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)
170 || (Character.getType(character) == Character.MATH_SYMBOL)
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
238
239
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
289 return keeper.getCommand(textRequest);
290 }
291
292 protected Command getDeleteSelectionCommand() {
293
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
311 if (!isSelectionEmpty())
312 return getDeleteSelectionCommand();
313
314
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
373
374
375
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
390
391
392 stateTransition(STATE_INITIAL, STATE_ACCESSIBLE_DRAG);
393
394 this.startSelectionKeeperUnderCaret = this.caretManager
395 .getActiveTextPieceKeeper();
396 this.startSelectionCaretOffset = this.caretManager
397 .getCaretOffset();
398 }
399
400 if (acceptableArrow(e)) {
401 performArrowMove(e);
402
403 stateTransition(STATE_ACCESSIBLE_DRAG,
404 STATE_ACCESSIBLE_DRAG_IN_PROGRESS);
405
406 if (isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
407
408 getXMLGraphicalViewer().setSelection(
409 computeGlobalSelection());
410 }
411 if (isInState(STATE_INITIAL)) {
412
413
414 getCurrentViewer().deselectAll();
415 }
416 return true;
417 }
418
419 if (acceptableText(e.character)) {
420
421
422 setState(STATE_INITIAL);
423
424
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
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
486 if (pointer1.equals(pointer2)) {
487
488
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
515 if (getCurrentInput().isShiftKeyDown()) {
516
517 return this.localSelection;
518 }
519
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
543
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 }