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