View Javadoc

1   package sk.uniba.euromath.editor.textEditor;
2   
3   import java.beans.PropertyChangeEvent;
4   import java.beans.PropertyChangeListener;
5   
6   import org.eclipse.draw2d.FigureCanvas;
7   import org.eclipse.draw2d.IFigure;
8   import org.eclipse.draw2d.Viewport;
9   import org.eclipse.draw2d.geometry.Point;
10  import org.eclipse.gef.GraphicalEditPart;
11  import org.eclipse.swt.SWT;
12  import org.eclipse.swt.widgets.Canvas;
13  import org.eclipse.swt.widgets.Caret;
14  
15  import sk.baka.ikslibs.ptr.DomPointer;
16  import sk.uniba.euromath.document.XMLAccess;
17  import sk.uniba.euromath.editor.textEditor.editParts.TextEditPart;
18  import sk.uniba.euromath.editor.textEditor.requests.RequestConstants;
19  import sk.uniba.euromath.editor.textEditor.requests.RetargetCaretRequest;
20  
21  /***
22   * Each TextEditor can have its own CaretManager. CaretManager holds an instance of Caret and
23   * shows it on Canvas, that is stated in constructor. 
24   * Using CaretManager can be divided into two parts
25   * <OL>
26   * <LI> <I> Model is <strong>not</strong> changed</I> - For example activating Caret at some position when clicking 
27   *      in text or moving Caret by pressing arrows. In this case no Command is used and no undo and redo
28   *      is available for this operations.
29   * <LI> <I> Model is changed</I> - When moving or activating Caret is trigered by text inserting or 
30   *      deleting, then the model is changed and undo and redo is needed. Also Caret have to be activated
31   *      after undo or redo operation on the correct location. How to do this is left on the Command.
32   * </OL>
33   * 
34   * @author Martin Kollar
35   * 3.2.2006
36   */
37  public class CaretManager {
38  	
39      /***
40       * Caret instance.
41       */
42      private Caret caret;
43      
44      /***
45       * Canvas on that is caret drawn
46       */
47      private Canvas canvas;
48      
49      /*** offset in ITextPieceKeeper in that is Caret active */
50      private int caretOffset;
51      
52      private ITextPieceKeeper keeperUnderCaret;
53      
54      private XMLAccess xmlAccess;
55      
56      /***
57       * Lisens to slide bars on the edges are moved or size of windows is changed
58       */
59      private PropertyChangeListener propertyListener;
60      
61      /***
62       * Constructor
63       * @param canvas Canvas on that the Caret will be painted
64       * @param xmlAccess The source document accessor
65       */
66      public CaretManager(Canvas canvas, XMLAccess xmlAccess){
67      	caret = new Caret(canvas, SWT.NONE);
68      	caretOffset = -1;
69      	this.xmlAccess = xmlAccess;
70      	this.canvas = canvas;
71      	
72      	propertyListener = new PropertyChangeListener() {
73  
74      		/***
75      		 * When slide bars on the edges are moved or size of windows is changed
76      		 * then position of Caret is adjusted in this method
77      		 */
78              public void propertyChange(PropertyChangeEvent evt) {
79  
80                  if (Viewport.PROPERTY_VIEW_LOCATION.equals(evt
81                          .getPropertyName())) {
82                      if ((caret == null) || caret.isDisposed())
83                          return;
84                      if (keeperUnderCaret == null)
85                      	return;
86                      IFigure hostFigure = keeperUnderCaret.getTextLocator();
87                      if (caret.isVisible() && (hostFigure.isShowing())
88                              && (getCaretOffset() != -1)) {
89  
90                          showCaretBefore(caretOffset, keeperUnderCaret);
91                      }
92                  }
93              }
94  
95          };
96          
97      }
98      
99      /***
100      * Constructor
101      * @param canvas Canvas on that the Caret will be painted
102      * @param xmlAccess The source document accessor
103      * @param keeper ITextPieceKeeper in that the Caret will be activated
104      * @param offset Index of character in ITextPieceKeeper before that Caret will be activated
105      */
106     public CaretManager(Canvas canvas, XMLAccess xmlAccess, ITextPieceKeeper keeper, int offset){
107     	this(canvas, xmlAccess);
108     	keeperUnderCaret = keeper;
109     	caretOffset = offset;
110     }
111         
112     /***
113      * Converts direction string to key code
114      * @param s One of RequestConstants.LEFT[RIGHT,UP,DOWN,HOME,END]
115      * @return SWT key code of relevant key
116      */
117     public static int directionToKeyCode(String s){
118     	if (s == RequestConstants.LEFT)
119     		return SWT.ARROW_LEFT;
120     	if (s == RequestConstants.RIGHT)
121     		return SWT.ARROW_RIGHT;
122     	if (s == RequestConstants.UP)
123     		return SWT.ARROW_UP;
124     	if (s == RequestConstants.DOWN)
125     		return SWT.ARROW_DOWN;
126     	if (s == RequestConstants.HOME)
127     		return SWT.HOME;
128     	if (s == RequestConstants.END)
129     		return SWT.END;
130     	throw new IllegalStateException("Wrong direction string - can not convert to key code");
131     }
132     
133     /***
134      * 
135      * @return TextPieceKeeper in that is the Caret activated. If it is not active,
136      *         then returns <code>null</code>
137      */
138     public ITextPieceKeeper getActiveTextPieceKeeper(){
139     	return this.keeperUnderCaret;
140     }
141     
142     /***
143      * see {@link Caret#getLocation() }
144      * @return Location of the Caret or <code>null</code> if it is not active
145      */
146     public Point getLocation(){
147     	if (checkCaret() && caret.isVisible())
148     	    return new Point(caret.getLocation().x, caret.getLocation().y);
149     	return null;
150     }
151     /***
152      * 
153      * @return DOMPointer in that is Caret activated. If it is not active, then returns <code>null</code>
154      */
155     public DomPointer getDOMPointer(){
156     	if (checkCaret() && caret.isVisible() && (getActiveTextPieceKeeper()!=null)){
157     		String id = getActiveTextPieceKeeper().getTextPieceInfo().getNodeID();
158     		
159     		return xmlAccess.getDocumentModifyHelper().getPointer(
160                     id, getActiveTextPieceKeeper().getWholeTextToPosition(caretOffset) );
161     	}
162     	return null;
163     }
164     
165     
166     /***
167      * 
168      * @return Caret offset in ITextPieceKeeper in that is active
169      */
170     public int getCaretOffset(){
171     	if (!(checkCaret() && caret.isVisible()))
172     	  throw new IllegalStateException("Caret is not active, so you can not find out its offset");
173     	return caretOffset;
174     		
175     }
176     
177     /***
178      * Activates Caret and shows it in <code>keeper</code> before character at
179      * offset <code>offset</code>. 
180      * Use this when, clicking in the text or when the model is changed and Caret have to be activated
181      * in this new model
182      * 
183      * @param keeper ITextPieceKeeper in that Caret will be shown
184      * @param offset Offset of character before that the Caret will be shown. If offset is less then 0
185      *        or more then last valid index, then IllegalArgumentException will be thrown
186      */
187     public void activateCaret(ITextPieceKeeper keeper, int offset){
188     	if ( (offset < 0) || (offset > keeper.getTextPieceInfo().getLastIndex()) )
189     			throw new IllegalArgumentException("Caret offset is wrong");
190     	caretOffset = offset;
191     	keeperUnderCaret = keeper;
192     	Viewport viewport = ((FigureCanvas) canvas).getViewport();
193     	viewport.removePropertyChangeListener(propertyListener);
194     	showCaretBefore(caretOffset, keeper);
195     	viewport.addPropertyChangeListener(propertyListener);
196     }
197     
198     /***
199      * Deactivates and hides Caret
200      */
201     public void deactivateCaret(){
202     	
203         if (propertyListener != null) {
204 //            try {
205                 ((FigureCanvas)canvas).getViewport().removePropertyChangeListener(
206                                 propertyListener);
207 //            } catch (NullPointerException npe) {
208 //            }
209         }
210 
211     	hideCaret();
212     	caretOffset = -1;
213     	keeperUnderCaret = null;
214     }
215     
216     /***
217      * Moves Caret from the current position to one positions further in
218      * LEFT,RIGHT,UP,DOWN or to the end or start of the current line.
219      * Use this method only when moving Caret using arrows or HOME,END. This is not suitable
220      * when inserting or deleting text, because the model is changed(use ActivateCaret insted).
221      *
222      * 
223      * @param direction One of <code>RequestConstants.LEFT[RIGHT,UP,DOWN,HOME,END]</code>
224      */
225     public void moveCaret(String direction){
226     	if ( (!checkCaret()) || (!caret.isVisible()) || (getActiveTextPieceKeeper()==null) )
227     		throw new IllegalStateException("Can not move Caret");
228     	if (!RequestConstants.isDirection(direction))
229     		throw new IllegalArgumentException("Wrong direction to move Caret");
230     	
231     	// Find out if the Caret can be moved
232     	ITextPieceKeeper oldPieceKeeper = getActiveTextPieceKeeper();
233     	Point p = new Point(caret.getLocation().x, oldPieceKeeper.getTextLocator().getTextBounds().y);
234     	RetargetCaretRequest request = new RetargetCaretRequest((TextEditPart)oldPieceKeeper, directionToKeyCode(direction),
235     			' ', false, p, caretOffset);
236     	
237     	ITextPieceKeeper nextPieceKeeper = (ITextPieceKeeper)((TextEditPart)oldPieceKeeper).getTargetEditPart(request);
238     	
239         if (nextPieceKeeper == null)
240            // return; // Cannot move using given event, so do nothing.
241         	throw new IllegalStateException("Can not move the Caret");
242         
243         //move the Caret
244         int movement = 0;
245          if (direction == RequestConstants.LEFT)
246             movement = -1;
247          else if (direction == RequestConstants.RIGHT)
248             movement = 1;
249          
250         if (oldPieceKeeper == nextPieceKeeper) {
251         	// movement is in the same ITextPieceKeeper
252             int newIndex;
253             if (direction == RequestConstants.HOME)
254                 newIndex = 0;
255             else if (direction == RequestConstants.END)
256                 newIndex = nextPieceKeeper.getText().length();
257             else
258                 newIndex = caretOffset + movement;
259 
260             newIndex = Math.max(0, newIndex);
261             newIndex = Math.min(oldPieceKeeper.getText().length(), newIndex);
262 
263             showCaretBefore(newIndex, oldPieceKeeper);
264 
265         } else {
266         	// moving from one ITextPieceKeeper to another
267         	
268         	if (direction == RequestConstants.END) {
269         		//moving to the end of line through at least one ITextPieceKeeper
270         		int newIndex = nextPieceKeeper.getText().length();
271         		showCaretBefore(newIndex, nextPieceKeeper);
272         		return;
273         	}
274         	if (direction == RequestConstants.HOME) {
275         		//moving to the start of line through at least one ITextPieceKeeper
276         		showCaretBefore(0, nextPieceKeeper);        		
277         		return;
278         	}
279         	
280         	int pieceIndex;
281         	int newIndex = caretOffset + movement;
282         	
283         	if (newIndex > oldPieceKeeper.getText().length()) {
284         		//Caret is after the last character of ITextPieceKeeper and would like to move more right
285         		if (oldPieceKeeper.getTextLocator().getBounds().y == nextPieceKeeper
286         				.getTextLocator().getBounds().y)
287         			// this happens when more ITextPieceKeeper appears in one line and more right from
288         			// this ITextPieceKeeper is at least one more
289         			pieceIndex = 1;
290         		else
291             		pieceIndex = 0;
292         		
293         	} else if (newIndex < 0) {
294         		//Caret is before first character of ITextPieceKeeper and would like to move more left
295         		if (oldPieceKeeper.getTextLocator().getBounds().y == nextPieceKeeper
296         				.getTextLocator().getBounds().y) 
297         			// this happens when more ITextPieceKeepers appears in one line and more left from
298         			// this ITextPieceKeeper is at least one more
299         			pieceIndex = nextPieceKeeper.getText().length() - 1;
300         			else
301                 		pieceIndex = nextPieceKeeper.getText().length();
302         		}
303         	 else {
304         		// Caret would like to move up or down 
305         		Point absPoint = new Point(caret.getLocation().x - 1,
306         				nextPieceKeeper.getTextLocator().getTextBounds().y);
307         		
308         		Point point = absPoint.getCopy();
309         		((GraphicalEditPart) nextPieceKeeper).getFigure()
310         		.translateToRelative(point);
311         		point.y = absPoint.y;
312         		pieceIndex = nextPieceKeeper.getTextLocator().getCharIndexAtPos(point);
313         	}
314         	
315         	showCaretBefore(pieceIndex, nextPieceKeeper);
316         }
317 
318    }
319     
320     /***
321      * Checks if Caret is ready to use
322      * @return <code>true</code> if caret is not <code>null</code> and is not disposed
323      */
324     protected boolean checkCaret() {
325         if ((this.caret == null) || this.caret.isDisposed())
326             return false;
327         return true;
328     }
329     
330     protected void showCaret(){
331         if (checkCaret()) 
332             if (this.caret.isVisible())
333             	this.caret.setVisible(false);
334     }
335 
336     protected void hideCaret(){
337         if (checkCaret()) 
338             if (!this.caret.isVisible())
339             	this.caret.setVisible(true);
340     }
341     
342     /***
343      * Shows caret before character at index <code>index</code> in ITextPieceKeeper <code>keeper</code>
344      * @param index Offset of charecter in ITextPieceKeeper before that the Caret will be shown
345      * @param keeper ITextPieceKeeper where to show Caret
346      */
347     protected void showCaretBefore(int index, ITextPieceKeeper keeper){
348     	if (checkCaret()){
349     	   Point p = keeper.getTextLocator().getStart(index);
350     	   keeper.getTextLocator().translateToAbsolute(p);
351     	   caret.setLocation(p.x, p.y);
352     	   caret.setSize(1, keeper.getTextLocator().getTextBounds().height);
353            if (!caret.isVisible()) {
354                caret.setVisible(true);
355            }
356            keeperUnderCaret = keeper;
357            caretOffset = index;
358     	}
359     	else
360     		throw new IllegalStateException("Caret is null or disposed");
361     	
362     }
363 }