Merge branch 'Jalview-BH/JAL-3026' into Jalview-BH/JAL-3048_dialogs
authorJim Procter <jprocter@issues.jalview.org>
Mon, 2 Jul 2018 14:41:53 +0000 (15:41 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Mon, 2 Jul 2018 14:41:53 +0000 (15:41 +0100)
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/io/JalviewFileChooser.java
src/jalview/util/ImageMaker.java
src/jalview/util/dialogrunner/DialogRunner.java [new file with mode: 0644]
src/jalview/util/dialogrunner/DialogRunnerI.java [new file with mode: 0644]
src/jalview/util/dialogrunner/Response.java [new file with mode: 0644]
src/jalview/util/dialogrunner/RunResponse.java [new file with mode: 0644]
test/jalview/util/dialogrunner/DialogRunnerTest.java [new file with mode: 0644]
test/jalview/util/dialogrunner/ResponseTest.java [new file with mode: 0644]
test/jalview/util/dialogrunner/RunResponseTest.java [new file with mode: 0644]

index aa0a1fe..e45dc5d 100644 (file)
@@ -88,6 +88,7 @@ import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.TCoffeeColourScheme;
 import jalview.util.MessageManager;
+import jalview.util.dialogrunner.RunResponse;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.ViewportRanges;
 import jalview.ws.DBRefFetcher;
@@ -1122,42 +1123,41 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     String format = currentFileFormat == null ? null
             : currentFileFormat.getName();
-    JalviewFileChooser chooser = JalviewFileChooser
+    final JalviewFileChooser chooser = JalviewFileChooser
             .forWrite(Cache.getProperty("LAST_DIRECTORY"), format);
-
+    final AlignFrame us = this;
     chooser.setFileView(new JalviewFileView());
     chooser.setDialogTitle(
             MessageManager.getString("label.save_alignment_to_file"));
     chooser.setToolTipText(MessageManager.getString("action.save"));
 
-    int value = chooser.showSaveDialog(this);
-
-
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    chooser.response(new RunResponse(JalviewFileChooser.APPROVE_OPTION)
     {
-      currentFileFormat = chooser.getSelectedFormat();
-      while (currentFileFormat == null)
-      {
-        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                MessageManager.getString(
-                        "label.select_file_format_before_saving"),
-                MessageManager.getString("label.file_format_not_specified"),
-                JvOptionPane.WARNING_MESSAGE);
+      @Override
+      public void run()
+        {
         currentFileFormat = chooser.getSelectedFormat();
-        value = chooser.showSaveDialog(this);
-        if (value != JalviewFileChooser.APPROVE_OPTION)
+        while (currentFileFormat == null)
         {
-          return;
+          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                  MessageManager.getString(
+                          "label.select_file_format_before_saving"),
+                  MessageManager
+                          .getString("label.file_format_not_specified"),
+                  JvOptionPane.WARNING_MESSAGE);
+          currentFileFormat = chooser.getSelectedFormat();
+          chooser.showSaveDialog(us);
         }
-      }
 
-      fileName = chooser.getSelectedFile().getPath();
+        fileName = chooser.getSelectedFile().getPath();
 
-      Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
+        Cache.setProperty("DEFAULT_FILE_FORMAT",
+                currentFileFormat.getName());
 
-      Cache.setProperty("LAST_DIRECTORY", fileName);
-      saveAlignment(fileName, currentFileFormat);
-    }
+        Cache.setProperty("LAST_DIRECTORY", fileName);
+        saveAlignment(fileName, currentFileFormat);
+      }
+    }).showSaveDialog(this);
   }
 
   public boolean saveAlignment(String file, FileFormatI format)
@@ -3915,33 +3915,40 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setToolTipText(
             MessageManager.getString("label.load_tree_file"));
 
-    int value = chooser.showOpenDialog(null);
-
-    if (value == JalviewFileChooser.APPROVE_OPTION)
-    {
-      String filePath = chooser.getSelectedFile().getPath();
-      Cache.setProperty("LAST_DIRECTORY", filePath);
-      NewickFile fin = null;
-      try
-      {
-        fin = new NewickFile(filePath, DataSourceType.FILE);
-        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
-      } catch (Exception ex)
-      {
-        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
-                MessageManager.getString("label.problem_reading_tree_file"),
-                JvOptionPane.WARNING_MESSAGE);
-        ex.printStackTrace();
-      }
-      if (fin != null && fin.hasWarningMessage())
-      {
-        JvOptionPane.showMessageDialog(Desktop.desktop,
-                fin.getWarningMessage(),
-                MessageManager
-                        .getString("label.possible_problem_with_tree_file"),
-                JvOptionPane.WARNING_MESSAGE);
-      }
-    }
+    chooser.response(
+            new jalview.util.dialogrunner.RunResponse(JalviewFileChooser.APPROVE_OPTION)
+            {
+              @Override
+              public void run()
+              {
+                String filePath = chooser.getSelectedFile().getPath();
+                Cache.setProperty("LAST_DIRECTORY", filePath);
+                NewickFile fin = null;
+                try
+                {
+                  fin = new NewickFile(new FileParse(
+                          chooser.getSelectedFile(), DataSourceType.FILE));
+                  viewport.setCurrentTree(
+                          showNewickTree(fin, filePath).getTree());
+                } catch (Exception ex)
+                {
+                  JvOptionPane.showMessageDialog(Desktop.desktop,
+                          ex.getMessage(),
+                          MessageManager.getString(
+                                  "label.problem_reading_tree_file"),
+                          JvOptionPane.WARNING_MESSAGE);
+                  ex.printStackTrace();
+                }
+                if (fin != null && fin.hasWarningMessage())
+                {
+                  JvOptionPane.showMessageDialog(Desktop.desktop,
+                          fin.getWarningMessage(),
+                          MessageManager.getString(
+                                  "label.possible_problem_with_tree_file"),
+                          JvOptionPane.WARNING_MESSAGE);
+                }
+              }
+            }).openDialog(this);
   }
 
   public TreePanel showNewickTree(NewickFile nf, String treeTitle)
index 06c3b36..774dd4c 100644 (file)
@@ -44,6 +44,7 @@ import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.UrlConstants;
+import jalview.util.dialogrunner.RunResponse;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.ParamManager;
 import jalview.ws.utils.UrlDownloadClient;
@@ -105,7 +106,6 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
-import javax.swing.JFileChooser;
 import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
@@ -344,9 +344,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   public Desktop()
   {
     /**
-     * A note to implementors. It is ESSENTIAL that any activities that might
-     * block are spawned off as threads rather than waited for during this
-     * constructor.
+     * A note to implementors. It is ESSENTIAL that any activities that might block
+     * are spawned off as threads rather than waited for during this constructor.
      */
     instance = this;
     doVamsasClientCheck();
@@ -371,13 +370,12 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     // This line prevents Windows Look&Feel resizing all new windows to maximum
     // if previous window was maximised
-    desktop.setDesktopManager(
-            new MyDesktopManager(
-                    (Platform.isWindows() ? new DefaultDesktopManager()
-                            : Platform.isAMac()
-                                    ? new AquaInternalFrameManager(
-                                            desktop.getDesktopManager())
-                                    : desktop.getDesktopManager())));
+    desktop.setDesktopManager(new MyDesktopManager(
+            (Platform.isWindows() ? new DefaultDesktopManager()
+                    : Platform.isAMac()
+                            ? new AquaInternalFrameManager(
+                                    desktop.getDesktopManager())
+                            : desktop.getDesktopManager())));
 
     Rectangle dims = getLastKnownDimensions("");
     if (dims != null)
@@ -543,10 +541,10 @@ public class Desktop extends jalview.jbgui.GDesktop
      * @j2sNative
      */
     {
-    final Desktop me = this;
-    // Thread off the news reader, in case there are connection problems.
-    addDialogThread(new Runnable()
-    {
+      final Desktop me = this;
+      // Thread off the news reader, in case there are connection problems.
+      addDialogThread(new Runnable()
+      {
         @Override
         public void run()
         {
@@ -556,7 +554,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           showNews.setVisible(true);
           Cache.log.debug("Completed news thread.");
         }
-    });
+      });
     }
   }
 
@@ -983,8 +981,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
-   * the window
+   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
+   * window
    * 
    * @param frame
    */
@@ -1129,7 +1127,7 @@ public class Desktop extends jalview.jbgui.GDesktop
             MessageManager.getString("label.open_local_file"));
     chooser.setToolTipText(MessageManager.getString("action.open"));
 
-    chooser.setCallback(new Runnable()
+    chooser.response(new RunResponse(JalviewFileChooser.APPROVE_OPTION)
     {
 
       @Override
@@ -1160,14 +1158,7 @@ public class Desktop extends jalview.jbgui.GDesktop
         new FileLoader().LoadFile(viewport, selectedFile,
                 DataSourceType.FILE, format);
       }
-    });
-
-    int value = chooser.showOpenDialog(this);
-    if (value == JFileChooser.APPROVE_OPTION)
-    {
-      chooser.getCallback().run();
-    }
-
+    }).openDialog(this);
   }
 
   /**
@@ -1215,9 +1206,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     String dialogOption = "label.input_alignment_from_url";
     desktop.dialogData = new Object[] { dialogOption, viewport, history };
-    desktop.onDialogReturn(
-            JvOptionPane.showInternalConfirmDialog(desktop, panel,
-            MessageManager.getString(dialogOption),
+    desktop.onDialogReturn(JvOptionPane.showInternalConfirmDialog(desktop,
+            panel, MessageManager.getString(dialogOption),
             JvOptionPane.OK_CANCEL_OPTION));
 
     // no code may follow this, as SwingJS will not block
@@ -1227,7 +1217,6 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   }
 
-
   /**
    * Opens the CutAndPaste window for the user to paste an alignment in to
    * 
@@ -1949,9 +1938,9 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * Gather expanded views (separate AlignFrame's) with the same sequence set
-   * identifier back in to this frame as additional views, and close the
-   * expanded views. Note the expanded frames may themselves have multiple
-   * views. We take the lot.
+   * identifier back in to this frame as additional views, and close the expanded
+   * views. Note the expanded frames may themselves have multiple views. We take
+   * the lot.
    * 
    * @param source
    */
@@ -2452,8 +2441,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * Proxy class for JDesktopPane which optionally displays the current memory
-   * usage and highlights the desktop area with a red bar if free memory runs
-   * low.
+   * usage and highlights the desktop area with a red bar if free memory runs low.
    * 
    * @author AMW
    */
@@ -2587,7 +2575,7 @@ public class Desktop extends jalview.jbgui.GDesktop
             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                     MessageManager.formatMessage("label.couldnt_locate",
                             new Object[]
-                    { url }),
+                            { url }),
                     MessageManager.getString("label.url_not_found"),
                     JvOptionPane.WARNING_MESSAGE);
 
@@ -2698,7 +2686,6 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
   }
 
-
   /**
    * Accessor method to quickly get all the AlignmentFrames loaded.
    * 
@@ -2846,8 +2833,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
-   * binding when opened
+   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
+   * when opened
    */
   protected void addQuitHandler()
   {
@@ -2980,9 +2967,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * This will return the first AlignFrame holding the given viewport instance.
-   * It will break if there are more than one AlignFrames viewing a particular
-   * av.
+   * This will return the first AlignFrame holding the given viewport instance. It
+   * will break if there are more than one AlignFrames viewing a particular av.
    * 
    * @param viewport
    * @return alignFrame for viewport
@@ -3348,8 +3334,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * Explode the views in the given SplitFrame into separate SplitFrame windows.
-   * This respects (remembers) any previous 'exploded geometry' i.e. the size
-   * and location last time the view was expanded (if any). However it does not
+   * This respects (remembers) any previous 'exploded geometry' i.e. the size and
+   * location last time the view was expanded (if any). However it does not
    * remember the split pane divider location - this is set to match the
    * 'exploding' frame.
    * 
@@ -3519,25 +3505,26 @@ public class Desktop extends jalview.jbgui.GDesktop
           List<DataSourceType> protocols, DropTargetDropEvent evt,
           Transferable t) throws Exception
   {
-    
+
     // BH 2018 changed List<String> to List<Object> to allow for File from SwingJS
 
-//    DataFlavor[] flavors = t.getTransferDataFlavors();
-//    for (int i = 0; i < flavors.length; i++) {
-//      if (flavors[i].isFlavorJavaFileListType()) {
-//              evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
-//              List<File> list = (List<File>) t.getTransferData(flavors[i]);
-//              for (int j = 0; j < list.size(); j++) {
-//                      File file = (File) list.get(j);
-//                      byte[] data = getDroppedFileBytes(file);
-//                      fileName.setText(file.getName() + " - " + data.length + " " + evt.getLocation());
-//                      JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
-//                      target.setText(new String(data));
-//              }
-//              dtde.dropComplete(true);
-//              return;
-//      }
-//
+    // DataFlavor[] flavors = t.getTransferDataFlavors();
+    // for (int i = 0; i < flavors.length; i++) {
+    // if (flavors[i].isFlavorJavaFileListType()) {
+    // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+    // List<File> list = (List<File>) t.getTransferData(flavors[i]);
+    // for (int j = 0; j < list.size(); j++) {
+    // File file = (File) list.get(j);
+    // byte[] data = getDroppedFileBytes(file);
+    // fileName.setText(file.getName() + " - " + data.length + " " +
+    // evt.getLocation());
+    // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
+    // target.setText(new String(data));
+    // }
+    // dtde.dropComplete(true);
+    // return;
+    // }
+    //
 
     DataFlavor uriListFlavor = new DataFlavor(
             "text/uri-list;class=java.lang.String"), urlFlavour = null;
@@ -3555,15 +3542,15 @@ public class Desktop extends jalview.jbgui.GDesktop
 
       try
       {
-      java.net.URL url = (URL) t.getTransferData(urlFlavour);
+        java.net.URL url = (URL) t.getTransferData(urlFlavour);
         // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
         // means url may be null.
-      if (url != null)
-      {
-        protocols.add(DataSourceType.URL);
-        files.add(url.toString());
-        Cache.log.debug("Drop handled as URL dataflavor "
-                + files.get(files.size() - 1));
+        if (url != null)
+        {
+          protocols.add(DataSourceType.URL);
+          files.add(url.toString());
+          Cache.log.debug("Drop handled as URL dataflavor "
+                  + files.get(files.size() - 1));
           return;
         }
         else
@@ -3574,7 +3561,7 @@ public class Desktop extends jalview.jbgui.GDesktop
                     "Please ignore plist error - occurs due to problem with java 8 on OSX");
           }
           ;
-      }
+        }
       } catch (Throwable ex)
       {
         Cache.log.debug("URL drop handler failed.", ex);
@@ -3698,9 +3685,11 @@ public class Desktop extends jalview.jbgui.GDesktop
                 && (source.endsWith(".lnk") || source.endsWith(".url")
                         || source.endsWith(".site")))
         {
-          try {
+          try
+          {
             Object obj = files.get(f);
-            File lf = (obj instanceof File ? (File) obj : new File((String) obj));
+            File lf = (obj instanceof File ? (File) obj
+                    : new File((String) obj));
             // process link file to get a URL
             Cache.log.debug("Found potential link file: " + lf);
             WindowsShortcut wscfile = new WindowsShortcut(lf);
@@ -3709,10 +3698,11 @@ public class Desktop extends jalview.jbgui.GDesktop
             files.set(f, fullname);
             Cache.log.debug("Parsed real filename " + fullname
                     + " to extract protocol: " + protocols.get(f));
-          }
-          catch (Exception ex)
+          } catch (Exception ex)
           {
-            Cache.log.error("Couldn't parse "+files.get(f)+" as a link file.",ex);
+            Cache.log.error(
+                    "Couldn't parse " + files.get(f) + " as a link file.",
+                    ex);
           }
         }
       }
@@ -3730,10 +3720,10 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * Answers a (possibly empty) list of any structure viewer frames (currently
-   * for either Jmol or Chimera) which are currently open. This may optionally
-   * be restricted to viewers of a specified class, or viewers linked to a
-   * specified alignment panel.
+   * Answers a (possibly empty) list of any structure viewer frames (currently for
+   * either Jmol or Chimera) which are currently open. This may optionally be
+   * restricted to viewers of a specified class, or viewers linked to a specified
+   * alignment panel.
    * 
    * @param apanel
    *          if not null, only return viewers linked to this panel
index ae6c9eb..cf72ffb 100755 (executable)
@@ -25,6 +25,9 @@ import jalview.bin.Cache;
 import jalview.gui.JvOptionPane;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.util.dialogrunner.DialogRunnerI;
+import jalview.util.dialogrunner.Response;
+import jalview.util.dialogrunner.RunResponse;
 
 import java.awt.Component;
 import java.awt.Dimension;
@@ -58,8 +61,10 @@ import javax.swing.plaf.basic.BasicFileChooserUI;
  *
  */
 public class JalviewFileChooser extends JFileChooser
-        implements PropertyChangeListener
+        implements PropertyChangeListener, DialogRunnerI
 {
+  jalview.util.dialogrunner.DialogRunner<JalviewFileChooser> runner = new jalview.util.dialogrunner.DialogRunner<>(
+          this);
   /**
    * Factory method to return a file chooser that offers readable alignment file
    * formats
@@ -159,25 +164,14 @@ public class JalviewFileChooser extends JFileChooser
     }
   }
 
-  private Runnable theCallback;
-
-  public void setCallback(Runnable callback)
-  {
-    this.theCallback = callback;
-  }
-
-  public Runnable getCallback()
-  {
-    return theCallback;
-  }
-
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
+    // TODO other properties need runners...
     switch (evt.getPropertyName())
     {
     case "SelectedFile": 
-      theCallback.run();
+      runner.run(APPROVE_OPTION);
       break;
     }
   }
@@ -197,6 +191,12 @@ public class JalviewFileChooser extends JFileChooser
     return f;
   }
 
+  public void openDialog(Component parent)
+  {
+    int value = showOpenDialog(this);
+    runner.run(value);
+  }
+
   /**
    * 
    * @param formats
@@ -304,44 +304,80 @@ public class JalviewFileChooser extends JFileChooser
     return null;
   }
 
-  @Override
-  public int showSaveDialog(Component parent) throws HeadlessException
+  Component saveparent;
+  RunResponse overwriteCheck = new RunResponse(
+          JalviewFileChooser.APPROVE_OPTION)
   {
-    this.setAccessory(null);
+    @Override
+    public void run()
+    {
+      // JBP Note - this code was executed regardless of 'SAVE' being pressed
+      // need to see if there were side effects
+      if (getFileFilter() instanceof JalviewFileFilter)
+      {
+        JalviewFileFilter jvf = (JalviewFileFilter) getFileFilter();
 
-    setDialogType(SAVE_DIALOG);
+        if (!jvf.accept(getSelectedFile()))
+        {
+          String withExtension = getSelectedFile() + "."
+                  + jvf.getAcceptableExtension();
+          setSelectedFile(new File(withExtension));
+        }
+      }
+      // All good, so we continue to save
+      returned = new Response(JalviewFileChooser.APPROVE_OPTION);
+
+      // TODO: ENSURE THAT FILES SAVED WITH A ':' IN THE NAME ARE REFUSED AND THE
+      // USER PROMPTED FOR A NEW FILENAME
+      /**
+       * @j2sNative
+       */
+      {
+        if (getSelectedFile().exists())
+        {
+          // TODO JAL-3048 - may not need to raise this for browser saves
+
+          // yes/no cancel
+          int confirm = JvOptionPane.showConfirmDialog(saveparent,
+                  MessageManager.getString("label.overwrite_existing_file"),
+                  MessageManager.getString("label.file_already_exists"),
+                  JvOptionPane.YES_NO_OPTION);
 
-    int ret = showDialog(parent, MessageManager.getString("action.save"));
+          if (confirm != JvOptionPane.YES_OPTION)
+          {
+            returned = new Response(JalviewFileChooser.CANCEL_OPTION);
+          }
+        }
+      }
+    };
+  };
 
+  @Override
+  public int showSaveDialog(Component parent) throws HeadlessException
+  {
+    this.setAccessory(null);
 
-    if (getFileFilter() instanceof JalviewFileFilter)
+    /*
+     * Save dialog is opened until user picks a file format 
+     */
+    if (!runner.isRegistered(overwriteCheck))
     {
-      JalviewFileFilter jvf = (JalviewFileFilter) getFileFilter();
-
-      if (!jvf.accept(getSelectedFile()))
-      {
-        String withExtension = getSelectedFile() + "."
-                + jvf.getAcceptableExtension();
-        setSelectedFile(new File(withExtension));
-      }
+      // first call for this instance
+      runner.firstResponse(overwriteCheck);
     }
-    // TODO: ENSURE THAT FILES SAVED WITH A ':' IN THE NAME ARE REFUSED AND THE
-    // USER PROMPTED FOR A NEW FILENAME
-    if ((ret == JalviewFileChooser.APPROVE_OPTION)
-            && getSelectedFile().exists())
+    else
     {
-      int confirm = JvOptionPane.showConfirmDialog(parent,
-              MessageManager.getString("label.overwrite_existing_file"),
-              MessageManager.getString("label.file_already_exists"),
-              JvOptionPane.YES_NO_OPTION);
-
-      if (confirm != JvOptionPane.YES_OPTION)
-      {
-        ret = JalviewFileChooser.CANCEL_OPTION;
-      }
+      // reset response flags
+      runner.resetResponses();
     }
 
-    return ret;
+    setDialogType(SAVE_DIALOG);
+    saveparent = parent;
+
+    int value = showDialog(parent, MessageManager.getString("action.save"));
+    
+    runner.run(value);
+    return value;
   }
 
   void recentListSelectionChanged(Object selection)
@@ -436,4 +472,10 @@ public class JalviewFileChooser extends JFileChooser
 
   }
 
+  @Override
+  public JalviewFileChooser response(RunResponse action)
+  {
+    return runner.response(action);
+  }
+
 }
index 3398fea..cf5a6b6 100755 (executable)
@@ -146,7 +146,7 @@ public class ImageMaker
                 "status.cancelled_image_export_operation", type.name));
       }
     }
-
+    // TODO JAL-3048 refactor to method called by constructor or callback
     if (file != null)
     {
       try
diff --git a/src/jalview/util/dialogrunner/DialogRunner.java b/src/jalview/util/dialogrunner/DialogRunner.java
new file mode 100644 (file)
index 0000000..b631128
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util.dialogrunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * daft gymnastics to allow Dialogs to extend from a Swing class and use this
+ * class to implement chained Response run() definition and execution.
+ * 
+ * There is probably a better way of doing this.
+ * 
+ * @author jprocter
+ *
+ * @param <T>
+ *          the actual dialog that will be shown - which will also initiate the
+ *          response chain.
+ */
+public class DialogRunner<T extends DialogRunnerI> implements DialogRunnerI
+{
+
+  private Map<Response, List<RunResponse>> callbacks = new java.util.HashMap<>();
+
+  public T dialog;
+
+  public DialogRunner(T ourDialog)
+  {
+    dialog = ourDialog;
+  }
+
+  /**
+   * clear all 'was ran' flags so responses can be called again, and firstRun will
+   * trigger response execution
+   */
+  public void resetResponses()
+  {
+    for (List<RunResponse> lr : callbacks.values())
+    {
+      for (RunResponse response : lr)
+      {
+        response.reset();
+      }
+    }
+    responses.clear();
+    firstRunWasCalled = false;
+  }
+
+  @Override
+  public T response(RunResponse action)
+  {
+    return addResponse(false, action);
+  }
+
+  /**
+   * insert a response at the beginning of the chain for the action. Useful to add
+   * pre-action validations local to the Dialog class
+   * 
+   * @param action
+   * @return
+   */
+  public T firstResponse(RunResponse action)
+  {
+    return addResponse(true, action);
+  }
+
+  protected T addResponse(boolean prePend, RunResponse action)
+  {
+    List<RunResponse> laction = callbacks.get(action.ourTrigger);
+    if (laction == null)
+    {
+      laction = new ArrayList<>();
+      callbacks.put(action.ourTrigger, laction);
+    }
+    if (prePend)
+    {
+      laction.add(0,action);
+    } else {
+      laction.add(action);
+    }
+    return dialog;
+  }
+
+  /**
+   * 
+   * @param action
+   * @return true if action is a registered callback
+   */
+  public boolean isRegistered(RunResponse action)
+  {
+    List<RunResponse> resp = callbacks.get(action.ourTrigger);
+    if (resp != null)
+    {
+      for (RunResponse r : resp)
+      {
+        if (r == action)
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+  /**
+   * handle a response
+   * 
+   * @param responseCode
+   */
+  public void run(int responseCode)
+  {
+    run(new Response(responseCode));
+  }
+
+  public void run(String responseString)
+  {
+    run(new Response(responseString));
+  }
+
+  public void run(Object responseObj)
+  {
+    run(new Response(responseObj));
+  }
+
+  /**
+   * start of response handling.
+   * 
+   * @param responseCode
+   */
+  public void firstRun(int responseCode)
+  {
+    doFirstRun(new Response(responseCode));
+  }
+
+  public void firstRun(String responseString)
+  {
+    doFirstRun(new Response(responseString));
+  }
+
+  public void firstRun(Object responseObj)
+  {
+    doFirstRun(new Response(responseObj));
+  }
+
+
+  boolean firstRunWasCalled = false;
+
+  private void doFirstRun(Response response)
+  {
+    if (firstRunWasCalled)
+    {
+      return;
+    }
+    firstRunWasCalled = true;
+    run(response);
+  }
+
+  private void run(Response response)
+  {
+    responses.add(response);
+    List<RunResponse> laction = callbacks.get(response);
+
+    if (laction == null)
+    {
+      System.err.println("Doing nothing for " + response);
+      return;
+    }
+    boolean wasRun = false;
+    int num = 0;
+    for (RunResponse action : laction)
+    {
+      num++;
+      // find next action to execute
+      if (!action.wasRun)
+      {
+        System.err
+                .println("Executing action (" + num + ") for " + response);
+        wasRun = true;
+        action.wasRun = true;
+        action.run();
+        if (action.returned != null)
+        {
+          run(action.returned);
+        }
+        break;
+      }
+    }
+    if (!wasRun)
+    {
+      System.err.println("Did nothing for " + response);
+    }
+  }
+
+  List<Response> responses = new ArrayList<>();
+
+}
diff --git a/src/jalview/util/dialogrunner/DialogRunnerI.java b/src/jalview/util/dialogrunner/DialogRunnerI.java
new file mode 100644 (file)
index 0000000..aaeb304
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util.dialogrunner;
+
+/**
+ * functional pattern for blocking dialog response handling
+ * 
+ * @author jprocter
+ *
+ */
+public interface DialogRunnerI<T extends DialogRunnerI>
+{
+
+  /**
+   * define a new response for this dialog. eg. dialog.response(new
+   * RunResponse(OK_PRessed) { run()...}).response(new RunResponse(CANCEL_PRESSED)
+   * { ... });
+   * 
+   * @param action
+   * @return the dialog
+   */
+  T response(RunResponse action);
+}
diff --git a/src/jalview/util/dialogrunner/Response.java b/src/jalview/util/dialogrunner/Response.java
new file mode 100644 (file)
index 0000000..15df8f8
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util.dialogrunner;
+
+public class Response
+{
+  int type = 0; // int = 0, String = 1, Object = 2;
+
+  int intresp;
+
+  String stringresp;
+
+  Object objresp;
+
+  public Response(int response)
+  {
+    type = 0;
+    intresp = response;
+  }
+
+  public Response(String response)
+  {
+    type = 1;
+    stringresp = response;
+  }
+
+  public Response(Object response)
+  {
+    if (response instanceof String)
+    {
+      type = 1;
+      stringresp = (String) response;
+      return;
+    }
+    if (response instanceof Integer)
+    {
+      type = 0;
+      intresp = ((Integer) response).intValue();
+      return;
+    }
+    objresp = response;
+    type = 2;
+  }
+
+  @Override
+  public boolean equals(Object obj)
+  {
+    if (obj == null || !(obj instanceof Response))
+    {
+      return false;
+    }
+    ;
+    if (((Response) obj).type == type)
+    {
+      switch (type)
+      {
+      case 0:
+        return ((((Response) obj).intresp) == intresp);
+      case 1:
+        return (((Response) obj).stringresp.equals(stringresp));
+      case 2:
+        return (((Response) obj).objresp).equals(objresp);
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode()
+  {
+    switch (type)
+    {
+    case 0:
+      return Integer.valueOf(intresp).hashCode();
+    case 1:
+      return stringresp.hashCode();
+    case 2:
+      return objresp.hashCode();
+    }
+    return super.hashCode();
+  }
+
+  @Override
+  public String toString()
+  {
+    switch (type)
+    {
+    case 0:
+      return "DialogRunner int: " + intresp;
+    case 1:
+      return "DialogRunner str: '" + stringresp + "'";
+    case 2:
+      return "DialogRunner obj: " + objresp.toString();
+    }
+    return "Unconfigured response.";
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/util/dialogrunner/RunResponse.java b/src/jalview/util/dialogrunner/RunResponse.java
new file mode 100644 (file)
index 0000000..acc53f0
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util.dialogrunner;
+
+/**
+ * A Runnable that is kind of like a future, that allows a sequence of Runabbles
+ * to be conditionally executed by DialogRunner
+ * 
+ * @author jprocter
+ *
+ */
+public abstract class RunResponse implements Runnable
+{
+  /**
+   * Response that triggers the Run method
+   */
+  public Response ourTrigger;
+
+  /**
+   * set by run() on exit
+   */
+  public Response returned = null;
+
+  /**
+   * set by dialog runner
+   */
+  public boolean wasRun = false;
+
+  public RunResponse(int trigger)
+  {
+    ourTrigger = new Response(trigger);
+  }
+
+  public RunResponse(Object trigger)
+  {
+    ourTrigger = new Response(trigger);
+  }
+
+  public RunResponse(String trigger)
+  {
+    ourTrigger = new Response(trigger);
+  }
+
+  public RunResponse(Response trigger)
+  {
+    ourTrigger = trigger;
+  }
+
+  public void reset()
+  {
+    wasRun = false;
+    returned = null;
+
+  }
+
+  @Override
+  public String toString()
+  {
+    return "Runner for " + ourTrigger;
+  }
+}
diff --git a/test/jalview/util/dialogrunner/DialogRunnerTest.java b/test/jalview/util/dialogrunner/DialogRunnerTest.java
new file mode 100644 (file)
index 0000000..e69ae87
--- /dev/null
@@ -0,0 +1,129 @@
+package jalview.util.dialogrunner;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class DialogRunnerTest
+{
+  public class MockDialog implements DialogRunnerI
+  {
+    DialogRunner<MockDialog> runner = new DialogRunner<>(this);
+
+    @Override
+    public MockDialog response(RunResponse action)
+    {
+      return runner.response(action);
+    }
+
+    public void doDialog(String resp)
+    {
+      runner.firstRun(resp);
+    }
+  }
+
+  MockDialog dialog = new MockDialog();
+
+  @Test
+  public void testDialogRunner()
+  {
+    RunResponse ok, cancel, help, ineed;
+    final Response ooh = new Response("OOOOoooOOOOH!"),
+            r_ok = new Response("OK"), r_cancel = new Response("CANCEL"),
+            r_done = new Response("DONE"), r_help = new Response("HELP"),
+            r_ddoit = new Response("DIDNT DOIT"),
+            r_needsb = new Response("I NEED SOMEBODY");
+
+    ok = new RunResponse("OK")
+    {
+
+      @Override
+      public void run()
+      {
+        returned = new Response("DONE");
+      }
+    };
+    final RunResponse befok = new RunResponse("OK")
+    {
+
+      @Override
+      public void run()
+      {
+        returned = new Response("OK");
+      }
+    };
+
+    cancel = new RunResponse("CANCEL")
+    {
+      @Override
+      public void run()
+      {
+        returned = r_ddoit;
+      }
+    };
+    help = new RunResponse("HELP")
+    {
+      @Override
+      public void run()
+      {
+        returned = r_needsb;
+
+      }
+    };
+    ineed = new RunResponse(r_needsb)
+    {
+      @Override
+      public void run()
+      {
+        returned = ooh;
+      }
+    };
+
+    Assert.assertFalse(dialog.runner.isRegistered(ok));
+
+    dialog.response(ok).response(cancel).response(help).response(ineed);
+
+    Assert.assertTrue(dialog.runner.isRegistered(ok));
+
+    Assert.assertFalse(dialog.runner.firstRunWasCalled);
+    dialog.doDialog("OK");
+    // OK called, nothing else.
+    Assert.assertTrue(dialog.runner.firstRunWasCalled);
+    Assert.assertTrue(ok.wasRun);
+    Assert.assertEquals(ok.returned, r_done);
+    Assert.assertFalse(cancel.wasRun);
+    Assert.assertEquals(dialog.runner.responses.size(), 2);
+
+    // do it again - check it doesn't trigger again
+    ok.wasRun = false;
+    dialog.doDialog("OK");
+    Assert.assertFalse(ok.wasRun);
+
+    // reset - everything false/null
+    dialog.runner.resetResponses();
+    Assert.assertFalse(dialog.runner.firstRunWasCalled);
+    Assert.assertFalse(ok.wasRun);
+    Assert.assertNull(ok.returned);
+    Assert.assertEquals(dialog.runner.responses.size(), 0);
+
+    // cancel called ..
+    dialog.doDialog("HELP");
+    Assert.assertTrue(dialog.runner.firstRunWasCalled);
+    Assert.assertFalse(ok.wasRun);
+    Assert.assertEquals(ineed.returned, ooh);
+    Assert.assertEquals(dialog.runner.responses.size(), 3);
+
+    // TODO: test prepend and chained execution of tasks for a response.
+    Assert.assertFalse(dialog.runner.isRegistered(befok));
+    dialog.runner.firstResponse(befok);
+
+    Assert.assertTrue(dialog.runner.isRegistered(befok));
+    Assert.assertTrue(dialog.runner.isRegistered(ok));
+
+    dialog.runner.resetResponses();
+
+    dialog.doDialog("OK");
+    Assert.assertTrue(befok.wasRun);
+    Assert.assertTrue(ok.wasRun);
+    Assert.assertEquals(dialog.runner.responses.size(), 3);
+  }
+}
diff --git a/test/jalview/util/dialogrunner/ResponseTest.java b/test/jalview/util/dialogrunner/ResponseTest.java
new file mode 100644 (file)
index 0000000..d722b1a
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util.dialogrunner;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ResponseTest
+{
+  @Test
+  public void testResonse() {
+    Response intr=new Response(1),intrCopy=new Response(1);
+    Response strr=new Response("1"),strrcopy=new Response("1");
+    Response objr=new Response(Double.valueOf(1d));
+    Assert.assertTrue(intr.equals(intrCopy));
+    Assert.assertTrue(strr.equals(strrcopy));
+    Assert.assertFalse(intr.equals(strr));
+    Assert.assertFalse(intr.equals(objr));
+    Assert.assertFalse(strr.equals(objr));
+    Assert.assertEquals(intr.toString(), "DialogRunner int: 1");
+    Assert.assertEquals(strr.toString(), "DialogRunner str: '1'");
+    Assert.assertEquals(objr.toString(), "DialogRunner obj: 1.0");
+  }
+}
diff --git a/test/jalview/util/dialogrunner/RunResponseTest.java b/test/jalview/util/dialogrunner/RunResponseTest.java
new file mode 100644 (file)
index 0000000..ff86675
--- /dev/null
@@ -0,0 +1,57 @@
+package jalview.util.dialogrunner;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class RunResponseTest
+{
+  @Test
+  public void testRunResponse()
+  {
+
+    RunResponse rr = new RunResponse("OK")
+    {
+      @Override
+      public void run()
+      {
+        returned = new Response("DONE");
+      }
+    };
+    Assert.assertEquals(rr.ourTrigger, new Response("OK"));
+    Assert.assertNotEquals(rr.ourTrigger, new Response("NOTOK"));
+    Assert.assertNull(rr.returned);
+    Assert.assertFalse(rr.wasRun);
+    // trivial ..
+    rr.wasRun = true;
+    rr.run();
+    Assert.assertTrue(rr.wasRun);
+
+    Assert.assertEquals(rr.returned, new Response("DONE"));
+    rr.reset();
+    Assert.assertNull(rr.returned);
+    Assert.assertFalse(rr.wasRun);
+
+    Assert.assertEquals(rr.toString(), "Runner for " + new Response("OK"));
+
+    // just test the other constructors
+    RunResponse rr12 = new RunResponse(12)
+    {
+      @Override
+      public void run()
+      {
+        returned = new Response("DONE");
+      }
+    };
+    RunResponse rrpi = new RunResponse(new Double(3.142))
+    {
+      @Override
+      public void run()
+      {
+        returned = new Response("DONE");
+      }
+    };
+    Assert.assertEquals(rr12.ourTrigger, new Response(12));
+    Assert.assertEquals(rrpi.ourTrigger,
+            new Response(Double.valueOf(3.142)));
+}
+}