1
2
3
4
5
6
7
8
9
10
11
12 package sk.uniba.euromath.editor.textEditor.tools;
13
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Map;
17
18 import org.eclipse.draw2d.Cursors;
19 import org.eclipse.draw2d.geometry.Point;
20 import org.eclipse.gef.EditPart;
21 import org.eclipse.gef.Request;
22 import org.eclipse.gef.tools.SelectionTool;
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.events.KeyEvent;
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.editor.textEditor.CaretManager;
30 import sk.uniba.euromath.editor.textEditor.ITextPieceKeeper;
31 import sk.uniba.euromath.editor.textEditor.TextPieceContainer;
32 import sk.uniba.euromath.editor.textEditor.editParts.TextEditPart;
33 import sk.uniba.euromath.editor.textEditor.requests.RequestConstants;
34 import sk.uniba.euromath.editor.textEditor.requests.RetargetCaretRequest;
35 import sk.uniba.euromath.editor.textEditor.requests.editTextRequests.DeleteTextRequest;
36 import sk.uniba.euromath.editor.textEditor.requests.editTextRequests.InsertTextRequest;
37 import sk.uniba.euromath.editor.xmlEditor.viewers.IXMLGraphicalViewer;
38
39 /***
40 * <B>TextTool</B> - Is responsible for editing of text. Switching between
41 * tools is performed by
42 * <OL>
43 * <LI>double-click in text. That switches StructureTool to TextTool(S -> T).
44 * <LI>Ctrl+T, that swithes these tools each other(T <-> S)
45 * <LI>Menu, [by icons].
46 * </OL>
47 * <H4> More deep in TextTool </H4>
48 * <P>
49 * <OL>
50 *
51 * <LI>Supports caret, caret is always activated. Click of mouse in text
52 * retargets caret. By clicking not directly on text, but in position where is
53 * short line of text, caret is shown at the end of line. If there is not a
54 * line, nothing happens. Caret is moved also by
55 * keyboard(arrows,HOME,END,ctrl-a, ...).
56 *
57 * <LI>Selections.
58 * <OL>
59 * <LI>Selections of text
60 * <UL>
61 * <LI>by holding <I>shift or ctrl or ...</I> and moving caret
62 * <LI>by draging of mouse [+ keys]
63 * </UL>
64 * <LI>Selections of structure - means elements, nodes
65 * <UL>
66 * <LI><I>alt + scroll = deep </I> by scrolling wheel is selected the bottom
67 * most element in doc containing caret or (parent or child towards caret) of
68 * selected element
69 * <LI><I>alt + right or left click = width</I>, selects brother of selected
70 * element
71 * <LI><I>alt + middleButtonClick = direct select</I>, selectes the bottom
72 * most element in doc containing cursor
73 * </UL>
74 * </OL>
75 *
76 * <LI>Undo, Redo, clipboard. Standard.
77 *
78 * <LI>Editing.
79 * <OL>
80 * <LI>Text
81 * <UL>
82 * <LI>Insert - direct keypressing in editor or by <B>InsetBox</B>
83 * <LI>delete - <I> del, backSpace</I> + [selection] or Menu
84 * </UL>
85 * <LI>Structure
86 * <UL>
87 * <LI>Insert,Modify - through Menu
88 * <LI>delete - <I> del, backSpace</I> + [selection] or Menu
89 * </UL>
90 * </OL>
91 *
92 * </OL>
93 * </P>
94 *
95 * @author Martin Kollar on 3.5.2005
96 */
97
98 public class TextTool extends SelectionTool {
99
100 /***
101 * Debug code.
102 */
103 private static final String CODE = "text editing";
104
105 /***
106 * New line break.
107 */
108 private static final char NEWLINE = '\n';
109
110 /***
111 * Standard space.
112 */
113 private static final char SPACE = '\u0020';
114
115 /***
116 * Non breakable space.
117 */
118 private static final char NBSPACE = '\u00A0';
119
120 /***
121 * Linebreak.
122 */
123 private static final char LINEBREAK = '\u2028';
124
125 /***
126 * Zero width space.
127 */
128 private static final char ZERO_WIDTH_SPACE = '\u200B';
129
130 /***
131 * Non breakable zero width space.
132 */
133 private static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF';
134
135 private DOMInterval localSelection;
136
137 /***
138 * Caret offset when the tool created This needs to be stored because of
139 * activating this Tool
140 */
141 private int startCaretOffset;
142
143 private EditPart partUnderCaret;
144
145 /***
146 * handle events during transformation
147 */
148 private final List<KeyEvent> eventBuffer = new ArrayList<KeyEvent>();
149
150 private boolean waitForRepaint;
151
152 /***
153 * Holds reference to caretManager.
154 */
155 private CaretManager caretManager;
156
157 /***
158 * Check if <code>character</code> can be written to text
159 *
160 * @param character
161 * character to check
162 * @return <code>true</code> if character can by written to text, else
163 * <code>false</code>
164 */
165 static public boolean acceptableText(char character) {
166 return Character.isLetterOrDigit(character)
167 || Character.isSpaceChar(character)
168 || (Character.getType(character) == Character.DASH_PUNCTUATION)
169 || (Character.getType(character) == Character.START_PUNCTUATION)
170 || (Character.getType(character) == Character.END_PUNCTUATION)
171 || (Character.getType(character) == Character.CONNECTOR_PUNCTUATION)
172 || (Character.getType(character) == Character.OTHER_PUNCTUATION)
173 || (Character.getType(character) == Character.MATH_SYMBOL)
174 || (Character.getType(character) == Character.CURRENCY_SYMBOL)
175 || (Character.getType(character) == Character.MODIFIER_SYMBOL);
176 }
177
178 /***
179 * @param c
180 * character to check for whitespace
181 * @return <code>true</code> if character is whitespace
182 */
183 static public boolean isWhiteSpace(char c) {
184 return ((c == SPACE) || (c == NEWLINE) || (c == NBSPACE)
185 || (c == LINEBREAK) || (c == ZERO_WIDTH_SPACE) || (c == ZERO_WIDTH_NOBREAK_SPACE));
186 }
187
188 /***
189 * @param priorTool
190 * tool used before this one
191 * @param source
192 * focused EditPart
193 * @param caretOffset
194 * caret offset from start of selectionStartPart EditPart
195 * @param cManager
196 * CaretManager
197 */
198 public TextTool(EditPart source, int caretOffset, CaretManager cManager) {
199 setDefaultCursor(Cursors.IBEAM);
200 setUnloadWhenFinished(false);
201 this.startCaretOffset = caretOffset;
202 setPartUnderCaret(source);
203 this.caretManager = cManager;
204 setWaitForRepaint(false);
205 }
206
207 /***
208 * Activates Tool and Caret on <code>startCaretOffset</code>
209 */
210 @Override
211 public void activate() {
212
213
214
215
216
217
218
219
220
221 super.activate();
222
223 this.caretManager.activateCaret((ITextPieceKeeper) getPartUnderCaret(),
224 this.startCaretOffset);
225
226
227
228
229
230
231
232
233
234
235 }
236
237 /***
238 * Deactivates Tool and Caret
239 */
240 @Override
241 public void deactivate() {
242 this.startCaretOffset = this.caretManager.getCaretOffset();
243 super.deactivate();
244 }
245
246
247
248
249
250
251 @Override
252 protected String getDebugName() {
253 return CODE;
254 }
255
256 /***
257 * @param event
258 * KeyEvent left,right,up,down
259 *
260 * @return RequestConstants.LEFT[RIGHT,UP,DOWN]
261 */
262 private String KeyEventToDirection(KeyEvent event) {
263 if (event.character == SWT.BS)
264 return RequestConstants.LEFT;
265 if (event.keyCode == SWT.ARROW_LEFT)
266 return RequestConstants.LEFT;
267 if (event.keyCode == SWT.ARROW_RIGHT)
268 return RequestConstants.RIGHT;
269 if (event.keyCode == SWT.ARROW_UP)
270 return RequestConstants.UP;
271 if (event.keyCode == SWT.ARROW_DOWN)
272 return RequestConstants.DOWN;
273 if (event.keyCode == SWT.HOME)
274 return RequestConstants.HOME;
275 if (event.keyCode == SWT.END)
276 return RequestConstants.END;
277
278 throw new IllegalArgumentException();
279 }
280
281 /***
282 * @param e
283 * Left,Right,Up,Down,Home,End KeyEvent
284 */
285 protected void performArrowMove(KeyEvent e) {
286 this.caretManager.moveCaret(KeyEventToDirection(e));
287 }
288
289
290 /***
291 * @param e
292 */
293 protected void performTextInput(KeyEvent e) {
294 if ((getPartUnderCaret() == null)
295 && (!(getPartUnderCaret() instanceof ITextPieceKeeper)))
296 return;
297
298 ITextPieceKeeper oldTextPieceKeeper = (ITextPieceKeeper) getPartUnderCaret();
299
300 InsertTextRequest textRequest = new InsertTextRequest(
301 oldTextPieceKeeper, String.valueOf(e.character),
302 this.caretManager.getCaretOffset());
303
304 if (isWhiteSpace(e.character)) {
305
306 if (this.caretManager.getCaretOffset() == 0)
307
308
309 return;
310
311
312 String containerText = ((TextEditPart) oldTextPieceKeeper)
313 .getWholeContainerText();
314 int index = oldTextPieceKeeper.getTextPieceInfo().getOffset()
315 + this.caretManager.getCaretOffset();
316
317 if (index == containerText.length()) {
318 if (isWhiteSpace(containerText.toCharArray()[index - 1]))
319
320
321 return;
322 } else if ((isWhiteSpace(containerText.toCharArray()[index]))
323 || (isWhiteSpace(containerText.toCharArray()[index - 1])))
324
325
326 return;
327 }
328
329 Point p = new Point(this.caretManager.getLocation().x,
330 oldTextPieceKeeper.getTextLocator().getTextBounds().y);
331
332 int caretOffset = this.caretManager.getCaretOffset();
333 RetargetCaretRequest retargetReq = new RetargetCaretRequest(
334 (EditPart) oldTextPieceKeeper, e.keyCode, e.character,
335 acceptableText(e.character), p, caretOffset);
336 ITextPieceKeeper nextTextPieceKeeper = (ITextPieceKeeper) ((EditPart) oldTextPieceKeeper)
337 .getTargetEditPart(retargetReq);
338
339 if (nextTextPieceKeeper == null)
340 throw new IllegalStateException("Cannot move to null EditPart");
341
342 if (oldTextPieceKeeper != nextTextPieceKeeper)
343 setPartUnderCaret((EditPart) nextTextPieceKeeper);
344
345 textRequest.setDestination(nextTextPieceKeeper);
346
347 sendRequest((EditPart) oldTextPieceKeeper, textRequest);
348 }
349
350 protected void performDelete(KeyEvent e) {
351 if (getPartUnderCaret() == null)
352 return;
353
354 int length = 0;
355 if (e.character == SWT.BS)
356 length = -1;
357 if (e.keyCode == 127)
358 length = 1;
359 DeleteTextRequest deleteRequest = new DeleteTextRequest(
360 (ITextPieceKeeper) getPartUnderCaret(), this.caretManager
361 .getCaretOffset(), length);
362
363
364
365
366
367
368
369
370 TextEditPart oldEditPart = (TextEditPart) getPartUnderCaret();
371 Point p = new Point(this.caretManager.getLocation().x, oldEditPart
372 .getTextLocator().getTextBounds().y);
373
374 int caretOffset = this.caretManager.getCaretOffset();
375 RetargetCaretRequest retargetReq = new RetargetCaretRequest(
376 oldEditPart, e.keyCode, e.character,
377 acceptableText(e.character), p, caretOffset);
378 EditPart nextEditPart = oldEditPart.getTargetEditPart(retargetReq);
379
380 if (nextEditPart == null)
381 throw new IllegalStateException("Cannot move to null EditPart");
382
383 if (oldEditPart != nextEditPart)
384 setPartUnderCaret(nextEditPart);
385
386 deleteRequest.setDestination((ITextPieceKeeper) nextEditPart);
387 sendRequest(getPartUnderCaret(), deleteRequest);
388 }
389
390
391 /***
392 * @param e
393 * @return
394 */
395 protected boolean acceptableArrow(KeyEvent e) {
396 int code = e.keyCode;
397 return (code == SWT.ARROW_LEFT) || (code == SWT.ARROW_RIGHT)
398 || (code == SWT.ARROW_DOWN) || (code == SWT.ARROW_UP)
399 || (code == SWT.HOME) || (code == SWT.END);
400 }
401
402 /***
403 * @param e
404 * @return
405 */
406 protected boolean isDeletingAction(KeyEvent e) {
407 return (e.keyCode == SWT.BS) || (e.keyCode == SWT.DEL);
408 }
409
410 @Override
411 protected boolean handleKeyDown(KeyEvent e) {
412 if (!((getCurrentInput().isShiftKeyDown() || e.keyCode == SWT.CONTROL) || (getCurrentInput()
413 .isControlKeyDown() || e.keyCode == SWT.SHIFT))) {
414 getCurrentViewer().deselectAll();
415 }
416
417 if ((e.keyCode == SWT.SHIFT) || (getCurrentInput().isShiftKeyDown()))
418 {
419 performInitSelection();
420 }
421
422 if (isWaitForRepaint()) {
423 this.eventBuffer.add(e);
424 return super.handleKeyDown(e);
425 }
426
427 if (acceptableArrow(e)) {
428 performArrowMove(e);
429 if (getCurrentInput().isShiftKeyDown())
430 performDragingSelection();
431
432 } else if (acceptableText(e.character)) {
433
434
435 performTextInput(e);
436
437 } else if (isDeletingAction(e)) {
438
439
440 performDelete(e);
441
442 }
443
444
445 return true;
446 }
447
448 @Override
449 protected boolean handleKeyUp(KeyEvent e) {
450 if ( (e.keyCode == SWT.ESC) && (getCurrentInput().isShiftKeyDown()) ){
451
452 clearSelection();
453 DOMIntervalSet globalSelection = ((IXMLGraphicalViewer)getCurrentViewer()).getDOMSelection();
454 ((IXMLGraphicalViewer)getCurrentViewer()).setSelection(globalSelection);
455 }
456
457
458 if ((e.keyCode == 116) && (getCurrentInput().isControlKeyDown())) {
459 this.caretManager.deactivateCaret();
460 getDomain().setActiveTool(
461 new TextStructureTool(((TextEditPart) getPartUnderCaret())
462 .getTextLocator().getTextBounds().getBottomLeft(),
463 getPartUnderCaret()));
464 }
465
466 if (e.keyCode == SWT.SHIFT) {
467 performFinishSelection();
468 }
469
470 return super.handleKeyUp(e);
471 }
472
473 /***
474 * @param target
475 * @param request
476 */
477 protected void sendRequest(EditPart target, Request request) {
478 assert (target != null);
479 target.performRequest(request);
480 }
481
482 @Override
483 protected boolean handleButtonDown(int button) {
484 clearSelection();
485 return super.handleButtonDown(button);
486 }
487
488 /***
489 * Switchs the tool to StructureTool if the structure element was clicked or
490 * performs MouseClick
491 *
492 * @param button
493 * that is released
494 * @return <code>true</code> if the button up was handled
495 */
496 @Override
497 protected boolean handleButtonUp(int button) {
498 return super.handleButtonUp(button);
499 }
500
501 protected void performInitSelection(){
502 this.localSelection = new DOMInterval(
503 this.caretManager.getDOMPointer(),this.caretManager.getDOMPointer());
504 }
505
506 /***
507 * Puts together(makes union) globalSelction with localSelction and updates
508 * this new globalSelection to GraphicalViewer
509 */
510 protected void performDragingSelection(){
511 DomPointer from = this.localSelection.from;
512 DomPointer to = this.caretManager.getDOMPointer();
513 this.localSelection = new DOMInterval(from,to);
514 DOMIntervalSet globalSelection = ((IXMLGraphicalViewer)getCurrentViewer()).getDOMSelection();
515 globalSelection.union(this.localSelection);
516
517
518
519
520
521 ((IXMLGraphicalViewer)getCurrentViewer()).showSelection(globalSelection);
522 }
523
524 protected void performFinishSelection(){
525 DOMIntervalSet globalSelection = ((IXMLGraphicalViewer)getCurrentViewer()).getDOMSelection();
526 globalSelection.union(this.localSelection);
527 ((IXMLGraphicalViewer)getCurrentViewer()).setSelection(globalSelection);
528 clearSelection();
529 }
530
531 protected void clearSelection(){
532 this.localSelection = null;
533 }
534
535 /***
536 * @param keepers
537 * @param nodeIndex
538 * @return
539 */
540 protected int findLine(List<ITextPieceKeeper> keepers, final int nodeIndex) {
541 int result = 0;
542 int endIndex;
543 for (ITextPieceKeeper keeper : keepers) {
544 endIndex = keeper.getTextPieceInfo().getOffset()
545 + keeper.getTextPieceInfo().getLength();
546 if (endIndex < nodeIndex)
547 result++;
548 else
549 break;
550 }
551 return Math.min(result, keepers.size() - 1);
552 }
553
554 /***
555 * Method to corect state of this tool after transformation of document.
556 * That means to set correctly caret exactly to the position in document
557 * where was before transformation and handle after caret events.
558 *
559 * @param map
560 */
561 public void reinit(Map<String, TextPieceContainer> map) {
562
563 }
564
565 /***
566 * @return
567 */
568 private boolean isWaitForRepaint() {
569 return this.waitForRepaint;
570 }
571
572 /***
573 * @param waitForRepaint
574 */
575 private void setWaitForRepaint(boolean waitForRepaint) {
576 this.waitForRepaint = waitForRepaint;
577 }
578
579 /***
580 * @return EditPart under Caret
581 */
582 public EditPart getPartUnderCaret() {
583 return this.partUnderCaret;
584 }
585
586 /***
587 * @param part
588 * EditPart that is now active
589 */
590 public void setPartUnderCaret(EditPart part) {
591 this.partUnderCaret = part;
592 }
593
594
595
596 }