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