diff --git a/api.visual/apichanges.xml b/api.visual/apichanges.xml --- a/api.visual/apichanges.xml +++ b/api.visual/apichanges.xml @@ -757,6 +757,21 @@ + + + Support for rotating widgets + + + + + + Added support for rotating widgets. Patch provided by user + bcallebaut. + + + + + diff --git a/api.visual/manifest.mf b/api.visual/manifest.mf --- a/api.visual/manifest.mf +++ b/api.visual/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.visual OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/visual/resources/Bundle.properties -OpenIDE-Module-Specification-Version: 2.34 +OpenIDE-Module-Specification-Version: 2.35 AutoUpdate-Essential-Module: true diff --git a/api.visual/src/org/netbeans/api/visual/action/WidgetAction.java b/api.visual/src/org/netbeans/api/visual/action/WidgetAction.java --- a/api.visual/src/org/netbeans/api/visual/action/WidgetAction.java +++ b/api.visual/src/org/netbeans/api/visual/action/WidgetAction.java @@ -1087,6 +1087,83 @@ } /** + * Utility class that provides a Chain object along with its name to the + * widget. + * + * @since 2.35 + * @author bcallebaut + */ + public static final class ChainProvider { + + private String name = ""; //NOI18N + + /** + * Get the chain name + * + * @return the chain name + */ + public String getName() { + return name; + } + + /** + * Set the chain name + * + * @param name new chain name + */ + public void setName(String name) { + this.name = name; + } + private Chain chain; + + /** + * Get the chain object provided by this provider + * + * @return the value of chain + */ + public Chain getChain() { + return chain; + } + + /** + * Set the chain provided by this provider + * + * @param chain new value of chain + */ + public void setChain(Chain chain) { + this.chain = chain; + } + + /** + * Constructor without parameter. Creates a non initialized provider + * instance. Set the chain name and the Chain instance before using it. + */ + public ChainProvider() { + } + + /** + * Creates a non fully initialized provider instance. Set the chain name + * before using it. + * + * @param chain Chain instance provided by this provider + */ + public ChainProvider(Chain chain) { + this.chain = chain; + } + + /** + * Creates a fully initialized Chain provider instance. + * + * @param name name of the chain + * @param chain chain instance + */ + public ChainProvider(String name, Chain chain) { + this.name = name; + this.chain = chain; + } + } + + /** * Represents an widget event. */ public static interface WidgetEvent { diff --git a/api.visual/src/org/netbeans/api/visual/widget/Scene.java b/api.visual/src/org/netbeans/api/visual/widget/Scene.java --- a/api.visual/src/org/netbeans/api/visual/widget/Scene.java +++ b/api.visual/src/org/netbeans/api/visual/widget/Scene.java @@ -56,6 +56,7 @@ import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import java.awt.*; +import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashSet; @@ -406,7 +407,7 @@ private void resolveRepaints () { for (Widget widget : repaintWidgets) { - Rectangle repaintBounds = widget.getBounds (); + Rectangle repaintBounds = getRealBounds(widget); if (repaintBounds == null) continue; repaintBounds = widget.convertLocalToScene (repaintBounds); @@ -684,4 +685,23 @@ } + /** + * Compute outer bounds sufficient even if the object is rotated. It is + * needed to repaint areas that were uncovered by rotating the widget. It is + * done by computing enclosing circle and getting its bounds. + */ + private Rectangle getRealBounds(Widget w) { + if (w.getTransform() == null || w.getShape() == null) { + return w.getBounds(); + } else { + Rectangle bounds = w.getShape().getBounds(); + int a = (int) bounds.getWidth() / 2; // half of width + int b = (int) bounds.getHeight() / 2; // half of height + int mX = (int) (bounds.getX() + a); // middle X + int mY = (int) (bounds.getY() + b); // middle Y + double radius = Math.sqrt(a * a + b * b); + return new Ellipse2D.Double(mX - radius, mY - radius, + 2 * radius, 2 * radius).getBounds(); + } + } } diff --git a/api.visual/src/org/netbeans/api/visual/widget/Widget.java b/api.visual/src/org/netbeans/api/visual/widget/Widget.java --- a/api.visual/src/org/netbeans/api/visual/widget/Widget.java +++ b/api.visual/src/org/netbeans/api/visual/widget/Widget.java @@ -89,11 +89,14 @@ * and actions. Yherefore you can define your own look and feel directly in the that method. * * Since version 2.6 Widget class implements Accessible interface. + * Since version 2.35 Widget class implements Lookup.Provider interface and + * tries to initialize itself using the lookup passed in the constructor. * * @author David Kaspar + * @author Benoit Callebaut */ // TODO - Should Widget be an abstract class? -public class Widget implements Accessible { +public class Widget implements Accessible, Lookup.Provider { static final String MESSAGE_NULL_BOUNDS = "Scene.validate was not called after last change. Widget is not validated. See first Q/A at http://graph.netbeans.org/faq.html page."; @@ -145,6 +148,7 @@ private Point location; private Rectangle bounds; + private Shape shape; private Rectangle calculatedPreferredBounds; private boolean requiresFullValidation; @@ -158,26 +162,44 @@ * a property is set by using using resources. */ private ResourceTable resourceTable = null; + private final Lookup lookup; + private AffineTransform transform = new AffineTransform(); /** * Creates a new widget which will be used in a specified scene. * @param scene the scene where the widget is going to be used + * @since 2.35 */ - public Widget (Scene scene) { - if (scene == null) + public Widget (Scene scene, Lookup lookup) { + if (scene == null) { scene = (Scene) this; + } this.scene = scene; + this.lookup = lookup; children = new ArrayList (); childrenUm = Collections.unmodifiableList (children); actionsChain = new WidgetAction.Chain (); + toolsActions = new HashMap(); + toolsActions.put(null, actionsChain); + + for (WidgetAction.ChainProvider provider : lookup.lookupAll(WidgetAction.ChainProvider.class)) { + toolsActions.put(provider.getName(), provider.getChain()); + } opaque = false; font = null; background = Color.WHITE; foreground = Color.BLACK; - border = BorderFactory.createEmptyBorder (); - layout = LayoutFactory.createAbsoluteLayout (); + + border = lookup.lookup(Border.class); + if (border == null) { + border = BorderFactory.createEmptyBorder (); + } + layout = lookup.lookup(Layout.class); + if (layout == null) { + layout = LayoutFactory.createAbsoluteLayout (); + } preferredLocation = null; preferredBounds = null; checkClipping = false; @@ -191,6 +213,24 @@ } /** + * Creates a new widget which will be used in a specified scene. + * + * @param scene the scene where the widget is going to be used + * @since 2.35 + */ + public Widget(Scene scene) { + this(scene, Lookup.EMPTY); + } + + /** + * @since 2.35 + */ + public Shape getShape() { + shape = getTransform().createTransformedShape (getBounds()); + return shape; + } + + /** * Returns a scene where the widget is assigned * @return the scene */ @@ -493,8 +533,9 @@ * Returns a lookup of the widget. * @return the lookup */ - public Lookup getLookup () { - return Lookup.EMPTY; + @Override + public Lookup getLookup() { + return lookup; } /** @@ -840,6 +881,22 @@ } /** + * @since 2.35 + * @return Widget transform + */ + public AffineTransform getTransform() { + return transform; + } + + /** + * @since 2.35 + */ + public void setTransform(AffineTransform transform) { + this.transform = transform; + this.revalidate(); + } + + /** * Returns the border of the widget. * @return the border */ @@ -1276,7 +1333,7 @@ * @return true, if the location belong to the widget */ public boolean isHitAt (Point localLocation) { - return visible && getBounds ().contains (localLocation); + return visible && getShape().contains (localLocation); } /** @@ -1399,10 +1456,11 @@ return; assert bounds != null : MESSAGE_NULL_BOUNDS; // NOI18N - Graphics2D gr = scene.getGraphics (); + Graphics2D gr = getGraphics (); AffineTransform previousTransform = gr.getTransform(); gr.translate (location.x, location.y); + gr.transform (transform); Shape tempClip = null; if (checkClipping) { diff --git a/api.visual/src/org/netbeans/modules/visual/graph/layout/HierarchicalLayout.java b/api.visual/src/org/netbeans/modules/visual/graph/layout/HierarchicalLayout.java --- a/api.visual/src/org/netbeans/modules/visual/graph/layout/HierarchicalLayout.java +++ b/api.visual/src/org/netbeans/modules/visual/graph/layout/HierarchicalLayout.java @@ -659,7 +659,7 @@ protected void run() { - layers = new List[layerCount]; + layers = (List.LayoutNode>[])new List[layerCount]; for (int i = 0; i < layerCount; i++) { layers[i] = new ArrayList();