/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2011 Nicolas Peransin. * Use is subject to license terms. */ package examples; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JPanel; import org.mypsycho.swing.app.Application; import org.mypsycho.swing.app.ApplicationContext; import org.mypsycho.swing.app.ApplicationListener; import org.mypsycho.swing.app.SingleFrameApplication; import org.mypsycho.swing.app.SwingBean; import org.mypsycho.swing.app.View; import org.mypsycho.swing.app.utils.SwingHelper; /** * This is a very simple example of a reusable {@code @Actions} class. The code defines * a JComponent subclass called BaseScenePanel that defines two @Actions: create * and remove, that add/remove an icon from the scene panel. These actions are added * to a right button popup menu for the component. * [TBD: demo resource shadowing too] * * @author Hans Muller (Hans.Muller@Sun.COM) * @author Peransin Nicolas */ public class ActionMapExample extends SingleFrameApplication { private static final Insets ZERO_INSETS = new Insets(0,0,0,0); @Override protected void startup() { addApplicationListener(ApplicationListener.console); View view = getMainView(); SwingHelper h = new SwingHelper(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); initGridBagConstraints(c); c.anchor = GridBagConstraints.WEST; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; h.add("baseScene", new BaseScenePanel(this), c); initGridBagConstraints(c); c.weightx = 0.5; c.weighty = 1.0; c.fill = GridBagConstraints.BOTH; h.add("sceneA", new DerivedScenePanelA(this), c); initGridBagConstraints(c); c.weightx = 0.5; c.weighty = 1.0; c.fill = GridBagConstraints.BOTH; c.gridwidth = GridBagConstraints.REMAINDER; h.add("sceneA", new DerivedScenePanelB(this), c); view.setComponent(h.<JComponent>get()); show(view); } private static void initGridBagConstraints(GridBagConstraints c) { c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.NONE; c.gridwidth = 1; c.gridheight = 1; c.gridx = GridBagConstraints.RELATIVE; c.gridy = GridBagConstraints.RELATIVE; c.insets = ZERO_INSETS; c.ipadx = 0; c.ipady = 0; c.weightx = 0.0; c.weighty = 0.0; } public static void main(String[] args) { new ActionMapExample().launch(args); } /** * A JComponent that renders a Scene and defines two {@code @Actions}: * <ul> * <li> {@code create} - adds a new Node to the scene to the right of the last one * <li> {@code remove} - removes the selected Node * </ul> * These actions are added to a popup menu. * <p> * Subclasses can override the {@code create} and {@code remove} methods to * change the corresponding actions. */ @SuppressWarnings("serial") public static class BaseScenePanel extends JPanel implements PropertyChangeListener { private final Scene scene; private final Application application; Icon icon; // // JPopupMenu menu = new JPopupMenu(); // // // /** // * Returns the menu. // * // * @return the menu // */ // public JPopupMenu getMenu() { // return menu; // } public Icon getIcon() { return icon; } public void setIcon(Icon icon) { this.icon = icon; } public void create() { Node node = new Node(getIcon()); // squareIcon Insets insets = getInsets(); int x = insets.left; int y = insets.top; List<Node> nodes = getScene().getNodes(); if (nodes.size() > 0) { int iconGap = 8; Node lastNode = nodes.get(nodes.size() - 1); Rectangle r = lastNode.getBounds(); x += r.x + r.width + iconGap; } node.setLocation(new Point(x, y)); getScene().add(node); } public void remove() { getScene().remove(getScene().getSelectedNode()); } public BaseScenePanel(Application application) { if (application == null) { throw new IllegalArgumentException("null application"); } this.application = application; setLayout(new GridBagLayout()); setBorder(BorderFactory.createTitledBorder(getClass().getSimpleName())); // addMouseListener(new PopupMenuListener()); addMouseListener(new SelectionListener()); scene = new Scene(); scene.addPropertyChangeListener(this); } private class SelectionListener extends MouseAdapter { public void mousePressed(MouseEvent e) { if (!e.isPopupTrigger()) { Node node = getScene().nodeAt(e.getX(), e.getY()); if (node != null) { getScene().setSelectedNode(node); } } } } // /* This is essentially boilerplate: popup the specified menu when // * the platform-specific mouse press/release event occurs. // */ // private class PopupMenuListener extends MouseAdapter { // // public void mousePressed(MouseEvent e) { // maybeShowPopup(e); // } // // public void mouseReleased(MouseEvent e) { // maybeShowPopup(e); // } // // private void maybeShowPopup(MouseEvent e) { // if (e.isPopupTrigger()) { // menu.show(e.getComponent(), e.getX(), e.getY()); // } // } // } public final Scene getScene() { // Public for testability return scene; } protected final Application getApplication() { return application; } protected final ApplicationContext getContext() { return application.getContext(); } public void propertyChange(PropertyChangeEvent e) { if (e.getPropertyName() == null) { repaint(); } else if (e.getPropertyName().equals("selectedNode")) { // Node node = getScene().getSelectedNode(); repaint(); // TBD oldSelection + newSelection bounds } } protected void paintComponent(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); for(Node node : getScene().getNodes()) { Icon icon = node.getIcon(); Point location = node.getLocation(); icon.paintIcon(this, g, location.x, location.y); if (node == getScene().getSelectedNode()) { g.setColor(getForeground()); Rectangle r = node.getBounds(); g.drawRect(r.x, r.y, r.width, r.height); } } } public Dimension getPreferredSize() { List<Node> nodes = getScene().getNodes(); int maxX = 128; int maxY = 128; for(Node node : nodes) { Rectangle r = node.getBounds(); maxX = Math.max(maxX, r.x); maxY = Math.max(maxY, r.y); } Insets insets = getInsets(); return new Dimension(maxX + insets.left + insets.right, maxY + insets.top + insets.bottom); } } @SuppressWarnings("serial") public static class DerivedScenePanelA extends BaseScenePanel { public DerivedScenePanelA(Application application) { super(application); } } @SuppressWarnings("serial") public static class DerivedScenePanelB extends BaseScenePanel { public DerivedScenePanelB(Application application) { super(application); } } /* Trivial scene model: just a list of Nodes and a selected * node property. Any change to the list of nodes is reported * to listeners with a PropertyChangeEvent whose name and old/new * values are null. This is a more or less conventional use of * the class, it means that "that an arbitrary set of * [the source object's] properties have changed". * See http://java.sun.com/javase/6/docs/api/java/beans/PropertyChangeEvent.html */ public static class Scene extends SwingBean { private final List<Node> nodes = new ArrayList<Node>(); private Node selectedNode = null; public void add(Node node) { if (node == null) { throw new IllegalArgumentException("null node"); } nodes.add(node); firePropertyChange(null, null, null); } public void remove(Node node) { if (nodes.remove(node)) { firePropertyChange(null, null, null); } } public final List<Node> getNodes() { // Public for testability return Collections.unmodifiableList(nodes); } public final Node nodeAt(int x, int y) { Node lastNode = null; for(Node node : nodes) { if (node.getBounds().contains(x, y)) { lastNode = node; } } return lastNode; } public Node getSelectedNode() { return selectedNode; } public void setSelectedNode(Node selectedNode) { Node oldValue = getSelectedNode(); this.selectedNode = selectedNode; firePropertyChange("selectedNode", oldValue, this.selectedNode); } } /* Trivial scene model element: an icon and the location it's to * appear in. The location property is bound. */ private static class Node extends SwingBean { private final Icon icon; private final Point location = new Point(0, 0); public Node(Icon icon) { if (icon == null) { throw new IllegalArgumentException("null icon"); } this.icon = icon; } public final Icon getIcon() { return icon; } public Point getLocation() { return new Point(location); } public void setLocation(Point location) { if (location == null) { throw new IllegalArgumentException("null location"); } Point oldValue = getLocation(); this.location.setLocation(location); firePropertyChange("location", oldValue, getLocation()); } public final Rectangle getBounds() { Point p = getLocation(); return new Rectangle(p.x, p.y, icon.getIconWidth(), icon.getIconHeight()); } } }