KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > lib > editor > view > ViewUtilitiesImpl


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.lib.editor.view;
21
22 import java.awt.Rectangle JavaDoc;
23 import java.awt.Shape JavaDoc;
24 import javax.swing.SwingConstants JavaDoc;
25 import javax.swing.text.BadLocationException JavaDoc;
26 import javax.swing.text.Position JavaDoc;
27 import javax.swing.text.View JavaDoc;
28 import org.netbeans.editor.view.spi.FlyView;
29
30 /**
31  * Various utility methods related to views.
32  *
33  * @author Miloslav Metelka
34  * @version 1.00
35  */

36
37 public class ViewUtilitiesImpl {
38
39     private ViewUtilitiesImpl() {
40     }
41
42     /**
43      * Find child index representing the given offset
44      * or -1 if the offset is not represented by any child view
45      * i.e. it is outside of the bounds of this view
46      * or when the given view does not have any children.
47      *
48      * @param view view having children to be inspected.
49      * @param offset the position >= 0.
50      * @return index of the child view representing the given position
51      * or -1 if <code>
52      * offset < view.getStartOffset()
53      * || offset >= view.getEndOffset()</code>
54      * || getViewCount() == 0
55      * </code>.
56      */

57     public static int findViewIndexBounded(View JavaDoc view, int offset) {
58     if ((offset >= view.getStartOffset()) && (offset < view.getEndOffset())) {
59         return findViewIndex(view, offset);
60     }
61
62     return -1; // outside of view bounds
63
}
64
65     /**
66      * Find the child view index that best represents the given offset.
67      * <br>
68      * Algorithm uses binary search.
69      * <br>
70      * The semantics is similar
71      * to {@link javax.swing.text.Element.getElementIndex(int)} semantics.
72      *
73      * @param view view having children to be inspected.
74      * @param offset the position &gt;= 0
75      * @return index of the child view representing the given position.
76      * <br>
77      * 0 if <code>offset < getView(0).getStartOffset()</code>.
78      * <br>
79      * <code>getViewCount() - 1</code>
80      * if <code>offset >= getView(viewCount - 1).getEndOffset() </code>.
81      * <br>
82      * -1 if <code>getViewCount() == 0</code>.
83      */

84     public static int findViewIndex(View JavaDoc view, int offset) {
85         return findViewIndex(view, offset, null);
86     }
87
88     /**
89      * Do the actual finding algorithm and use updater
90      * in case there is a view starting exactly
91      * at the requested offset.
92      */

93     private static int findViewIndex(View JavaDoc view, int offset, OffsetMatchUpdater updater) {
94         FlyView.Parent flyParent = (view instanceof FlyView.Parent)
95             ? (FlyView.Parent)view
96             : null;
97
98         int low = 0;
99         int high = view.getViewCount() - 1;
100         
101         if (high == -1) { // no children => return -1
102
return -1;
103         }
104         
105         while (low <= high) {
106             int mid = (low + high) / 2;
107             int midStartOffset = (flyParent != null)
108                 ? flyParent.getStartOffset(mid)
109                 : view.getView(mid).getStartOffset();
110             
111             if (midStartOffset < offset) {
112                 low = mid + 1;
113             } else if (midStartOffset > offset) {
114                 high = mid - 1;
115             } else { // element starts at offset
116
if (updater != null) {
117                     mid = updater.updateIndex(mid, offset, view, flyParent);
118                 }
119                 return mid;
120             }
121         }
122
123         if (high < 0) {
124             high = 0;
125         }
126         return high;
127     }
128
129     /**
130      * The semantics is the same like <code>findViewIndex()</code>
131      * but if there are any empty views at the offset (e.g. because
132      * of removal) then they are skipped and the first empty view
133      * is returned.
134      *
135      * @param view view having children to be inspected.
136      * @param offset the position &gt;= 0
137      * @param lowerAdjacent if set to true
138      * and the returned child view would start right at offset
139      * then return the non-empty left adjacent view (which ends
140      * at the <code>offset</code>). This may be useful e.g. in case
141      * of removal when the view was affected by removal (view's end was removed)
142      * but now after removal it seems that it naturally ends at <code>offset</code>.
143      * @return index of the child view representing the given offset.
144      */

145     public static int findLowerViewIndex(View JavaDoc view, int offset, boolean lowerAdjacent) {
146         OffsetMatchUpdater updater = lowerAdjacent
147             ? LowerOffsetMatchUpdater.adjacent
148             : LowerOffsetMatchUpdater.normal;
149
150         return findViewIndex(view, offset, updater);
151     }
152
153     /**
154      * The semantics is the same like <code>findViewIndex()</code>
155      * but if there are any empty views at the offset (e.g. because
156      * of removal) then they are skipped and the first non-empty view
157      * starting at the offset is returned.
158      *
159      * @param view view having children to be inspected.
160      * @param offset the position &gt;= 0
161      * @param excludeAtOffset if set to true
162      * and the returned non-empty child view would start right at offset
163      * then return the left adjacent view (which ends
164      * at the <code>offset</code>). This may be useful e.g. in case
165      * of insertion to not replace a view that starts right at the offset
166      * and was not in fact affected by insertion.
167      * @return index of the child view representing the given offset.
168      */

169     public static int findUpperViewIndex(View JavaDoc view, int offset, boolean excludeAtOffset) {
170         OffsetMatchUpdater updater = excludeAtOffset
171             ? UpperOffsetMatchUpdater.exclude
172             : UpperOffsetMatchUpdater.normal;
173
174         return findViewIndex(view, offset, updater);
175     }
176
177     /**
178      * Return existing rectangle or create new one.
179      * @param r rectangle which is checked to correspond to next parameters.
180      * @param x x coordinate of returned rectangle.
181      * @param y y coordinate of returned rectangle.
182      * @param width width of returned rectangle.
183      * @param height height of returned rectangle.
184      * @return rectangle <code>r</code> passed to this method
185      * in case it corresponds to all the other parameters.
186      * Newly created rectangle instance otherwise.
187      */

188     public static Rectangle JavaDoc maybeNew(Rectangle JavaDoc r, int x, int y, int width, int height) {
189         if (r == null || r.x != x || r.y != y || r.width != width || r.height != height) {
190             return new Rectangle JavaDoc(x, y, width, height);
191         } else {
192             return r;
193         }
194     }
195     
196     /**
197      * Return existing rectangle or create new one.
198      * @param origRect rectangle which is checked to correspond to tested rect.
199      * @param testRect rectangle being tested for equality to origRect.
200      * @return rectangle <code>origRect</code> passed to this method
201      * in case it corresponds to <code>testRect</code>.
202      * Newly created rectangle instance (being equal to testRect) otherwise.
203      */

204     public static Rectangle JavaDoc maybeNew(Rectangle JavaDoc origRect, Rectangle JavaDoc testRect) {
205         if (origRect == null || !origRect.equals(testRect)) {
206             origRect = new Rectangle JavaDoc(testRect);
207         }
208         return origRect;
209     }
210
211     public static String JavaDoc axisToString(int axis) {
212         switch (axis) {
213             case View.X_AXIS:
214                 return "x"; // NOI18N
215

216             case View.Y_AXIS:
217                 return "y"; // NOI18N
218

219             default:
220                 return "<invalid-axis-value=" + axis + ">"; // NOI18N
221
}
222     }
223
224     /**
225      * Provides a way to determine the next visually represented model
226      * location that one might place a caret. Some views may not be visible,
227      * they might not be in the same order found in the model, or they just
228      * might not allow access to some of the locations in the model.
229      * <p>
230      * This implementation assumes the views are layed out in a logical
231      * manner. That is, that the view at index x + 1 is visually after
232      * the View at index x, and that the View at index x - 1 is visually
233      * before the View at x. There is support for reversing this behavior
234      * only if the passed in <code>View</code> is an instance of
235      * <code>GapBoxView</code>.
236      * The <code>GapBoxView</code>
237      * must then override the <code>flipEastAndWestAtEnds</code> method.
238      *
239      * @param v View to query
240      * @param pos the position to convert >= 0
241      * @param a the allocated region to render into
242      * @param direction the direction from the current position that can
243      * be thought of as the arrow keys typically found on a keyboard;
244      * this may be one of the following:
245      * <ul>
246      * <li><code>SwingConstants.WEST</code>
247      * <li><code>SwingConstants.EAST</code>
248      * <li><code>SwingConstants.NORTH</code>
249      * <li><code>SwingConstants.SOUTH</code>
250      * </ul>
251      * @param biasRet an array contain the bias that was checked
252      * @return the location within the model that best represents the next
253      * location visual position
254      * @exception BadLocationException
255      * @exception IllegalArgumentException if <code>direction</code> is invalid
256      */

257     static int getNextVisualPositionFrom(View JavaDoc v, int pos, Position.Bias JavaDoc b,
258     Shape JavaDoc alloc, int direction, Position.Bias JavaDoc[] biasRet)
259     throws BadLocationException JavaDoc {
260
261         if (v.getViewCount() == 0) {
262             // Nothing to do.
263
return pos;
264         }
265
266         boolean top = (direction == SwingConstants.NORTH ||
267         direction == SwingConstants.WEST);
268         int retValue;
269         if (pos == -1) {
270             // Start from the first View.
271
int childIndex = (top) ? v.getViewCount() - 1 : 0;
272             View JavaDoc child = v.getView(childIndex);
273             Shape JavaDoc childBounds = v.getChildAllocation(childIndex, alloc);
274             retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
275             direction, biasRet);
276             if (retValue == -1 && !top && v.getViewCount() > 1) {
277                 // Special case that should ONLY happen if first view
278
// isn't valid (can happen when end position is put at
279
// beginning of line.
280
child = v.getView(1);
281                 childBounds = v.getChildAllocation(1, alloc);
282                 retValue = child.getNextVisualPositionFrom(-1, biasRet[0],
283                 childBounds,
284                 direction, biasRet);
285             }
286
287         } else {
288             int increment = (top) ? -1 : 1;
289             int childIndex;
290             if (b == Position.Bias.Backward && pos > 0) {
291                 childIndex = v.getViewIndex(pos - 1, Position.Bias.Forward);
292             } else {
293                 childIndex = v.getViewIndex(pos, Position.Bias.Forward);
294             }
295
296             View JavaDoc child = v.getView(childIndex);
297             Shape JavaDoc childBounds = v.getChildAllocation(childIndex, alloc);
298             retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
299             direction, biasRet);
300 /* if ((direction == SwingConstants.EAST ||
301                 direction == SwingConstants.WEST) &&
302                 (v instanceof GapBoxView) &&
303                 ((GapBoxView)v).flipEastAndWestAtEnds(pos, b)
304             ) {
305                 increment *= -1;
306             }
307  */

308
309             childIndex += increment;
310             if (retValue == -1 && childIndex >= 0 &&
311             childIndex < v.getViewCount()) {
312                 child = v.getView(childIndex);
313                 childBounds = v.getChildAllocation(childIndex, alloc);
314                 retValue = child.getNextVisualPositionFrom(
315                 -1, b, childBounds, direction, biasRet);
316                 // If there is a bias change, it is a fake position
317
// and we should skip it. This is usually the result
318
// of two elements side be side flowing the same way.
319
if (retValue == pos && biasRet[0] != b) {
320                     return getNextVisualPositionFrom(v, pos, biasRet[0],
321                     alloc, direction,
322                     biasRet);
323                 }
324             }
325             else if (retValue != -1 && biasRet[0] != b &&
326                 ((increment == 1 && child.getEndOffset() == retValue) ||
327                 (increment == -1 &&
328                 child.getStartOffset() == retValue)) &&
329                 childIndex >= 0 && childIndex < v.getViewCount()
330             ) {
331                 // Reached the end of a view, make sure the next view
332
// is a different direction.
333
child = v.getView(childIndex);
334                 childBounds = v.getChildAllocation(childIndex, alloc);
335                 Position.Bias JavaDoc originalBias = biasRet[0];
336                 int nextPos = child.getNextVisualPositionFrom(
337                     -1, b, childBounds, direction, biasRet);
338
339                 if (biasRet[0] == b) {
340                     retValue = nextPos;
341                 } else {
342                     biasRet[0] = originalBias;
343                 }
344             }
345         }
346         return retValue;
347     }
348
349     public static void checkViewHierarchy(View JavaDoc v) {
350         checkChildrenParent(v);
351     }
352     
353     private static void checkChildrenParent(View JavaDoc v) {
354         int cnt = v.getViewCount();
355         for (int i = 0; i < cnt; i++) {
356             View JavaDoc child = v.getView(i);
357             View JavaDoc childParent = child.getParent();
358             if (childParent != v) {
359                 throw new IllegalStateException JavaDoc("child=" + child // NOI18N
360
+ " has parent=" + childParent // NOI18N
361
+ " instead of " + v // NOI18N
362
);
363             }
364             checkChildrenParent(child);
365         }
366     }
367
368     /**
369      * Updates index of the view returned by <code>findViewIndex()</code>
370      * in case there is a view starting exactly at the given offset.
371      */

372     interface OffsetMatchUpdater {
373         
374         int updateIndex(int viewIndex, int offset, View JavaDoc view, FlyView.Parent flyParent);
375
376     }
377     
378     static class LowerOffsetMatchUpdater implements OffsetMatchUpdater {
379         
380         static final LowerOffsetMatchUpdater normal = new LowerOffsetMatchUpdater(false);
381         static final LowerOffsetMatchUpdater adjacent = new LowerOffsetMatchUpdater(true);
382         
383         private final boolean lowerAdjacent;
384         
385         LowerOffsetMatchUpdater(boolean lowerAdjacent) {
386             this.lowerAdjacent = lowerAdjacent;
387         }
388        
389         public int updateIndex(int viewIndex, int offset, View JavaDoc view, FlyView.Parent flyParent) {
390             while (--viewIndex >= 0) {
391                 int startOffset = (flyParent != null)
392                     ? flyParent.getStartOffset(viewIndex)
393                     : view.getView(viewIndex).getStartOffset();
394                 if (startOffset != offset) { // view starts below offset
395
if (lowerAdjacent) {
396                         viewIndex--; // return the lower view that ends at offset
397
}
398
399                     break;
400                 }
401             }
402             
403             return viewIndex + 1;
404         }
405
406     }
407         
408     static class UpperOffsetMatchUpdater implements OffsetMatchUpdater {
409         
410         static final UpperOffsetMatchUpdater normal = new UpperOffsetMatchUpdater(false);
411         static final UpperOffsetMatchUpdater exclude = new UpperOffsetMatchUpdater(true);
412         
413         private final boolean excludeAtOffset;
414         
415         UpperOffsetMatchUpdater(boolean excludeAtOffset) {
416             this.excludeAtOffset = excludeAtOffset;
417         }
418        
419         public int updateIndex(int viewIndex, int offset, View JavaDoc view, FlyView.Parent flyParent) {
420             int lastViewIndex = view.getViewCount() - 1;
421
422             while (true) {
423                 int endOffset = (flyParent != null)
424                     ? flyParent.getEndOffset(viewIndex)
425                     : view.getView(viewIndex).getEndOffset();
426                 if (endOffset != offset) { // view ends after offset
427
if (excludeAtOffset) {
428                         viewIndex--; // return the lower view that ends at offset
429
}
430                     break;
431                 }
432              
433                 if (viewIndex == lastViewIndex) { // over last view
434
break;
435                 }
436                 viewIndex++;
437             }
438             
439             return viewIndex;
440         }
441
442     }
443         
444 }
445
Popular Tags