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