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