001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.api.visual.model;
042:
043: import org.netbeans.api.visual.action.*;
044: import org.netbeans.api.visual.widget.Scene;
045: import org.netbeans.api.visual.widget.Widget;
046:
047: import java.util.*;
048: import java.util.List;
049: import java.awt.*;
050:
051: /**
052: * This class manages mapping between model-objects and widgets on a scene. Object mapping is added/removed using addObject and removeObject methods.
053: * You can query the mapping using the findWidget(Object) and the findObject(Widget) methods.
054: * <p>
055: * It also manages object-oriented states and creates a object-specific action that could be assigned to widgets to provide
056: * functionality like object-based selection, object-based hovering, ...
057: *
058: * @author David Kaspar
059: */
060: public class ObjectScene extends Scene {
061:
062: private static final ObjectSceneListener[] EMPTY_LISTENERS = new ObjectSceneListener[0];
063: private static final Set<Object> EMPTY_SET = Collections
064: .unmodifiableSet(Collections.emptySet());
065: private static final Widget[] EMPTY_WIDGETS_ARRAY = new Widget[0];
066: private static final List<Widget> EMPTY_WIDGETS_LIST = Collections
067: .emptyList();
068:
069: private HashMap<Object, Object> objects = new HashMap<Object, Object>();
070: private Set<Object> objectsUm = Collections.unmodifiableSet(objects
071: .keySet());
072:
073: private HashMap<Object, Widget> object2widget = new HashMap<Object, Widget>();
074: private HashMap<Object, List<Widget>> object2widgets = new HashMap<Object, List<Widget>>();
075: private HashMap<Widget, Object> widget2object = new HashMap<Widget, Object>();
076:
077: private HashMap<Object, ObjectState> objectStates = new HashMap<Object, ObjectState>();
078:
079: private HashSet<Object> selectedObjects = new HashSet<Object>();
080: private Set<Object> selectedObjectsUm = Collections
081: .unmodifiableSet(selectedObjects);
082:
083: private HashSet<Object> highlightedObjects = new HashSet<Object>();
084: private Set<Object> highlightedObjectsUm = Collections
085: .unmodifiableSet(highlightedObjects);
086:
087: private Object focusedObject = null;
088: private Object hoveredObject = null;
089:
090: private WidgetAction selectAction = ActionFactory
091: .createSelectAction(new ObjectSelectProvider());
092: private WidgetAction objectHoverAction;
093:
094: private Map<ObjectSceneEventType, List<ObjectSceneListener>> listeners = new java.util.EnumMap<ObjectSceneEventType, List<ObjectSceneListener>>(
095: ObjectSceneEventType.class);
096: private ObjectSceneEvent event = new ObjectSceneEvent(this );
097:
098: /**
099: * Adds a mapping between an object and a widget.
100: * Note that it does not add the widget into the scene automatically - it has to be done manually before this method is called.
101: * @param object the model object; the object must not be a Widget
102: * @param widgets the scene widgets; if it is empty or it is a single null value then the object is non-visual and does not have any widget assigned;
103: * otherwise the widgets cannot contain null values
104: */
105: public final void addObject(Object object, Widget... widgets) {
106: assert object != null && !(object instanceof Widget)
107: && !objects.containsKey(object);
108: Widget mainWidget = widgets.length > 0 ? widgets[0] : null;
109: if (mainWidget == null)
110: widgets = EMPTY_WIDGETS_ARRAY;
111: for (Widget widget : widgets) {
112: assert widget != null;
113: assert !widget2object.containsKey(widget)
114: && widget.getScene() == this ;
115: }
116:
117: objects.put(object, object);
118: object2widget.put(object, mainWidget);
119: object2widgets.put(object, mainWidget != null ? Arrays
120: .asList(widgets) : EMPTY_WIDGETS_LIST);
121: objectStates.put(object, ObjectState.createNormal());
122:
123: for (Widget widget : widgets) {
124: widget2object.put(widget, object);
125: widget.setState(ObjectState.createNormal());
126: }
127:
128: for (ObjectSceneListener listener : getListeners(ObjectSceneEventType.OBJECT_ADDED))
129: listener.objectAdded(event, object);
130: }
131:
132: /**
133: * Removes a mapping for an object.
134: * Note that it does not remove the widget from the scene automatically - it has to be done manually after this method is called.
135: * @param object the object for which the mapping is removed
136: */
137: public final void removeObject(Object object) {
138: assert object != null && objects.containsKey(object);
139: if (selectedObjects.contains(object)) {
140: HashSet<Object> temp = new HashSet<Object>(selectedObjects);
141: temp.remove(object);
142: setSelectedObjects(temp);
143: }
144: if (highlightedObjects.contains(object)) {
145: HashSet<Object> temp = new HashSet<Object>(
146: highlightedObjects);
147: temp.remove(object);
148: setHighlightedObjects(temp);
149: }
150: if (object.equals(hoveredObject)) {
151: setHoveredObject(null);
152: }
153: if (object.equals(focusedObject)) {
154: setFocusedObject(null);
155: }
156: objectStates.remove(object);
157: object2widget.remove(object);
158: List<Widget> widgets = object2widgets.remove(object);
159: for (Widget widget : widgets)
160: widget2object.remove(widget);
161: objects.remove(object);
162: for (ObjectSceneListener listener : getListeners(ObjectSceneEventType.OBJECT_REMOVED))
163: listener.objectRemoved(event, object);
164: }
165:
166: /**
167: * Returns a set of objects with registered mapping.
168: * @return the set of register objects
169: */
170: public final Set<?> getObjects() {
171: return objectsUm;
172: }
173:
174: /**
175: * Returns whether a specified object is registered.
176: * @param object the object to be checked
177: * @return true if the object is register; false if the object is not registered
178: */
179: public final boolean isObject(Object object) {
180: return objects.containsKey(object);
181: }
182:
183: /**
184: * Returns a set of selected objects.
185: * @return the set of selected objects
186: */
187: public final Set<?> getSelectedObjects() {
188: return selectedObjectsUm;
189: }
190:
191: /**
192: * Sets a set of selected objects.
193: * @param selectedObjects the set of selected objects
194: */
195: public final void setSelectedObjects(Set<?> selectedObjects) {
196: ObjectSceneListener[] listeners = getListeners(ObjectSceneEventType.OBJECT_STATE_CHANGED);
197: ObjectSceneListener[] selectionListeners = getListeners(ObjectSceneEventType.OBJECT_SELECTION_CHANGED);
198: Set<Object> previouslySelectedObject = selectionListeners.length != 0 ? Collections
199: .unmodifiableSet(new HashSet<Object>(
200: this .selectedObjects))
201: : EMPTY_SET;
202:
203: for (Iterator<Object> iterator = this .selectedObjects
204: .iterator(); iterator.hasNext();) {
205: Object object = iterator.next();
206: if (!selectedObjects.contains(object)) {
207: iterator.remove();
208: ObjectState previousState = objectStates.get(object);
209: ObjectState newState = previousState
210: .deriveSelected(false);
211: objectStates.put(object, newState);
212: for (Widget widget : object2widgets.get(object))
213: widget.setState(widget.getState().deriveSelected(
214: false));
215: for (ObjectSceneListener listener : listeners)
216: listener.objectStateChanged(event, object,
217: previousState, newState);
218: }
219: }
220:
221: for (Object object : selectedObjects) {
222: if (!this .selectedObjects.contains(object)) {
223: this .selectedObjects.add(object);
224: ObjectState previousState = objectStates.get(object);
225: ObjectState newState = previousState
226: .deriveSelected(true);
227: objectStates.put(object, newState);
228: for (Widget widget : object2widgets.get(object))
229: widget.setState(widget.getState().deriveSelected(
230: true));
231: for (ObjectSceneListener listener : listeners)
232: listener.objectStateChanged(event, object,
233: previousState, newState);
234: }
235: }
236:
237: for (ObjectSceneListener listener : selectionListeners)
238: listener.selectionChanged(event, previouslySelectedObject,
239: this .selectedObjectsUm);
240: }
241:
242: /**
243: * Returns a set of highlighted objects.
244: * @return the set of highlighted objects
245: */
246: public final Set<?> getHighlightedObjects() {
247: return highlightedObjectsUm;
248: }
249:
250: /**
251: * Sets a set of highlighted objects.
252: * @param highlightedObjects the set of highlighted objects
253: */
254: public final void setHighlightedObjects(Set<?> highlightedObjects) {
255: ObjectSceneListener[] listeners = getListeners(ObjectSceneEventType.OBJECT_STATE_CHANGED);
256: ObjectSceneListener[] highlightingListeners = getListeners(ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED);
257: Set<Object> previouslyHighlightedObject = highlightingListeners.length != 0 ? Collections
258: .unmodifiableSet(new HashSet<Object>(
259: this .highlightedObjects))
260: : EMPTY_SET;
261:
262: for (Iterator<Object> iterator = this .highlightedObjects
263: .iterator(); iterator.hasNext();) {
264: Object object = iterator.next();
265: if (!highlightedObjects.contains(object)) {
266: iterator.remove();
267: ObjectState previousState = objectStates.get(object);
268: ObjectState newState = previousState
269: .deriveHighlighted(false);
270: objectStates.put(object, newState);
271: for (Widget widget : object2widgets.get(object))
272: widget.setState(widget.getState()
273: .deriveHighlighted(false));
274: for (ObjectSceneListener listener : listeners)
275: listener.objectStateChanged(event, object,
276: previousState, newState);
277: }
278: }
279:
280: for (Object object : highlightedObjects) {
281: if (!this .highlightedObjects.contains(object)) {
282: this .highlightedObjects.add(object);
283: ObjectState previousState = objectStates.get(object);
284: ObjectState newState = previousState
285: .deriveHighlighted(true);
286: objectStates.put(object, newState);
287: for (Widget widget : object2widgets.get(object))
288: widget.setState(widget.getState()
289: .deriveHighlighted(true));
290: for (ObjectSceneListener listener : listeners)
291: listener.objectStateChanged(event, object,
292: previousState, newState);
293: }
294: }
295:
296: for (ObjectSceneListener listener : highlightingListeners)
297: listener.highlightingChanged(event,
298: previouslyHighlightedObject,
299: this .highlightedObjectsUm);
300: }
301:
302: /**
303: * Returns a hovered object. There could be only one hovered object at maximum at the same time.
304: * @return the hovered object; null if no object is hovered
305: */
306: public final Object getHoveredObject() {
307: return hoveredObject;
308: }
309:
310: /**
311: * Sets a hovered object.
312: * @param hoveredObject the hovered object; if null, then the scene does not have hovered object
313: */
314: public final void setHoveredObject(Object hoveredObject) {
315: if (hoveredObject != null) {
316: if (hoveredObject.equals(this .hoveredObject))
317: return;
318: } else {
319: if (this .hoveredObject == null)
320: return;
321: }
322:
323: ObjectSceneListener[] listeners = getListeners(ObjectSceneEventType.OBJECT_STATE_CHANGED);
324: ObjectSceneListener[] hoverListeners = getListeners(ObjectSceneEventType.OBJECT_HOVER_CHANGED);
325: Object previouslyHoveredObject = this .hoveredObject;
326:
327: if (this .hoveredObject != null) {
328: ObjectState previousState = objectStates
329: .get(this .hoveredObject);
330: ObjectState newState = previousState
331: .deriveObjectHovered(false);
332: objectStates.put(this .hoveredObject, newState);
333: for (Widget widget : object2widgets.get(this .hoveredObject))
334: widget.setState(widget.getState().deriveObjectHovered(
335: false));
336: for (ObjectSceneListener listener : listeners)
337: listener.objectStateChanged(event, this .hoveredObject,
338: previousState, newState);
339: }
340:
341: this .hoveredObject = hoveredObject;
342:
343: if (this .hoveredObject != null) {
344: ObjectState previousState = objectStates
345: .get(this .hoveredObject);
346: ObjectState newState = previousState
347: .deriveObjectHovered(true);
348: objectStates.put(this .hoveredObject, newState);
349: for (Widget widget : object2widgets.get(this .hoveredObject))
350: widget.setState(widget.getState().deriveObjectHovered(
351: true));
352: for (ObjectSceneListener listener : listeners)
353: listener.objectStateChanged(event, this .hoveredObject,
354: previousState, newState);
355: }
356:
357: for (ObjectSceneListener listener : hoverListeners)
358: listener.hoverChanged(event, previouslyHoveredObject,
359: this .hoveredObject);
360: }
361:
362: /**
363: * Returns a focused object. There could be only one focused object at maximum at the same time.
364: * @return the focused object; null if no object is focused
365: */
366: public final Object getFocusedObject() {
367: return focusedObject;
368: }
369:
370: /**
371: * Sets a focused object.
372: * @param focusedObject the focused object; if null, then the scene does not have focused object
373: */
374: public final void setFocusedObject(Object focusedObject) {
375: if (focusedObject != null) {
376: if (focusedObject.equals(this .focusedObject))
377: return;
378: } else {
379: if (this .focusedObject == null)
380: return;
381: }
382:
383: ObjectSceneListener[] listeners = getListeners(ObjectSceneEventType.OBJECT_STATE_CHANGED);
384: ObjectSceneListener[] focusListeners = getListeners(ObjectSceneEventType.OBJECT_FOCUS_CHANGED);
385: Object previouslyFocusedObject = this .focusedObject;
386:
387: if (this .focusedObject != null) {
388: ObjectState previousState = objectStates
389: .get(this .focusedObject);
390: ObjectState newState = previousState
391: .deriveObjectFocused(false);
392: objectStates.put(this .focusedObject, newState);
393: for (Widget widget : object2widgets.get(this .focusedObject))
394: widget.setState(widget.getState().deriveObjectFocused(
395: false));
396: for (ObjectSceneListener listener : listeners)
397: listener.objectStateChanged(event, this .focusedObject,
398: previousState, newState);
399: }
400:
401: this .focusedObject = focusedObject;
402:
403: if (this .focusedObject != null) {
404: ObjectState previousState = objectStates
405: .get(this .focusedObject);
406: ObjectState newState = previousState
407: .deriveObjectFocused(true);
408: objectStates.put(this .focusedObject, newState);
409: for (Widget widget : object2widgets.get(this .focusedObject))
410: widget.setState(widget.getState().deriveObjectFocused(
411: true));
412: for (ObjectSceneListener listener : listeners)
413: listener.objectStateChanged(event, this .focusedObject,
414: previousState, newState);
415: setFocusedWidget(object2widget.get(this .focusedObject));
416: } else
417: setFocusedWidget(null);
418:
419: for (ObjectSceneListener listener : focusListeners)
420: listener.focusChanged(event, previouslyFocusedObject,
421: this .focusedObject);
422:
423: }
424:
425: /**
426: * Creates a object-oriented select action.
427: * @return the object-oriented select action
428: */
429: public final WidgetAction createSelectAction() {
430: return selectAction;
431: }
432:
433: /**
434: * Returns a object-oriented hover action.
435: * @return the object-oriented hover action
436: */
437: public final WidgetAction createObjectHoverAction() {
438: if (objectHoverAction == null) {
439: objectHoverAction = ActionFactory
440: .createHoverAction(new ObjectHoverProvider());
441: getActions().addAction(objectHoverAction);
442: }
443: return objectHoverAction;
444: }
445:
446: /**
447: * Returns the widget that is mapped to a specified object.
448: * @param object the object; must not be a Widget
449: * @return the widget from the registered mapping; null if the object is non-visual or no mapping is registered
450: */
451: public final Widget findWidget(Object object) {
452: assert !(object instanceof Widget) : "Use findObject method for getting an object assigned to a specific Widget"; // NOI18N
453: return object2widget.get(object);
454: }
455:
456: /**
457: * Returns a list of all widgets that are mapped to a specified object.
458: * @param object the object; must not be a Widget
459: * @return the list of all widgets from the registered mapping; empty list if the object is non-visual; null if no mapping is registered
460: */
461: public final List<Widget> findWidgets(Object object) {
462: assert !(object instanceof Widget) : "Use findObject method for getting an object assigned to a specific Widget"; // NOI18N
463: return object2widgets.get(object);
464: }
465:
466: /**
467: * Returns an object which is assigned to a widget.
468: * If the widget is not mapped to any object then the method recursively searches for an object of the parent widget.
469: * @param widget the widget
470: * @return the mapped object; null if no object is assigned to a widget or any of its parent widgets
471: */
472: public final Object findObject(Widget widget) {
473: while (widget != null) {
474: Object o = widget2object.get(widget);
475: if (o != null)
476: return o;
477: widget = widget.getParentWidget();
478: }
479: return null;
480: }
481:
482: /**
483: * Returns an instance of stored object.
484: * It searches for an instance of an object stored internally in the class using "equals" method on an object.
485: * @param object the object that is equals (observed by calling the "equals" method on the instances stored in the class);
486: * the object must not be a Widget
487: * @return the stored instance of the object
488: */
489: public final Object findStoredObject(Object object) {
490: assert !(object instanceof Widget) : "Use findObject method for getting an object assigned to a specific Widget"; // NOI18N
491: return objects.get(object);
492: }
493:
494: /**
495: * Returns an object-state of a specified object.
496: * @param object the object
497: * @return the object-state of the specified object; null if the object is not registered
498: */
499: public final ObjectState getObjectState(Object object) {
500: return objectStates.get(object);
501: }
502:
503: /**
504: * Set by actions for setting selected objects invoked by an user.
505: * @param suggestedSelectedObjects the selected objects suggested by an user
506: * @param invertSelection the invert selection is specified by an user
507: */
508: public void userSelectionSuggested(Set<?> suggestedSelectedObjects,
509: boolean invertSelection) {
510: if (invertSelection) {
511: HashSet<Object> objects = new HashSet<Object>(
512: getSelectedObjects());
513: for (Object o : suggestedSelectedObjects) {
514: if (objects.contains(o))
515: objects.remove(o);
516: else
517: objects.add(o);
518: }
519: setSelectedObjects(objects);
520: } else {
521: setSelectedObjects(suggestedSelectedObjects);
522: }
523: }
524:
525: /**
526: * This method returns an identity code. It should be unique for each object in the scene.
527: * The identity code is a Comparable and could be used for sorting.
528: * The method implementation should be fast.
529: * @param object the object
530: * @return the identity code of the object; null, if the object is null
531: */
532: public Comparable getIdentityCode(Object object) {
533: return object != null ? System.identityHashCode(object) : null;
534: }
535:
536: /**
537: * Adds object scene listener for specified object scene event types.
538: * @param listener the object scene listener
539: * @param types the object scene event types
540: */
541: public final void addObjectSceneListener(
542: ObjectSceneListener listener, ObjectSceneEventType... types) {
543: for (ObjectSceneEventType type : types)
544: addObjectSceneListenerCore(listener, type);
545: }
546:
547: private void addObjectSceneListenerCore(
548: ObjectSceneListener listener, ObjectSceneEventType type) {
549: List<ObjectSceneListener> list = listeners.get(type);
550: if (list == null) {
551: list = new ArrayList<ObjectSceneListener>();
552: listeners.put(type, list);
553: }
554: list.add(listener);
555: }
556:
557: /**
558: * Removes object scene listener for specified object scene event types.
559: * @param listener the object scene listener
560: * @param types the object scene event types
561: */
562: public final void removeObjectSceneListener(
563: ObjectSceneListener listener, ObjectSceneEventType... types) {
564: for (ObjectSceneEventType type : types)
565: removeObjectSceneListenerCore(listener, type);
566: }
567:
568: private void removeObjectSceneListenerCore(
569: ObjectSceneListener listener, ObjectSceneEventType type) {
570: List<ObjectSceneListener> list = listeners.get(type);
571: if (list == null)
572: return;
573: list.remove(listener);
574: if (list.isEmpty())
575: listeners.remove(type);
576: }
577:
578: private ObjectSceneListener[] getListeners(ObjectSceneEventType type) {
579: List<ObjectSceneListener> listeners = this .listeners.get(type);
580: if (listeners == null)
581: return EMPTY_LISTENERS;
582: return listeners.toArray(new ObjectSceneListener[listeners
583: .size()]);
584: }
585:
586: private class ObjectSelectProvider implements SelectProvider {
587:
588: public boolean isAimingAllowed(Widget widget,
589: Point localLocation, boolean invertSelection) {
590: return false;
591: }
592:
593: public boolean isSelectionAllowed(Widget widget,
594: Point localLocation, boolean invertSelection) {
595: return findObject(widget) != null;
596: }
597:
598: public void select(Widget widget, Point localLocation,
599: boolean invertSelection) {
600: Object object = findObject(widget);
601:
602: setFocusedObject(object);
603: if (object != null) {
604: if (!invertSelection
605: && getSelectedObjects().contains(object))
606: return;
607: userSelectionSuggested(Collections.singleton(object),
608: invertSelection);
609: } else
610: userSelectionSuggested(Collections.emptySet(),
611: invertSelection);
612: }
613: }
614:
615: private class ObjectHoverProvider implements HoverProvider {
616:
617: public void widgetHovered(Widget widget) {
618: if (ObjectScene.this == widget)
619: widget = null;
620: setHoveredObject(findObject(widget));
621: }
622:
623: }
624:
625: }
|