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.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"; //$NON-NLS-1$
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) // 24
173                 || (Character.getType(character) == Character.MATH_SYMBOL) // 25
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         // TODO GUI: Here can be a problem, when Tool is deactivated, than some
213         // changes happen
214         // and than again activated - partUnderCaret, do not have to exist or
215         // startCaretOffset can
216         // be out of range
217         // It could be solved like not to store EditPart and offset of Caret
218         // where to be caret
219         // activated, but (x,y) location - this is a problem too, because (x,y)
220     	// location is relative
221         super.activate();
222 
223         this.caretManager.activateCaret((ITextPieceKeeper) getPartUnderCaret(),
224                 this.startCaretOffset);
225         /*
226          * Point loc = getLocation().getCopy();
227          * ((ITextPieceKeeper)getActive()).getTextLocator().translateToRelative(loc);
228          * 
229          * int caretIndex =
230          * ((ITextPieceKeeper)getActive()).getTextLocator().getCharIndexAtPos(loc);
231          * 
232          * sendRequest(getActive(), new
233          * ActivateCaretRequest(this.startCaretOffset));
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      * (non-Javadoc)
248      * 
249      * @see org.eclipse.gef.tools.AbstractTool#getDebugName()
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             // TODO Kollar Solve this casting to TextEditPart
306             if (this.caretManager.getCaretOffset() == 0)
307                 // space can not be written here because of start of line or
308                 // TextNode
309                 return;
310 
311             // TODO Kollar Solve this casting to TextEditPart
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                     // we are trying to write a space at the at of TextNode a
320                     // the last character is space
321                     return;
322             } else if ((isWhiteSpace(containerText.toCharArray()[index]))
323                     || (isWhiteSpace(containerText.toCharArray()[index - 1])))
324                 // space can not be written here because of there can not be two
325                 // adjacent spaces
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"); //$NON-NLS-1$
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          * request.setAction(EditTextRequest.DELETE);
365          * 
366          * EditPart target = getActive().getTargetEditPart(request);
367          * 
368          * if (target == null) return;
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"); //$NON-NLS-1$
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             // TODO Kollar: delete selected text and input new text
434             // setCaretMovement(1);
435             performTextInput(e);
436             // setWaitForRepaint(true);
437         } else if (isDeletingAction(e)) {
438             // TODO Kollar: delete selected text
439             // setCaretMovement(0);
440             performDelete(e);
441             // setWaitForRepaint(true);
442         }
443 
444         // return super.handleKeyDown(e);
445         return true;
446     }
447 
448     @Override
449     protected boolean handleKeyUp(KeyEvent e) {
450         if ( (e.keyCode == SWT.ESC) && (getCurrentInput().isShiftKeyDown()) ){
451         	//while selecting ESC is pressed - this cause discarding current selection
452             clearSelection();
453         	DOMIntervalSet globalSelection = ((IXMLGraphicalViewer)getCurrentViewer()).getDOMSelection();
454         	((IXMLGraphicalViewer)getCurrentViewer()).setSelection(globalSelection);
455         }
456         
457         // 166 = t/T
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     	//TODO: Kollar DOMIntervalSet.union returns set difference between
517     	// globalSelection and localSelection, it is DOMIntervalSet - set of DOMIntervals
518     	// that if will be add to old globalSelection, that this will make the same
519     	// new globalSelection. This can be used to define the change between old selection and
520     	// new selection. Only this change should be then updated in SelectionPolicy for instance.
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         // do some corrective steps
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 }