001: /*
002: * This file is part of the Tucana Echo2 Library.
003: * Copyright (C) 2006.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package tucana.echo2.app;
031:
032: import java.util.EventListener;
033: import java.util.HashMap;
034: import java.util.Map;
035:
036: import nextapp.echo2.app.Border;
037: import nextapp.echo2.app.Color;
038: import nextapp.echo2.app.Component;
039: import nextapp.echo2.app.Extent;
040: import nextapp.echo2.app.event.ActionEvent;
041: import nextapp.echo2.app.event.ActionListener;
042: import tucana.echo2.app.widgetdash.WidgetContainer;
043: import tucana.echo2.app.widgetdash.WidgetContainerFactory;
044: import tucana.echo2.app.widgetdash.WidgetDashState;
045: import tucana.echo2.app.widgetdash.WidgetIdentifier;
046: import tucana.echo2.app.widgetdash.WidgetPosition;
047:
048: /**
049: * A Widget Panel is a container organized in columns in which
050: * {@link WidgetContainer} components can be placed. The Widgets
051: * (WidgetContainers) can be repositioned in the panel such that they all line
052: * up in columns. The state of the panel (the user's layout preference) can be
053: * extracted and stored server-side, to be used later to recreate the panel for
054: * a user.
055: *
056: * @author Jeremy Volkman
057: *
058: */
059: public class WidgetDash extends Component {
060:
061: public static final String ACTION_POSITIONS_UPDATED = "positionsUpdated";
062:
063: public static final String ACTION_LISTENERS_CHANGED_PROPERTY = "actionListeners";
064:
065: /**
066: *
067: */
068: private static final long serialVersionUID = 1L;
069:
070: /**
071: * Horizontal spacing between columns.
072: */
073: public static final String PROPERTY_COLUMN_SPACING = "columnSpacing";
074:
075: /**
076: * Number of columns in panel.
077: */
078: public static final String PROPERTY_COLUMN_COUNT = "columnCount";
079:
080: /**
081: * Vertical spacing betwen widgets.
082: */
083: public static final String PROPERTY_WIDGET_SPACING = "widgetSpacing";
084:
085: /**
086: * Type of widget shadow to use.
087: */
088: public static final String PROPERTY_SHADOW_TYPE = "shadowType";
089:
090: /**
091: * Opacity of widget shadow (clone mode).
092: */
093: public static final String PROPERTY_SHADOW_OPACITY = "shadowOpacity";
094:
095: /**
096: * Opacity of dragged widget.
097: */
098: public static final String PROPERTY_DRAGGED_WIDGET_OPACITY = "widgetOpacity";
099:
100: /**
101: * Widget shadow border (border mode).
102: */
103: public static final String PROPERTY_SHADOW_BORDER = "shadowBorder";
104:
105: /**
106: * Container to be used for widget dragging.
107: */
108: public static final String PROPERTY_DRAG_CONTAINER = "dragContainer";
109:
110: /**
111: * Whether or not to drag widget in the HTML body element.
112: */
113: public static final String PROPERTY_DRAG_IN_BODY = "dragInBody";
114:
115: /**
116: * The type of dragged widget return to use (snap or drift (animated)).
117: */
118: public static final String PROPERTY_RETURN_METHOD = "returnMethod";
119:
120: /**
121: * The distance in PX of each drift animation step.
122: */
123: public static final String PROPERTY_DRIFT_STEP = "driftStep";
124:
125: /**
126: * The amount of time (ms) between drift animation steps.
127: */
128: public static final String PROPERTY_DRIFT_INTERVAL = "driftInterval";
129:
130: /**
131: * A cloned node widget shadow
132: */
133: public static final int SHADOW_TYPE_CLONE = 0;
134:
135: /**
136: * A border widget shadow
137: */
138: public static final int SHADOW_TYPE_BORDER = 1;
139:
140: /**
141: * Snap into position after dropping widget
142: */
143: public static final int RETURN_METHOD_SNAP = 0;
144:
145: /**
146: * Drift into position after dropping widget
147: */
148: public static final int RETURN_METHOD_DRIFT = 1;
149:
150: public static final float DEFAULT_SHADOW_OPACITY = 6.0f;
151:
152: public static final float DEFAULT_DRAGGED_WIDGET_OPACITY = 9.0f;
153:
154: public static final int DEFAULT_SHADOW_TYPE = SHADOW_TYPE_CLONE;
155:
156: public static final Border DEFAULT_SHADOW_BORDER = new Border(2,
157: Color.BLACK, Border.STYLE_DASHED);
158:
159: public static final int DEFAULT_COLUMN_COUNT = 3;
160:
161: public static final Extent DEFAULT_COLUMN_SPACING = new Extent(10,
162: Extent.PX);
163:
164: public static final Extent DEFAULT_WIDGET_SPACING = new Extent(20,
165: Extent.PX);
166:
167: public static final int DEFAULT_DRIFT_STEP = 20;
168:
169: public static final long DEFAULT_DRIFT_INTERVAL = 20;
170:
171: /**
172: * Default contstructor. Sets default values for all of the properties.
173: */
174: public WidgetDash() {
175: setColumnCount(DEFAULT_COLUMN_COUNT);
176: setColumnSpacing(DEFAULT_COLUMN_SPACING);
177: setWidgetSpacing(DEFAULT_WIDGET_SPACING);
178: setShadowBorder(DEFAULT_SHADOW_BORDER);
179: setShadowType(SHADOW_TYPE_CLONE);
180: setShadowOpacity(DEFAULT_SHADOW_OPACITY);
181: setDraggedWidgetOpacity(DEFAULT_DRAGGED_WIDGET_OPACITY);
182: setDragContainer(this );
183: setDragInBody(false);
184: setReturnMethod(RETURN_METHOD_SNAP);
185: setDriftStep(DEFAULT_DRIFT_STEP);
186: setDriftInterval(DEFAULT_DRIFT_INTERVAL);
187: }
188:
189: /**
190: * Construct a WidgetPane given the previously stored
191: * {@link WidgetDashState} and a {@link WidgetContainerFactory}. Widgets
192: * listed in the WidgetPaneState will be added to this WidgetPane.
193: *
194: * @param state
195: * The WidgetPaneState to construct from
196: * @param factory
197: * The WidgetFactory to use when building Widgets
198: */
199: public WidgetDash(WidgetDashState state,
200: WidgetContainerFactory factory) {
201: this ();
202: restore(state, factory);
203: }
204:
205: /**
206: * Populate the WidgetPane given the previously stored
207: * {@link WidgetDashState} and a {@link WidgetContainerFactory}. Widgets
208: * listed in the WidgetPaneState will be added to this WidgetPane. Any widgets
209: * in the dash before this call will be removed.
210: *
211: * @param state
212: * The WidgetPaneState to construct from
213: * @param factory
214: * The WidgetFactory to use when building Widgets
215: */
216: public void restore(WidgetDashState state,
217: WidgetContainerFactory factory) {
218: removeAll();
219: setColumnCount(state.getColumnCount());
220: WidgetIdentifier[] identifiers = state.getWidgetIdentifiers();
221: for (int i = 0; i < identifiers.length; i++) {
222: WidgetContainer container = factory
223: .createWidgetContainer(identifiers[i]);
224: if (container != null) {
225: WidgetPosition position = state
226: .getWidgetPosition(identifiers[i]);
227: if (position != null) {
228: container.setWidgetPosition(position);
229: }
230: add(container);
231: }
232: }
233: }
234:
235: /**
236: * Set the vertical spacing between widgets in a column
237: *
238: * @param spacing
239: * The vertical spacing
240: */
241: public void setWidgetSpacing(Extent spacing) {
242: setProperty(PROPERTY_WIDGET_SPACING, spacing);
243: }
244:
245: /**
246: * Get the vertical spacing between widgets in a column
247: *
248: * @return The vertical spacing, or <code>null</code> if it is not set.
249: */
250: public Extent getWidgetSpacing() {
251: return (Extent) getProperty(PROPERTY_WIDGET_SPACING);
252: }
253:
254: /**
255: * Only allow WidgetContainer objects to be added to the WidgetPane
256: */
257: @Override
258: public boolean isValidChild(Component child) {
259: return child instanceof WidgetContainer;
260: }
261:
262: /**
263: * Return the number of columns in this WidgetPane
264: *
265: * @return column count, or <code>-1</code> if it is not set.
266: */
267: public int getColumnCount() {
268: Integer columnCount = (Integer) getProperty(PROPERTY_COLUMN_COUNT);
269: if (columnCount != null) {
270: return columnCount.intValue();
271: }
272:
273: return -1;
274: }
275:
276: /**
277: * Set the number of columns
278: *
279: * @param newCount
280: * column count
281: */
282: public void setColumnCount(int newCount) {
283: if (newCount <= 0) {
284: throw new IllegalArgumentException(
285: "Invalid number of columns: " + newCount);
286: }
287: setProperty(PROPERTY_COLUMN_COUNT, new Integer(newCount));
288: }
289:
290: /**
291: * Set the horizontal spacing between columns
292: *
293: * @param spacing
294: * The column spacing
295: */
296: public void setColumnSpacing(Extent spacing) {
297: setProperty(PROPERTY_COLUMN_SPACING, spacing);
298: }
299:
300: /**
301: * Get the horizontal spacing between columns
302: *
303: * @return The column spacing, or <code>null</code> if it is not set.
304: */
305: public Extent getColumnSpacing() {
306: return (Extent) getProperty(PROPERTY_COLUMN_SPACING);
307: }
308:
309: /**
310: * Return the current shadow type
311: *
312: * @return The current shadow type, or <code>-1</code> if one is not set.
313: *
314: * @see #SHADOW_TYPE_CLONE
315: * @see #SHADOW_TYPE_BORDER
316: */
317: public int getShadowType() {
318: Integer typeInteger = (Integer) getProperty(PROPERTY_SHADOW_TYPE);
319: if (typeInteger == null) {
320: return -1;
321: }
322: return typeInteger.intValue();
323: }
324:
325: /**
326: * Set the shadow type
327: *
328: * @param type
329: * The new shadow type
330: *
331: * @see #SHADOW_TYPE_CLONE
332: * @see #SHADOW_TYPE_BORDER
333: */
334: public void setShadowType(int type) {
335: if (type != SHADOW_TYPE_BORDER && type != SHADOW_TYPE_CLONE) {
336: throw new IllegalArgumentException("Invalid shadow type");
337: }
338: setProperty(PROPERTY_SHADOW_TYPE, new Integer(type));
339: }
340:
341: /**
342: * Set the shadow border to use.
343: *
344: * @param border
345: * The new shadow border to use
346: *
347: * @see #SHADOW_TYPE_BORDER
348: */
349: public void setShadowBorder(Border border) {
350: setProperty(PROPERTY_SHADOW_BORDER, border);
351: }
352:
353: /**
354: * Get the current shadow border.
355: *
356: * @return The current shadow border, or <code>null</code> if one is not
357: * set.
358: *
359: * @see #SHADOW_TYPE_BORDER
360: */
361: public Border getShadowBorder() {
362: Border border = (Border) getProperty(PROPERTY_SHADOW_BORDER);
363: return border;
364: }
365:
366: /**
367: * Set the clone shadow opacity value
368: *
369: * @param opacity
370: * The new opacity value
371: *
372: * @see #SHADOW_TYPE_CLONE
373: */
374: public void setShadowOpacity(float opacity) {
375: setProperty(PROPERTY_SHADOW_OPACITY, new Float(opacity));
376: }
377:
378: /**
379: * Get the clone shadow opacity value
380: *
381: * @return The current opacity value, or <code>-1f</code> if one is not
382: * set.
383: *
384: * @see #SHADOW_TYPE_CLONE
385: */
386: public float getShadowOpacity() {
387: Float opacityFloat = (Float) getProperty(PROPERTY_SHADOW_OPACITY);
388: if (opacityFloat == null) {
389: return -1f;
390: }
391: return opacityFloat.floatValue();
392: }
393:
394: /**
395: * Set the dragged widget opacity value. This value is applied to a widget
396: * when it is clicked to be dragged. The widget's original opacity is reset
397: * when it is dropped.
398: *
399: * @param opacity
400: * The new opacity value.
401: */
402: public void setDraggedWidgetOpacity(float opacity) {
403: setProperty(PROPERTY_DRAGGED_WIDGET_OPACITY, new Float(opacity));
404: }
405:
406: /**
407: * Get the current dragged widget opacity value.
408: *
409: * @return The current opacity value, or <code>-1f</code> if one is not
410: * set.
411: */
412: public float getDraggedWidgetOpacity() {
413: Float opacityFloat = (Float) getProperty(PROPERTY_DRAGGED_WIDGET_OPACITY);
414: if (opacityFloat == null) {
415: return -1f;
416: }
417: return opacityFloat.floatValue();
418: }
419:
420: /**
421: * Return whether or not widgets are dragged in the HTML body element
422: *
423: * @return Drag-in-body status
424: */
425: public boolean isDragInBody() {
426: Boolean propBoolean = (Boolean) getProperty(PROPERTY_DRAG_IN_BODY);
427: if (propBoolean != null) {
428: return propBoolean.booleanValue();
429: }
430: return false;
431: }
432:
433: /**
434: * Sets whether or not widgets are dragged in the HTML body element
435: *
436: * @param dragInBody
437: * New drag-in-body status
438: *
439: * @see #setDragContainer(Component)
440: */
441: public void setDragInBody(boolean dragInBody) {
442: setProperty(PROPERTY_DRAG_IN_BODY, new Boolean(dragInBody));
443: }
444:
445: /**
446: * Get the current drag container being used.
447: *
448: * @return The current drag container, or null if one is not set.
449: */
450: public Component getDragContainer() {
451: Component dragContainer = (Component) getProperty(PROPERTY_DRAG_CONTAINER);
452: if (dragContainer == null) {
453: return this ;
454: }
455: return dragContainer;
456: }
457:
458: /**
459: * Set the container (component) that widgets should be moved to when being
460: * dragged. For example, if the WidgetDash is a child of a SplitPane,
461: * dragging works best if the drag container is the parent SplitPane.
462: *
463: * @param dragContainer
464: * The new drag container.
465: */
466: public void setDragContainer(Component dragContainer) {
467: setProperty(PROPERTY_DRAG_CONTAINER, dragContainer);
468: }
469:
470: /**
471: * Set the method to be used when returning a dropped widget to its shadow's
472: * position.
473: *
474: * @see #RETURN_METHOD_SNAP
475: * @see #RETURN_METHOD_DRIFT
476: *
477: * @param returnMethod
478: */
479: public void setReturnMethod(int returnMethod) {
480: if (returnMethod != RETURN_METHOD_SNAP
481: && returnMethod != RETURN_METHOD_DRIFT) {
482: throw new IllegalArgumentException(
483: "Invalid return method type: " + returnMethod);
484: }
485: setProperty(PROPERTY_RETURN_METHOD, new Integer(returnMethod));
486: }
487:
488: /**
489: * Get the current return method
490: *
491: * @return The current return method, or <code>-1</code> if one is not set.
492: *
493: * @see #RETURN_METHOD_SNAP
494: * @see #RETURN_METHOD_DRIFT
495: */
496: public int getReturnMethod() {
497: Integer returnMethod = (Integer) getProperty(PROPERTY_RETURN_METHOD);
498: if (returnMethod != null) {
499: return returnMethod.intValue();
500: }
501: return -1;
502: }
503:
504: /**
505: * Set the drift step amount (px)
506: * @param driftStep The step amount.
507: *
508: * @see #PROPERTY_DRIFT_STEP
509: */
510: public void setDriftStep(int driftStep) {
511: setProperty(PROPERTY_DRIFT_STEP, new Integer(driftStep));
512: }
513:
514: /**
515: * Get the drift step amount (px)
516: *
517: * @return The current step amount
518: *
519: * @see #PROPERTY_DRIFT_STEP
520: */
521: public int getDriftStep() {
522: Integer driftStep = (Integer) getProperty(PROPERTY_DRIFT_STEP);
523: if (driftStep != null) {
524: return driftStep.intValue();
525: }
526: return -1;
527: }
528:
529: /**
530: * Set the drift step interval (ms).
531: * @param driftStep The new step interval.
532: *
533: * @see #PROPERTY_DRIFT_INTERVAL
534: */
535: public void setDriftInterval(long driftStep) {
536: setProperty(PROPERTY_DRIFT_INTERVAL, new Long(driftStep));
537: }
538:
539: /**
540: * Get the drift step interval (ms).
541: * @return The current step interval.
542: *
543: * @see #PROPERTY_DRIFT_INTERVAL
544: */
545: public long getDriftInterval() {
546: Long driftStep = (Long) getProperty(PROPERTY_DRIFT_STEP);
547: if (driftStep != null) {
548: return driftStep.longValue();
549: }
550: return -1L;
551: }
552:
553: /**
554: * Get the current state of this WidgetDash
555: *
556: * @return A new WidgetDashState object. This object should not change
557: * after being returned from this method (i.e., it should not
558: * reference internal structures)
559: */
560: public WidgetDashState getWidgetDashState() {
561: return new WidgetDashStateImpl(getColumnCount(),
562: getComponents());
563: }
564:
565: /**
566: * Implementation of WidgetPaneState.
567: *
568: * @author Jeremy Volkman
569: */
570: private static class WidgetDashStateImpl implements WidgetDashState {
571:
572: private int columnCount;
573:
574: private Map widgetMap;
575:
576: public WidgetDashStateImpl(int columnCount,
577: Component[] widgetContainers) {
578: this .columnCount = columnCount;
579: widgetMap = new HashMap();
580: for (int i = 0; i < widgetContainers.length; i++) {
581: WidgetContainer container = (WidgetContainer) widgetContainers[i];
582: widgetMap.put(container.getWidgetIdentifier(),
583: container.getWidgetPosition());
584: }
585: }
586:
587: public int getColumnCount() {
588: return columnCount;
589: }
590:
591: public WidgetIdentifier[] getWidgetIdentifiers() {
592: return (WidgetIdentifier[]) widgetMap.keySet().toArray(
593: new WidgetIdentifier[widgetMap.size()]);
594: }
595:
596: public WidgetPosition getWidgetPosition(WidgetIdentifier widget) {
597: return (WidgetPosition) widgetMap.get(widget);
598: }
599:
600: }
601:
602: /**
603: * Adds an <code>ActionListener</code> to receive notification of user
604: * actions, i.e., widget position changes.
605: *
606: * @param l the listener to add
607: */
608: public void addActionListener(ActionListener l) {
609: getEventListenerList().addListener(ActionListener.class, l);
610: // Notification of action listener changes is provided due to
611: // existence of hasActionListeners() method.
612: firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, null, l);
613: }
614:
615: /**
616: * Removes an <code>ActionListener</code> from being notified of user
617: * actions, i.e., widget position changes.
618: *
619: * @param l the listener to remove
620: */
621: public void removeActionListener(ActionListener l) {
622: if (!hasEventListenerList()) {
623: return;
624: }
625: getEventListenerList().removeListener(ActionListener.class, l);
626: // Notification of action listener changes is provided due to
627: // existence of hasActionListeners() method.
628: firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, l, null);
629: }
630:
631: @Override
632: public void processInput(String name, Object value) {
633: if (ACTION_POSITIONS_UPDATED.equals(name)) {
634: EventListener[] listeners = getEventListenerList()
635: .getListeners(ActionListener.class);
636: ActionEvent e = new ActionEvent(this ,
637: ACTION_POSITIONS_UPDATED);
638: for (int i = 0; i < listeners.length; i++) {
639: ActionListener actionListener = (ActionListener) listeners[i];
640: actionListener.actionPerformed(e);
641: }
642: }
643: }
644:
645: }
|