%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.jetspeed.portlets.layout.ColumnLayout |
|
|
1 | /* |
|
2 | * Licensed to the Apache Software Foundation (ASF) under one or more |
|
3 | * contributor license agreements. See the NOTICE file distributed with |
|
4 | * this work for additional information regarding copyright ownership. |
|
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 |
|
6 | * (the "License"); you may not use this file except in compliance with |
|
7 | * the License. You may obtain a copy of the License at |
|
8 | * |
|
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
10 | * |
|
11 | * Unless required by applicable law or agreed to in writing, software |
|
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 | * See the License for the specific language governing permissions and |
|
15 | * limitations under the License. |
|
16 | */ |
|
17 | package org.apache.jetspeed.portlets.layout; |
|
18 | ||
19 | import java.io.Serializable; |
|
20 | import java.text.DecimalFormat; |
|
21 | import java.text.DecimalFormatSymbols; |
|
22 | import java.util.ArrayList; |
|
23 | import java.util.Collection; |
|
24 | import java.util.Collections; |
|
25 | import java.util.HashMap; |
|
26 | import java.util.Iterator; |
|
27 | import java.util.List; |
|
28 | import java.util.Locale; |
|
29 | import java.util.Map; |
|
30 | import java.util.SortedMap; |
|
31 | import java.util.TreeMap; |
|
32 | ||
33 | import org.apache.jetspeed.om.page.Fragment; |
|
34 | ||
35 | /** |
|
36 | * <h2>Basics</h2> |
|
37 | * <p> |
|
38 | * <code>ColumnLayout</code> is the model used to support any 1 to <i>n</i> |
|
39 | * column-based layout. <code>ColumnLayout</code> is constrained by a number |
|
40 | * columns that will not be exceeded, even if a fragment specifies a column |
|
41 | * outside of this constraint. Any fragment exceeded the specified column |
|
42 | * constraint will be deposited into the right-most column. |
|
43 | * </p> |
|
44 | * |
|
45 | * <h2>Characteristics:</h2> |
|
46 | * <ul> |
|
47 | * <li>Columns and rows always start at 0.</li> |
|
48 | * <li>Unless otherwise noted, assume all Collections returned are immutable.</li> |
|
49 | * <li>Unless otherwise noted, assume that no public method will ever return <code>null</code>.</li> |
|
50 | * </ul> |
|
51 | * |
|
52 | * |
|
53 | * <h2>Layout Events</h2> |
|
54 | * <p> |
|
55 | * When any move*() method is invoked and a portlet is actually moved (see indvidual |
|
56 | * methods for what causes these circumstances), an initial LayoutEvent is dispatched. |
|
57 | * This may cause a cascade of LayoutEvents to be fired in turn if the movement of the |
|
58 | * target fragment cause other fragments to be repositioned. In this case a LayoutEvent |
|
59 | * is dispatched for each portlet moved, which in turn may our may not cause another |
|
60 | * LayoutEvent to be fired. |
|
61 | * </p> |
|
62 | * @see org.apache.jetspeed.portlets.layout.LayoutEvent |
|
63 | * @see org.apache.jetspeed.portlets.layout.LayoutEventListener |
|
64 | * @see org.apache.jetspeed.portlets.layout.LayoutCoordinate |
|
65 | * @see org.apache.jetspeed.om.page.Fragment |
|
66 | * |
|
67 | * @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a> |
|
68 | * |
|
69 | */ |
|
70 | public class ColumnLayout implements Serializable |
|
71 | { |
|
72 | /** Percentage widths gutter width */ |
|
73 | private final static double PERCENTAGE_WIDTH_GUTTER = 0.01; |
|
74 | ||
75 | /** Percentage widths format */ |
|
76 | 0 | private final static DecimalFormat PERCENTAGE_WIDTH_FORMAT = new DecimalFormat("0.00'%'", new DecimalFormatSymbols( |
77 | Locale.ENGLISH)); |
|
78 | ||
79 | /** Constrains the columns for this layout */ |
|
80 | private final int numberOfColumns; |
|
81 | ||
82 | /** SortedMap of Columns (which are also sorted maps */ |
|
83 | private final SortedMap columns; |
|
84 | ||
85 | /** Width settings for eacah column */ |
|
86 | private final String[] columnWidths; |
|
87 | ||
88 | /** Efficent way to always be aware of the next available row in a column */ |
|
89 | private final int[] nextRowNumber; |
|
90 | ||
91 | /** maps Fragments (key) to it's current LayoutCoordinate (value) in this layout */ |
|
92 | private final Map coordinates; |
|
93 | ||
94 | /** All of the LayoutEventListeners registered to this layout */ |
|
95 | private final List eventListeners; |
|
96 | ||
97 | /** |
|
98 | * |
|
99 | * @param numberOfColumns |
|
100 | * the maximum number of columns this layout will have. |
|
101 | * @param layoutType |
|
102 | * this value corresponds to the property settings of the |
|
103 | * fragments within your psml. Layout type allows segration of |
|
104 | * property settings based on the type of layout in use. This |
|
105 | * effectively allows for the interchange of multiple layout |
|
106 | * formats without one format effecting the settings of another. |
|
107 | * @param columnWidths |
|
108 | * widths for each column that accumulate to 100% if percentages |
|
109 | * are used. |
|
110 | * @see org.apache.jetspeed.om.page.Fragment#getType() |
|
111 | */ |
|
112 | public ColumnLayout(int numberOfColumns, String layoutType, String[] columnWidths) |
|
113 | 0 | { |
114 | 0 | this.numberOfColumns = numberOfColumns; |
115 | 0 | this.columnWidths = columnWidths; |
116 | 0 | eventListeners = new ArrayList(); |
117 | ||
118 | 0 | columns = new TreeMap(); |
119 | 0 | coordinates = new HashMap(); |
120 | ||
121 | 0 | for (int i = 0; i < numberOfColumns; i++) |
122 | { |
|
123 | 0 | columns.put(new Integer(i), class="keyword">new TreeMap()); |
124 | } |
|
125 | ||
126 | 0 | nextRowNumber = new int[numberOfColumns]; |
127 | ||
128 | 0 | for (int i = 0; i < numberOfColumns; i++) |
129 | { |
|
130 | 0 | nextRowNumber[i] = 0; |
131 | } |
|
132 | 0 | } |
133 | ||
134 | /** |
|
135 | * Same as ColumnLayout(int numberOfColumns, String layoutType) but also |
|
136 | * supplies a Collection of fragmetns to initially populate the layout |
|
137 | * with. Adding these fragments <strong>WILL NOT</strong> cause |
|
138 | * a LayoutEvent to be dispatched. |
|
139 | * |
|
140 | * @see ColumnLayout(int numberOfColumns, String layoutType) |
|
141 | * @param numberOfColumns |
|
142 | * the maximum number of columns this layout will have. |
|
143 | * @param layoutType |
|
144 | * this value corresponds to the property settings of the |
|
145 | * fragments within your psml. Layout type allows segration of |
|
146 | * property settings based on the type of layout in use. This |
|
147 | * effectively allows for the interchange of multiple layout |
|
148 | * formats without one format effecting the settings of another. |
|
149 | * @param fragments Initial set of fragments to add to this layout. |
|
150 | * @param columnWidths |
|
151 | * widths for each column that accumulate to 100% if percentages |
|
152 | * are used. |
|
153 | * @throws LayoutEventException |
|
154 | */ |
|
155 | public ColumnLayout(int numberOfColumns, String layoutType, Collection fragments, String[] columnWidths) throws LayoutEventException |
|
156 | { |
|
157 | 0 | this(numberOfColumns, layoutType, columnWidths); |
158 | 0 | Iterator fragmentsItr = fragments.iterator(); |
159 | try |
|
160 | { |
|
161 | 0 | while (fragmentsItr.hasNext()) |
162 | { |
|
163 | 0 | Fragment fragment = (Fragment) fragmentsItr.next(); |
164 | 0 | doAdd(getColumn(fragment), getRow(getColumn(fragment), fragment), fragment); |
165 | 0 | } |
166 | } |
|
167 | 0 | catch (InvalidLayoutLocationException e) |
168 | { |
|
169 | // This should NEVER happen as getColumn() should |
|
170 | // automatically constrain any fragments who's column |
|
171 | // setting would cause this exception. |
|
172 | 0 | throw new LayoutError("A malformed fragment could not be adjusted.", e); |
173 | 0 | } |
174 | 0 | } |
175 | ||
176 | /** |
|
177 | * <p> |
|
178 | * Adds a fragment to the layout using fragment properties of |
|
179 | * <code> row </code> and <code> column </code> as hints on where to put |
|
180 | * this fragment. The following rules apply to malformed fragment |
|
181 | * definitions: |
|
182 | * </p> |
|
183 | * <ul> |
|
184 | * <li>Fragments without a row defined are placed at the bottom of their |
|
185 | * respective column </li> |
|
186 | * <li>Fragments without a column are placed in the right-most column. |
|
187 | * </li> |
|
188 | * <li> Fragments with overlapping row numbers. The last fragment has |
|
189 | * priority pushing the fragment in that row down one row. </li> |
|
190 | * </ul> |
|
191 | * |
|
192 | * @param fragment |
|
193 | * Fragment to add to this layout. |
|
194 | * @throws LayoutEventException |
|
195 | * @see org.apache.jetspeed.om.page.Fragment |
|
196 | * |
|
197 | */ |
|
198 | public void addFragment(Fragment fragment) throws LayoutEventException |
|
199 | { |
|
200 | try |
|
201 | { |
|
202 | 0 | doAdd(getColumn(fragment), getRow(getColumn(fragment), fragment), fragment); |
203 | 0 | LayoutCoordinate coordinate = getCoordinate(fragment); |
204 | 0 | processEvent(new LayoutEvent(LayoutEvent.ADDED, fragment, coordinate, coordinate)); |
205 | } |
|
206 | 0 | catch (InvalidLayoutLocationException e) |
207 | { |
|
208 | // This should NEVER happen as getColumn() should |
|
209 | // automatically constrain any fragments who's column |
|
210 | // setting would cause this exception. |
|
211 | 0 | throw new LayoutError("A malformed fragment could not be adjusted.", e); |
212 | } |
|
213 | 0 | catch (FragmentNotInLayoutException e) |
214 | { |
|
215 | 0 | throw new LayoutError("Failed to add coordinate to this ColumnLayout.", e); |
216 | 0 | } |
217 | 0 | } |
218 | ||
219 | /** |
|
220 | * Adds a LayoutEventListener to this layout that will be fired any time |
|
221 | * a LayoutEvent is disaptched. |
|
222 | * |
|
223 | * @param eventListener |
|
224 | * @see LayoutEventListener |
|
225 | * @see LayoutEventListener |
|
226 | */ |
|
227 | public void addLayoutEventListener(LayoutEventListener eventListener) |
|
228 | { |
|
229 | 0 | eventListeners.add(eventListener); |
230 | 0 | } |
231 | ||
232 | /** |
|
233 | * |
|
234 | * @param columnNumber |
|
235 | * Number of column to retreive |
|
236 | * @return requested column (as a immutable Collection). Never returns |
|
237 | * <code>null.</code> |
|
238 | * @throws InvalidLayoutLocationException |
|
239 | * if the column is outisde of the constraints of this layout |
|
240 | */ |
|
241 | public Collection getColumn(int columnNumber) throws InvalidLayoutLocationException |
|
242 | { |
|
243 | 0 | return Collections.unmodifiableCollection(getColumnMap(columnNumber).values()); |
244 | } |
|
245 | ||
246 | /** |
|
247 | * returns the width to be used with the specified column. If |
|
248 | * there is no specific column setting sfor the specified column |
|
249 | * 0 is returned. |
|
250 | * |
|
251 | * @param columnNumber whose width has been requested. |
|
252 | * @return the width to be used with the specified column. Or 0 if no value |
|
253 | * has been specified. |
|
254 | */ |
|
255 | public String getColumnWidth(int columnNumber) |
|
256 | { |
|
257 | 0 | if ((columnWidths != null) && (columnNumber < numberOfColumns)) |
258 | { |
|
259 | 0 | String columnWidth = columnWidths[columnNumber]; |
260 | ||
261 | // subtract "gutter" width from last percentage |
|
262 | // column to prevent wrapping on rounding errors |
|
263 | // of column widths when rendered in the browser |
|
264 | 0 | if ((numberOfColumns > 1) && (columnNumber == (numberOfColumns - 1))) |
265 | { |
|
266 | 0 | int percentIndex = columnWidth.lastIndexOf('%'); |
267 | 0 | if (percentIndex > 0) |
268 | { |
|
269 | try |
|
270 | { |
|
271 | 0 | double width = Double.parseDouble(columnWidth.substring(0,percentIndex).trim()); |
272 | 0 | synchronized (PERCENTAGE_WIDTH_FORMAT) |
273 | { |
|
274 | 0 | columnWidth = PERCENTAGE_WIDTH_FORMAT.format(width - PERCENTAGE_WIDTH_GUTTER); |
275 | 0 | } |
276 | } |
|
277 | 0 | catch (NumberFormatException nfe) |
278 | { |
|
279 | 0 | } |
280 | } |
|
281 | } |
|
282 | 0 | return columnWidth; |
283 | } |
|
284 | 0 | return "0"; |
285 | } |
|
286 | ||
287 | /** |
|
288 | * returns the float to be used with the specified column. |
|
289 | * |
|
290 | * @param columnNumber whose width has been requested. |
|
291 | * @return "right" for the last column, "left" if more than one |
|
292 | * column, or "none" otherwise. |
|
293 | */ |
|
294 | public String getColumnFloat(int columnNumber) |
|
295 | { |
|
296 | 0 | if ((numberOfColumns > 1) && (columnNumber < numberOfColumns)) |
297 | { |
|
298 | 0 | if (columnNumber == (numberOfColumns - 1)) |
299 | { |
|
300 | 0 | return "right"; |
301 | } |
|
302 | else |
|
303 | { |
|
304 | 0 | return "left"; |
305 | } |
|
306 | } |
|
307 | 0 | return "none"; |
308 | } |
|
309 | ||
310 | /** |
|
311 | * @return <code>java.util.Collection</code> all of columns (also |
|
312 | * Collection objects) in order within this layout. All Collections |
|
313 | * are immutable. |
|
314 | */ |
|
315 | public Collection getColumns() |
|
316 | { |
|
317 | 0 | ArrayList columnList = new ArrayList(getNumberOfColumns()); |
318 | 0 | Iterator itr = columns.values().iterator(); |
319 | 0 | while (itr.hasNext()) |
320 | { |
|
321 | 0 | columnList.add(Collections.unmodifiableCollection(((Map) itr.next()).values())); |
322 | } |
|
323 | ||
324 | 0 | return Collections.unmodifiableCollection(columnList); |
325 | } |
|
326 | ||
327 | /** |
|
328 | * |
|
329 | * Returns the index of the last row in the specified column. |
|
330 | * |
|
331 | * @param columnNumber column form whom we ant to identify the |
|
332 | * last row. |
|
333 | * @return the index of the last row in the specified column. |
|
334 | */ |
|
335 | public int getLastRowNumber(class="keyword">int columnNumber) |
|
336 | { |
|
337 | 0 | return nextRowNumber[columnNumber] - 1; |
338 | } |
|
339 | ||
340 | /** |
|
341 | * Returns an immutable Collection of all the Fragments contained within |
|
342 | * this ColumnLayout in no sepcific order. |
|
343 | * @return Immutable Collection of Fragments. |
|
344 | */ |
|
345 | public Collection getFragments() |
|
346 | { |
|
347 | 0 | return Collections.unmodifiableCollection(coordinates.keySet()); |
348 | } |
|
349 | ||
350 | /** |
|
351 | * Retrieves the fragment at the specified loaction. |
|
352 | * |
|
353 | * @param columnNumber Column coordinate (first column starts at 0) |
|
354 | * @param rowNumber Row coordinate (first row starts at 0) |
|
355 | * @return Fragment at the specified coordinate. Never returns <code>null</code>. |
|
356 | * @throws EmptyLayoutLocationException if there is no fragment currently located at the specified coordinate. |
|
357 | * @throws InvalidLayoutLocationException if the coordinate lies outside the confines of this layout, i.e., the |
|
358 | * <code>columnNumber</code> exceeds the max columns setting for this layout. |
|
359 | */ |
|
360 | public Fragment getFragmentAt(int columnNumber, class="keyword">int rowNumber) throws EmptyLayoutLocationException, |
|
361 | InvalidLayoutLocationException |
|
362 | { |
|
363 | 0 | SortedMap column = getColumnMap(columnNumber); |
364 | 0 | Integer rowInteger = new Integer(rowNumber); |
365 | 0 | if (column.containsKey(rowInteger)) |
366 | { |
|
367 | 0 | return (Fragment) column.get(rowInteger); |
368 | } |
|
369 | else |
|
370 | { |
|
371 | 0 | throw new EmptyLayoutLocationException(columnNumber, rowNumber); |
372 | } |
|
373 | } |
|
374 | ||
375 | /** |
|
376 | * |
|
377 | * Retrieves the fragment at the specified loaction. |
|
378 | * |
|
379 | * @param coodinate LayoutCoordinate object that will be used to located a fragment in this |
|
380 | * layout. |
|
381 | * @see LayoutCoordinate |
|
382 | * @return Fragment at the specified coordinate. Never returns <code>null</code>. |
|
383 | * @throws EmptyLayoutLocationException if there is no fragment currently located at the specified coordinate. |
|
384 | * @throws InvalidLayoutLocationException if the coordinate lies outside the confines of this layout, i.e., the |
|
385 | * <code>columnNumber</code> exceeds the max columns setting for this layout. |
|
386 | * @see LayoutCoordinate |
|
387 | */ |
|
388 | public Fragment getFragmentAt(LayoutCoordinate coodinate) throws EmptyLayoutLocationException, |
|
389 | InvalidLayoutLocationException |
|
390 | { |
|
391 | 0 | return getFragmentAt(coodinate.getX(), coodinate.getY()); |
392 | } |
|
393 | ||
394 | /** |
|
395 | * |
|
396 | * @return The total number of columns in this layout. |
|
397 | */ |
|
398 | public int getNumberOfColumns() |
|
399 | { |
|
400 | 0 | return numberOfColumns; |
401 | } |
|
402 | ||
403 | /** |
|
404 | * |
|
405 | * @return The last column in this layout. The Collection is immutable. |
|
406 | */ |
|
407 | public Collection getLastColumn() |
|
408 | { |
|
409 | try |
|
410 | { |
|
411 | 0 | return Collections.unmodifiableCollection(getColumnMap(numberOfColumns - 1).values()); |
412 | } |
|
413 | 0 | catch (InvalidLayoutLocationException e) |
414 | { |
|
415 | // This should NEVER happen as getLastColumn() is |
|
416 | // always correctly constrained and should always exists. |
|
417 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify its last column.", e); |
418 | } |
|
419 | } |
|
420 | ||
421 | /** |
|
422 | * |
|
423 | * @return The last column in this layout. The Collection is immutable. |
|
424 | */ |
|
425 | public Collection getFirstColumn() |
|
426 | { |
|
427 | try |
|
428 | { |
|
429 | 0 | return Collections.unmodifiableCollection(getColumnMap(0).values()); |
430 | } |
|
431 | 0 | catch (InvalidLayoutLocationException e) |
432 | { |
|
433 | // This should NEVER happen as getLastColumn() is |
|
434 | // always correctly constrained and should always exists. |
|
435 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify its first column.", e); |
436 | } |
|
437 | } |
|
438 | ||
439 | /** |
|
440 | * |
|
441 | * Moves a fragment one column to the right. A LayoutEvent is triggered by |
|
442 | * this action. |
|
443 | * |
|
444 | * <p> |
|
445 | * If the fragment currently |
|
446 | * resides in right-most column, no action is taking and no event LayoutEvent |
|
447 | * is fired. |
|
448 | * </p> |
|
449 | * |
|
450 | * @param fragment fragment to move. |
|
451 | * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout. |
|
452 | * @throws LayoutEventException If a triggered LayoutEvent fails. |
|
453 | */ |
|
454 | public void moveRight(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException |
|
455 | { |
|
456 | 0 | LayoutCoordinate coordinate = getCoordinate(fragment); |
457 | 0 | LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX() + 1, coordinate.getY()); |
458 | ||
459 | 0 | if (newCoordinate.getX() < numberOfColumns) |
460 | { |
|
461 | ||
462 | try |
|
463 | { |
|
464 | 0 | doMove(fragment, coordinate, newCoordinate); |
465 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_RIGHT, fragment, coordinate, class="keyword">newCoordinate)); |
466 | // now move the fragment below up one level. |
|
467 | try |
|
468 | { |
|
469 | 0 | Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1)); |
470 | 0 | moveUp(fragmentBelow); |
471 | } |
|
472 | 0 | catch (EmptyLayoutLocationException e) |
473 | { |
|
474 | // indicates no fragment below |
|
475 | 0 | } |
476 | } |
|
477 | 0 | catch (InvalidLayoutLocationException e) |
478 | { |
|
479 | // This should NEVER happen as the location has already been verfied to be valid |
|
480 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e); |
481 | 0 | } |
482 | } |
|
483 | 0 | } |
484 | ||
485 | /** |
|
486 | * Moves a fragment one column to the left. A LayoutEvent is triggered by |
|
487 | * this action. |
|
488 | * |
|
489 | * <p> |
|
490 | * If the fragment currently |
|
491 | * resides in left-most column, no action is taking and no event LayoutEvent |
|
492 | * is fired. |
|
493 | * </p> |
|
494 | * |
|
495 | * @param fragment |
|
496 | * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout. |
|
497 | * @throws LayoutEventException If a triggered LayoutEvent fails. |
|
498 | */ |
|
499 | public void moveLeft(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException |
|
500 | { |
|
501 | 0 | LayoutCoordinate coordinate = getCoordinate(fragment); |
502 | 0 | LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX() - 1, coordinate.getY()); |
503 | ||
504 | 0 | if (newCoordinate.getX() >= 0) |
505 | { |
|
506 | try |
|
507 | { |
|
508 | 0 | doMove(fragment, coordinate, newCoordinate); |
509 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_LEFT, fragment, coordinate, class="keyword">newCoordinate)); |
510 | // now move the fragment below up one level. |
|
511 | try |
|
512 | { |
|
513 | 0 | Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1)); |
514 | 0 | moveUp(fragmentBelow); |
515 | } |
|
516 | 0 | catch (EmptyLayoutLocationException e) |
517 | { |
|
518 | // indicates no fragment below |
|
519 | 0 | } |
520 | } |
|
521 | 0 | catch (InvalidLayoutLocationException e) |
522 | { |
|
523 | // This should NEVER happen as the location has already been verfied to be valid |
|
524 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e); |
525 | 0 | } |
526 | ||
527 | } |
|
528 | ||
529 | 0 | } |
530 | ||
531 | /** |
|
532 | * Moves a fragment one row to the up. A LayoutEvent is triggered by |
|
533 | * this action. |
|
534 | * |
|
535 | * <p> |
|
536 | * If the fragment currently |
|
537 | * resides in top-most row, no action is taking and no event LayoutEvent |
|
538 | * is fired. |
|
539 | * </p> |
|
540 | * @param fragment |
|
541 | * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout. |
|
542 | * @throws LayoutEventException If a triggered LayoutEvent fails. |
|
543 | */ |
|
544 | public void moveUp(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException |
|
545 | { |
|
546 | 0 | LayoutCoordinate coordinate = getCoordinate(fragment); |
547 | 0 | LayoutCoordinate aboveLayoutCoordinate = new LayoutCoordinate(coordinate.getX(), coordinate.getY() - 1); |
548 | 0 | LayoutCoordinate newCoordinate = aboveLayoutCoordinate; |
549 | ||
550 | // never go "above" 0. |
|
551 | 0 | if (newCoordinate.getY() >= 0) |
552 | { |
|
553 | try |
|
554 | { |
|
555 | try |
|
556 | { |
|
557 | // now move the fragment above down one level. |
|
558 | 0 | /*Fragment fragmentAbove =*/ getFragmentAt(aboveLayoutCoordinate); |
559 | 0 | doMove(fragment, coordinate, newCoordinate); |
560 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragment, coordinate, class="keyword">newCoordinate)); |
561 | } |
|
562 | 0 | catch (EmptyLayoutLocationException e) |
563 | { |
|
564 | // Nothing above??? Then scoot all elements below up one level. |
|
565 | 0 | doMove(fragment, coordinate, newCoordinate); |
566 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragment, coordinate, class="keyword">newCoordinate)); |
567 | ||
568 | // If this the last row, make sure to update the next row pointer accordingly. |
|
569 | 0 | if(coordinate.getY() == (nextRowNumber[coordinate.getX()] - 1)) |
570 | { |
|
571 | 0 | nextRowNumber[coordinate.getX()] = coordinate.getX(); |
572 | } |
|
573 | ||
574 | try |
|
575 | { |
|
576 | 0 | Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(), |
577 | coordinate.getY() + 1)); |
|
578 | 0 | moveUp(fragmentBelow); |
579 | } |
|
580 | 0 | catch (EmptyLayoutLocationException e1) |
581 | { |
|
582 | ||
583 | 0 | } |
584 | 0 | } |
585 | } |
|
586 | 0 | catch (InvalidLayoutLocationException e) |
587 | { |
|
588 | // This should NEVER happen as the location has already been verfied to be valid |
|
589 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e); |
590 | 0 | } |
591 | } |
|
592 | 0 | } |
593 | ||
594 | /** |
|
595 | * |
|
596 | * @param fragment |
|
597 | * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout. |
|
598 | * @throws LayoutEventException If a triggered LayoutEvent fails. |
|
599 | */ |
|
600 | public void moveDown(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException |
|
601 | { |
|
602 | 0 | LayoutCoordinate coordinate = getCoordinate(fragment); |
603 | 0 | LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1); |
604 | ||
605 | // never move past the current bottom row |
|
606 | 0 | if (newCoordinate.getY() < nextRowNumber[coordinate.getX()]) |
607 | { |
|
608 | try |
|
609 | { |
|
610 | try |
|
611 | { |
|
612 | // the best approach to move a fragment down is to actually move |
|
613 | // its neighbor underneath up |
|
614 | 0 | LayoutCoordinate aboveCoord = new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1); |
615 | 0 | Fragment fragmentBelow = getFragmentAt(aboveCoord); |
616 | 0 | doMove(fragmentBelow, aboveCoord, coordinate); |
617 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragmentBelow, aboveCoord, coordinate)); |
618 | // Since this logic path is a somewhat special case, the processing of the MOVED_DOWN |
|
619 | // event happens within the doAdd() method. |
|
620 | } |
|
621 | 0 | catch (EmptyLayoutLocationException e) |
622 | { |
|
623 | 0 | doMove(fragment, coordinate, newCoordinate); |
624 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_DOWN, fragment, coordinate, class="keyword">newCoordinate)); |
625 | 0 | } |
626 | } |
|
627 | 0 | catch (InvalidLayoutLocationException e) |
628 | { |
|
629 | // This should NEVER happen as the location has already been verfied to be valid |
|
630 | 0 | throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e); |
631 | 0 | } |
632 | ||
633 | } |
|
634 | 0 | } |
635 | ||
636 | /** |
|
637 | * Performs the actual movement of a fragment. |
|
638 | * |
|
639 | * |
|
640 | * @param fragment |
|
641 | * @param oldCoordinate |
|
642 | * @param newCoordinate |
|
643 | * @throws InvalidLayoutLocationException |
|
644 | * @throws LayoutEventException |
|
645 | */ |
|
646 | protected void doMove(Fragment fragment, LayoutCoordinate oldCoordinate, LayoutCoordinate newCoordinate) |
|
647 | throws InvalidLayoutLocationException, LayoutEventException |
|
648 | { |
|
649 | 0 | SortedMap oldColumn = getColumnMap(oldCoordinate.getX()); |
650 | 0 | oldColumn.remove(new Integer(oldCoordinate.getY())); |
651 | 0 | coordinates.remove(fragment); |
652 | ||
653 | 0 | doAdd(newCoordinate.getX(), newCoordinate.getY(), fragment); |
654 | 0 | } |
655 | ||
656 | /** |
|
657 | * |
|
658 | * |
|
659 | * @param fragment fragment whose LayoutCoordinate we ant. |
|
660 | * @return LayoutCoordinate representing the current location of this |
|
661 | * Fragment within this layout. |
|
662 | * @throws FragmentNotInLayoutException if the Fragment is not present in this layout. |
|
663 | * @see LayoutCoordinate |
|
664 | */ |
|
665 | public LayoutCoordinate getCoordinate(Fragment fragment) throws FragmentNotInLayoutException |
|
666 | { |
|
667 | 0 | if (coordinates.containsKey(fragment)) |
668 | { |
|
669 | 0 | return (LayoutCoordinate) coordinates.get(fragment); |
670 | } |
|
671 | else |
|
672 | { |
|
673 | 0 | throw new FragmentNotInLayoutException(fragment); |
674 | } |
|
675 | } |
|
676 | ||
677 | /** |
|
678 | * Adds a fragment at the indicated <code>columnNumber</code> |
|
679 | * and <code>rowNumber</code>. |
|
680 | * |
|
681 | * @param columnNumber |
|
682 | * @param rowNumber |
|
683 | * @param fragment |
|
684 | * @throws InvalidLayoutLocationException if the coordinates are outside the bounds of this layout. |
|
685 | * @throws LayoutEventException id a LayoutEvent fails |
|
686 | */ |
|
687 | protected void doAdd(int columnNumber, class="keyword">int rowNumber, Fragment fragment) throws InvalidLayoutLocationException, LayoutEventException |
|
688 | { |
|
689 | 0 | SortedMap column = getColumnMap(columnNumber); |
690 | ||
691 | 0 | Integer rowInteger = new Integer(rowNumber); |
692 | 0 | LayoutCoordinate targetCoordinate = new LayoutCoordinate(columnNumber, rowNumber); |
693 | 0 | if (column.containsKey(rowInteger)) |
694 | { |
|
695 | // If the row has something in it, push everythin down 1 |
|
696 | 0 | Fragment existingFragment = (Fragment) column.get(rowInteger); |
697 | 0 | column.put(rowInteger, fragment); |
698 | 0 | coordinates.put(fragment, targetCoordinate); |
699 | 0 | doAdd(columnNumber, ++rowNumber, existingFragment); |
700 | ||
701 | 0 | LayoutCoordinate oneDownCoordinate = new LayoutCoordinate(targetCoordinate.getX(), targetCoordinate.getY() + 1); |
702 | 0 | processEvent(new LayoutEvent(LayoutEvent.MOVED_DOWN, existingFragment, targetCoordinate, oneDownCoordinate)); |
703 | 0 | } |
704 | else |
|
705 | { |
|
706 | 0 | column.put(rowInteger, fragment); |
707 | 0 | coordinates.put(fragment, targetCoordinate); |
708 | 0 | rowNumber++; |
709 | 0 | if(rowNumber > nextRowNumber[columnNumber]) |
710 | { |
|
711 | 0 | nextRowNumber[columnNumber] = rowNumber; |
712 | } |
|
713 | } |
|
714 | ||
715 | 0 | } |
716 | ||
717 | /** |
|
718 | * Retrieves this specified <code>columnNumber</code> as a |
|
719 | * SortedMap. |
|
720 | * |
|
721 | * @param columnNumber |
|
722 | * @return |
|
723 | * @throws InvalidLayoutLocationException if the <code>columnNumber</code> resides |
|
724 | * outside the bounds of this layout. |
|
725 | */ |
|
726 | protected final SortedMap getColumnMap(int columnNumber) throws InvalidLayoutLocationException |
|
727 | { |
|
728 | 0 | Integer columnNumberIneteger = new Integer(columnNumber); |
729 | ||
730 | 0 | if (columns.containsKey(columnNumberIneteger)) |
731 | { |
|
732 | 0 | return ((SortedMap) columns.get(columnNumberIneteger)); |
733 | } |
|
734 | else |
|
735 | { |
|
736 | 0 | throw new InvalidLayoutLocationException(columnNumber); |
737 | } |
|
738 | ||
739 | } |
|
740 | ||
741 | /** |
|
742 | * Gets the row number of this fragment to looking the <code>layoutType</code> |
|
743 | * property <i>row</i>. If this property is undefined, the bottom-most row |
|
744 | * number of <code>currentColumn</code> is returned. |
|
745 | * |
|
746 | * @param currentColumn |
|
747 | * @param fragment |
|
748 | * @return valid row for this fragment within this layout. |
|
749 | */ |
|
750 | protected final int getRow(class="keyword">int currentColumn, Fragment fragment) |
|
751 | { |
|
752 | 0 | String propertyValue = fragment.getProperty(Fragment.ROW_PROPERTY_NAME); |
753 | 0 | if (propertyValue != null) |
754 | { |
|
755 | 0 | return Integer.parseInt(propertyValue); |
756 | } |
|
757 | else |
|
758 | { |
|
759 | 0 | return nextRowNumber[currentColumn]; |
760 | } |
|
761 | ||
762 | } |
|
763 | ||
764 | /** |
|
765 | * Gets the row number of this fragment to looking the <code>layoutType</code> |
|
766 | * property <i>column</i>. |
|
767 | * |
|
768 | * If the <i>column</i> is undefined or exceeds the constriants of this |
|
769 | * layout, the value returned is <code>numberOfColumns - 1</code>. If the |
|
770 | * value is less than 0, 0 is returned. |
|
771 | * |
|
772 | * |
|
773 | * @param fragment |
|
774 | * @return |
|
775 | */ |
|
776 | protected final int getColumn(Fragment fragment) |
|
777 | { |
|
778 | 0 | String propertyValue = fragment.getProperty(Fragment.COLUMN_PROPERTY_NAME); |
779 | 0 | if (propertyValue != null) |
780 | { |
|
781 | 0 | int columnNumber = Integer.parseInt(propertyValue); |
782 | ||
783 | // Exceeded columns get put into the last column |
|
784 | 0 | if (columnNumber >= numberOfColumns) |
785 | { |
|
786 | 0 | columnNumber = (numberOfColumns - 1); |
787 | } |
|
788 | // Columns less than 1 go in the first column |
|
789 | 0 | else if (columnNumber < 0) |
790 | { |
|
791 | 0 | columnNumber = 0; |
792 | } |
|
793 | ||
794 | 0 | return columnNumber; |
795 | } |
|
796 | else |
|
797 | { |
|
798 | 0 | return (numberOfColumns - 1); |
799 | } |
|
800 | } |
|
801 | ||
802 | /** |
|
803 | * Dispatches a LayoutEvent to all LayoutEventListeners registered to this layout. |
|
804 | * |
|
805 | * @param event |
|
806 | * @throws LayoutEventException if an error occurs while processing a the LayoutEvent. |
|
807 | */ |
|
808 | protected final void processEvent(LayoutEvent event) throws LayoutEventException |
|
809 | { |
|
810 | 0 | Iterator itr = eventListeners.iterator(); |
811 | 0 | while(itr.hasNext()) |
812 | { |
|
813 | 0 | LayoutEventListener eventListener = (LayoutEventListener) itr.next(); |
814 | 0 | eventListener.handleEvent(event); |
815 | 0 | } |
816 | ||
817 | 0 | } |
818 | ||
819 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |