author | xdono |
Wed, 02 Jul 2008 12:55:45 -0700 | |
changeset 715 | f16baef3a20e |
parent 673 | effa1ee249d6 |
child 1299 | 027d966d5658 |
permissions | -rw-r--r-- |
2 | 1 |
/* |
715 | 2 |
* Copyright 1999-2008 Sun Microsystems, Inc. All Rights Reserved. |
2 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
7 |
* published by the Free Software Foundation. Sun designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Sun in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 |
* have any questions. |
|
24 |
*/ |
|
25 |
package javax.swing.text; |
|
26 |
||
27 |
import java.awt.*; |
|
28 |
import java.util.Vector; |
|
29 |
import javax.swing.event.*; |
|
30 |
import javax.swing.SizeRequirements; |
|
31 |
||
32 |
/** |
|
33 |
* A View that tries to flow it's children into some |
|
34 |
* partially constrained space. This can be used to |
|
35 |
* build things like paragraphs, pages, etc. The |
|
36 |
* flow is made up of the following pieces of functionality. |
|
37 |
* <ul> |
|
38 |
* <li>A logical set of child views, which as used as a |
|
39 |
* layout pool from which a physical view is formed. |
|
40 |
* <li>A strategy for translating the logical view to |
|
41 |
* a physical (flowed) view. |
|
42 |
* <li>Constraints for the strategy to work against. |
|
43 |
* <li>A physical structure, that represents the flow. |
|
44 |
* The children of this view are where the pieces of |
|
45 |
* of the logical views are placed to create the flow. |
|
46 |
* </ul> |
|
47 |
* |
|
48 |
* @author Timothy Prinzing |
|
49 |
* @see View |
|
50 |
* @since 1.3 |
|
51 |
*/ |
|
52 |
public abstract class FlowView extends BoxView { |
|
53 |
||
54 |
/** |
|
55 |
* Constructs a FlowView for the given element. |
|
56 |
* |
|
57 |
* @param elem the element that this view is responsible for |
|
58 |
* @param axis may be either View.X_AXIS or View.Y_AXIS |
|
59 |
*/ |
|
60 |
public FlowView(Element elem, int axis) { |
|
61 |
super(elem, axis); |
|
62 |
layoutSpan = Integer.MAX_VALUE; |
|
63 |
strategy = new FlowStrategy(); |
|
64 |
} |
|
65 |
||
66 |
/** |
|
67 |
* Fetches the axis along which views should be |
|
68 |
* flowed. By default, this will be the axis |
|
69 |
* orthogonal to the axis along which the flow |
|
70 |
* rows are tiled (the axis of the default flow |
|
71 |
* rows themselves). This is typically used |
|
72 |
* by the <code>FlowStrategy</code>. |
|
73 |
*/ |
|
74 |
public int getFlowAxis() { |
|
75 |
if (getAxis() == Y_AXIS) { |
|
76 |
return X_AXIS; |
|
77 |
} |
|
78 |
return Y_AXIS; |
|
79 |
} |
|
80 |
||
81 |
/** |
|
82 |
* Fetch the constraining span to flow against for |
|
83 |
* the given child index. This is called by the |
|
84 |
* FlowStrategy while it is updating the flow. |
|
85 |
* A flow can be shaped by providing different values |
|
86 |
* for the row constraints. By default, the entire |
|
87 |
* span inside of the insets along the flow axis |
|
88 |
* is returned. |
|
89 |
* |
|
90 |
* @param index the index of the row being updated. |
|
91 |
* This should be a value >= 0 and < getViewCount(). |
|
92 |
* @see #getFlowStart |
|
93 |
*/ |
|
94 |
public int getFlowSpan(int index) { |
|
95 |
return layoutSpan; |
|
96 |
} |
|
97 |
||
98 |
/** |
|
99 |
* Fetch the location along the flow axis that the |
|
100 |
* flow span will start at. This is called by the |
|
101 |
* FlowStrategy while it is updating the flow. |
|
102 |
* A flow can be shaped by providing different values |
|
103 |
* for the row constraints. |
|
104 |
||
105 |
* @param index the index of the row being updated. |
|
106 |
* This should be a value >= 0 and < getViewCount(). |
|
107 |
* @see #getFlowSpan |
|
108 |
*/ |
|
109 |
public int getFlowStart(int index) { |
|
110 |
return 0; |
|
111 |
} |
|
112 |
||
113 |
/** |
|
114 |
* Create a View that should be used to hold a |
|
115 |
* a rows worth of children in a flow. This is |
|
116 |
* called by the FlowStrategy when new children |
|
117 |
* are added or removed (i.e. rows are added or |
|
118 |
* removed) in the process of updating the flow. |
|
119 |
*/ |
|
120 |
protected abstract View createRow(); |
|
121 |
||
122 |
// ---- BoxView methods ------------------------------------- |
|
123 |
||
124 |
/** |
|
125 |
* Loads all of the children to initialize the view. |
|
126 |
* This is called by the <code>setParent</code> method. |
|
127 |
* This is reimplemented to not load any children directly |
|
128 |
* (as they are created in the process of formatting). |
|
129 |
* If the layoutPool variable is null, an instance of |
|
130 |
* LogicalView is created to represent the logical view |
|
131 |
* that is used in the process of formatting. |
|
132 |
* |
|
133 |
* @param f the view factory |
|
134 |
*/ |
|
135 |
protected void loadChildren(ViewFactory f) { |
|
136 |
if (layoutPool == null) { |
|
137 |
layoutPool = new LogicalView(getElement()); |
|
138 |
} |
|
139 |
layoutPool.setParent(this); |
|
140 |
||
141 |
// This synthetic insertUpdate call gives the strategy a chance |
|
142 |
// to initialize. |
|
143 |
strategy.insertUpdate(this, null, null); |
|
144 |
} |
|
145 |
||
146 |
/** |
|
147 |
* Fetches the child view index representing the given position in |
|
148 |
* the model. |
|
149 |
* |
|
150 |
* @param pos the position >= 0 |
|
151 |
* @return index of the view representing the given position, or |
|
152 |
* -1 if no view represents that position |
|
153 |
*/ |
|
154 |
protected int getViewIndexAtPosition(int pos) { |
|
155 |
if (pos >= getStartOffset() && (pos < getEndOffset())) { |
|
156 |
for (int counter = 0; counter < getViewCount(); counter++) { |
|
157 |
View v = getView(counter); |
|
158 |
if(pos >= v.getStartOffset() && |
|
159 |
pos < v.getEndOffset()) { |
|
160 |
return counter; |
|
161 |
} |
|
162 |
} |
|
163 |
} |
|
164 |
return -1; |
|
165 |
} |
|
166 |
||
167 |
/** |
|
168 |
* Lays out the children. If the span along the flow |
|
169 |
* axis has changed, layout is marked as invalid which |
|
170 |
* which will cause the superclass behavior to recalculate |
|
171 |
* the layout along the box axis. The FlowStrategy.layout |
|
172 |
* method will be called to rebuild the flow rows as |
|
173 |
* appropriate. If the height of this view changes |
|
174 |
* (determined by the perferred size along the box axis), |
|
175 |
* a preferenceChanged is called. Following all of that, |
|
176 |
* the normal box layout of the superclass is performed. |
|
177 |
* |
|
178 |
* @param width the width to lay out against >= 0. This is |
|
179 |
* the width inside of the inset area. |
|
180 |
* @param height the height to lay out against >= 0 This |
|
181 |
* is the height inside of the inset area. |
|
182 |
*/ |
|
183 |
protected void layout(int width, int height) { |
|
184 |
final int faxis = getFlowAxis(); |
|
185 |
int newSpan; |
|
186 |
if (faxis == X_AXIS) { |
|
187 |
newSpan = (int)width; |
|
188 |
} else { |
|
189 |
newSpan = (int)height; |
|
190 |
} |
|
191 |
if (layoutSpan != newSpan) { |
|
192 |
layoutChanged(faxis); |
|
193 |
layoutChanged(getAxis()); |
|
194 |
layoutSpan = newSpan; |
|
195 |
} |
|
196 |
||
197 |
// repair the flow if necessary |
|
198 |
if (! isLayoutValid(faxis)) { |
|
199 |
final int heightAxis = getAxis(); |
|
200 |
int oldFlowHeight = (int)((heightAxis == X_AXIS)? getWidth() : getHeight()); |
|
201 |
strategy.layout(this); |
|
202 |
int newFlowHeight = (int) getPreferredSpan(heightAxis); |
|
203 |
if (oldFlowHeight != newFlowHeight) { |
|
204 |
View p = getParent(); |
|
205 |
if (p != null) { |
|
206 |
p.preferenceChanged(this, (heightAxis == X_AXIS), (heightAxis == Y_AXIS)); |
|
207 |
} |
|
208 |
||
209 |
// PENDING(shannonh) |
|
210 |
// Temporary fix for 4250847 |
|
211 |
// Can be removed when TraversalContext is added |
|
212 |
Component host = getContainer(); |
|
213 |
if (host != null) { |
|
214 |
//nb idk 12/12/2001 host should not be equal to null. We need to add assertion here |
|
215 |
host.repaint(); |
|
216 |
} |
|
217 |
} |
|
218 |
} |
|
219 |
||
220 |
super.layout(width, height); |
|
221 |
} |
|
222 |
||
223 |
/** |
|
224 |
* Calculate equirements along the minor axis. This |
|
225 |
* is implemented to forward the request to the logical |
|
226 |
* view by calling getMinimumSpan, getPreferredSpan, and |
|
227 |
* getMaximumSpan on it. |
|
228 |
*/ |
|
229 |
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { |
|
230 |
if (r == null) { |
|
231 |
r = new SizeRequirements(); |
|
232 |
} |
|
233 |
float pref = layoutPool.getPreferredSpan(axis); |
|
234 |
float min = layoutPool.getMinimumSpan(axis); |
|
235 |
// Don't include insets, Box.getXXXSpan will include them. |
|
236 |
r.minimum = (int)min; |
|
237 |
r.preferred = Math.max(r.minimum, (int) pref); |
|
238 |
r.maximum = Integer.MAX_VALUE; |
|
239 |
r.alignment = 0.5f; |
|
240 |
return r; |
|
241 |
} |
|
242 |
||
243 |
// ---- View methods ---------------------------------------------------- |
|
244 |
||
245 |
/** |
|
246 |
* Gives notification that something was inserted into the document |
|
247 |
* in a location that this view is responsible for. |
|
248 |
* |
|
249 |
* @param changes the change information from the associated document |
|
250 |
* @param a the current allocation of the view |
|
251 |
* @param f the factory to use to rebuild if the view has children |
|
252 |
* @see View#insertUpdate |
|
253 |
*/ |
|
254 |
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) { |
|
255 |
layoutPool.insertUpdate(changes, a, f); |
|
256 |
strategy.insertUpdate(this, changes, getInsideAllocation(a)); |
|
257 |
} |
|
258 |
||
259 |
/** |
|
260 |
* Gives notification that something was removed from the document |
|
261 |
* in a location that this view is responsible for. |
|
262 |
* |
|
263 |
* @param changes the change information from the associated document |
|
264 |
* @param a the current allocation of the view |
|
265 |
* @param f the factory to use to rebuild if the view has children |
|
266 |
* @see View#removeUpdate |
|
267 |
*/ |
|
268 |
public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) { |
|
269 |
layoutPool.removeUpdate(changes, a, f); |
|
270 |
strategy.removeUpdate(this, changes, getInsideAllocation(a)); |
|
271 |
} |
|
272 |
||
273 |
/** |
|
274 |
* Gives notification from the document that attributes were changed |
|
275 |
* in a location that this view is responsible for. |
|
276 |
* |
|
277 |
* @param changes the change information from the associated document |
|
278 |
* @param a the current allocation of the view |
|
279 |
* @param f the factory to use to rebuild if the view has children |
|
280 |
* @see View#changedUpdate |
|
281 |
*/ |
|
282 |
public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) { |
|
283 |
layoutPool.changedUpdate(changes, a, f); |
|
284 |
strategy.changedUpdate(this, changes, getInsideAllocation(a)); |
|
285 |
} |
|
286 |
||
287 |
/** {@inheritDoc} */ |
|
288 |
public void setParent(View parent) { |
|
289 |
super.setParent(parent); |
|
290 |
if (parent == null |
|
291 |
&& layoutPool != null ) { |
|
292 |
layoutPool.setParent(null); |
|
293 |
} |
|
294 |
} |
|
295 |
||
296 |
// --- variables ----------------------------------------------- |
|
297 |
||
298 |
/** |
|
299 |
* Default constraint against which the flow is |
|
300 |
* created against. |
|
301 |
*/ |
|
302 |
protected int layoutSpan; |
|
303 |
||
304 |
/** |
|
305 |
* These are the views that represent the child elements |
|
306 |
* of the element this view represents (The logical view |
|
307 |
* to translate to a physical view). These are not |
|
308 |
* directly children of this view. These are either |
|
309 |
* placed into the rows directly or used for the purpose |
|
310 |
* of breaking into smaller chunks, to form the physical |
|
311 |
* view. |
|
312 |
*/ |
|
313 |
protected View layoutPool; |
|
314 |
||
315 |
/** |
|
316 |
* The behavior for keeping the flow updated. By |
|
317 |
* default this is a singleton shared by all instances |
|
318 |
* of FlowView (FlowStrategy is stateless). Subclasses |
|
319 |
* can create an alternative strategy, which might keep |
|
320 |
* state. |
|
321 |
*/ |
|
322 |
protected FlowStrategy strategy; |
|
323 |
||
324 |
/** |
|
325 |
* Strategy for maintaining the physical form |
|
326 |
* of the flow. The default implementation is |
|
327 |
* completely stateless, and recalculates the |
|
328 |
* entire flow if the layout is invalid on the |
|
329 |
* given FlowView. Alternative strategies can |
|
330 |
* be implemented by subclassing, and might |
|
331 |
* perform incrementatal repair to the layout |
|
332 |
* or alternative breaking behavior. |
|
333 |
* @since 1.3 |
|
334 |
*/ |
|
335 |
public static class FlowStrategy { |
|
673
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
336 |
Position damageStart = null; |
2 | 337 |
Vector<View> viewBuffer; |
338 |
||
339 |
void addDamage(FlowView fv, int offset) { |
|
340 |
if (offset >= fv.getStartOffset() && offset < fv.getEndOffset()) { |
|
673
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
341 |
if (damageStart == null || offset < damageStart.getOffset()) { |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
342 |
try { |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
343 |
damageStart = fv.getDocument().createPosition(offset); |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
344 |
} catch (BadLocationException e) { |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
345 |
// shouldn't happen since offset is inside view bounds |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
346 |
assert(false); |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
347 |
} |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
348 |
} |
2 | 349 |
} |
350 |
} |
|
351 |
||
352 |
void unsetDamage() { |
|
673
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
353 |
damageStart = null; |
2 | 354 |
} |
355 |
||
356 |
/** |
|
357 |
* Gives notification that something was inserted into the document |
|
358 |
* in a location that the given flow view is responsible for. The |
|
359 |
* strategy should update the appropriate changed region (which |
|
360 |
* depends upon the strategy used for repair). |
|
361 |
* |
|
362 |
* @param e the change information from the associated document |
|
363 |
* @param alloc the current allocation of the view inside of the insets. |
|
364 |
* This value will be null if the view has not yet been displayed. |
|
365 |
* @see View#insertUpdate |
|
366 |
*/ |
|
367 |
public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { |
|
368 |
// FlowView.loadChildren() makes a synthetic call into this, |
|
369 |
// passing null as e |
|
370 |
if (e != null) { |
|
371 |
addDamage(fv, e.getOffset()); |
|
372 |
} |
|
373 |
||
374 |
if (alloc != null) { |
|
375 |
Component host = fv.getContainer(); |
|
376 |
if (host != null) { |
|
377 |
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); |
|
378 |
} |
|
379 |
} else { |
|
380 |
fv.preferenceChanged(null, true, true); |
|
381 |
} |
|
382 |
} |
|
383 |
||
384 |
/** |
|
385 |
* Gives notification that something was removed from the document |
|
386 |
* in a location that the given flow view is responsible for. |
|
387 |
* |
|
388 |
* @param e the change information from the associated document |
|
389 |
* @param alloc the current allocation of the view inside of the insets. |
|
390 |
* @see View#removeUpdate |
|
391 |
*/ |
|
392 |
public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { |
|
393 |
addDamage(fv, e.getOffset()); |
|
394 |
if (alloc != null) { |
|
395 |
Component host = fv.getContainer(); |
|
396 |
if (host != null) { |
|
397 |
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); |
|
398 |
} |
|
399 |
} else { |
|
400 |
fv.preferenceChanged(null, true, true); |
|
401 |
} |
|
402 |
} |
|
403 |
||
404 |
/** |
|
405 |
* Gives notification from the document that attributes were changed |
|
406 |
* in a location that this view is responsible for. |
|
407 |
* |
|
408 |
* @param fv the <code>FlowView</code> containing the changes |
|
409 |
* @param e the <code>DocumentEvent</code> describing the changes |
|
410 |
* done to the Document |
|
411 |
* @param alloc Bounds of the View |
|
412 |
* @see View#changedUpdate |
|
413 |
*/ |
|
414 |
public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { |
|
415 |
addDamage(fv, e.getOffset()); |
|
416 |
if (alloc != null) { |
|
417 |
Component host = fv.getContainer(); |
|
418 |
if (host != null) { |
|
419 |
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height); |
|
420 |
} |
|
421 |
} else { |
|
422 |
fv.preferenceChanged(null, true, true); |
|
423 |
} |
|
424 |
} |
|
425 |
||
426 |
/** |
|
427 |
* This method gives flow strategies access to the logical |
|
428 |
* view of the FlowView. |
|
429 |
*/ |
|
430 |
protected View getLogicalView(FlowView fv) { |
|
431 |
return fv.layoutPool; |
|
432 |
} |
|
433 |
||
434 |
/** |
|
435 |
* Update the flow on the given FlowView. By default, this causes |
|
436 |
* all of the rows (child views) to be rebuilt to match the given |
|
437 |
* constraints for each row. This is called by a FlowView.layout |
|
438 |
* to update the child views in the flow. |
|
439 |
* |
|
440 |
* @param fv the view to reflow |
|
441 |
*/ |
|
442 |
public void layout(FlowView fv) { |
|
443 |
View pool = getLogicalView(fv); |
|
444 |
int rowIndex, p0; |
|
445 |
int p1 = fv.getEndOffset(); |
|
446 |
||
447 |
if (fv.majorAllocValid) { |
|
673
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
448 |
if (damageStart == null) { |
2 | 449 |
return; |
450 |
} |
|
451 |
// In some cases there's no view at position damageStart, so |
|
452 |
// step back and search again. See 6452106 for details. |
|
673
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
453 |
int offset = damageStart.getOffset(); |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
454 |
while ((rowIndex = fv.getViewIndexAtPosition(offset)) < 0) { |
effa1ee249d6
6606443: Infinite loop in FlowView.layout when using HTML tables in JEditorPane
peterz
parents:
2
diff
changeset
|
455 |
offset--; |
2 | 456 |
} |
457 |
if (rowIndex > 0) { |
|
458 |
rowIndex--; |
|
459 |
} |
|
460 |
p0 = fv.getView(rowIndex).getStartOffset(); |
|
461 |
} else { |
|
462 |
rowIndex = 0; |
|
463 |
p0 = fv.getStartOffset(); |
|
464 |
} |
|
465 |
reparentViews(pool, p0); |
|
466 |
||
467 |
viewBuffer = new Vector<View>(10, 10); |
|
468 |
int rowCount = fv.getViewCount(); |
|
469 |
while (p0 < p1) { |
|
470 |
View row; |
|
471 |
if (rowIndex >= rowCount) { |
|
472 |
row = fv.createRow(); |
|
473 |
fv.append(row); |
|
474 |
} else { |
|
475 |
row = fv.getView(rowIndex); |
|
476 |
} |
|
477 |
p0 = layoutRow(fv, rowIndex, p0); |
|
478 |
rowIndex++; |
|
479 |
} |
|
480 |
viewBuffer = null; |
|
481 |
||
482 |
if (rowIndex < rowCount) { |
|
483 |
fv.replace(rowIndex, rowCount - rowIndex, null); |
|
484 |
} |
|
485 |
unsetDamage(); |
|
486 |
} |
|
487 |
||
488 |
/** |
|
489 |
* Creates a row of views that will fit within the |
|
490 |
* layout span of the row. This is called by the layout method. |
|
491 |
* This is implemented to fill the row by repeatedly calling |
|
492 |
* the createView method until the available span has been |
|
493 |
* exhausted, a forced break was encountered, or the createView |
|
494 |
* method returned null. If the remaining span was exhaused, |
|
495 |
* the adjustRow method will be called to perform adjustments |
|
496 |
* to the row to try and make it fit into the given span. |
|
497 |
* |
|
498 |
* @param rowIndex the index of the row to fill in with views. The |
|
499 |
* row is assumed to be empty on entry. |
|
500 |
* @param pos The current position in the children of |
|
501 |
* this views element from which to start. |
|
502 |
* @return the position to start the next row |
|
503 |
*/ |
|
504 |
protected int layoutRow(FlowView fv, int rowIndex, int pos) { |
|
505 |
View row = fv.getView(rowIndex); |
|
506 |
float x = fv.getFlowStart(rowIndex); |
|
507 |
float spanLeft = fv.getFlowSpan(rowIndex); |
|
508 |
int end = fv.getEndOffset(); |
|
509 |
TabExpander te = (fv instanceof TabExpander) ? (TabExpander)fv : null; |
|
510 |
final int flowAxis = fv.getFlowAxis(); |
|
511 |
||
512 |
int breakWeight = BadBreakWeight; |
|
513 |
float breakX = 0f; |
|
514 |
float breakSpan = 0f; |
|
515 |
int breakIndex = -1; |
|
516 |
int n = 0; |
|
517 |
||
518 |
viewBuffer.clear(); |
|
519 |
while (pos < end && spanLeft >= 0) { |
|
520 |
View v = createView(fv, pos, (int)spanLeft, rowIndex); |
|
521 |
if (v == null) { |
|
522 |
break; |
|
523 |
} |
|
524 |
||
525 |
int bw = v.getBreakWeight(flowAxis, x, spanLeft); |
|
526 |
if (bw >= ForcedBreakWeight) { |
|
527 |
View w = v.breakView(flowAxis, pos, x, spanLeft); |
|
528 |
if (w != null) { |
|
529 |
viewBuffer.add(w); |
|
530 |
} else if (n == 0) { |
|
531 |
// if the view does not break, and it is the only view |
|
532 |
// in a row, use the whole view |
|
533 |
viewBuffer.add(v); |
|
534 |
} |
|
535 |
break; |
|
536 |
} else if (bw >= breakWeight && bw > BadBreakWeight) { |
|
537 |
breakWeight = bw; |
|
538 |
breakX = x; |
|
539 |
breakSpan = spanLeft; |
|
540 |
breakIndex = n; |
|
541 |
} |
|
542 |
||
543 |
float chunkSpan; |
|
544 |
if (flowAxis == X_AXIS && v instanceof TabableView) { |
|
545 |
chunkSpan = ((TabableView)v).getTabbedSpan(x, te); |
|
546 |
} else { |
|
547 |
chunkSpan = v.getPreferredSpan(flowAxis); |
|
548 |
} |
|
549 |
||
550 |
if (chunkSpan > spanLeft && breakIndex >= 0) { |
|
551 |
// row is too long, and we may break |
|
552 |
if (breakIndex < n) { |
|
553 |
v = viewBuffer.get(breakIndex); |
|
554 |
} |
|
555 |
for (int i = n - 1; i >= breakIndex; i--) { |
|
556 |
viewBuffer.remove(i); |
|
557 |
} |
|
558 |
v = v.breakView(flowAxis, v.getStartOffset(), breakX, breakSpan); |
|
559 |
} |
|
560 |
||
561 |
spanLeft -= chunkSpan; |
|
562 |
x += chunkSpan; |
|
563 |
viewBuffer.add(v); |
|
564 |
pos = v.getEndOffset(); |
|
565 |
n++; |
|
566 |
} |
|
567 |
||
568 |
View[] views = new View[viewBuffer.size()]; |
|
569 |
viewBuffer.toArray(views); |
|
570 |
row.replace(0, row.getViewCount(), views); |
|
571 |
return (views.length > 0 ? row.getEndOffset() : pos); |
|
572 |
} |
|
573 |
||
574 |
/** |
|
575 |
* Adjusts the given row if possible to fit within the |
|
576 |
* layout span. By default this will try to find the |
|
577 |
* highest break weight possible nearest the end of |
|
578 |
* the row. If a forced break is encountered, the |
|
579 |
* break will be positioned there. |
|
580 |
* |
|
581 |
* @param rowIndex the row to adjust to the current layout |
|
582 |
* span. |
|
583 |
* @param desiredSpan the current layout span >= 0 |
|
584 |
* @param x the location r starts at. |
|
585 |
*/ |
|
586 |
protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) { |
|
587 |
final int flowAxis = fv.getFlowAxis(); |
|
588 |
View r = fv.getView(rowIndex); |
|
589 |
int n = r.getViewCount(); |
|
590 |
int span = 0; |
|
591 |
int bestWeight = BadBreakWeight; |
|
592 |
int bestSpan = 0; |
|
593 |
int bestIndex = -1; |
|
594 |
View v; |
|
595 |
for (int i = 0; i < n; i++) { |
|
596 |
v = r.getView(i); |
|
597 |
int spanLeft = desiredSpan - span; |
|
598 |
||
599 |
int w = v.getBreakWeight(flowAxis, x + span, spanLeft); |
|
600 |
if ((w >= bestWeight) && (w > BadBreakWeight)) { |
|
601 |
bestWeight = w; |
|
602 |
bestIndex = i; |
|
603 |
bestSpan = span; |
|
604 |
if (w >= ForcedBreakWeight) { |
|
605 |
// it's a forced break, so there is |
|
606 |
// no point in searching further. |
|
607 |
break; |
|
608 |
} |
|
609 |
} |
|
610 |
span += v.getPreferredSpan(flowAxis); |
|
611 |
} |
|
612 |
if (bestIndex < 0) { |
|
613 |
// there is nothing that can be broken, leave |
|
614 |
// it in it's current state. |
|
615 |
return; |
|
616 |
} |
|
617 |
||
618 |
// Break the best candidate view, and patch up the row. |
|
619 |
int spanLeft = desiredSpan - bestSpan; |
|
620 |
v = r.getView(bestIndex); |
|
621 |
v = v.breakView(flowAxis, v.getStartOffset(), x + bestSpan, spanLeft); |
|
622 |
View[] va = new View[1]; |
|
623 |
va[0] = v; |
|
624 |
View lv = getLogicalView(fv); |
|
625 |
int p0 = r.getView(bestIndex).getStartOffset(); |
|
626 |
int p1 = r.getEndOffset(); |
|
627 |
for (int i = 0; i < lv.getViewCount(); i++) { |
|
628 |
View tmpView = lv.getView(i); |
|
629 |
if (tmpView.getEndOffset() > p1) { |
|
630 |
break; |
|
631 |
} |
|
632 |
if (tmpView.getStartOffset() >= p0) { |
|
633 |
tmpView.setParent(lv); |
|
634 |
} |
|
635 |
} |
|
636 |
r.replace(bestIndex, n - bestIndex, va); |
|
637 |
} |
|
638 |
||
639 |
void reparentViews(View pool, int startPos) { |
|
640 |
int n = pool.getViewIndex(startPos, Position.Bias.Forward); |
|
641 |
if (n >= 0) { |
|
642 |
for (int i = n; i < pool.getViewCount(); i++) { |
|
643 |
pool.getView(i).setParent(pool); |
|
644 |
} |
|
645 |
} |
|
646 |
} |
|
647 |
||
648 |
/** |
|
649 |
* Creates a view that can be used to represent the current piece |
|
650 |
* of the flow. This can be either an entire view from the |
|
651 |
* logical view, or a fragment of the logical view. |
|
652 |
* |
|
653 |
* @param fv the view holding the flow |
|
654 |
* @param startOffset the start location for the view being created |
|
655 |
* @param spanLeft the about of span left to fill in the row |
|
656 |
* @param rowIndex the row the view will be placed into |
|
657 |
*/ |
|
658 |
protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) { |
|
659 |
// Get the child view that contains the given starting position |
|
660 |
View lv = getLogicalView(fv); |
|
661 |
int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward); |
|
662 |
View v = lv.getView(childIndex); |
|
663 |
if (startOffset==v.getStartOffset()) { |
|
664 |
// return the entire view |
|
665 |
return v; |
|
666 |
} |
|
667 |
||
668 |
// return a fragment. |
|
669 |
v = v.createFragment(startOffset, v.getEndOffset()); |
|
670 |
return v; |
|
671 |
} |
|
672 |
} |
|
673 |
||
674 |
/** |
|
675 |
* This class can be used to represent a logical view for |
|
676 |
* a flow. It keeps the children updated to reflect the state |
|
677 |
* of the model, gives the logical child views access to the |
|
678 |
* view hierarchy, and calculates a preferred span. It doesn't |
|
679 |
* do any rendering, layout, or model/view translation. |
|
680 |
*/ |
|
681 |
static class LogicalView extends CompositeView { |
|
682 |
||
683 |
LogicalView(Element elem) { |
|
684 |
super(elem); |
|
685 |
} |
|
686 |
||
687 |
protected int getViewIndexAtPosition(int pos) { |
|
688 |
Element elem = getElement(); |
|
689 |
if (elem.isLeaf()) { |
|
690 |
return 0; |
|
691 |
} |
|
692 |
return super.getViewIndexAtPosition(pos); |
|
693 |
} |
|
694 |
||
695 |
protected void loadChildren(ViewFactory f) { |
|
696 |
Element elem = getElement(); |
|
697 |
if (elem.isLeaf()) { |
|
698 |
View v = new LabelView(elem); |
|
699 |
append(v); |
|
700 |
} else { |
|
701 |
super.loadChildren(f); |
|
702 |
} |
|
703 |
} |
|
704 |
||
705 |
/** |
|
706 |
* Fetches the attributes to use when rendering. This view |
|
707 |
* isn't directly responsible for an element so it returns |
|
708 |
* the outer classes attributes. |
|
709 |
*/ |
|
710 |
public AttributeSet getAttributes() { |
|
711 |
View p = getParent(); |
|
712 |
return (p != null) ? p.getAttributes() : null; |
|
713 |
} |
|
714 |
||
715 |
/** |
|
716 |
* Determines the preferred span for this view along an |
|
717 |
* axis. |
|
718 |
* |
|
719 |
* @param axis may be either View.X_AXIS or View.Y_AXIS |
|
720 |
* @return the span the view would like to be rendered into. |
|
721 |
* Typically the view is told to render into the span |
|
722 |
* that is returned, although there is no guarantee. |
|
723 |
* The parent may choose to resize or break the view. |
|
724 |
* @see View#getPreferredSpan |
|
725 |
*/ |
|
726 |
public float getPreferredSpan(int axis) { |
|
727 |
float maxpref = 0; |
|
728 |
float pref = 0; |
|
729 |
int n = getViewCount(); |
|
730 |
for (int i = 0; i < n; i++) { |
|
731 |
View v = getView(i); |
|
732 |
pref += v.getPreferredSpan(axis); |
|
733 |
if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) { |
|
734 |
maxpref = Math.max(maxpref, pref); |
|
735 |
pref = 0; |
|
736 |
} |
|
737 |
} |
|
738 |
maxpref = Math.max(maxpref, pref); |
|
739 |
return maxpref; |
|
740 |
} |
|
741 |
||
742 |
/** |
|
743 |
* Determines the minimum span for this view along an |
|
744 |
* axis. The is implemented to find the minimum unbreakable |
|
745 |
* span. |
|
746 |
* |
|
747 |
* @param axis may be either View.X_AXIS or View.Y_AXIS |
|
748 |
* @return the span the view would like to be rendered into. |
|
749 |
* Typically the view is told to render into the span |
|
750 |
* that is returned, although there is no guarantee. |
|
751 |
* The parent may choose to resize or break the view. |
|
752 |
* @see View#getPreferredSpan |
|
753 |
*/ |
|
754 |
public float getMinimumSpan(int axis) { |
|
755 |
float maxmin = 0; |
|
756 |
float min = 0; |
|
757 |
boolean nowrap = false; |
|
758 |
int n = getViewCount(); |
|
759 |
for (int i = 0; i < n; i++) { |
|
760 |
View v = getView(i); |
|
761 |
if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) == BadBreakWeight) { |
|
762 |
min += v.getPreferredSpan(axis); |
|
763 |
nowrap = true; |
|
764 |
} else if (nowrap) { |
|
765 |
maxmin = Math.max(min, maxmin); |
|
766 |
nowrap = false; |
|
767 |
min = 0; |
|
768 |
} |
|
769 |
if (v instanceof ComponentView) { |
|
770 |
maxmin = Math.max(maxmin, v.getMinimumSpan(axis)); |
|
771 |
} |
|
772 |
} |
|
773 |
maxmin = Math.max(maxmin, min); |
|
774 |
return maxmin; |
|
775 |
} |
|
776 |
||
777 |
/** |
|
778 |
* Forward the DocumentEvent to the given child view. This |
|
779 |
* is implemented to reparent the child to the logical view |
|
780 |
* (the children may have been parented by a row in the flow |
|
781 |
* if they fit without breaking) and then execute the superclass |
|
782 |
* behavior. |
|
783 |
* |
|
784 |
* @param v the child view to forward the event to. |
|
785 |
* @param e the change information from the associated document |
|
786 |
* @param a the current allocation of the view |
|
787 |
* @param f the factory to use to rebuild if the view has children |
|
788 |
* @see #forwardUpdate |
|
789 |
* @since 1.3 |
|
790 |
*/ |
|
791 |
protected void forwardUpdateToView(View v, DocumentEvent e, |
|
792 |
Shape a, ViewFactory f) { |
|
793 |
View parent = v.getParent(); |
|
794 |
v.setParent(this); |
|
795 |
super.forwardUpdateToView(v, e, a, f); |
|
796 |
v.setParent(parent); |
|
797 |
} |
|
798 |
||
799 |
// The following methods don't do anything useful, they |
|
800 |
// simply keep the class from being abstract. |
|
801 |
||
802 |
/** |
|
803 |
* Renders using the given rendering surface and area on that |
|
804 |
* surface. This is implemented to do nothing, the logical |
|
805 |
* view is never visible. |
|
806 |
* |
|
807 |
* @param g the rendering surface to use |
|
808 |
* @param allocation the allocated region to render into |
|
809 |
* @see View#paint |
|
810 |
*/ |
|
811 |
public void paint(Graphics g, Shape allocation) { |
|
812 |
} |
|
813 |
||
814 |
/** |
|
815 |
* Tests whether a point lies before the rectangle range. |
|
816 |
* Implemented to return false, as hit detection is not |
|
817 |
* performed on the logical view. |
|
818 |
* |
|
819 |
* @param x the X coordinate >= 0 |
|
820 |
* @param y the Y coordinate >= 0 |
|
821 |
* @param alloc the rectangle |
|
822 |
* @return true if the point is before the specified range |
|
823 |
*/ |
|
824 |
protected boolean isBefore(int x, int y, Rectangle alloc) { |
|
825 |
return false; |
|
826 |
} |
|
827 |
||
828 |
/** |
|
829 |
* Tests whether a point lies after the rectangle range. |
|
830 |
* Implemented to return false, as hit detection is not |
|
831 |
* performed on the logical view. |
|
832 |
* |
|
833 |
* @param x the X coordinate >= 0 |
|
834 |
* @param y the Y coordinate >= 0 |
|
835 |
* @param alloc the rectangle |
|
836 |
* @return true if the point is after the specified range |
|
837 |
*/ |
|
838 |
protected boolean isAfter(int x, int y, Rectangle alloc) { |
|
839 |
return false; |
|
840 |
} |
|
841 |
||
842 |
/** |
|
843 |
* Fetches the child view at the given point. |
|
844 |
* Implemented to return null, as hit detection is not |
|
845 |
* performed on the logical view. |
|
846 |
* |
|
847 |
* @param x the X coordinate >= 0 |
|
848 |
* @param y the Y coordinate >= 0 |
|
849 |
* @param alloc the parent's allocation on entry, which should |
|
850 |
* be changed to the child's allocation on exit |
|
851 |
* @return the child view |
|
852 |
*/ |
|
853 |
protected View getViewAtPoint(int x, int y, Rectangle alloc) { |
|
854 |
return null; |
|
855 |
} |
|
856 |
||
857 |
/** |
|
858 |
* Returns the allocation for a given child. |
|
859 |
* Implemented to do nothing, as the logical view doesn't |
|
860 |
* perform layout on the children. |
|
861 |
* |
|
862 |
* @param index the index of the child, >= 0 && < getViewCount() |
|
863 |
* @param a the allocation to the interior of the box on entry, |
|
864 |
* and the allocation of the child view at the index on exit. |
|
865 |
*/ |
|
866 |
protected void childAllocation(int index, Rectangle a) { |
|
867 |
} |
|
868 |
} |
|
869 |
||
870 |
||
871 |
} |