Mercurial > projects > dwt-addons
comparison dwtx/jface/text/link/LinkedModeModel.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2008 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.text.link.LinkedModeModel; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.Arrays; | |
19 import java.util.HashSet; | |
20 import java.util.Iterator; | |
21 import java.util.List; | |
22 import java.util.Map; | |
23 import java.util.Set; | |
24 | |
25 import dwtx.core.runtime.Assert; | |
26 import dwtx.jface.text.BadLocationException; | |
27 import dwtx.jface.text.BadPositionCategoryException; | |
28 import dwtx.jface.text.DocumentEvent; | |
29 import dwtx.jface.text.IDocument; | |
30 import dwtx.jface.text.IDocumentExtension; | |
31 import dwtx.jface.text.IDocumentListener; | |
32 import dwtx.jface.text.IPositionUpdater; | |
33 import dwtx.jface.text.Position; | |
34 import dwtx.jface.text.IDocumentExtension.IReplace; | |
35 import dwtx.text.edits.MalformedTreeException; | |
36 import dwtx.text.edits.TextEdit; | |
37 | |
38 | |
39 /** | |
40 * The model for linked mode, umbrellas several | |
41 * {@link LinkedPositionGroup}s. Once installed, the model | |
42 * propagates any changes to a position to all its siblings in the same position | |
43 * group. | |
44 * <p> | |
45 * Setting up a model consists of first adding | |
46 * <code>LinkedPositionGroup</code>s to it, and then installing the | |
47 * model by either calling {@link #forceInstall()} or | |
48 * {@link #tryInstall()}. After installing the model, it becomes | |
49 * <em>sealed</em> and no more groups may be added. | |
50 * </p> | |
51 * <p> | |
52 * If a document change occurs that would modify more than one position | |
53 * group or that would invalidate the disjointness requirement of the positions, | |
54 * the model is torn down and all positions are deleted. The same happens | |
55 * upon calling {@link #exit(int)}. | |
56 * </p> | |
57 * <h4>Nesting</h4> | |
58 * <p> | |
59 * A <code>LinkedModeModel</code> may be nested into another model. This | |
60 * happens when installing a model the positions of which all fit into a | |
61 * single position in a parent model that has previously been installed on | |
62 * the same document(s). | |
63 * </p> | |
64 * <p> | |
65 * Clients may instantiate instances of this class. | |
66 * </p> | |
67 * | |
68 * @since 3.0 | |
69 * @noextend This class is not intended to be subclassed by clients. | |
70 */ | |
71 public class LinkedModeModel { | |
72 | |
73 /** | |
74 * Checks whether there is already a model installed on <code>document</code>. | |
75 * | |
76 * @param document the <code>IDocument</code> of interest | |
77 * @return <code>true</code> if there is an existing model, <code>false</code> | |
78 * otherwise | |
79 */ | |
80 public static bool hasInstalledModel(IDocument document) { | |
81 // if there is a manager, there also is a model | |
82 return LinkedModeManager.hasManager(document); | |
83 } | |
84 | |
85 /** | |
86 * Checks whether there is already a linked mode model installed on any of | |
87 * the <code>documents</code>. | |
88 * | |
89 * @param documents the <code>IDocument</code>s of interest | |
90 * @return <code>true</code> if there is an existing model, <code>false</code> | |
91 * otherwise | |
92 */ | |
93 public static bool hasInstalledModel(IDocument[] documents) { | |
94 // if there is a manager, there also is a model | |
95 return LinkedModeManager.hasManager(documents); | |
96 } | |
97 | |
98 /** | |
99 * Cancels any linked mode model on the specified document. If there is no | |
100 * model, nothing happens. | |
101 * | |
102 * @param document the document whose <code>LinkedModeModel</code> should | |
103 * be canceled | |
104 */ | |
105 public static void closeAllModels(IDocument document) { | |
106 LinkedModeManager.cancelManager(document); | |
107 } | |
108 | |
109 /** | |
110 * Returns the model currently active on <code>document</code> at | |
111 * <code>offset</code>, or <code>null</code> if there is none. | |
112 * | |
113 * @param document the document for which the caller asks for a | |
114 * model | |
115 * @param offset the offset into <code>document</code>, as there may be | |
116 * several models on a document | |
117 * @return the model currently active on <code>document</code>, or | |
118 * <code>null</code> | |
119 */ | |
120 public static LinkedModeModel getModel(IDocument document, int offset) { | |
121 if (!hasInstalledModel(document)) | |
122 return null; | |
123 | |
124 LinkedModeManager mgr= LinkedModeManager.getLinkedManager(new IDocument[] {document}, false); | |
125 if (mgr !is null) | |
126 return mgr.getTopEnvironment(); | |
127 return null; | |
128 } | |
129 | |
130 /** | |
131 * Encapsulates the edition triggered by a change to a linking position. Can | |
132 * be applied to a document as a whole. | |
133 */ | |
134 private class Replace : IReplace { | |
135 | |
136 /** The edition to apply on a document. */ | |
137 private TextEdit fEdit; | |
138 | |
139 /** | |
140 * Creates a new instance. | |
141 * | |
142 * @param edit the edition to apply to a document. | |
143 */ | |
144 public Replace(TextEdit edit) { | |
145 fEdit= edit; | |
146 } | |
147 | |
148 /* | |
149 * @see dwtx.jface.text.IDocumentExtension.IReplace#perform(dwtx.jface.text.IDocument, dwtx.jface.text.IDocumentListener) | |
150 */ | |
151 public void perform(IDocument document, IDocumentListener owner) throws RuntimeException, MalformedTreeException { | |
152 document.removeDocumentListener(owner); | |
153 fIsChanging= true; | |
154 try { | |
155 fEdit.apply(document, TextEdit.UPDATE_REGIONS | TextEdit.CREATE_UNDO); | |
156 } catch (BadLocationException e) { | |
157 /* XXX: perform should really throw a BadLocationException | |
158 * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=52950 | |
159 */ | |
160 throw new RuntimeException(e); | |
161 } finally { | |
162 document.addDocumentListener(owner); | |
163 fIsChanging= false; | |
164 } | |
165 } | |
166 | |
167 } | |
168 | |
169 /** | |
170 * The document listener triggering the linked updating of positions | |
171 * managed by this model. | |
172 */ | |
173 private class DocumentListener : IDocumentListener { | |
174 | |
175 private bool fExit= false; | |
176 | |
177 /** | |
178 * Checks whether <code>event</code> occurs within any of the positions | |
179 * managed by this model. If not, the linked mode is left. | |
180 * | |
181 * @param event {@inheritDoc} | |
182 */ | |
183 public void documentAboutToBeChanged(DocumentEvent event) { | |
184 // don't react on changes executed by the parent model | |
185 if (fParentEnvironment !is null && fParentEnvironment.isChanging()) | |
186 return; | |
187 | |
188 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
189 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
190 if (!group.isLegalEvent(event)) { | |
191 fExit= true; | |
192 return; | |
193 } | |
194 } | |
195 } | |
196 | |
197 /** | |
198 * Propagates a change to a linked position to all its sibling positions. | |
199 * | |
200 * @param event {@inheritDoc} | |
201 */ | |
202 public void documentChanged(DocumentEvent event) { | |
203 if (fExit) { | |
204 LinkedModeModel.this.exit(ILinkedModeListener.EXTERNAL_MODIFICATION); | |
205 return; | |
206 } | |
207 fExit= false; | |
208 | |
209 // don't react on changes executed by the parent model | |
210 if (fParentEnvironment !is null && fParentEnvironment.isChanging()) | |
211 return; | |
212 | |
213 // collect all results | |
214 Map result= null; | |
215 for (Iterator it= fGroups.iterator(); it.hasNext();) { | |
216 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
217 | |
218 Map map= group.handleEvent(event); | |
219 if (result !is null && map !is null) { | |
220 // exit if more than one position was changed | |
221 LinkedModeModel.this.exit(ILinkedModeListener.EXTERNAL_MODIFICATION); | |
222 return; | |
223 } | |
224 if (map !is null) | |
225 result= map; | |
226 } | |
227 | |
228 if (result !is null) { | |
229 // edit all documents | |
230 for (Iterator it2= result.keySet().iterator(); it2.hasNext(); ) { | |
231 IDocument doc= (IDocument) it2.next(); | |
232 TextEdit edit= (TextEdit) result.get(doc); | |
233 Replace replace= new Replace(edit); | |
234 | |
235 // apply the edition, either as post notification replace | |
236 // on the calling document or directly on any other | |
237 // document | |
238 if (doc is event.getDocument()) { | |
239 if (doc instanceof IDocumentExtension) { | |
240 ((IDocumentExtension) doc).registerPostNotificationReplace(this, replace); | |
241 } else { | |
242 // ignore - there is no way we can log from JFace text... | |
243 } | |
244 } else { | |
245 replace.perform(doc, this); | |
246 } | |
247 } | |
248 } | |
249 } | |
250 | |
251 } | |
252 | |
253 /** The set of linked position groups. */ | |
254 private final List fGroups= new ArrayList(); | |
255 /** The set of documents spanned by this group. */ | |
256 private final Set fDocuments= new HashSet(); | |
257 /** The position updater for linked positions. */ | |
258 private final IPositionUpdater fUpdater= new InclusivePositionUpdater(getCategory()); | |
259 /** The document listener on the documents affected by this model. */ | |
260 private final DocumentListener fDocumentListener= new DocumentListener(); | |
261 /** The parent model for a hierarchical set up, or <code>null</code>. */ | |
262 private LinkedModeModel fParentEnvironment; | |
263 /** | |
264 * The position in <code>fParentEnvironment</code> that includes all | |
265 * positions in this object, or <code>null</code> if there is no parent | |
266 * model. | |
267 */ | |
268 private LinkedPosition fParentPosition= null; | |
269 /** | |
270 * A model is sealed once it has children - no more positions can be | |
271 * added. | |
272 */ | |
273 private bool fIsSealed= false; | |
274 /** <code>true</code> when this model is changing documents. */ | |
275 private bool fIsChanging= false; | |
276 /** The linked listeners. */ | |
277 private final List fListeners= new ArrayList(); | |
278 /** Flag telling whether we have exited: */ | |
279 private bool fIsActive= true; | |
280 /** | |
281 * The sequence of document positions as we are going to iterate through | |
282 * them. | |
283 */ | |
284 private List fPositionSequence= new ArrayList(); | |
285 | |
286 /** | |
287 * Whether we are in the process of editing documents (set by <code>Replace</code>, | |
288 * read by <code>DocumentListener</code>. | |
289 * | |
290 * @return <code>true</code> if we are in the process of editing a | |
291 * document, <code>false</code> otherwise | |
292 */ | |
293 private bool isChanging() { | |
294 return fIsChanging || fParentEnvironment !is null && fParentEnvironment.isChanging(); | |
295 } | |
296 | |
297 /** | |
298 * Throws a <code>BadLocationException</code> if <code>group</code> | |
299 * conflicts with this model's groups. | |
300 * | |
301 * @param group the group being checked | |
302 * @throws BadLocationException if <code>group</code> conflicts with this | |
303 * model's groups | |
304 */ | |
305 private void enforceDisjoint(LinkedPositionGroup group) throws BadLocationException { | |
306 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
307 LinkedPositionGroup g= (LinkedPositionGroup) it.next(); | |
308 g.enforceDisjoint(group); | |
309 } | |
310 } | |
311 | |
312 /** | |
313 * Causes this model to exit. Called either if an illegal document change | |
314 * is detected, or by the UI. | |
315 * | |
316 * @param flags the exit flags as defined in {@link ILinkedModeListener} | |
317 */ | |
318 public void exit(int flags) { | |
319 if (!fIsActive) | |
320 return; | |
321 | |
322 fIsActive= false; | |
323 | |
324 for (Iterator it= fDocuments.iterator(); it.hasNext(); ) { | |
325 IDocument doc= (IDocument) it.next(); | |
326 try { | |
327 doc.removePositionCategory(getCategory()); | |
328 } catch (BadPositionCategoryException e) { | |
329 // won't happen | |
330 Assert.isTrue(false); | |
331 } | |
332 doc.removePositionUpdater(fUpdater); | |
333 doc.removeDocumentListener(fDocumentListener); | |
334 } | |
335 | |
336 fDocuments.clear(); | |
337 fGroups.clear(); | |
338 | |
339 List listeners= new ArrayList(fListeners); | |
340 fListeners.clear(); | |
341 for (Iterator it= listeners.iterator(); it.hasNext(); ) { | |
342 ILinkedModeListener listener= (ILinkedModeListener) it.next(); | |
343 listener.left(this, flags); | |
344 } | |
345 | |
346 | |
347 if (fParentEnvironment !is null) | |
348 fParentEnvironment.resume(flags); | |
349 } | |
350 | |
351 /** | |
352 * Causes this model to stop forwarding updates. The positions are not | |
353 * unregistered however, which will only happen when <code>exit</code> | |
354 * is called, or after the next document change. | |
355 * | |
356 * @param flags the exit flags as defined in {@link ILinkedModeListener} | |
357 * @since 3.1 | |
358 */ | |
359 public void stopForwarding(int flags) { | |
360 fDocumentListener.fExit= true; | |
361 } | |
362 | |
363 /** | |
364 * Puts <code>document</code> into the set of managed documents. This | |
365 * involves registering the document listener and adding our position | |
366 * category. | |
367 * | |
368 * @param document the new document | |
369 */ | |
370 private void manageDocument(IDocument document) { | |
371 if (!fDocuments.contains(document)) { | |
372 fDocuments.add(document); | |
373 document.addPositionCategory(getCategory()); | |
374 document.addPositionUpdater(fUpdater); | |
375 document.addDocumentListener(fDocumentListener); | |
376 } | |
377 | |
378 } | |
379 | |
380 /** | |
381 * Returns the position category used by this model. | |
382 * | |
383 * @return the position category used by this model | |
384 */ | |
385 private String getCategory() { | |
386 return toString(); | |
387 } | |
388 | |
389 /** | |
390 * Adds a position group to this <code>LinkedModeModel</code>. This | |
391 * method may not be called if the model has been installed. Also, if | |
392 * a UI has been set up for this model, it may not pick up groups | |
393 * added afterwards. | |
394 * <p> | |
395 * If the positions in <code>group</code> conflict with any other group in | |
396 * this model, a <code>BadLocationException</code> is thrown. Also, | |
397 * if this model is nested inside another one, all positions in all | |
398 * groups of the child model have to reside within a single position in the | |
399 * parent model, otherwise a <code>BadLocationException</code> is thrown. | |
400 * </p> | |
401 * <p> | |
402 * If <code>group</code> already exists, nothing happens. | |
403 * </p> | |
404 * | |
405 * @param group the group to be added to this model | |
406 * @throws BadLocationException if the group conflicts with the other groups | |
407 * in this model or violates the nesting requirements. | |
408 * @throws IllegalStateException if the method is called when the | |
409 * model is already sealed | |
410 */ | |
411 public void addGroup(LinkedPositionGroup group) throws BadLocationException { | |
412 if (group is null) | |
413 throw new IllegalArgumentException("group may not be null"); //$NON-NLS-1$ | |
414 if (fIsSealed) | |
415 throw new IllegalStateException("model is already installed"); //$NON-NLS-1$ | |
416 if (fGroups.contains(group)) | |
417 // nothing happens | |
418 return; | |
419 | |
420 enforceDisjoint(group); | |
421 group.seal(); | |
422 fGroups.add(group); | |
423 } | |
424 | |
425 /** | |
426 * Creates a new model. | |
427 * @since 3.1 | |
428 */ | |
429 public LinkedModeModel() { | |
430 } | |
431 | |
432 /** | |
433 * Installs this model, which includes registering as document | |
434 * listener on all involved documents and storing global information about | |
435 * this model. Any conflicting model already present will be | |
436 * closed. | |
437 * <p> | |
438 * If an exception is thrown, the installation failed and | |
439 * the model is unusable. | |
440 * </p> | |
441 * | |
442 * @throws BadLocationException if some of the positions of this model | |
443 * were not valid positions on their respective documents | |
444 */ | |
445 public void forceInstall() throws BadLocationException { | |
446 if (!install(true)) | |
447 Assert.isTrue(false); | |
448 } | |
449 | |
450 /** | |
451 * Installs this model, which includes registering as document | |
452 * listener on all involved documents and storing global information about | |
453 * this model. If there is another model installed on the | |
454 * document(s) targeted by the receiver that conflicts with it, installation | |
455 * may fail. | |
456 * <p> | |
457 * The return value states whether installation was | |
458 * successful; if not, the model is not installed and will not work. | |
459 * </p> | |
460 * | |
461 * @return <code>true</code> if installation was successful, | |
462 * <code>false</code> otherwise | |
463 * @throws BadLocationException if some of the positions of this model | |
464 * were not valid positions on their respective documents | |
465 */ | |
466 public bool tryInstall() throws BadLocationException { | |
467 return install(false); | |
468 } | |
469 | |
470 /** | |
471 * Installs this model, which includes registering as document | |
472 * listener on all involved documents and storing global information about | |
473 * this model. The return value states whether installation was | |
474 * successful; if not, the model is not installed and will not work. | |
475 * The return value can only then become <code>false</code> if | |
476 * <code>force</code> was set to <code>false</code> as well. | |
477 * | |
478 * @param force if <code>true</code>, any other model that cannot | |
479 * coexist with this one is canceled; if <code>false</code>, | |
480 * install will fail when conflicts occur and return false | |
481 * @return <code>true</code> if installation was successful, | |
482 * <code>false</code> otherwise | |
483 * @throws BadLocationException if some of the positions of this model | |
484 * were not valid positions on their respective documents | |
485 */ | |
486 private bool install(bool force) throws BadLocationException { | |
487 if (fIsSealed) | |
488 throw new IllegalStateException("model is already installed"); //$NON-NLS-1$ | |
489 enforceNotEmpty(); | |
490 | |
491 IDocument[] documents= getDocuments(); | |
492 LinkedModeManager manager= LinkedModeManager.getLinkedManager(documents, force); | |
493 // if we force creation, we require a valid manager | |
494 Assert.isTrue(!(force && manager is null)); | |
495 if (manager is null) | |
496 return false; | |
497 | |
498 if (!manager.nestEnvironment(this, force)) | |
499 if (force) | |
500 Assert.isTrue(false); | |
501 else | |
502 return false; | |
503 | |
504 // we set up successfully. After this point, exit has to be called to | |
505 // remove registered listeners... | |
506 fIsSealed= true; | |
507 if (fParentEnvironment !is null) | |
508 fParentEnvironment.suspend(); | |
509 | |
510 // register positions | |
511 try { | |
512 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
513 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
514 group.register(this); | |
515 } | |
516 return true; | |
517 } catch (BadLocationException e){ | |
518 // if we fail to add, make sure to release all listeners again | |
519 exit(ILinkedModeListener.NONE); | |
520 throw e; | |
521 } | |
522 } | |
523 | |
524 /** | |
525 * Asserts that there is at least one linked position in this linked mode | |
526 * model, throws an IllegalStateException otherwise. | |
527 */ | |
528 private void enforceNotEmpty() { | |
529 bool hasPosition= false; | |
530 for (Iterator it= fGroups.iterator(); it.hasNext(); ) | |
531 if (!((LinkedPositionGroup) it.next()).isEmpty()) { | |
532 hasPosition= true; | |
533 break; | |
534 } | |
535 if (!hasPosition) | |
536 throw new IllegalStateException("must specify at least one linked position"); //$NON-NLS-1$ | |
537 | |
538 } | |
539 | |
540 /** | |
541 * Collects all the documents that contained positions are set upon. | |
542 * @return the set of documents affected by this model | |
543 */ | |
544 private IDocument[] getDocuments() { | |
545 Set docs= new HashSet(); | |
546 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
547 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
548 docs.addAll(Arrays.asList(group.getDocuments())); | |
549 } | |
550 return (IDocument[]) docs.toArray(new IDocument[docs.size()]); | |
551 } | |
552 | |
553 /** | |
554 * Returns whether the receiver can be nested into the given <code>parent</code> | |
555 * model. If yes, the parent model and its position that the receiver | |
556 * fits in are remembered. | |
557 * | |
558 * @param parent the parent model candidate | |
559 * @return <code>true</code> if the receiver can be nested into <code>parent</code>, <code>false</code> otherwise | |
560 */ | |
561 bool canNestInto(LinkedModeModel parent) { | |
562 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
563 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
564 if (!enforceNestability(group, parent)) { | |
565 fParentPosition= null; | |
566 return false; | |
567 } | |
568 } | |
569 | |
570 Assert.isNotNull(fParentPosition); | |
571 fParentEnvironment= parent; | |
572 return true; | |
573 } | |
574 | |
575 /** | |
576 * Called by nested models when a group is added to them. All | |
577 * positions in all groups of a nested model have to fit inside a | |
578 * single position in the parent model. | |
579 * | |
580 * @param group the group of the nested model to be adopted. | |
581 * @param model the model to check against | |
582 * @return <code>false</code> if it failed to enforce nestability | |
583 */ | |
584 private bool enforceNestability(LinkedPositionGroup group, LinkedModeModel model) { | |
585 Assert.isNotNull(model); | |
586 Assert.isNotNull(group); | |
587 | |
588 try { | |
589 for (Iterator it= model.fGroups.iterator(); it.hasNext(); ) { | |
590 LinkedPositionGroup pg= (LinkedPositionGroup) it.next(); | |
591 LinkedPosition pos; | |
592 pos= pg.adopt(group); | |
593 if (pos !is null && fParentPosition !is null && fParentPosition !is pos) | |
594 return false; // group does not fit into one parent position, which is illegal | |
595 else if (fParentPosition is null && pos !is null) | |
596 fParentPosition= pos; | |
597 } | |
598 } catch (BadLocationException e) { | |
599 return false; | |
600 } | |
601 | |
602 // group must fit into exactly one of the parent's positions | |
603 return fParentPosition !is null; | |
604 } | |
605 | |
606 /** | |
607 * Returns whether this model is nested. | |
608 * | |
609 * <p> | |
610 * This method is part of the private protocol between | |
611 * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>. | |
612 * </p> | |
613 * | |
614 * @return <code>true</code> if this model is nested, | |
615 * <code>false</code> otherwise | |
616 */ | |
617 public bool isNested() { | |
618 return fParentEnvironment !is null; | |
619 } | |
620 | |
621 /** | |
622 * Returns the positions in this model that have a tab stop, in the | |
623 * order they were added. | |
624 * | |
625 * <p> | |
626 * This method is part of the private protocol between | |
627 * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>. | |
628 * </p> | |
629 * | |
630 * @return the positions in this model that have a tab stop, in the | |
631 * order they were added | |
632 */ | |
633 public List getTabStopSequence() { | |
634 return fPositionSequence; | |
635 } | |
636 | |
637 /** | |
638 * Adds <code>listener</code> to the set of listeners that are informed | |
639 * upon state changes. | |
640 * | |
641 * @param listener the new listener | |
642 */ | |
643 public void addLinkingListener(ILinkedModeListener listener) { | |
644 Assert.isNotNull(listener); | |
645 if (!fListeners.contains(listener)) | |
646 fListeners.add(listener); | |
647 } | |
648 | |
649 /** | |
650 * Removes <code>listener</code> from the set of listeners that are | |
651 * informed upon state changes. | |
652 * | |
653 * @param listener the new listener | |
654 */ | |
655 public void removeLinkingListener(ILinkedModeListener listener) { | |
656 fListeners.remove(listener); | |
657 } | |
658 | |
659 /** | |
660 * Finds the position in this model that is closest after | |
661 * <code>toFind</code>. <code>toFind</code> needs not be a position in | |
662 * this model and serves merely as an offset. | |
663 * | |
664 * <p> | |
665 * This method part of the private protocol between | |
666 * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>. | |
667 * </p> | |
668 * | |
669 * @param toFind the position to search from | |
670 * @return the closest position in the same document as <code>toFind</code> | |
671 * after the offset of <code>toFind</code>, or <code>null</code> | |
672 */ | |
673 public LinkedPosition findPosition(LinkedPosition toFind) { | |
674 LinkedPosition position= null; | |
675 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
676 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
677 position= group.getPosition(toFind); | |
678 if (position !is null) | |
679 break; | |
680 } | |
681 return position; | |
682 } | |
683 | |
684 /** | |
685 * Registers a <code>LinkedPosition</code> with this model. Called | |
686 * by <code>PositionGroup</code>. | |
687 * | |
688 * @param position the position to register | |
689 * @throws BadLocationException if the position cannot be added to its | |
690 * document | |
691 */ | |
692 void register(LinkedPosition position) throws BadLocationException { | |
693 Assert.isNotNull(position); | |
694 | |
695 IDocument document= position.getDocument(); | |
696 manageDocument(document); | |
697 try { | |
698 document.addPosition(getCategory(), position); | |
699 } catch (BadPositionCategoryException e) { | |
700 // won't happen as the category has been added by manageDocument() | |
701 Assert.isTrue(false); | |
702 } | |
703 int seqNr= position.getSequenceNumber(); | |
704 if (seqNr !is LinkedPositionGroup.NO_STOP) { | |
705 fPositionSequence.add(position); | |
706 } | |
707 } | |
708 | |
709 /** | |
710 * Suspends this model. | |
711 */ | |
712 private void suspend() { | |
713 List l= new ArrayList(fListeners); | |
714 for (Iterator it= l.iterator(); it.hasNext(); ) { | |
715 ILinkedModeListener listener= (ILinkedModeListener) it.next(); | |
716 listener.suspend(this); | |
717 } | |
718 } | |
719 | |
720 /** | |
721 * Resumes this model. <code>flags</code> can be <code>NONE</code> | |
722 * or <code>SELECT</code>. | |
723 * | |
724 * @param flags <code>NONE</code> or <code>SELECT</code> | |
725 */ | |
726 private void resume(int flags) { | |
727 List l= new ArrayList(fListeners); | |
728 for (Iterator it= l.iterator(); it.hasNext(); ) { | |
729 ILinkedModeListener listener= (ILinkedModeListener) it.next(); | |
730 listener.resume(this, flags); | |
731 } | |
732 } | |
733 | |
734 /** | |
735 * Returns whether an offset is contained by any position in this | |
736 * model. | |
737 * | |
738 * @param offset the offset to check | |
739 * @return <code>true</code> if <code>offset</code> is included by any | |
740 * position (see {@link LinkedPosition#includes(int)}) in this | |
741 * model, <code>false</code> otherwise | |
742 */ | |
743 public bool anyPositionContains(int offset) { | |
744 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
745 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
746 if (group.contains(offset)) | |
747 // take the first hit - exclusion is guaranteed by enforcing | |
748 // disjointness when adding positions | |
749 return true; | |
750 } | |
751 return false; | |
752 } | |
753 | |
754 /** | |
755 * Returns the linked position group that contains <code>position</code>, | |
756 * or <code>null</code> if <code>position</code> is not contained in any | |
757 * group within this model. Group containment is tested by calling | |
758 * <code>group.contains(position)</code> for every <code>group</code> in | |
759 * this model. | |
760 * | |
761 * <p> | |
762 * This method part of the private protocol between | |
763 * <code>LinkedModeUI</code> and <code>LinkedModeModel</code>. | |
764 * </p> | |
765 * | |
766 * @param position the position the group of which is requested | |
767 * @return the first group in this model for which | |
768 * <code>group.contains(position)</code> returns <code>true</code>, | |
769 * or <code>null</code> if no group contains <code>position</code> | |
770 */ | |
771 public LinkedPositionGroup getGroupForPosition(Position position) { | |
772 for (Iterator it= fGroups.iterator(); it.hasNext(); ) { | |
773 LinkedPositionGroup group= (LinkedPositionGroup) it.next(); | |
774 if (group.contains(position)) | |
775 return group; | |
776 } | |
777 return null; | |
778 } | |
779 } |