Mercurial > projects > dwt-addons
annotate dwtx/jface/text/link/LinkedPositionGroup.d @ 153:f70d9508c95c
Fix java Collection imports
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 25 Aug 2008 00:27:31 +0200 |
parents | 000f9136b8f7 |
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.LinkedPositionGroup; | |
14 | |
131 | 15 import dwtx.jface.text.link.LinkedModeModel; // packageimport |
16 import dwtx.jface.text.link.LinkedPosition; // packageimport | |
17 import dwtx.jface.text.link.ILinkedModeListener; // packageimport | |
18 import dwtx.jface.text.link.TabStopIterator; // packageimport | |
19 import dwtx.jface.text.link.LinkedModeUI; // packageimport | |
20 import dwtx.jface.text.link.InclusivePositionUpdater; // 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:
147
diff
changeset
|
28 import dwtx.dwtxhelper.Collection; |
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
147
diff
changeset
|
29 |
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
147
diff
changeset
|
30 |
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
147
diff
changeset
|
31 |
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
147
diff
changeset
|
32 |
f70d9508c95c
Fix java Collection imports
Frank Benoit <benoit@tionex.de>
parents:
147
diff
changeset
|
33 |
129 | 34 |
35 import dwtx.core.runtime.Assert; | |
36 import dwtx.jface.text.BadLocationException; | |
37 import dwtx.jface.text.DocumentEvent; | |
38 import dwtx.jface.text.IDocument; | |
39 import dwtx.jface.text.IRegion; | |
40 import dwtx.jface.text.Position; | |
41 import dwtx.jface.text.Region; | |
42 import dwtx.text.edits.MalformedTreeException; | |
43 import dwtx.text.edits.MultiTextEdit; | |
44 import dwtx.text.edits.ReplaceEdit; | |
45 import dwtx.text.edits.TextEdit; | |
46 | |
47 | |
48 /** | |
49 * A group of positions in multiple documents that are simultaneously modified - | |
50 * if one gets edited, all other positions in a group are edited the same way. | |
51 * All linked positions in a group have the same content. | |
52 * <p> | |
53 * Normally, new positions are given a tab stop weight which can be used by | |
54 * clients, e.g. the UI. If no weight is given, a position will not be visited. | |
55 * If no weights are used at all, the first position in a document is taken as | |
56 * the only stop as to comply with the behavior of the old linked position | |
57 * infrastructure. | |
58 * </p> | |
59 * <p> | |
60 * Clients may instantiate this class. | |
61 * </p> | |
62 * | |
63 * @since 3.0 | |
64 * @noextend This class is not intended to be subclassed by clients. | |
65 */ | |
66 public class LinkedPositionGroup { | |
67 | |
68 /** Sequence constant declaring that a position should not be stopped by. */ | |
147 | 69 public static const int NO_STOP= -1; |
129 | 70 |
71 /* members */ | |
72 | |
73 /** The linked positions of this group. */ | |
146 | 74 private const List fPositions= new LinkedList(); |
129 | 75 /** Whether we are sealed or not. */ |
76 private bool fIsSealed= false; | |
77 /** | |
78 * <code>true</code> if there are custom iteration weights. For backward | |
79 * compatibility. | |
80 */ | |
81 private bool fHasCustomIteration= false; | |
82 | |
83 /* | |
84 * iteration variables, set to communicate state between isLegalEvent and | |
85 * handleEvent | |
86 */ | |
87 /** The position including the most recent <code>DocumentEvent</code>. */ | |
88 private LinkedPosition fLastPosition; | |
89 /** The region covered by <code>fLastPosition</code> before the document | |
90 * change. | |
91 */ | |
92 private IRegion fLastRegion; | |
93 | |
94 /** | |
95 * Adds a position to this group. The document region defined by the | |
96 * position must contain the same content (and thus have the same length) as | |
97 * any of the other positions already in this group. Additionally, all | |
98 * positions added must be disjoint; otherwise a | |
99 * <code>BadLocationException</code> is thrown. | |
100 * <p> | |
101 * Positions added using this method are owned by this group afterwards and | |
102 * may not be updated or modified thereafter. | |
103 * </p> | |
104 * <p> | |
105 * Once a group has been added to a <code>LinkedModeModel</code>, it | |
106 * becomes <em>sealed</em> and no positions may be added any more. | |
107 * </p> | |
108 * | |
109 * @param position the position to add | |
110 * @throws BadLocationException if the position is invalid or conflicts with | |
111 * other positions in the group | |
112 * @throws IllegalStateException if the group has already been added to a | |
113 * model | |
114 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
115 public void addPosition(LinkedPosition position) { |
129 | 116 /* |
117 * Enforces constraints and sets the custom iteration flag. If the | |
118 * position is already in this group, nothing happens. | |
119 */ | |
120 Assert.isNotNull(position); | |
121 if (fIsSealed) | |
122 throw new IllegalStateException("cannot add positions after the group is added to an model"); //$NON-NLS-1$ | |
123 | |
124 if (!fPositions.contains(position)) { | |
125 enforceDisjoint(position); | |
126 enforceEqualContent(position); | |
127 fPositions.add(position); | |
128 fHasCustomIteration |= position.getSequenceNumber() !is LinkedPositionGroup.NO_STOP; | |
129 } else | |
130 return; // nothing happens | |
131 } | |
132 | |
133 /** | |
134 * Enforces the invariant that all positions must contain the same string. | |
135 * | |
136 * @param position the position to check | |
137 * @throws BadLocationException if the equal content check fails | |
138 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
139 private void enforceEqualContent(LinkedPosition position) { |
129 | 140 if (fPositions.size() > 0) { |
134 | 141 LinkedPosition groupPosition= cast(LinkedPosition) fPositions.get(0); |
129 | 142 String groupContent= groupPosition.getContent(); |
143 String positionContent= position.getContent(); | |
144 if (!groupContent.equals(positionContent)) | |
145 throw new BadLocationException( | |
146 "First position: '" + groupContent + "' at " + groupPosition.getOffset() + //$NON-NLS-1$ //$NON-NLS-2$ | |
147 ", this position: '" + positionContent + "' at " + position.getOffset()); //$NON-NLS-1$ //$NON-NLS-2$ | |
148 } | |
149 } | |
150 | |
151 /** | |
152 * Enforces the invariant that all positions must be disjoint. | |
153 * | |
154 * @param position the position to check | |
155 * @throws BadLocationException if the disjointness check fails | |
156 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
157 private void enforceDisjoint(LinkedPosition position) { |
129 | 158 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { |
134 | 159 LinkedPosition p= cast(LinkedPosition) it.next(); |
129 | 160 if (p.overlapsWith(position)) |
161 throw new BadLocationException(); | |
162 } | |
163 } | |
164 | |
165 /** | |
166 * Enforces the disjointness for another group | |
167 * | |
168 * @param group the group to check | |
169 * @throws BadLocationException if the disjointness check fails | |
170 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
171 void enforceDisjoint(LinkedPositionGroup group) { |
129 | 172 Assert.isNotNull(group); |
173 for (Iterator it= group.fPositions.iterator(); it.hasNext(); ) { | |
134 | 174 LinkedPosition p= cast(LinkedPosition) it.next(); |
129 | 175 enforceDisjoint(p); |
176 } | |
177 } | |
178 | |
179 /** | |
180 * Checks whether <code>event</code> is a legal event for this group. An | |
181 * event is legal if it touches at most one position contained within this | |
182 * group. | |
183 * | |
184 * @param event the document event to check | |
185 * @return <code>true</code> if <code>event</code> is legal | |
186 */ | |
187 bool isLegalEvent(DocumentEvent event) { | |
188 fLastPosition= null; | |
189 fLastRegion= null; | |
190 | |
191 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { | |
134 | 192 LinkedPosition pos= cast(LinkedPosition) it.next(); |
129 | 193 if (overlapsOrTouches(pos, event)) { |
194 if (fLastPosition !is null) { | |
195 fLastPosition= null; | |
196 fLastRegion= null; | |
197 return false; | |
198 } | |
199 | |
200 fLastPosition= pos; | |
201 fLastRegion= new Region(pos.getOffset(), pos.getLength()); | |
202 } | |
203 } | |
204 | |
205 return true; | |
206 } | |
207 | |
208 /** | |
209 * Checks whether the given event touches the given position. To touch means | |
210 * to overlap or come up to the borders of the position. | |
211 * | |
212 * @param position the position | |
213 * @param event the event | |
214 * @return <code>true</code> if <code>position</code> and | |
215 * <code>event</code> are not absolutely disjoint | |
216 * @since 3.1 | |
217 */ | |
218 private bool overlapsOrTouches(LinkedPosition position, DocumentEvent event) { | |
219 return position.getDocument().equals(event.getDocument()) && position.getOffset() <= event.getOffset() + event.getLength() && position.getOffset() + position.getLength() >= event.getOffset(); | |
220 } | |
221 | |
222 /** | |
223 * Creates an edition of a document change that will forward any | |
224 * modification in one position to all linked siblings. The return value is | |
225 * a map from <code>IDocument</code> to <code>TextEdit</code>. | |
226 * | |
227 * @param event the document event to check | |
228 * @return a map of edits, grouped by edited document, or <code>null</code> | |
229 * if there are no edits | |
230 */ | |
231 Map handleEvent(DocumentEvent event) { | |
232 | |
233 if (fLastPosition !is null) { | |
234 | |
235 Map map= new HashMap(); | |
236 | |
237 | |
238 int relativeOffset= event.getOffset() - fLastRegion.getOffset(); | |
239 if (relativeOffset < 0) { | |
240 relativeOffset= 0; | |
241 } | |
242 | |
243 int eventEnd= event.getOffset() + event.getLength(); | |
244 int lastEnd= fLastRegion.getOffset() + fLastRegion.getLength(); | |
245 int length; | |
246 if (eventEnd > lastEnd) | |
247 length= lastEnd - relativeOffset - fLastRegion.getOffset(); | |
248 else | |
249 length= eventEnd - relativeOffset - fLastRegion.getOffset(); | |
250 | |
251 String text= event.getText(); | |
252 if (text is null) | |
253 text= ""; //$NON-NLS-1$ | |
254 | |
255 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { | |
134 | 256 LinkedPosition p= cast(LinkedPosition) it.next(); |
129 | 257 if (p is fLastPosition || p.isDeleted()) |
258 continue; // don't re-update the origin of the change | |
259 | |
134 | 260 List edits= cast(List) map.get(p.getDocument()); |
129 | 261 if (edits is null) { |
262 edits= new ArrayList(); | |
263 map.put(p.getDocument(), edits); | |
264 } | |
265 | |
266 edits.add(new ReplaceEdit(p.getOffset() + relativeOffset, length, text)); | |
267 } | |
268 | |
269 try { | |
270 for (Iterator it= map.keySet().iterator(); it.hasNext();) { | |
134 | 271 IDocument d= cast(IDocument) it.next(); |
129 | 272 TextEdit edit= new MultiTextEdit(0, d.getLength()); |
134 | 273 edit.addChildren((TextEdit[]) (cast(List) map.get(d)).toArray(new TextEdit[0])); |
129 | 274 map.put(d, edit); |
275 } | |
276 | |
277 return map; | |
278 } catch (MalformedTreeException x) { | |
279 // may happen during undo, as LinkedModeModel does not know | |
280 // that the changes technically originate from a parent environment | |
281 // if this happens, post notification changes are not accepted anyway and | |
282 // we can simply return null - any changes will be undone by the undo | |
283 // manager | |
284 return null; | |
285 } | |
286 | |
287 } | |
288 | |
289 return null; | |
290 } | |
291 | |
292 /** | |
293 * Sets the model of this group. Once a model has been set, no | |
294 * more positions can be added and the model cannot be changed. | |
295 */ | |
296 void seal() { | |
297 Assert.isTrue(!fIsSealed); | |
298 fIsSealed= true; | |
299 | |
300 if (fHasCustomIteration is false && fPositions.size() > 0) { | |
134 | 301 (cast(LinkedPosition) fPositions.get(0)).setSequenceNumber(0); |
129 | 302 } |
303 } | |
304 | |
305 IDocument[] getDocuments() { | |
306 IDocument[] docs= new IDocument[fPositions.size()]; | |
307 int i= 0; | |
308 for (Iterator it= fPositions.iterator(); it.hasNext(); i++) { | |
134 | 309 LinkedPosition pos= cast(LinkedPosition) it.next(); |
129 | 310 docs[i]= pos.getDocument(); |
311 } | |
312 return docs; | |
313 } | |
314 | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
315 void register(LinkedModeModel model) { |
129 | 316 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { |
134 | 317 LinkedPosition pos= cast(LinkedPosition) it.next(); |
129 | 318 model.register(pos); |
319 } | |
320 } | |
321 | |
322 /** | |
323 * Returns the position in this group that encompasses all positions in | |
324 * <code>group</code>. | |
325 * | |
326 * @param group the group to be adopted | |
327 * @return a position in the receiver that contains all positions in <code>group</code>, | |
328 * or <code>null</code> if none can be found | |
329 * @throws BadLocationException if more than one position are affected by | |
330 * <code>group</code> | |
331 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
134
diff
changeset
|
332 LinkedPosition adopt(LinkedPositionGroup group) { |
129 | 333 LinkedPosition found= null; |
334 for (Iterator it= group.fPositions.iterator(); it.hasNext(); ) { | |
134 | 335 LinkedPosition pos= cast(LinkedPosition) it.next(); |
129 | 336 LinkedPosition localFound= null; |
337 for (Iterator it2= fPositions.iterator(); it2.hasNext(); ) { | |
134 | 338 LinkedPosition myPos= cast(LinkedPosition) it2.next(); |
129 | 339 if (myPos.includes(pos)) { |
340 if (found is null) | |
341 found= myPos; | |
342 else if (found !is myPos) | |
343 throw new BadLocationException(); | |
344 if (localFound is null) | |
345 localFound= myPos; | |
346 } | |
347 } | |
348 | |
349 if (localFound !is found) | |
350 throw new BadLocationException(); | |
351 } | |
352 return found; | |
353 } | |
354 | |
355 /** | |
356 * Finds the closest position to <code>toFind</code>. | |
357 * | |
358 * @param toFind the linked position for which to find the closest position | |
359 * @return the closest position to <code>toFind</code>. | |
360 */ | |
361 LinkedPosition getPosition(LinkedPosition toFind) { | |
362 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { | |
134 | 363 LinkedPosition p= cast(LinkedPosition) it.next(); |
129 | 364 if (p.includes(toFind)) |
365 return p; | |
366 } | |
367 return null; | |
368 } | |
369 | |
370 /** | |
371 * Returns <code>true</code> if <code>offset</code> is contained in any | |
372 * position in this group. | |
373 * | |
374 * @param offset the offset to check | |
375 * @return <code>true</code> if offset is contained by this group | |
376 */ | |
377 bool contains(int offset) { | |
378 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { | |
134 | 379 LinkedPosition pos= cast(LinkedPosition) it.next(); |
129 | 380 if (pos.includes(offset)) { |
381 return true; | |
382 } | |
383 } | |
384 return false; | |
385 } | |
386 | |
387 /** | |
388 * Returns whether this group contains any positions. | |
389 * | |
390 * @return <code>true</code> if this group is empty, <code>false</code> otherwise | |
391 * @since 3.1 | |
392 */ | |
393 public bool isEmpty() { | |
394 return fPositions.size() is 0; | |
395 } | |
396 | |
397 /** | |
398 * Returns whether this group contains any positions. | |
399 * | |
400 * @return <code>true</code> if this group is empty, <code>false</code> otherwise | |
401 * @deprecated As of 3.1, replaced by {@link #isEmpty()} | |
402 */ | |
403 public bool isEmtpy() { | |
404 return isEmpty(); | |
405 } | |
406 | |
407 /** | |
408 * Returns the positions contained in the receiver as an array. The | |
409 * positions are the actual positions and must not be modified; the array | |
410 * is a copy of internal structures. | |
411 * | |
412 * @return the positions of this group in no particular order | |
413 */ | |
414 public LinkedPosition[] getPositions() { | |
415 return (LinkedPosition[]) fPositions.toArray(new LinkedPosition[0]); | |
416 } | |
417 | |
418 /** | |
419 * Returns <code>true</code> if the receiver contains <code>position</code>. | |
420 * | |
421 * @param position the position to check | |
422 * @return <code>true</code> if the receiver contains <code>position</code> | |
423 */ | |
424 bool contains(Position position) { | |
425 for (Iterator it= fPositions.iterator(); it.hasNext(); ) { | |
134 | 426 LinkedPosition p= cast(LinkedPosition) it.next(); |
129 | 427 if (position.equals(p)) |
428 return true; | |
429 } | |
430 return false; | |
431 } | |
432 } |