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.swt.SWT;
11 import org.eclipse.swt.widgets.Canvas;
12 import org.eclipse.swt.widgets.Caret;
13
14 import sk.baka.ikslibs.ptr.DomPointer;
15 import sk.uniba.euromath.document.XMLAccess;
16 import sk.uniba.euromath.editor.textEditor.requests.RequestConstants;
17
18 /***
19 * Each TextEditor can have its own CaretManager. CaretManager holds an instance
20 * of Caret, shows it on Canvas and moves it. Caret is always placed in
21 * ITextPieceKeeper at character gap. Character gap is gap between characters,
22 * specialls are gaps at start and end of text. Are index from zero to length of
23 * text. First gap(index 0) is before first character(index 0), last gap(index
24 * length) is after last character(index length -1).<br>
25 *
26 * @author Martin Kollar 3.2.2006
27 */
28 public class CaretManager {
29
30 /***
31 * Caret instance.
32 */
33 private Caret caret;
34
35 /***
36 * Canvas on that is caret drawn.
37 */
38 private Canvas canvas;
39
40 /***
41 * Caret offset measured in character gaps in ITextPieceKeeper.
42 */
43 private int caretOffset;
44
45 /***
46 * ITextPieceKeeper where is Caret shown.
47 */
48 private ITextPieceKeeper keeperUnderCaret;
49
50 /***
51 * XMLAccess instance.
52 */
53 private XMLAccess xmlAccess;
54
55 /***
56 * Listens to scrolling or resizeing of the window.
57 */
58 private PropertyChangeListener propertyListener;
59
60 /***
61 * Constructor.
62 *
63 * @param canvas
64 * Canvas on that the Caret will be painted
65 * @param xmlAccess
66 * The source document accessor
67 */
68 public CaretManager(Canvas canvas, XMLAccess xmlAccess) {
69 caret = new Caret(canvas, SWT.NONE);
70 caretOffset = -1;
71 this.xmlAccess = xmlAccess;
72 this.canvas = canvas;
73
74 propertyListener = new PropertyChangeListener() {
75
76 /***
77 * When slide bars on the edges are moved or size of
78 * windows is changed then position of Caret is adjusted
79 * in this method
80 */
81 public void propertyChange(PropertyChangeEvent evt) {
82
83 if (Viewport.PROPERTY_VIEW_LOCATION.equals(evt
84 .getPropertyName())) {
85 if ((caret == null)
86 || caret.isDisposed())
87 return;
88 if (keeperUnderCaret == null)
89 return;
90 IFigure hostFigure = keeperUnderCaret
91 .getTextLocator();
92 if (caret.isVisible()
93 && (hostFigure
94 .isShowing())
95 && (getCaretOffset() != -1)) {
96
97 showCaretAtCharGap(
98 keeperUnderCaret,
99 caretOffset);
100 }
101 }
102 }
103
104 };
105
106 }
107
108 /***
109 * Converts direction string to key code.
110 *
111 * @param s
112 * One of RequestConstants.LEFT[RIGHT,UP,DOWN,HOME,END]
113 * @return SWT key code of relevant key
114 */
115 public static int directionToKeyCode(String direction) {
116 if (direction == RequestConstants.LEFT)
117 return SWT.ARROW_LEFT;
118 if (direction == RequestConstants.RIGHT)
119 return SWT.ARROW_RIGHT;
120 if (direction == RequestConstants.UP)
121 return SWT.ARROW_UP;
122 if (direction == RequestConstants.DOWN)
123 return SWT.ARROW_DOWN;
124 if (direction == RequestConstants.HOME)
125 return SWT.HOME;
126 if (direction == RequestConstants.END)
127 return SWT.END;
128 throw new IllegalStateException(
129 "Wrong direction string - can not convert to key code");
130 }
131
132 /***
133 * Returns text piece keeper where caret is.
134 *
135 * @return TextPieceKeeper or null
136 */
137 public ITextPieceKeeper getActiveTextPieceKeeper() {
138 return this.keeperUnderCaret;
139 }
140
141 /***
142 * see {@link Caret#getLocation() }
143 *
144 * @return Location of the Caret or <code>null</code> if it is not
145 * active
146 */
147 public Point getLocation() {
148 if (checkCaret() && caret.isVisible())
149 return new Point(caret.getLocation().x, caret
150 .getLocation().y);
151 return null;
152 }
153
154 /***
155 *
156 * @return DOMPointer in that is Caret activated. If it is not active,
157 * then returns <code>null</code>
158 */
159 public DomPointer getDOMPointer() {
160 if (checkCaret() && caret.isVisible()
161 && (getActiveTextPieceKeeper() != null)) {
162 return getActiveTextPieceKeeper().getTextPieceInfo()
163 .getDomPointer(this.caretOffset,
164 this.xmlAccess);
165
166 }
167 return null;
168 }
169
170 /***
171 * Returns index of character gap, where caret is shown.
172 *
173 * @return chracter gap index
174 */
175 public int getCaretOffset() {
176 if (!(checkCaret() && caret.isVisible()))
177 throw new IllegalStateException(
178 "Caret is not active, so you can not find out its offset");
179 return caretOffset;
180
181 }
182
183 /***
184 * True if caret is active and visible.
185 */
186 public boolean isCaretActive() {
187 return checkCaret() && caret.isVisible();
188 }
189
190 /***
191 * Activates Caret and shows it in <code>keeper</code> at character
192 * gap <code>gapIndex</code>.
193 *
194 * @param keeper
195 * ITextPieceKeeper where Caret will be shown
196 * @param gapIndex
197 * character gap index, where to show caret
198 *
199 * @see CaretManager javadoc to find out more about character gaps
200 */
201 public void activateCaret(ITextPieceKeeper keeper, int gapIndex) {
202 if ((gapIndex < 0) || (gapIndex > keeper.getText().length()))
203 throw new IllegalArgumentException("Gap index is wrong");
204 caretOffset = gapIndex;
205 keeperUnderCaret = keeper;
206 Viewport viewport = ((FigureCanvas) canvas).getViewport();
207 viewport.removePropertyChangeListener(propertyListener);
208 showCaretAtCharGap(keeper, caretOffset);
209 viewport.addPropertyChangeListener(propertyListener);
210 }
211
212 /***
213 * Deactivates and hides Caret
214 */
215 public void deactivateCaret() {
216
217 if (propertyListener != null) {
218
219 ((FigureCanvas) canvas).getViewport()
220 .removePropertyChangeListener(
221 propertyListener);
222
223
224 }
225
226 hideCaret();
227 caretOffset = -1;
228 keeperUnderCaret = null;
229 }
230
231 /***
232 * TODO GUI ctrl movement Moves Caret from the current position to one
233 * positions further in LEFT,RIGHT,UP,DOWN or to the end or start of the
234 * current line, if possible.
235 *
236 *
237 * @param direction
238 * direction of movement
239 *
240 * @param number
241 * of chars to move
242 *
243 * @return true if caret was moved, false if caret wasn't moved because
244 * there is no place where to move caret in such direction
245 */
246 public boolean moveCaret(Direction direction, int count) {
247 if ((!checkCaret()) || (!caret.isVisible())
248 || (getActiveTextPieceKeeper() == null))
249
250
251 return false;
252
253 if (direction.equals(Direction.Up)
254 || direction.equals(Direction.Down)) {
255 ITextPieceKeeper keeper = getActiveTextPieceKeeper();
256 while (count > 0) {
257 keeper = keeper.getKeeperInDirection(direction,
258 this.caret.getLocation().x);
259 if (keeper == null)
260 return false;
261 count--;
262 }
263 int caretOffset = keeper
264 .getTextLocator()
265 .getNearestCharGapIndexAtPos(
266 this.caret
267 .getLocation().x);
268 showCaretAtCharGap(keeper, caretOffset);
269 return true;
270 }
271 if (direction.equals(Direction.Left)) {
272 ITextPieceKeeper keeper = getActiveTextPieceKeeper();
273 int offsetForMovement = this.caretOffset;
274 while (count > offsetForMovement) {
275 keeper = keeper
276 .getNextKeeperInDirection(direction);
277 if (keeper == null)
278 return false;
279
280
281 count--;
282 count = count - offsetForMovement;
283
284 offsetForMovement = keeper.getText().length();
285 }
286
287 showCaretAtCharGap(keeper, offsetForMovement - count);
288 return true;
289 }
290 if (direction.equals(Direction.Right)) {
291 ITextPieceKeeper keeper = getActiveTextPieceKeeper();
292 int offsetForMovement = keeper.getText().length()
293 - this.caretOffset;
294 while (count > offsetForMovement) {
295 keeper = keeper
296 .getNextKeeperInDirection(direction);
297 if (keeper == null)
298 return false;
299
300
301 count--;
302 count = count - offsetForMovement;
303 offsetForMovement = keeper.getText().length();
304 }
305
306 if (keeper == getActiveTextPieceKeeper()) {
307 showCaretAtCharGap(keeper, this.caretOffset
308 + count);
309 return true;
310 }
311 showCaretAtCharGap(keeper, count);
312 return true;
313 }
314
315 throw new IllegalArgumentException();
316 }
317
318 /***
319 * Checks if Caret is ready to use
320 *
321 * @return <code>true</code> if caret is not <code>null</code> and
322 * 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(true);
334 }
335
336 protected void hideCaret() {
337 if (checkCaret())
338 if (this.caret.isVisible())
339 this.caret.setVisible(false);
340 }
341
342 /***
343 * Shows caret at character gap charGapIndex in keeper.
344 *
345 * @param charGapIndex
346 * index of character gap where to show caret
347 * @param keeper
348 * ITextPieceKeeper where to show Caret
349 */
350 protected void showCaretAtCharGap(ITextPieceKeeper keeper,
351 int charGapIndex) {
352 if ((charGapIndex < 0)
353 || (charGapIndex > keeper.getText().length()))
354 throw new IllegalArgumentException(
355 "CharGapIndex is wrong.");
356 if (checkCaret()) {
357 Point p;
358 if (charGapIndex == keeper.getText().length())
359
360
361 p = keeper.getTextLocator().getEnd(
362 charGapIndex - 1);
363 else {
364 p = keeper.getTextLocator().getStart(
365 charGapIndex);
366 }
367
368 keeper.getTextLocator().translateToAbsolute(p);
369 this.caret.setLocation(p.x, p.y);
370 this.caret.setSize(1, keeper.getTextLocator()
371 .getTextBounds().height);
372 if (!this.caret.isVisible()) {
373 this.caret.setVisible(true);
374 }
375 this.keeperUnderCaret = keeper;
376 this.caretOffset = charGapIndex;
377 } else
378 throw new IllegalStateException(
379 "Caret is null or disposed");
380 }
381 }