/** * Copyright (C) 2008-2011 Daniel Senff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package de.danielsenff.imageflow; import ij.IJ; import ij.ImagePlus; import ij.WindowManager; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.ScrollPane; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.EventObject; import java.util.HashSet; import javax.imageio.ImageIO; import javax.swing.ActionMap; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JTree; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileFilter; import org.jdesktop.application.Action; import org.jdesktop.application.Application; import org.jdesktop.application.FrameView; import org.jdesktop.application.ResourceMap; import org.jdesktop.application.Task.BlockingScope; import visualap.Node; import visualap.Selection; import de.danielsenff.imageflow.controller.DelegatesController; import de.danielsenff.imageflow.controller.GraphController; import de.danielsenff.imageflow.controller.GraphControllerManager; import de.danielsenff.imageflow.gui.Dashboard; import de.danielsenff.imageflow.gui.DelegatesPanel; import de.danielsenff.imageflow.gui.DelegatesTreeListener; import de.danielsenff.imageflow.gui.GPanelPopup; import de.danielsenff.imageflow.gui.GraphPanel; import de.danielsenff.imageflow.gui.InsertUnitMenu; import de.danielsenff.imageflow.gui.StatusBar; import de.danielsenff.imageflow.imagej.MacroFlowRunner; import de.danielsenff.imageflow.imagej.MacroGenerator; import de.danielsenff.imageflow.models.Displayable; import de.danielsenff.imageflow.models.Model; import de.danielsenff.imageflow.models.ModelListener; import de.danielsenff.imageflow.models.SelectionList; import de.danielsenff.imageflow.models.SelectionList.SelectionListListener; import de.danielsenff.imageflow.models.connection.Connection; import de.danielsenff.imageflow.models.connection.ConnectionList; import de.danielsenff.imageflow.models.connection.Input; import de.danielsenff.imageflow.models.connection.Output; import de.danielsenff.imageflow.models.datatype.ImageDataType; import de.danielsenff.imageflow.models.parameter.Parameter; import de.danielsenff.imageflow.models.unit.GroupUnitElement; import de.danielsenff.imageflow.models.unit.UnitElement; import de.danielsenff.imageflow.models.unit.UnitFactory; import de.danielsenff.imageflow.models.unit.UnitList; import de.danielsenff.imageflow.models.unit.UnitModelComponent.Size; import de.danielsenff.imageflow.tasks.ExportMacroTask; import de.danielsenff.imageflow.tasks.GenerateMacroTask; import de.danielsenff.imageflow.tasks.ImportGraphTask; import de.danielsenff.imageflow.tasks.LoadFlowGraphTask; import de.danielsenff.imageflow.tasks.RunMacroTask; import de.danielsenff.imageflow.tasks.SaveFlowGraphTask; /** * Controller of one workspace. Contains all necessary data of an opened * graph. * @author danielsenff * */ public class ImageFlowView extends FrameView { // private static final Logger logger = Logger.getLogger(DocumentEditorView.class.getName()); private JDialog aboutBox; private CodePreviewDialog codePreviewBox; private GraphController graphController; private File file; private JPanel mainPanel; /** * Workspace panel */ protected GraphPanel graphPanel; private static JProgressBar progressBar; private StatusBar statusBar = null; /** * Document has been modified. */ private boolean modified = false; /** * Any number of nodes has been selected. */ private boolean selected = false; /** * Something is in the internal clipboard. */ private boolean paste = false; /** * Option to display the generated macro code is activated. */ private boolean showCode = false; /** * Option to display the ImageJ main windows on workflow execution. */ private boolean showImageJ = true; /** * Option to close any existing windows before executing the workflow. */ private boolean closeAll = false; private JCheckBoxMenuItem chkBoxDisplayUnit; private JCheckBoxMenuItem chkBoxCollapseIcon; private Dashboard dashboardPanel; /** * @param app */ public ImageFlowView(final Application app) { super(app); this.graphController = new GraphController(); GraphControllerManager.getInstance().setController(this.graphController); if (IJ.isMacOSX()) { System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("apple.awt.brushMetalRounded", "true"); } initComponents(); getApplication().addExitListener(new ConfirmExit()); setFile(new File("new document")); } private void initAppIcon() { try { this.getFrame().setIconImage( ImageIO.read(this.getClass().getResourceAsStream( getResourceString("mainFrame.icon")))); } catch (IOException e) { e.printStackTrace(); } } private void initComponents() { initAppIcon(); addComponents(); setMenuBar(addMenu()); // register listeners registerModelListeners(); } /** * Register the ModelListeners. */ private void registerModelListeners() { for (Node node : getNodes()) { UnitFactory.registerModelListener(node); } getNodes().addModelListener(new ModelListener() { public void modelChanged(Model model) { graphPanel.repaint(); setModified(true); } }); getConnections().addModelListener(new ModelListener() { public void modelChanged(Model model) { graphPanel.repaint(); setModified(true); } }); getSelections().addSelectionListListener(new SelectionListListener() { public void selectionChanged(SelectionList selections) { setSelected(selections.hasSelections()); } }); setModified(false); } /** * Adds all components of */ private JMenuBar addMenu() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(getResourceString("file.menu")); fileMenu.add(getAction("newDocument")); fileMenu.add(getAction("open")); fileMenu.add(getAction("save")); fileMenu.add(getAction("saveAs")); fileMenu.add(new JSeparator()); fileMenu.add(getAction("importGraph")); fileMenu.add(getAction("export")); fileMenu.add(new JSeparator()); fileMenu.add(getAction("generateMacro")); fileMenu.add(getAction("runMacro")); if(!IJ.isMacintosh()) { fileMenu.add(new JSeparator()); fileMenu.add(getAction("quit")); } JMenu editMenu = new JMenu(getResourceString("edit.menu")); editMenu.add(getAction("cut")); editMenu.add(getAction("copy")); editMenu.add(getAction("paste")); editMenu.add(getAction("unbind")); editMenu.add(getAction("delete")); // editMenu.add(getAction("clear")); // make new document instead editMenu.add(getAction("selectAll")); editMenu.add(getAction("addToDashboard")); editMenu.add(getAction("addOutputToDashboard")); editMenu.add(new JSeparator()); this.chkBoxDisplayUnit = new JCheckBoxMenuItem(getAction("setDisplayUnit")); editMenu.add(chkBoxDisplayUnit); this.chkBoxCollapseIcon = new JCheckBoxMenuItem(getAction("setUnitComponentSize")); editMenu.add(chkBoxCollapseIcon); editMenu.add(getAction("showUnitParameters")); editMenu.add(getAction("group")); JMenu viewMenu = new JMenu(getResourceString("view.menu")); viewMenu.add(new JCheckBoxMenuItem(getAction("alignElements"))); viewMenu.add(new JCheckBoxMenuItem(getAction("setDrawGrid"))); JMenu debugMenu = new JMenu(getResourceString("debug.menu")); debugMenu.add(getAction("debugPrintNodes")); debugMenu.add(getAction("debugPrintNodeDetails")); debugMenu.add(getAction("debugPrintEdges")); debugMenu.add(getAction("debugDrawClonedWorkflow")); debugMenu.add(getAction("debugDisplayImages")); debugMenu.add(getAction("debugUnitSubgraph")); debugMenu.add(new JSeparator()); debugMenu.add(getAction("exampleFlow1")); debugMenu.add(getAction("exampleFlow2")); debugMenu.add(getAction("exampleFlow3")); JMenu insertMenu = new InsertUnitMenu(graphPanel); /*JMenu windowMenu = new JMenu(getResourceString("window.menu")); windowMenu.add(getAction("minimize"));*/ JMenu helpMenu = new JMenu(getResourceString("help.menu")); helpMenu.add(getAction("openDevblogURL")); helpMenu.add(getAction("openImageJURL")); if(!IJ.isMacintosh()) { helpMenu.add(new JSeparator()); helpMenu.add(getAction("showAboutBox")); } menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(viewMenu); menuBar.add(insertMenu); menuBar.add(debugMenu); // menuBar.add(windowMenu); menuBar.add(helpMenu); menuBar.setVisible(true); return menuBar; } private void updateMenu() { if(!getSelections().isEmpty() && getSelections().size() == 1 && getSelections().get(0) instanceof UnitElement) { boolean isCollapsedIcon = ((UnitElement)getSelections().get(0)).getCompontentSize() == Size.SMALL; this.chkBoxCollapseIcon.setSelected(isCollapsedIcon); boolean isDisplayUnit = ((UnitElement)getSelections().get(0)).isDisplay(); this.chkBoxDisplayUnit.setSelected(isDisplayUnit); } else { this.chkBoxCollapseIcon.setSelected(false); this.chkBoxDisplayUnit.setSelected(false); } } /** * Adds all components to the JFrame */ private void addComponents() { ResourceMap resourceMap = getResourceMap(); ScrollPane graphScrollpane = initWorkspacePanel(resourceMap); JPanel sidePane = initSidebarPanel(); JSplitPane workspaceSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidePane, graphScrollpane); workspaceSplitPane.setEnabled(true); workspaceSplitPane.setOneTouchExpandable(true); // enables continuous redrawing while moving the JSplitPane-Divider workspaceSplitPane.setContinuousLayout(true); // init Dashboard dashboardPanel = new Dashboard(); graphController.setDashboard(dashboardPanel); JScrollPane dashboardScrollPane = new JScrollPane(dashboardPanel); JSplitPane dashWorkflowSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); dashWorkflowSplitPane.add(workspaceSplitPane); dashWorkflowSplitPane.add(dashboardScrollPane); // bottomPanel including start button, options and progress/messages JPanel bottomPanel = initBottomPanel(resourceMap); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add(dashWorkflowSplitPane, BorderLayout.CENTER); mainPanel.add(bottomPanel, BorderLayout.SOUTH); setComponent(mainPanel); dashWorkflowSplitPane.setDividerLocation(460); //dashWorkflowSplitPane.setDividerLocation(0.8); // for the moment unused, stub for making the app multi-document on osx //if (IJ.isMacOSX()) // getFrame().setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); } private JPanel initSidebarPanel() { DelegatesPanel delegatesPanel = new DelegatesPanel(this.getNodes()); DelegatesTreeListener delegatesTreeListener = new DelegatesTreeListener(getInstance(), graphController); JTree delegatesTree = delegatesPanel.getDelegatesTree(); delegatesTree.addMouseListener(delegatesTreeListener); delegatesTree.addKeyListener(delegatesTreeListener); delegatesTree.setDragEnabled(true); JPanel sidePane = new JPanel(); sidePane.setLayout(new BorderLayout()); sidePane.add(delegatesPanel, BorderLayout.CENTER); // setting of MinimumSize is required for drag-ability of JSplitPane sidePane.setMinimumSize(new Dimension(190, 100)); return sidePane; } private ScrollPane initWorkspacePanel(ResourceMap resourceMap) { //working area aka graphpanel GPanelPopup popup = new GPanelPopup(); graphPanel = new GraphPanel(popup, graphController); resourceMap.injectComponent(graphPanel); popup.setActivePanel(graphPanel); graphPanel.setSelections(getSelections()); ScrollPane graphScrollpane = new ScrollPane(); graphScrollpane.add(graphPanel); graphScrollpane.getInsets().bottom = 5; graphScrollpane.setPreferredSize(new Dimension(400, 300)); graphScrollpane.setMinimumSize(new Dimension(100, 100)); return graphScrollpane; } private JPanel initBottomPanel(ResourceMap resourceMap) { JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BorderLayout()); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); JButton buttonRun = new JButton(getAction("runMacro")); buttonPanel.add(buttonRun); JCheckBox chkShowImageJ = new JCheckBox(getResourceString("showImageJ")); chkShowImageJ.setSelected(this.showImageJ); resourceMap.injectComponent(chkShowImageJ); chkShowImageJ.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { showImageJ= ((JCheckBox)e.getSource()).isSelected(); }}); buttonPanel.add(chkShowImageJ); JCheckBox chkShowCode = new JCheckBox(getResourceString("showLog")); resourceMap.injectComponent(chkShowCode); chkShowCode.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { showCode = ((JCheckBox)e.getSource()).isSelected(); }}); buttonPanel.add(chkShowCode); JCheckBox chkCloseAll = new JCheckBox(getResourceString("closeAll.text")); chkCloseAll.setToolTipText(getResourceString("closeAll.shortDescription")); resourceMap.injectComponent(chkCloseAll); chkCloseAll.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { closeAll = ((JCheckBox)e.getSource()).isSelected(); }}); buttonPanel.add(chkCloseAll); bottomPanel.add(buttonPanel, BorderLayout.LINE_START); JPanel progressPanel = new JPanel(); progressPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); statusBar = new StatusBar(getApplication(), getContext().getTaskMonitor()); progressBar = new JProgressBar(); progressBar.setMinimum(0); progressBar.setMaximum(100); progressBar.setSize(150, 20); progressBar.setVisible(false); progressPanel.add(statusBar); bottomPanel.add(progressPanel, BorderLayout.LINE_END); return bottomPanel; } /* * getter and setters */ /** * @return */ public GraphController getGraphController() { return graphController; } private String getResourceString(final String key) { return getResourceMap().getString(key); } /** * @param newGraphController */ public void setGraphController(final GraphController newGraphController) { GraphController oldValue = this.graphController; this.graphController = newGraphController; GraphControllerManager.getInstance().setController(graphController); graphPanel.setGraphController(this.graphController); registerModelListeners(); firePropertyChange("graphController", oldValue, newGraphController); } /** * Returns a Singleton-Instance of a {@link CodePreviewDialog}. * @return */ public CodePreviewDialog showCodePreviewBox() { return new CodePreviewDialog(ImageFlow.getApplication().getMainFrame(), "Generated Macro"); } /** * JDialog for displaying macro code. * @author danielsenff * */ public class CodePreviewDialog extends JDialog { private final JTextArea ta; public CodePreviewDialog(final JFrame frame, final String title) { setPreferredSize(new Dimension(350,150)); ta = new JTextArea(); setLayout(new BorderLayout()); setTitle(title); final JScrollPane scrollPane = new JScrollPane(ta); add(scrollPane, BorderLayout.CENTER); pack(); setVisible(true); } public void setMacroCode(final String code) { this.ta.setText(code); } public String getMacroCode() { return this.ta.getText(); } } /** * Returns the current selection in the {@link GraphPanel}. * @return */ public final SelectionList getSelections() { return graphController.getSelections(); } private ImageFlowView getInstance() { return this; } /** * Convenience method for getting the UnitList of the current GraphController * @return the nodes */ public UnitList getNodes() { return graphController.getUnitElements(); } /** * @return the connections */ public ConnectionList getConnections() { return graphController.getConnections(); } /** * @return the progressBar */ public static synchronized final JProgressBar getProgressBar() { return progressBar; } /** * * @return */ public JPanel getMainPanel() { return this.mainPanel; } /** * Set the bound file property and update the GUI. * @param file */ public void setFile(final File file) { File oldValue = this.file; this.file = file; setTitleFilename(this.file.getName()); firePropertyChange("file", oldValue, this.file); } /** * True if the file value has been modified but not saved. The * default value of this property is false. * <p> * This is a bound read-only property. * * @return the value of the modified property. * @see #isModified */ public boolean isModified() { return this.modified; } /** * Returns true if a {@link UnitElement} is selected in the workflow. * @return */ public boolean isSelected() { return this.selected; } /** * Set filename displayed in the the frame title. * @param filename */ public void setTitleFilename(String filename) { String appId = getResourceString("Application.id"); getFrame().setTitle(filename + " - " + appId); } /** * Sets the modified flag. * @param modified */ public void setModified(final boolean modified) { boolean oldValue = this.modified; this.modified = modified; // on program start, file may not be initialized if(getFile() != null){ String changed = modified ? "*" : ""; setTitleFilename(getFile().getName() + changed); } firePropertyChange("modified", oldValue, this.modified); } /** * Returns true if a {@link UnitElement} is selected. * @param selected */ public void setSelected(final boolean selected) { boolean oldValue = this.selected; this.selected = selected; updateMenu(); firePropertyChange("selected", oldValue, this.selected); } /** * @return the paste */ public boolean isPaste() { return paste; } /** * @param hasPaste the hasPaste to set */ public void setPaste(boolean hasPaste) { boolean oldValue = this.paste; this.paste = hasPaste; firePropertyChange("paste", oldValue, hasPaste ); } /** * @return the showlog */ public boolean isShowlog() { return showCode; } /** * @param showcode the showlog to set */ public void setShowlog(boolean showcode) { boolean oldValue = showcode; this.showCode = showcode; firePropertyChange("showcode", oldValue, showcode); } /** * Returns the currently loaded workflow-file. * @return */ public File getFile() { return this.file; } /* * Action related stuff * */ /** * convenient Example workflow * @return * @throws MalformedURLException */ @Action public LoadFlowGraphTask exampleFlow1() throws MalformedURLException { clearWorkspace(); return new LoadFlowGraphTask( getFlowURL(getResourceString("exampleFlow1.Path"))); } /** * convenient Example workflow * @return * @throws MalformedURLException */ @Action public LoadFlowGraphTask exampleFlow2() throws MalformedURLException { clearWorkspace(); return new LoadFlowGraphTask( getFlowURL(getResourceString("exampleFlow2.Path"))); } /** * convenient Example workflow * @return * @throws MalformedURLException */ @Action public LoadFlowGraphTask exampleFlow3() throws MalformedURLException { clearWorkspace(); return new LoadFlowGraphTask( getFlowURL(getResourceString("exampleFlow3.Path"))); } private URL getFlowURL(String path) throws MalformedURLException { URL resourcePath = ImageFlowView.class.getClassLoader().getResource(DelegatesController.getClassResourceBase()); String protocol = resourcePath.getProtocol(); URL resourcesBase = null; if (protocol.equals("file")) { resourcesBase = new URL(resourcePath, DelegatesController.getAbsolutePathToWorkingFolder()); } else if (protocol.equals("jar")) { resourcesBase = new URL(resourcePath, "/"); } return new URL(resourcesBase, path); } /** * Converts the current workflow into a macro and executes it in ImageJ. * @return */ @Action (block = BlockingScope.ACTION) public RunMacroTask runMacro() { return new RunMacroTask(this.getApplication(), graphController, this.showImageJ, this.showCode, this.closeAll, false); } /** * Converts the current workflow into a macro and displays it. * @return */ @Action public GenerateMacroTask generateMacro() { return new GenerateMacroTask(this.getApplication(), graphController); } /** * A number of units are collapsed into one group-unit. */ @Action(enabledProperty = "selected") public void group() { if(getSelections().size() == 1 && getSelections().get(0) instanceof GroupUnitElement) { GroupUnitElement group = (GroupUnitElement) getSelections().get(0); graphController.ungroup(group); } else { for (Node node : getSelections()) { if(node instanceof GroupUnitElement) { System.out.println("Group disallowed: No conistent connections between units"); JOptionPane.showMessageDialog(this.getFrame(), "Groups may not be included in groups.", "Grouping refused", JOptionPane.INFORMATION_MESSAGE); return; } } try { graphController.group(); } catch (Exception e) { System.out.println("Group disallowed: No conistent connections between units"); JOptionPane.showMessageDialog(this.getFrame(), "This grouping of units is not permitted. " + '\n' + "The connected units need to form a conistent branch.", "Grouping refused", JOptionPane.WARNING_MESSAGE); } } } /** * A selected {@link GroupUnitElement} is exploded into it's original contents. */ @Action(enabledProperty = "selected") public void degroup() { if(getSelections().size() == 1 && getSelections().get(0) instanceof GroupUnitElement) { GroupUnitElement group = (GroupUnitElement) getSelections().get(0); graphController.ungroup(group); } } /** * Import workflow from XML. * The current workflow will remain and the second workflow will be added without replacement * @return * @throws MalformedURLException */ @Action public ImportGraphTask importGraph() throws MalformedURLException { final JFileChooser fc = new JFileChooser(); final String filesExtension = getResourceString("flowXMLFileExtension"); final String filesDesc = getResourceString("flowXMLFileExtensionDescription"); fc.setFileFilter(new DescriptiveFileFilter(filesExtension, filesDesc)); ImportGraphTask task = null; final int option = fc.showOpenDialog(null); if (option == JFileChooser.APPROVE_OPTION) { task = new ImportGraphTask(fc.getSelectedFile().toURI().toURL()); } return task; } /** * Action that toggles the display-status of a {@link UnitElement} */ @Action(enabledProperty = "selected") public void setDisplayUnit() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof Displayable) { final Displayable node = (Displayable) selectedElement; node.toggleDisplay(); } } graphPanel.repaint(); } @Action(enabledProperty = "selected") public void setSilentDisplayUnit() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { final UnitElement node = (UnitElement) selectedElement; node.toggleDisplaySilent(); } } graphPanel.repaint(); } @Action(enabledProperty = "selected") public void togglePropertiesOnDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { UnitElement unit = (UnitElement) selectedElement; if (!dashboardPanel.hasWidget(selectedElement.getNodeID())) { addPropertiesToDashboard(unit); } else { removePropertiesFromDashboard(unit); } } } dashboardPanel.revalidate(); dashboardPanel.repaint(); } @Action(enabledProperty = "selected") public void addToDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { addPropertiesToDashboard((UnitElement)selectedElement); } } dashboardPanel.revalidate(); } private void addPropertiesToDashboard(UnitElement unit) { graphController.addWidget(unit); } @Action(enabledProperty = "selected") public void removeFromDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { removePropertiesFromDashboard((UnitElement)selectedElement); } } dashboardPanel.revalidate(); } private void removePropertiesFromDashboard(UnitElement selectedElement) { dashboardPanel.removeWidget(selectedElement); } @Action(enabledProperty = "selected") public void togglePreviewOnDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { UnitElement unit = (UnitElement) selectedElement; if (!dashboardPanel.hasPreviewWidget(selectedElement.getNodeID()+"")) { addPreviewToDashboard(unit); } else { removePreviewFromDashboard(unit); } } } dashboardPanel.revalidate(); dashboardPanel.repaint(); } @Action(enabledProperty = "selected") public void addOutputToDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { addPreviewToDashboard(selectedElement); } } dashboardPanel.revalidate(); } private void addPreviewToDashboard(Node selectedElement) { UnitElement unit = (UnitElement) selectedElement; unit.setDisplaySilent(true); graphController.addPreviewWidget(unit); } @Action(enabledProperty = "selected") public void removeOutputFromDashboard() { for (Node selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { UnitElement unit = (UnitElement) selectedElement; removePreviewFromDashboard(unit); } } dashboardPanel.revalidate(); dashboardPanel.repaint(); } private void removePreviewFromDashboard(UnitElement unit) { unit.setDisplaySilent(false); dashboardPanel.removePreviewWidget(unit); } /** * Changes the icon size of a {@link UnitElement}. */ @Action(enabledProperty = "selected") public void setUnitComponentSize() { UnitElement unit; Size newSize; for (Object selectedElement : getSelections()) { if(selectedElement instanceof UnitElement) { unit = (UnitElement) selectedElement; newSize = (unit.getCompontentSize() == Size.BIG) ? Size.SMALL : Size.BIG; unit.setCompontentSize(newSize); } } graphPanel.repaint(); } /** * Activates to draw a grid on the workspace. */ @Action public void setDrawGrid() { boolean drawGrid = graphPanel.isDrawGrid() ? false : true; graphPanel.setDrawGrid(drawGrid); graphPanel.repaint(); } /** * Activates element alignment on the workspace. */ @Action public void alignElements() { boolean align = graphPanel.isAlign() ? false : true; graphPanel.setAlign(align); graphPanel.repaint(); } /** * Removes all connections of the selected {@link UnitElement} */ @Action(enabledProperty = "selected") public void unbind() { final Selection<Node> selection = getSelections(); for (final Node unit : selection) { getNodes().unbindUnit((UnitElement)unit); } graphPanel.repaint(); } /** * Deletes a selected {@link UnitElement} */ @Action(enabledProperty = "selected") public void delete() { final Selection<Node> selection = getSelections(); for (final Node node : selection) { graphController.removeNode(node); if (node instanceof UnitElement) { removePropertiesFromDashboard((UnitElement)node); dashboardPanel.removePreviewWidget((UnitElement) node); dashboardPanel.invalidate(); dashboardPanel.repaint(); } } graphPanel.repaint(); } /** * Selects all {@link UnitElement}s */ @Action public void selectAll() { final UnitList list = graphController.getUnitElements(); final Selection<Node> selection = getSelections(); selection.clear(); for (final Node unit : list) { selection.add(unit); } graphPanel.repaint(); } /** * Clears the workflow from all {@link UnitElement}s */ @Action public void clear() { clearWorkspace(); } /** * Cut {@link UnitElement} from the workflow. */ @Action(enabledProperty = "selected") public void cut() { final Selection<Node> selectedNodes = getSelections(); final ArrayList<Node> copyUnitsList = graphController.getCopyNodesList(); // TODO think about dashboards! if (selectedNodes.size() > 0) { // il problema java.util.ConcurrentModificationException � stato risolto introducendo la lista garbage final HashSet<Connection> garbage = new HashSet<Connection>(); copyUnitsList.clear(); for (final Node t : selectedNodes) { copyUnitsList.add(t); graphController.removeNode(t); } for (final Connection c : garbage) { graphController.getConnections().remove(c); } selectedNodes.clear(); setPaste(true); } graphPanel.repaint(); } /** * Copy {@link UnitElement} from the workflow. */ @Action(enabledProperty = "selected") public void copy() { final Selection<Node> selectedNodes = getSelections(); final ArrayList<Node> copyUnitsList = graphController.getCopyNodesList(); if (!selectedNodes.isEmpty()) { copyUnitsList.clear(); Node clone; for (final Node t : selectedNodes) { try { clone = t.clone(); clone.setLabel(t.getLabel()); copyUnitsList.add(clone); } catch (final CloneNotSupportedException e) { e.printStackTrace(); } setPaste(true); } } } /** * Paste {@link UnitElement} into the workflow. */ @Action(enabledProperty = "paste") public void paste() { final Selection<Node> selectedNodes = getSelections(); final ArrayList<Node> copyUnitsList = graphController.getCopyNodesList(); if (!copyUnitsList.isEmpty()) { selectedNodes.clear(); // this is added here so that the new pasted units are selected selectedNodes.addAll(copyUnitsList); copyUnitsList.clear(); Node clone; for (final Node t : selectedNodes) { try { t.setSelected(true); // retain a copy, in case he pastes several times clone = t.clone(); clone.setLabel(t.getLabel()); getNodes().add(t); copyUnitsList.add(clone); } catch(final CloneNotSupportedException ex) { // ErrorPrinter.printInfo("CloneNotSupportedException"); } } graphPanel.repaint(); } } /** * Opens a dialog with the available {@link Input} for the selected {@link UnitElement}. */ @Action(enabledProperty = "selected") public void showUnitParameters() { for (Node node : getSelections()) { if(node instanceof UnitElement) { //Point position = (Point) node.getOrigin().clone(); //position.translate(30, 30); // TODO get absolute px position of requesting unit ((UnitElement)node).showProperties(null); // giving null as location centers on screen } } } @Action(enabledProperty = "selected") public void debugUnitSubgraph() { for (Node node : getSelections()) { if(node instanceof UnitElement) { UnitList subgraph = graphController.getSubgraph((UnitElement)node); showSimpleListOfUnits(subgraph); } } } private void showSimpleListOfUnits(UnitList unitList) { final JDialog dialog = new JDialog(); final DefaultListModel lm = new DefaultListModel(); for (final Node node : unitList) { lm.addElement(node); } final JList list = new JList(lm); dialog.add(new ScrollPane().add(list)); dialog.pack(); dialog.setVisible(true); } /** * Creates a new document and an empty workflow. */ @Action public void newDocument() { if(isModified()) { int optionSave = showSaveConfirmation(); if(optionSave == JOptionPane.OK_OPTION) { save().run(); }else if(optionSave == JOptionPane.CANCEL_OPTION) { return; } } clearWorkspace(); setFile(new File("new document")); this.setModified(false); graphPanel.repaint(); } private void clearWorkspace() { getNodes().clear(); dashboardPanel.clear(); dashboardPanel.revalidate(); dashboardPanel.repaint(); } /** * Open a workflow file from hard drive. * @return * @throws MalformedURLException */ @Action public LoadFlowGraphTask open() throws MalformedURLException { if(isModified()) { final int optionSave = showSaveConfirmation(); if(optionSave == JOptionPane.OK_OPTION) { save().run(); }else if(optionSave == JOptionPane.CANCEL_OPTION) { return null; } } final JFileChooser fc = new JFileChooser(); final String fileExtension = getResourceString("flowXMLFileExtension"); final String filesDesc = getResourceString("flowXMLFileExtensionDescription"); fc.setFileFilter(new DescriptiveFileFilter("XML", filesDesc + " Version 1.0")); fc.setFileFilter(new DescriptiveFileFilter(fileExtension, filesDesc)); LoadFlowGraphTask task = null; final int option = fc.showOpenDialog(null); if (option == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); clearWorkspace(); task = new LoadFlowGraphTask(file.toURI().toURL()); } return task; } private int showSaveConfirmation() { return JOptionPane.showConfirmDialog(this.getFrame(), "The workflow has modifications that haven not been saved yet." +'\n'+"Do you want to save changes now?", "Save changes?", JOptionPane.INFORMATION_MESSAGE); } /** * Save the contents of the workspace to the current {@link #getFile file}. * <p> * The text is written to the file on a worker thread because we don't want to * block the EDT while the file system is accessed. To do that, this * Action method returns a new SaveFlowGraphTask instance. The task * is executed when the "save" Action's actionPerformed method runs. * The SaveFlowGraphTask is responsible for updating the GUI after it * has successfully completed saving the file. * @return * @see #getFile */ @Action(enabledProperty = "modified") public SaveFlowGraphTask save() { if(getFile().exists()) { return new SaveFlowGraphTask(getFile()); } else return saveAs(); } /** * Save the contents of the workspace to the current file. * <p> * This action is nearly identical to {@link #open open}. In * this case, if the user chooses a file, a {@code SaveFlowGraphTask} * is returned. Note that the selected file only becomes the * value of the {@code file} property if the file is saved * successfully. * @return */ @Action public SaveFlowGraphTask saveAs() { final JFileChooser fc = createFileChooser("saveAsFileChooser"); getResourceMap().injectComponents(fc); final String fileExtension = getResourceString("flowXMLFileExtension"); final String filesDesc = getResourceString("flowXMLFileExtensionDescription"); fc.setFileFilter(new DescriptiveFileFilter(fileExtension, filesDesc)); final int option = fc.showSaveDialog(getFrame()); SaveFlowGraphTask task = null; if (JFileChooser.APPROVE_OPTION == option) { File selectedFile = fc.getSelectedFile(); if(!selectedFile.getName().toLowerCase().endsWith("."+fileExtension)) { selectedFile = new File(selectedFile.getAbsoluteFile()+"."+fileExtension); } if(selectedFile.exists()) { final int response = JOptionPane.showConfirmDialog(getMainPanel(), "This file already exists. Do you want to overwrite it?", "Overwrite existing file?", JOptionPane.OK_CANCEL_OPTION); if (!(response == JOptionPane.OK_OPTION)) { return null; } } task = new SaveFlowGraphTask(selectedFile); } return task; } /** * Converts the current workflow into a macro and saves this to file. * @return */ @Action public ExportMacroTask export() { JFileChooser fc = createFileChooser("saveAsFileChooser"); getResourceMap().injectComponents(fc); fc.setFileFilter(new DescriptiveFileFilter( getResourceString("imageJMacroTXTFileExtension"), getResourceString("imageJMacroTXTFileExtensionDescription"))); fc.setFileFilter(new DescriptiveFileFilter( getResourceString("imageJMacroFileExtension"), getResourceString("imageJMacroFileExtensionDescription"))); int option = fc.showSaveDialog(getFrame()); ExportMacroTask task = null; if (JFileChooser.APPROVE_OPTION == option) { File selectedFile = fc.getSelectedFile(); if(selectedFile.exists()) { int response = JOptionPane.showConfirmDialog(this.getFrame(), "This file already exists. Do you want to overwrite it?", "Overwrite existing file?", JOptionPane.OK_CANCEL_OPTION); if (!(response == JOptionPane.OK_OPTION)) { return null; } } task = new ExportMacroTask(selectedFile, graphController); } return task; } private JFileChooser createFileChooser(String name) { JFileChooser fc = new JFileChooser(this.file); fc.setDialogTitle(getResourceString(name + ".dialogTitle")); return fc; } /** * Opens a dialog with the list of {@link UnitElement}s in the workflow. */ @Action public void debugPrintNodes() { System.out.println(graphController.toString()); showSimpleListOfUnits(getNodes()); } @Action public void debugDisplayImages() { final JDialog dialog = new JDialog(); final DefaultListModel lm = new DefaultListModel(); int imageID; for (int i = 1; i < WindowManager.getImageCount()+1; i++) { imageID = WindowManager.getNthImageID(i); ImagePlus ip = WindowManager.getImage(imageID); if (ip != null) { lm.addElement(ip); } } final JList list = new JList(lm); dialog.add(new ScrollPane().add(list)); dialog.pack(); dialog.setVisible(true); } /** * Opens a dialog with the list of {@link Connection} in the workflow. */ @Action public void debugPrintEdges() { final JDialog dialog = new JDialog(); final DefaultListModel lm = new DefaultListModel(); for (final Connection connection : getConnections()) { lm.addElement("Connection between "+ connection.getOutput().getName() + " and " + connection.getInput().getName()); } final JList list = new JList(lm); dialog.add(new ScrollPane().add(list)); dialog.pack(); dialog.setVisible(true); } /** * Simulates the cloning of workflows. * This is convenient for work on the {@link MacroFlowRunner} and * the {@link MacroGenerator}, because they use only a cloned * version of the actual workflow. This simulates the cloning to * check if the clone is identical. */ @Action public void debugDrawClonedWorkflow() { final JDialog dialog = new JDialog(); final GPanelPopup popup = new GPanelPopup(); final GraphPanel gpanel = new GraphPanel(popup, graphController); popup.setActivePanel(gpanel); final UnitList cloneUnitList = getNodes().clone(); gpanel.setNodeL(cloneUnitList); gpanel.setEdgeL(cloneUnitList.getConnections()); dialog.add(new ScrollPane().add(gpanel)); dialog.setSize(400, 300); dialog.setVisible(true); } /** * Opens a dialog with debugging information about the selected {@link UnitElement} */ @Action(enabledProperty = "selected") public void debugPrintNodeDetails() { DefaultListModel lm; JDialog dialog; UnitElement unit; for (Node node : getSelections()) { dialog = new JDialog(); dialog.setTitle(node.getLabel()); lm = new DefaultListModel(); lm.addElement(node.getOrigin()); if(node instanceof UnitElement) { unit = (UnitElement)node; // list parameters for (final Parameter parameter : unit.getParameters()) { lm.addElement(parameter); } for (final Input input : unit.getInputs()) { lm.addElement(input); lm.addElement("name:"+input.getName()); lm.addElement("datatype: "+input.getDataType()); if(input.getDataType() instanceof ImageDataType) lm.addElement("imagetype def:" +((ImageDataType)input.getDataType()).getImageBitDepth()); lm.addElement("connected to:"); lm.addElement(input.getConnection()); } for (final Output output : unit.getOutputs()) { lm.addElement(output); lm.addElement("name:"+output.getName()); lm.addElement("datatype:"+output.getDataType()); if(output.getDataType() instanceof ImageDataType) lm.addElement("imagetype:" +((ImageDataType)output.getDataType()).getImageBitDepth()); lm.addElement("connected to:"); for (Connection conn : output.getConnections()) { lm.addElement(conn); } } } final JList list = new JList(lm); dialog.add(new ScrollPane().add(list)); dialog.pack(); dialog.setVisible(true); } } /** * Opens a {@link JDialog} with the contents of a selected {@link GroupUnitElement}. */ @Action public void showGroupContents() { if(getSelections().size() == 1 && getSelections().get(0) instanceof GroupUnitElement) { final JDialog dialog = new JDialog(); final GroupUnitElement group = (GroupUnitElement) getSelections().get(0); // group.showGroupWindow(); dialog.setTitle(group.getLabel()); final GPanelPopup popup = new GPanelPopup(); final GraphPanel gpanel = new GraphPanel(popup, graphController); gpanel.setNodeL(group.getNodes()); popup.setActivePanel(gpanel); gpanel.setEdgeL(group.getInternalConnections()); dialog.add(new ScrollPane().add(gpanel)); dialog.setSize(400, 300); dialog.setVisible(true); } } /** * Opens the URL to the development blog of the project. */ @Action public void openDevblogURL() { try { ij.plugin.BrowserLauncher.openURL(getResourceString("openDevblogURL.Url")); } catch (final IOException e) { e.printStackTrace(); } } /** * Opens the URL to the ImageJ-website. */ @Action public void openImageJURL() { try { ij.plugin.BrowserLauncher.openURL(getResourceString("openImageJURL.Url")); } catch (final IOException e) { e.printStackTrace(); } } /** * Minimize this frame. */ @Action public void minimize() { this.getFrame().setState(Frame.ICONIFIED); } /** * Displays the About-dialog */ @Action public void showAboutBox() { if (aboutBox == null) { final JFrame mainFrame = getFrame(); aboutBox = new ImageFlowAboutBox(mainFrame); aboutBox.setLocationRelativeTo(mainFrame); } ImageFlow.getApplication().show(aboutBox); // aboutBox.setVisible(true); // ((ImageFlow)getApplication()).addWindow(aboutBox); } private javax.swing.Action getAction(String actionName) { ActionMap actionMap = getContext().getActionMap(ImageFlowView.class, this); return actionMap.get(actionName); } /** * The GraphPanel of this View-instance. * @return */ public GraphPanel getGraphPanel() { return this.graphPanel; } private class ConfirmExit implements Application.ExitListener { public boolean canExit(EventObject e) { if (isModified()) { int option = showSaveConfirmation(); if(option == JOptionPane.YES_OPTION) { save().run(); } else if (option == JOptionPane.CANCEL_OPTION) { return false; } } return true; } public void willExit(EventObject e) { } } /** This is a substitute for FileNameExtensionFilter, which is * only available on Java SE 6. */ private static class DescriptiveFileFilter extends FileFilter { private final String description; private final String extension; public DescriptiveFileFilter(String extension, String description) { this.extension = extension; this.description = extension + " - " +description; } @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String fileName = f.getName(); int i = fileName.lastIndexOf('.'); if ((i > 0) && (i < (fileName.length() - 1))) { String fileExt = fileName.substring(i + 1); if (extension.equalsIgnoreCase(fileExt)) { return true; } } return false; } @Override public String getDescription() { return description; } } public Dashboard getDashboard() { return this.dashboardPanel; } }