package org.biopcd.widgets; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.datatransfer.Transferable; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionListener; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSeparator; /** * A panel widget enables a PCD menu to have panels with sub-widgets. This * functionality enables PCD menu creators to group some options for a program * vertically, when they would normally be arranged horizontally. This is * especially useful for the Run and Help buttons which are beside each other * horizontally (instead of on top of one another vertically.) ** * @author Graham Alvare * @author Brian Fristensky */ public class PanelWidget implements Widget { /** * Temporarily stores parameters before they are added to the PanelWidget. */ Map subwidgets; /** * Used for serialization purposes. */ private static final long serialVersionUID = 7526472295622776161L; /** * The icon for the widget. */ public static final Icon WIDGET_ICON = new ImageIcon( NumberWidget.class.getClassLoader().getResource( "org/biopcd/icons/panelwidget.png")); /** * Creates a new empty instance of a panel widget * (this specific constructor is used by the PCD editor ONLY!). */ public PanelWidget () { this.subwidgets = new LinkedHashMap(); } /** * Creates a new instance of a panel widget ** * @param subwidgets the widgets that should be contained within the panel */ public PanelWidget (Map subwidgets) { this.subwidgets = subwidgets; } /** * Displays the current widget within the container 'dest'. ** * @param dest the destination Container to display the widget. Note that * this will almost definitely be different from the window * parameter, and in most cases, should be a JPanel object. * @param window the parent window to communicate with. The communication * involved is supposed to be limited to just using 'window' * to create modal dialog boxes when necessary (for example, * the AbstractFileChooser's "Browse" file choice dialog box). * Please note that this field may be null!! (e.g. displaying * the current state of the widget in the editor canvas) */ public void display(Container dest, CloseableWindow window) { // Create the new box object to display the sub-widgets horizontally. Box container = new Box(BoxLayout.LINE_AXIS); // Add all of the sub-widgets to the box object for (Widget widget : subwidgets.values()) { widget.display(container, window); } // Add the Box object to the destination Container object. dest.add(container); } /** *

Creates a new widget instance of the widget

** *

A widget instance is an object that stores the value of a widget past * after the widget has been closed. This is useful for concurrency. * Because more than one BioLegato PCD command can be run simultaneously, * BioLegato needs to store the values used to run each command separately. * For instance, if the user runs command A, then changing the value of a * widget in A's parameter window should not affect the currently running * job (i.e. command A). This is achieved through WidgetInstance objects. *

* *

In this case PanelWidgets do not have any values (i.e. display only); * however, PanelWidgets CAN house house other widgets (such as * NumberWidgets), which CAN have values. Therefore, the PanelWidget class * returns a map object (generated by recursively calling each of its child * widgets' getInstance functions) contained inside a WidgetInstace object. * The map is comprised of WidgetInstances for each child widget to the * panel. The keys of the map are the variable names for the child widgets, * and the entries are the actual WidgetInstance objects.

** * @return a widget instance for usage in the current menu. */ public WidgetInstance getInstance () { // Create a new map to store all of the WidgetInstance objects for // the widgets contained within the panel. Map valuemap = new LinkedHashMap(); // Iteate through each sub-widget. for (Map.Entry widget : subwidgets.entrySet()) { // Add the widget to the map "valuemap". // The 'name' of the widget is the map key, and the // WidgetInstance is the value of the map entry. valuemap.put(widget.getKey(), widget.getValue().getInstance()); } // Create and return a new WidgetInstance, which will house 'valuemap' return new WidgetInstance(valuemap); } /** * Populates a container with the component objects * necessary for editing the current widget ** * @param parent the JDialog/JFrame/etc. which contains the edit * information (this parameter should ONLY be used for * creating modal child windows for displaying error * messages and the like!) * @param dest the destination to display the widget-editing components * @return the action listener associated with updating the current widget */ public ActionListener edit(Component parent, Container dest) { return null; } /** * Writes the BioPCD representation of the menu widget to a writer object * (see BioLegato's BioPCD editor for more details) ** * @param scope the level of scope to write the menu widget. In the case * of PCD, the scope of each line is indicated by the number * of spaced preceding the line. Every 4 spaces count as * one level of scope (any number not divisible by 4 is * considered an error), thus if a line is preceded by 4 * spaces, its scope level is considered to be 1 * @param out the Appendable object to output the BioPCD code. */ @Override public void pcdOut(int scope, Appendable out) throws IOException { // Adjust the scope level of the current line by adding // groups of four (4) spaces for each scope level. for (int count = 0; count < scope; count++) { out.append(" "); } // Print 'panel' (this precedes any sub-widgets, which will be // printed at one level higher scope. out.append("panel\n"); // Print out every widget contained within the panel. // NOTE: the scope of each widget will be 1 levels higher than "panel". for (Map.Entry wentry : subwidgets.entrySet()) { wentry.getValue().pcdOut(scope + 1, out); } } /** * Displays the current widget in an editor panel. This is completely * customizable; for example, TabbedWidgets display an JTabbedPane with * buttons at the bottom for adding tabs. To see this function in action, * using the PCD editor, drag and drop a widget into a new menu. What you * see in the new menu is EXACTLY the Component object returned by this * function. ** * @param mainFrame a JFrame object for adding modality to any dialog * boxes, which are created by this function. * @return a component object to display in the editor. */ public Component displayEdit(final JFrame mainFrame) { // The panel to display everything. This is where all of the Components // associated with editing the PanelWidget will be displayed. final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); panel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); panel.add(new JLabel("horizontal panel", WIDGET_ICON, JLabel.CENTER)); panel.add(new JSeparator()); // Again, needs to negotiate with the draggable object panel.setTransferHandler(new WidgetTransferHandler()); // Create a new DropTarget to accept widgets which are dropped onto // the PanelWidget. This is important because it allows the user to // add widgets to a tab within the PanelWidget. panel.setDropTarget(new DropTarget(panel, new DropTargetListener() { /** * A running count of the number of widgets added to the * PanelWidget. This will be used for ensuring each variable * has a unique name within the 'subwidgets' hashtable. */ private int added = 0; /** * Handles when the drop action has changed * (DOES NOTHING AT THIS MOMENT!) ** * @param dtde Ignored, because this method currently does * nothing. */ public void dragEnter(DropTargetDragEvent dtde) { } /** * Handles when a drag cursor enters the drop target area. ** * @param dtde Ignored, because this method changes the cursor * regardless of whatever extraneous information * about the event is available. */ public void dragOver(DropTargetDragEvent dtde) { panel.setCursor(Cursor.getPredefinedCursor( Cursor.HAND_CURSOR)); } /** * Handles when the drop action has changed * (DOES NOTHING AT THIS MOMENT!) ** * @param dtde Ignored, because this method currently does * nothing. */ public void dropActionChanged(DropTargetDragEvent dtde) { } /** * Handles when a drag cursor leaves the drop target area. ** * @param dtde Ignored, because this method changes the cursor * regardless of whatever extraneous information * about the event is available. */ public void dragExit(DropTargetEvent dte) { panel.setCursor(Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR)); } /** *

The user drops the item. Performs the drag and drop * calculations and layout.

** * @param dtde The event object containing all of the information * necessary and available for the drag-and-drop * procedure. */ public void drop(DropTargetDropEvent dtde) { // Declare the variables for performing the drop. Widget widget = null; Object transferableObj = null; Transferable trans = null; // Reset the cursor to normal. panel.setCursor(Cursor.getDefaultCursor()); try { // Get the transferable object for the event. trans = dtde.getTransferable(); // What does the Transferable support? If it supports // WidgetGenerators, then we can perform a drop action. if (trans.isDataFlavorSupported(WidgetGenerator.FLAVOUR)) { transferableObj = dtde.getTransferable() .getTransferData(WidgetGenerator.FLAVOUR); ///////////////////////////// // Perform the drop action // ///////////////////////////// // 1. Create a widget from the dropped // WidgetGenerator object. // 2. Add the widget to the panel (so that it is // displayed within the editor panel.) // 3. Add the widget to the sub-widgets hashtable. // 4. Increment the number of variables added // counter, such that each variable has a unique // identifier within the PanelWidget. This is // important because the new variable // should have a unique name, ///////////////////////////// widget = ((WidgetGenerator)transferableObj).drop(); panel.add(widget.displayEdit(mainFrame)); subwidgets.put("var" + added, widget); added++; } // Update the layout of the editor display panel. panel.doLayout(); panel.setSize(panel.getPreferredSize()); panel.validate(); panel.repaint(50L); } catch (Exception ex) { // Print any error messages produced by the drop event. ex.printStackTrace(System.err); } } })); // Add all of the sub-widgets to the panel for display. for (Widget w : subwidgets.values()) { panel.add(w.displayEdit(mainFrame)); } // Return the openPane object to the calling method. panel.doLayout(); panel.setSize(panel.getPreferredSize()); panel.validate(); panel.repaint(50L); return panel; } /** *

Changes the current value for the widget. This is used to ensure that * any Components that the widget creates for a PCD menu will update the * widget object itself. This is important because the widget is expected * to store the last value it was set to after a window was closed.

* *

For example, if you opened a PCD menu and set a NumberWidget to 10, * and then closed the window, if you reopen the window the NumberWidget * should still be 10 (regardless of any default values).

* *

NOTE: because this widget does not use values (it is a * PanelWidget), this method is left empty.

** * @param newValue the new value for the widget. */ public void setValue(String newValue) { } }