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
205 ((FigureCanvas)canvas).getViewport().removePropertyChangeListener(
206 propertyListener);
207
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
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
241 throw new IllegalStateException("Can not move the Caret");
242
243
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
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
267
268 if (direction == RequestConstants.END) {
269
270 int newIndex = nextPieceKeeper.getText().length();
271 showCaretBefore(newIndex, nextPieceKeeper);
272 return;
273 }
274 if (direction == RequestConstants.HOME) {
275
276 showCaretBefore(0, nextPieceKeeper);
277 return;
278 }
279
280 int pieceIndex;
281 int newIndex = caretOffset + movement;
282
283 if (newIndex > oldPieceKeeper.getText().length()) {
284
285 if (oldPieceKeeper.getTextLocator().getBounds().y == nextPieceKeeper
286 .getTextLocator().getBounds().y)
287
288
289 pieceIndex = 1;
290 else
291 pieceIndex = 0;
292
293 } else if (newIndex < 0) {
294
295 if (oldPieceKeeper.getTextLocator().getBounds().y == nextPieceKeeper
296 .getTextLocator().getBounds().y)
297
298
299 pieceIndex = nextPieceKeeper.getText().length() - 1;
300 else
301 pieceIndex = nextPieceKeeper.getText().length();
302 }
303 else {
304
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 }