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.widget;
042:
043: import org.netbeans.api.visual.action.ActionFactory;
044: import org.netbeans.api.visual.action.TwoStateHoverProvider;
045: import org.netbeans.api.visual.action.WidgetAction;
046: import org.netbeans.api.visual.animator.SceneAnimator;
047: import org.netbeans.api.visual.laf.InputBindings;
048: import org.netbeans.api.visual.laf.LookFeel;
049: import org.netbeans.modules.visual.util.GeomUtil;
050: import org.netbeans.modules.visual.widget.SatelliteComponent;
051:
052: import javax.swing.*;
053: import javax.swing.event.AncestorEvent;
054: import javax.swing.event.AncestorListener;
055: import java.awt.*;
056: import java.awt.geom.Rectangle2D;
057: import java.util.ArrayList;
058: import java.util.HashSet;
059:
060: /**
061: * The scene is a widget which also controls and represents whole rendered area.
062: * <p>
063: * After all changes in a scene is done, the validate method have to be called for validating changed
064: * and calculating new locations and boundaries of all modified widgets.
065: * <p>
066: * The scene allows to create a view JComponent which can be used anywhere in Swing based application. Only one view
067: * can be created using the createView method.
068: * The scene allows to create multiple satellite views using the createSatelliteView method. The satellite view is just
069: * showing the scene and allows quick navigator and panning using a mouse.
070: * <p>
071: * The scene contains additional scene-specific properties like lookFeel, activeTool, defaultFont, animator.
072: * <p>
073: * It is able to create a widget-specific hover action.
074: *
075: * @author David Kaspar
076: */
077: // TODO - take SceneComponent dimension and correct Scene.resolveBounds
078: // TODO - remove SuppressWarnings
079: public class Scene extends Widget {
080:
081: private double zoomFactor = 1.0;
082: private SceneAnimator sceneAnimator;
083:
084: private JComponent component = null;
085: private Graphics2D graphics = null;
086: private boolean viewShowing = false;
087: private boolean paintEverything = true;
088:
089: private Rectangle repaintRegion = null;
090: private HashSet<Widget> repaintWidgets = new HashSet<Widget>();
091:
092: private Font defaultFont;
093: private LookFeel lookFeel = LookFeel.createDefaultLookFeel();
094: private InputBindings inputBindings = InputBindings.create();
095: private String activeTool = null;
096: private Rectangle maximumBounds = new Rectangle(
097: Integer.MIN_VALUE / 2, Integer.MIN_VALUE / 2,
098: Integer.MAX_VALUE, Integer.MAX_VALUE);
099:
100: private final ArrayList<SceneListener> sceneListeners = new ArrayList<SceneListener>(); // TODO - use CopyOnWriteArrayList
101:
102: private EventProcessingType keyEventProcessingType = EventProcessingType.ALL_WIDGETS;
103:
104: private WidgetAction.Chain priorActions = new WidgetAction.Chain();
105:
106: private Widget focusedWidget = this ;
107: private WidgetAction widgetHoverAction;
108: boolean extendSceneOnly = false;
109:
110: /**
111: * Creates a scene.
112: */
113: public Scene() {
114: super (null);
115: defaultFont = Font.decode(null);
116: resolveBounds(new Point(), new Rectangle());
117: setOpaque(true);
118: setFont(defaultFont);
119: setBackground(lookFeel.getBackground());
120: setForeground(lookFeel.getForeground());
121: sceneAnimator = new SceneAnimator(this );
122: }
123:
124: /**
125: * Creates a view. This method could be called once only. Call the getView method for getting created instance of a view.
126: * @return the created view
127: */
128: public JComponent createView() {
129: assert component == null;
130: component = new SceneComponent(this );
131: component.addAncestorListener(new AncestorListener() {
132: public void ancestorAdded(AncestorEvent event) {
133: repaintSatellite();
134: }
135:
136: public void ancestorRemoved(AncestorEvent event) {
137: repaintSatellite();
138: }
139:
140: public void ancestorMoved(AncestorEvent event) {
141: }
142: });
143: return component;
144: }
145:
146: /**
147: * Returns an instance of created view
148: * @return the instance of created view; null if no view is created yet
149: */
150: public JComponent getView() {
151: return component;
152: }
153:
154: /**
155: * Creates a satellite view.
156: * @return the satellite view
157: */
158: public JComponent createSatelliteView() {
159: return new SatelliteComponent(this );
160: }
161:
162: /**
163: * Creates a bird view with specific zoom factor.
164: * @return the bird view controller
165: * @since 2.7
166: */
167: public BirdViewController createBirdView() {
168: return new BirdViewController(this );
169: }
170:
171: void setViewShowing(boolean viewShowing) {
172: assert this .viewShowing != viewShowing : "Duplicate setViewShowing: "
173: + viewShowing;
174: this .viewShowing = viewShowing;
175: if (viewShowing)
176: dispatchNotifyAddedCore();
177: else
178: dispatchNotifyRemovedCore();
179: }
180:
181: void dispatchNotifyAdded(Widget widget) {
182: assert widget != null;
183: Widget w = widget;
184: for (;;) {
185: if (w == this )
186: break;
187: w = w.getParentWidget();
188: if (w == null)
189: return;
190: }
191: if (!viewShowing)
192: return;
193: widget.dispatchNotifyAddedCore();
194: }
195:
196: void dispatchNotifyRemoved(Widget widget) {
197: assert widget != null;
198: Widget w = widget;
199: for (;;) {
200: if (w == this ) {
201: if (viewShowing)
202: return;
203: break;
204: }
205: w = w.getParentWidget();
206: if (w == null)
207: break;
208: }
209: if (!viewShowing)
210: return;
211: widget.dispatchNotifyRemovedCore();
212: }
213:
214: /**
215: * Returns an instance of Graphics2D which is used for calculating boundaries and rendering all widgets in the scene.
216: * @return the instance of Graphics2D
217: */
218: public final Graphics2D getGraphics() {
219: return graphics;
220: }
221:
222: // HACK - used by SceneComponent and ConvolutionWidget
223: final void setGraphics(Graphics2D graphics) {
224: this .graphics = graphics;
225: }
226:
227: /**
228: * This method invokes Scene.validate method with a specific Graphics2D instance. This is useful for rendering a scene off-screen
229: * without creating and showning the main scene view. See test.view.OffscreenRenderingTest example for usages.
230: * <p>
231: * Note: Do not call this method unless you know the consequences. The scene is going to be validated using the specified Graphics2D
232: * instance even after the method call therefore it may break scene layout when your main scene view is finally created and shown.
233: * @param graphics the graphics instance used for validation
234: * @since 2.7
235: */
236: public final void validate(Graphics2D graphics) {
237: Graphics2D prevoiusGraphics = getGraphics();
238: setGraphics(graphics);
239: validate();
240: setGraphics(prevoiusGraphics);
241: }
242:
243: /**
244: * Paints the whole scene into the graphics instance. The method calls validate before rendering.
245: * @param graphics the Graphics2D instance where the scene is going to be painted
246: */
247: public final void paint(Graphics2D graphics) {
248: validate();
249: Graphics2D prevoiusGraphics = getGraphics();
250: setGraphics(graphics);
251: paint();
252: setGraphics(prevoiusGraphics);
253: }
254:
255: /**
256: * Returns maximum bounds of the scene.
257: * @return the maximum bounds
258: */
259: public final Rectangle getMaximumBounds() {
260: return new Rectangle(maximumBounds);
261: }
262:
263: /**
264: * Sets maximum bounds of the scene.
265: * @param maximumBounds the non-null maximum bounds
266: */
267: public final void setMaximumBounds(Rectangle maximumBounds) {
268: assert maximumBounds != null;
269: this .maximumBounds = new Rectangle(maximumBounds);
270: revalidate();
271: }
272:
273: /**
274: * Returns a default font of the scene.
275: * @return the default font
276: */
277: public Font getDefaultFont() {
278: return defaultFont;
279: }
280:
281: /**
282: * Returns whether the whole scene is validated and there is no widget or region that has to be revalidated.
283: * @return true, if the whole scene is validated
284: */
285: public boolean isValidated() {
286: return super .isValidated() && repaintRegion == null
287: && repaintWidgets.isEmpty();
288: }
289:
290: /**
291: * Returns whether the layer widget requires to repainted after revalidation.
292: * @return always false
293: */
294: protected boolean isRepaintRequiredForRevalidating() {
295: return false;
296: }
297:
298: // TODO - maybe it could improve the perfomance, if bounds != null then do nothing
299: // WARNING - you have to asure that there will be no component/widget that will change its location/bounds between this and validate method calls
300: final void revalidateWidget(Widget widget) {
301: Rectangle widgetBounds = widget.getBounds();
302: if (widgetBounds != null) {
303: Rectangle sceneBounds = widget
304: .convertLocalToScene(widgetBounds);
305: if (repaintRegion == null)
306: repaintRegion = sceneBounds;
307: else
308: repaintRegion.add(sceneBounds);
309: }
310: repaintWidgets.add(widget);
311: }
312:
313: // TODO - requires optimalization while changing preferred size and calling revalidate/repaint
314: private void layoutScene() {
315: Point preLocation = getLocation();
316: Rectangle preBounds = getBounds();
317:
318: layout(false);
319: resolveBounds(null, null);
320: justify();
321:
322: Rectangle rect = null;
323: for (Widget widget : getChildren()) {
324: Point location = widget.getLocation();
325: Rectangle bounds = widget.getBounds();
326: bounds.translate(location.x, location.y);
327: if (rect == null)
328: rect = bounds;
329: else
330: rect.add(bounds);
331: }
332: if (rect != null) {
333: Insets insets = getBorder().getInsets();
334: rect.x -= insets.left;
335: rect.y -= insets.top;
336: rect.width += insets.left + insets.right;
337: rect.height += insets.top + insets.bottom;
338: rect = rect.intersection(maximumBounds);
339: }
340:
341: if (extendSceneOnly && rect != null && preBounds != null)
342: rect.add(new Rectangle(preBounds.x + preLocation.x,
343: preBounds.y + preLocation.y, preBounds.width,
344: preBounds.height));
345: resolveBounds(rect != null ? new Point(-rect.x, -rect.y)
346: : new Point(), rect);
347:
348: Dimension preferredSize = rect != null ? rect.getSize()
349: : new Dimension();
350: preferredSize = new Dimension(
351: (int) (preferredSize.width * zoomFactor),
352: (int) (preferredSize.height * zoomFactor));
353: Rectangle bounds = getBounds();
354: if (component != null) {
355: if (!preferredSize.equals(component.getPreferredSize())) {
356: component.setPreferredSize(preferredSize);
357: component.revalidate();
358: bounds = getBounds();
359: // repaintSatellite ();
360: }
361:
362: Dimension componentSize = component.getSize();
363: componentSize.width = (int) (componentSize.width / zoomFactor);
364: componentSize.height = (int) (componentSize.height / zoomFactor);
365:
366: boolean sceneResized = false;
367: if (bounds.width < componentSize.width) {
368: bounds.width = componentSize.width;
369: sceneResized = true;
370: }
371: if (bounds.height < componentSize.height) {
372: bounds.height = componentSize.height;
373: sceneResized = true;
374: }
375: if (sceneResized)
376: resolveBounds(getLocation(), bounds);
377: }
378: if (!getLocation().equals(preLocation)
379: || !getBounds().equals(preBounds)) {
380: Rectangle rectangle = convertLocalToScene(getBounds());
381: if (repaintRegion == null)
382: repaintRegion = rectangle;
383: else
384: repaintRegion.add(rectangle);
385: }
386: }
387:
388: /**
389: * Validates all widget in the whole scene. The validation is done repeatively until there is no invalid widget
390: * in the scene after validating process. It also schedules invalid regions in the view for repainting.
391: */
392: @SuppressWarnings("unchecked")
393: public final void validate() {
394: if (graphics == null)
395: return;
396:
397: while (!isValidated()) {
398: SceneListener[] ls;
399: synchronized (sceneListeners) {
400: ls = sceneListeners
401: .toArray(new SceneListener[sceneListeners
402: .size()]);
403: }
404:
405: for (SceneListener listener : ls)
406: listener.sceneValidating();
407:
408: layoutScene();
409:
410: for (Widget widget : repaintWidgets) {
411: Rectangle repaintBounds = widget.getBounds();
412: if (repaintBounds == null)
413: continue;
414: repaintBounds = widget
415: .convertLocalToScene(repaintBounds);
416: if (repaintRegion != null)
417: repaintRegion.add(repaintBounds);
418: else
419: repaintRegion = repaintBounds;
420: }
421: repaintWidgets.clear();
422: // System.out.println ("r = " + r);
423:
424: // NOTE - maybe improves performance when component.repaint will be called for all widgets/rectangles separately
425: if (repaintRegion != null) {
426: Rectangle r = convertSceneToView(repaintRegion);
427: r.grow(1, 1);
428: if (component != null)
429: component.repaint(r);
430: repaintSatellite();
431: repaintRegion = null;
432: }
433: // System.out.println ("time: " + System.currentTimeMillis ());
434:
435: for (SceneListener listener : ls)
436: listener.sceneValidated();
437: }
438: }
439:
440: /**
441: * This methods makes the scene to be extended only - no shrunk.
442: * @param extendSceneOnly if true, the scene is going to be extended only
443: */
444: void setExtendSceneOnly(boolean extendSceneOnly) {
445: this .extendSceneOnly = extendSceneOnly;
446: }
447:
448: private void repaintSatellite() {
449: SceneListener[] ls;
450: synchronized (sceneListeners) {
451: ls = sceneListeners
452: .toArray(new SceneListener[sceneListeners.size()]);
453: }
454:
455: for (SceneListener listener : ls)
456: listener.sceneRepaint();
457: }
458:
459: final boolean isPaintEverything() {
460: return paintEverything;
461: }
462:
463: final void setPaintEverything(boolean paintEverything) {
464: this .paintEverything = paintEverything;
465: }
466:
467: /**
468: * Returns a key events processing type of the scene.
469: * @return the processing type for key events
470: */
471: public final EventProcessingType getKeyEventProcessingType() {
472: return keyEventProcessingType;
473: }
474:
475: /**
476: * Sets a key events processing type of the scene.
477: * @param keyEventProcessingType the processing type for key events
478: */
479: public final void setKeyEventProcessingType(
480: EventProcessingType keyEventProcessingType) {
481: assert keyEventProcessingType != null;
482: this .keyEventProcessingType = keyEventProcessingType;
483: }
484:
485: /**
486: * Returns a prior actions. These actions are executed before any other action in the scene.
487: * If any of these actions consumes an event that the event processsing is stopped. Action locking is ignored.
488: * @return the prior actions
489: */
490: public final WidgetAction.Chain getPriorActions() {
491: return priorActions;
492: }
493:
494: /**
495: * Returns a focused widget of the scene.
496: * @return the focused widget; null if no widget is focused
497: */
498: public final Widget getFocusedWidget() {
499: return focusedWidget;
500: }
501:
502: /**
503: * Sets a focused widget of the scene.
504: * @param focusedWidget the focused widget; if null, then the scene itself is taken as the focused widget
505: */
506: public final void setFocusedWidget(Widget focusedWidget) {
507: if (focusedWidget == null)
508: focusedWidget = this ;
509: else
510: assert focusedWidget.getScene() == this ;
511: this .focusedWidget.setState(this .focusedWidget.getState()
512: .deriveWidgetFocused(false));
513: this .focusedWidget = focusedWidget;
514: this .focusedWidget.setState(this .focusedWidget.getState()
515: .deriveWidgetFocused(true));
516: }
517:
518: /**
519: * Returns a zoom factor.
520: * @return the zoom factor
521: */
522: public final double getZoomFactor() {
523: return zoomFactor;
524: }
525:
526: /**
527: * Sets a zoom factor for the scene.
528: * @param zoomFactor the zoom factor
529: */
530: public final void setZoomFactor(double zoomFactor) {
531: this .zoomFactor = zoomFactor;
532: revalidate();
533: }
534:
535: /**
536: * Returns a scene animator of the scene.
537: * @return the scene animator
538: */
539: public final SceneAnimator getSceneAnimator() {
540: return sceneAnimator;
541: }
542:
543: /**
544: * Returns a look'n'feel of the scene.
545: * @return the look'n'feel
546: */
547: public final LookFeel getLookFeel() {
548: return lookFeel;
549: }
550:
551: /**
552: * Sets a look'n'feel of the scene. This method does affect current state of the scene - already created components
553: * will not be refreshed.
554: * @param lookFeel the look'n'feel
555: */
556: public final void setLookFeel(LookFeel lookFeel) {
557: assert lookFeel != null;
558: this .lookFeel = lookFeel;
559: }
560:
561: /**
562: * Returns input bindings of the scene.
563: * @return the input bindings
564: * @since 2.4
565: */
566: public final InputBindings getInputBindings() {
567: return inputBindings;
568: }
569:
570: /**
571: * Returns an active tool of the scene.
572: * @return the active tool; if null, then only default action chain of widgets will be used
573: */
574: public final String getActiveTool() {
575: return activeTool;
576: }
577:
578: /**
579: * Sets an active tool.
580: * @param activeTool the active tool; if null, then the active tool is unset and only default action chain of widgets will be used
581: */
582: public void setActiveTool(String activeTool) {
583: this .activeTool = activeTool;
584: }
585:
586: /**
587: * Registers a scene listener.
588: * @param listener the scene listener
589: */
590: public final void addSceneListener(SceneListener listener) {
591: assert listener != null;
592: synchronized (sceneListeners) {
593: sceneListeners.add(listener);
594: }
595: }
596:
597: /**
598: * Unregisters a scene listener.
599: * @param listener the scene listener
600: */
601: public final void removeSceneListener(SceneListener listener) {
602: synchronized (sceneListeners) {
603: sceneListeners.remove(listener);
604: }
605: }
606:
607: /**
608: * Converts a location in the scene coordination system to the view coordination system.
609: * @param sceneLocation the scene location
610: * @return the view location
611: */
612: public final Point convertSceneToView(Point sceneLocation) {
613: Point location = getLocation();
614: return new Point(
615: (int) (zoomFactor * (location.x + sceneLocation.x)),
616: (int) (zoomFactor * (location.y + sceneLocation.y)));
617: }
618:
619: /**
620: * Converts a rectangle in the scene coordination system to the view coordination system.
621: * @param sceneRectangle the scene rectangle
622: * @return the view rectangle
623: */
624: public final Rectangle convertSceneToView(Rectangle sceneRectangle) {
625: Point location = getLocation();
626: return GeomUtil.roundRectangle(new Rectangle2D.Double(
627: (double) (sceneRectangle.x + location.x) * zoomFactor,
628: (double) (sceneRectangle.y + location.y) * zoomFactor,
629: (double) sceneRectangle.width * zoomFactor,
630: (double) sceneRectangle.height * zoomFactor));
631: }
632:
633: /**
634: * Converts a location in the view coordination system to the scene coordination system.
635: * @param viewLocation the view location
636: * @return the scene location
637: */
638: public Point convertViewToScene(Point viewLocation) {
639: return new Point((int) ((double) viewLocation.x / zoomFactor)
640: - getLocation().x,
641: (int) ((double) viewLocation.y / zoomFactor)
642: - getLocation().y);
643: }
644:
645: /**
646: * Creates a widget-specific hover action.
647: * @return the widget-specific hover action
648: */
649: public WidgetAction createWidgetHoverAction() {
650: if (widgetHoverAction == null) {
651: widgetHoverAction = ActionFactory
652: .createHoverAction(new WidgetHoverAction());
653: getActions().addAction(widgetHoverAction);
654: }
655: return widgetHoverAction;
656: }
657:
658: private class WidgetHoverAction implements TwoStateHoverProvider {
659:
660: public void unsetHovering(Widget widget) {
661: widget.setState(widget.getState()
662: .deriveWidgetHovered(false));
663: }
664:
665: public void setHovering(Widget widget) {
666: widget
667: .setState(widget.getState().deriveWidgetHovered(
668: true));
669: }
670:
671: }
672:
673: /**
674: * The scene listener which is notified about repainting, validating progress.
675: */
676: public interface SceneListener {
677:
678: /**
679: * Called to notify that the whole scene was repainted.
680: */
681: void sceneRepaint();
682:
683: /**
684: * Called to notify that the scene is going to be validated.
685: */
686: void sceneValidating();
687:
688: /**
689: * Called to notify that the scene has been validated.
690: */
691: void sceneValidated();
692:
693: }
694:
695: }
|