JAL-1998 JAL-3772 JAL-3416 Merge conflict resolution of JalviewFileChooser and JvOpti...
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:53:36 +0000 (17:53 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 4 Nov 2022 17:53:36 +0000 (17:53 +0000)
1  2 
src/jalview/bin/Jalview.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/JvOptionPane.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/UserDefinedColours.java
src/jalview/io/JalviewFileChooser.java

@@@ -61,8 -61,6 +61,8 @@@ import jalview.ext.so.SequenceOntology
  import jalview.gui.AlignFrame;
  import jalview.gui.Desktop;
  import jalview.gui.PromptUserConfig;
 +import jalview.gui.QuitHandler;
 +import jalview.gui.QuitHandler.QResponse;
  import jalview.io.AppletFormatAdapter;
  import jalview.io.BioJsHTMLOutput;
  import jalview.io.DataSourceType;
@@@ -273,28 -271,6 +273,28 @@@ public class Jalvie
      if (!Platform.isJS())
      {
        System.setSecurityManager(null);
 +
 +      Runtime.getRuntime().addShutdownHook(new Thread()
 +      {
 +        public void run()
 +        {
 +          Console.debug("Running shutdown hook");
 +          if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
 +          {
 +            // Got to here by a SIGTERM signal.
 +            // Note we will not actually cancel the quit from here -- it's too
 +            // late -- but we can wait for saving files.
 +            Console.debug("Checking for saving files");
 +            QuitHandler.getQuitResponse(false);
 +          }
 +          else
 +          {
 +            Console.debug("Nothing more to do");
 +          }
 +          Console.debug("Exiting, bye!");
 +          // shutdownHook cannot be cancelled, JVM will now halt
 +        }
 +      });
      }
  
      System.out
      System.out.println(System.getProperty("os.arch") + " "
              + System.getProperty("os.name") + " "
              + System.getProperty("os.version"));
 +
      String val = System.getProperty("sys.install4jVersion");
      if (val != null)
      {
      Cache.loadBuildProperties(true);
  
      ArgsParser aparser = new ArgsParser(args);
 +
      boolean headless = false;
  
      String usrPropsFile = aparser.getValue("props");
 -    Cache.loadProperties(usrPropsFile); // must do this before
 +    Cache.loadProperties(usrPropsFile); // must do this
 +                                        // before
      if (usrPropsFile != null)
      {
        System.out.println(
      try
      {
        Console.initLogger();
 -    } catch (NoClassDefFoundError error)
 +    } catch (
 +
 +    NoClassDefFoundError error)
      {
        error.printStackTrace();
        System.out.println("\nEssential logging libraries not found."
      }
  
      String file = null, data = null;
 +
      FileFormatI format = null;
 +
      DataSourceType protocol = null;
 +
      FileLoader fileLoader = new FileLoader(!headless);
  
      String groovyscript = null; // script to execute after all loading is
        System.out.println("No files to open!");
        System.exit(1);
      }
 +
      long progress = -1;
      // Finally, deal with the remaining input data.
      if (file != null)
          }
        }
      }
 +
      AlignFrame startUpAlframe = null;
      // We'll only open the default file if the desktop is visible.
      // And the user
        UIManager.put("TabbedPane.tabWidthMode", "compact");
        UIManager.put("TabbedPane.selectedBackground", Color.white);
      }
+     Desktop.setLiveDragMode(Cache.getDefault("FLAT_LIVE_DRAG_MODE", true));
      return set;
    }
  
    }
  
    /**
 -   * Quit method delegates to Desktop.quit - unless running in headless mode
 -   * when it just ends the JVM
 +   * jalview.bin.Jalview.quit() will just run the non-GUI shutdownHook and exit
     */
    public void quit()
    {
 -    if (desktop != null)
 -    {
 -      desktop.quit();
 -    }
 -    else
 -    {
 -      System.exit(0);
 -    }
 +    // System.exit will run the shutdownHook first
 +    System.exit(0);
    }
  
    public static AlignFrame getCurrentAlignFrame()
@@@ -59,7 -59,6 +59,7 @@@ import java.util.Hashtable
  import java.util.List;
  import java.util.Locale;
  import java.util.Vector;
 +import java.util.concurrent.Callable;
  
  import javax.swing.ButtonGroup;
  import javax.swing.JCheckBoxMenuItem;
@@@ -350,6 -349,8 +350,8 @@@ public class AlignFrame extends GAlignF
     */
    void init()
    {
+     setFrameIcon(WindowIcons.alignmentIcon);
      // setBackground(Color.white); // BH 2019
  
      if (!Jalview.isHeadlessMode())
        lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
                shortName);
  
 +      Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
 +      if (lastSaveSuccessful)
 +      {
 +        this.getViewport().setSavedUpToDate(true);
 +      }
 +
        statusBar.setText(MessageManager.formatMessage(
                "label.successfully_saved_to_file_in_format", new Object[]
                { file, format }));
      }
  
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 -    Runnable cancelAction = new Runnable()
 -    {
 -      @Override
 -      public void run()
 +    Callable<Void> cancelAction = () -> {
 +      lastSaveSuccessful = false;
 +      return null;
 +    };
 +    Callable<Void> outputAction = () -> {
 +      // todo defer this to inside formatSequences (or later)
 +      AlignmentExportData exportData = viewport.getAlignExportData(options);
 +      String output = new FormatAdapter(alignPanel, options)
 +              .formatSequences(format, exportData.getAlignment(),
 +                      exportData.getOmitHidden(),
 +                      exportData.getStartEndPostions(),
 +                      viewport.getAlignment().getHiddenColumns());
 +      if (output == null)
        {
          lastSaveSuccessful = false;
        }
 -    };
 -    Runnable outputAction = new Runnable()
 -    {
 -      @Override
 -      public void run()
 +      else
        {
 -        // todo defer this to inside formatSequences (or later)
 -        AlignmentExportData exportData = viewport
 -                .getAlignExportData(options);
 -        String output = new FormatAdapter(alignPanel, options)
 -                .formatSequences(format, exportData.getAlignment(),
 -                        exportData.getOmitHidden(),
 -                        exportData.getStartEndPostions(),
 -                        viewport.getAlignment().getHiddenColumns());
 -        if (output == null)
 +        // create backupfiles object and get new temp filename destination
 +        boolean doBackup = BackupFiles.getEnabled();
 +        BackupFiles backupfiles = null;
 +        if (doBackup)
          {
 -          lastSaveSuccessful = false;
 +          Console.trace("ALIGNFRAME making backupfiles object for " + file);
 +          backupfiles = new BackupFiles(file);
          }
 -        else
 +        try
          {
 -          // create backupfiles object and get new temp filename destination
 -          boolean doBackup = BackupFiles.getEnabled();
 -          BackupFiles backupfiles = null;
 -          if (doBackup)
 +          String tempFilePath = doBackup ? backupfiles.getTempFilePath()
 +                  : file;
 +          Console.trace("ALIGNFRAME setting PrintWriter");
 +          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 +
 +          if (backupfiles != null)
            {
 -            Console.trace(
 -                    "ALIGNFRAME making backupfiles object for " + file);
 -            backupfiles = new BackupFiles(file);
 +            Console.trace("ALIGNFRAME about to write to temp file "
 +                    + backupfiles.getTempFilePath());
            }
 -          try
 -          {
 -            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
 -                    : file;
 -            Console.trace("ALIGNFRAME setting PrintWriter");
 -            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
  
 -            if (backupfiles != null)
 -            {
 -              Console.trace("ALIGNFRAME about to write to temp file "
 -                      + backupfiles.getTempFilePath());
 -            }
 +          out.print(output);
 +          Console.trace("ALIGNFRAME about to close file");
 +          out.close();
 +          Console.trace("ALIGNFRAME closed file");
 +          AlignFrame.this.setTitle(file);
 +          statusBar.setText(MessageManager.formatMessage(
 +                  "label.successfully_saved_to_file_in_format", new Object[]
 +                  { fileName, format.getName() }));
 +          lastSaveSuccessful = true;
 +        } catch (IOException e)
 +        {
 +          lastSaveSuccessful = false;
 +          Console.error(
 +                  "ALIGNFRAME Something happened writing the temp file");
 +          Console.error(e.getMessage());
 +          Console.debug(Cache.getStackTraceString(e));
 +        } catch (Exception ex)
 +        {
 +          lastSaveSuccessful = false;
 +          Console.error(
 +                  "ALIGNFRAME Something unexpected happened writing the temp file");
 +          Console.error(ex.getMessage());
 +          Console.debug(Cache.getStackTraceString(ex));
 +        }
  
 -            out.print(output);
 -            Console.trace("ALIGNFRAME about to close file");
 -            out.close();
 -            Console.trace("ALIGNFRAME closed file");
 -            AlignFrame.this.setTitle(file);
 -            statusBar.setText(MessageManager.formatMessage(
 -                    "label.successfully_saved_to_file_in_format",
 -                    new Object[]
 -                    { fileName, format.getName() }));
 -            lastSaveSuccessful = true;
 -          } catch (IOException e)
 -          {
 -            lastSaveSuccessful = false;
 -            Console.error(
 -                    "ALIGNFRAME Something happened writing the temp file");
 -            Console.error(e.getMessage());
 -            Console.debug(Cache.getStackTraceString(e));
 -          } catch (Exception ex)
 -          {
 -            lastSaveSuccessful = false;
 -            Console.error(
 -                    "ALIGNFRAME Something unexpected happened writing the temp file");
 -            Console.error(ex.getMessage());
 -            Console.debug(Cache.getStackTraceString(ex));
 -          }
 +        if (doBackup)
 +        {
 +          backupfiles.setWriteSuccess(lastSaveSuccessful);
 +          Console.debug("ALIGNFRAME writing temp file was "
 +                  + (lastSaveSuccessful ? "" : "NOT ") + "successful");
 +          // do the backup file roll and rename the temp file to actual file
 +          Console.trace("ALIGNFRAME about to rollBackupsAndRenameTempFile");
 +          lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
 +          Console.debug("ALIGNFRAME performed rollBackupsAndRenameTempFile "
 +                  + (lastSaveSuccessful ? "" : "un") + "successfully");
 +        }
  
 -          if (doBackup)
 -          {
 -            backupfiles.setWriteSuccess(lastSaveSuccessful);
 -            Console.debug("ALIGNFRAME writing temp file was "
 -                    + (lastSaveSuccessful ? "" : "NOT ") + "successful");
 -            // do the backup file roll and rename the temp file to actual file
 -            Console.trace(
 -                    "ALIGNFRAME about to rollBackupsAndRenameTempFile");
 -            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
 -            Console.debug(
 -                    "ALIGNFRAME performed rollBackupsAndRenameTempFile "
 -                            + (lastSaveSuccessful ? "" : "un")
 -                            + "successfully");
 -          }
 +        Console.debug("lastSaveSuccessful=" + lastSaveSuccessful);
 +        if (lastSaveSuccessful)
 +        {
 +          AlignFrame.this.getViewport().setSavedUpToDate(true);
          }
        }
 +      return null;
      };
  
      /*
      }
      else
      {
 -      outputAction.run();
 +      try
 +      {
 +        outputAction.call();
 +      } catch (Exception e)
 +      {
 +        // TODO Auto-generated catch block
 +        e.printStackTrace();
 +      }
      }
    }
  
      FileFormatI fileFormat = FileFormats.getInstance()
              .forName(fileFormatName);
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 -    Runnable outputAction = new Runnable()
 -    {
 -      @Override
 -      public void run()
 +    Callable<Void> outputAction = () -> {
 +      // todo defer this to inside formatSequences (or later)
 +      AlignmentExportData exportData = viewport.getAlignExportData(options);
 +      CutAndPasteTransfer cap = new CutAndPasteTransfer();
 +      cap.setForInput(null);
 +      try
        {
 -        // todo defer this to inside formatSequences (or later)
 -        AlignmentExportData exportData = viewport
 -                .getAlignExportData(options);
 -        CutAndPasteTransfer cap = new CutAndPasteTransfer();
 -        cap.setForInput(null);
 -        try
 -        {
 -          FileFormatI format = fileFormat;
 -          cap.setText(new FormatAdapter(alignPanel, options)
 -                  .formatSequences(format, exportData.getAlignment(),
 -                          exportData.getOmitHidden(),
 -                          exportData.getStartEndPostions(),
 -                          viewport.getAlignment().getHiddenColumns()));
 -          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
 -                  "label.alignment_output_command", new Object[]
 -                  { fileFormat.getName() }), 600, 500);
 -        } catch (OutOfMemoryError oom)
 -        {
 -          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
 -                  oom);
 -          cap.dispose();
 -        }
 +        FileFormatI format = fileFormat;
 +        cap.setText(new FormatAdapter(alignPanel, options).formatSequences(
 +                format, exportData.getAlignment(),
 +                exportData.getOmitHidden(),
 +                exportData.getStartEndPostions(),
 +                viewport.getAlignment().getHiddenColumns()));
 +        Desktop.addInternalFrame(cap, MessageManager.formatMessage(
 +                "label.alignment_output_command", new Object[]
 +                { fileFormat.getName() }), 600, 500);
 +      } catch (OutOfMemoryError oom)
 +      {
 +        new OOMWarning("Outputting alignment as " + fileFormat.getName(),
 +                oom);
 +        cap.dispose();
        }
 +      return null;
      };
  
      /*
      }
      else
      {
 -      outputAction.run();
 +      try
 +      {
 +        outputAction.call();
 +      } catch (Exception e)
 +      {
 +        e.printStackTrace();
 +      }
      }
    }
  
              .getString("label.load_jalview_annotations");
      chooser.setDialogTitle(tooltip);
      chooser.setToolTipText(tooltip);
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        String choice = chooser.getSelectedFile().getPath();
 -        Cache.setProperty("LAST_DIRECTORY", choice);
 -        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
 -      }
 +    chooser.setResponseHandler(0, () -> {
 +      String choice = chooser.getSelectedFile().getPath();
 +      Cache.setProperty("LAST_DIRECTORY", choice);
 +      loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
 +      return null;
      });
  
      chooser.showOpenDialog(this);
        return;
      }
  
 -    Runnable okAction = new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        SequenceI[] cut = sg.getSequences()
 -                .toArray(new SequenceI[sg.getSize()]);
 +    Callable okAction = () -> {
 +      SequenceI[] cut = sg.getSequences()
 +              .toArray(new SequenceI[sg.getSize()]);
  
 -        addHistoryItem(new EditCommand(
 -                MessageManager.getString("label.cut_sequences"), Action.CUT,
 -                cut, sg.getStartRes(),
 -                sg.getEndRes() - sg.getStartRes() + 1,
 -                viewport.getAlignment()));
 +      addHistoryItem(new EditCommand(
 +              MessageManager.getString("label.cut_sequences"), Action.CUT,
 +              cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
 +              viewport.getAlignment()));
  
 -        viewport.setSelectionGroup(null);
 -        viewport.sendSelection();
 -        viewport.getAlignment().deleteGroup(sg);
 +      viewport.setSelectionGroup(null);
 +      viewport.sendSelection();
 +      viewport.getAlignment().deleteGroup(sg);
  
 -        viewport.firePropertyChange("alignment", null,
 -                viewport.getAlignment().getSequences());
 -        if (viewport.getAlignment().getHeight() < 1)
 +      viewport.firePropertyChange("alignment", null,
 +              viewport.getAlignment().getSequences());
 +      if (viewport.getAlignment().getHeight() < 1)
 +      {
 +        try
 +        {
 +          AlignFrame.this.setClosed(true);
 +        } catch (Exception ex)
          {
 -          try
 -          {
 -            AlignFrame.this.setClosed(true);
 -          } catch (Exception ex)
 -          {
 -          }
          }
        }
 +      return null;
      };
  
      /*
      }
      else
      {
 -      okAction.run();
 +      try
 +      {
 +        okAction.call();
 +      } catch (Exception e)
 +      {
 +        e.printStackTrace();
 +      }
      }
    }
  
              .formatMessage("label.overview_params", new Object[]
              { this.getTitle() }), true, frame.getWidth(), frame.getHeight(),
              true, true);
+     frame.setFrameIcon(WindowIcons.overviewIcon);
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      frame.addInternalFrameListener(
      chooser.setToolTipText(
              MessageManager.getString("label.load_tree_file"));
  
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 +    chooser.setResponseHandler(0, () -> {
 +      String filePath = chooser.getSelectedFile().getPath();
 +      Cache.setProperty("LAST_DIRECTORY", filePath);
 +      NewickFile fin = null;
 +      try
        {
 -        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);
 -        }
 +        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);
 +      }
 +      return null;
      });
      chooser.showOpenDialog(this);
    }
      chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
      chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
      final AlignFrame us = this;
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        String choice = chooser.getSelectedFile().getPath();
 -        Cache.setProperty("LAST_DIRECTORY", choice);
 -        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 -        new VCFLoader(choice).loadVCF(seqs, us);
 -      }
 +    chooser.setResponseHandler(0, () -> {
 +      String choice = chooser.getSelectedFile().getPath();
 +      Cache.setProperty("LAST_DIRECTORY", choice);
 +      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 +      new VCFLoader(choice).loadVCF(seqs, us);
 +      return null;
      });
      chooser.showOpenDialog(null);
  
@@@ -64,7 -64,6 +64,7 @@@ import java.util.List
  import java.util.ListIterator;
  import java.util.Locale;
  import java.util.Vector;
 +import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Semaphore;
@@@ -82,7 -81,6 +82,7 @@@ import javax.swing.JCheckBox
  import javax.swing.JComboBox;
  import javax.swing.JComponent;
  import javax.swing.JDesktopPane;
 +import javax.swing.JFrame;
  import javax.swing.JInternalFrame;
  import javax.swing.JLabel;
  import javax.swing.JMenuItem;
@@@ -92,7 -90,6 +92,7 @@@ import javax.swing.JProgressBar
  import javax.swing.JTextField;
  import javax.swing.KeyStroke;
  import javax.swing.SwingUtilities;
 +import javax.swing.WindowConstants;
  import javax.swing.event.HyperlinkEvent;
  import javax.swing.event.HyperlinkEvent.EventType;
  import javax.swing.event.InternalFrameAdapter;
@@@ -105,7 -102,6 +105,7 @@@ import jalview.api.AlignmentViewPanel
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
  import jalview.gui.ImageExporter.ImageWriterI;
 +import jalview.gui.QuitHandler.QResponse;
  import jalview.io.BackupFiles;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
@@@ -191,6 -187,14 +191,14 @@@ public class Desktop extends jalview.jb
  
    public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
  
+   private static int DRAG_MODE = JDesktopPane.OUTLINE_DRAG_MODE;
+   public static void setLiveDragMode(boolean b)
+   {
+     DRAG_MODE = b ? JDesktopPane.LIVE_DRAG_MODE
+             : JDesktopPane.OUTLINE_DRAG_MODE;
+   }
    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
  
    public static boolean nosplash = false;
  
      setIconImages(ChannelProperties.getIconList());
  
 +    // override quit handling when GUI OS close [X] button pressed
 +    this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
      addWindowListener(new WindowAdapter()
      {
        @Override
        public void windowClosing(WindowEvent ev)
        {
 -        quit();
 +        QuitHandler.QResponse ret = desktopQuit(true, true); // ui, disposeFlag
        }
      });
  
      }
  
      getContentPane().add(desktop, BorderLayout.CENTER);
-     desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+     desktop.setDragMode(DRAG_MODE);
  
      // This line prevents Windows Look&Feel resizing all new windows to maximum
      // if previous window was maximised
  
      this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
  
 -    this.addWindowListener(new WindowAdapter()
 -    {
 -      @Override
 -      public void windowClosing(WindowEvent evt)
 -      {
 -        quit();
 -      }
 -    });
 -
      MouseAdapter ma;
      this.addMouseListener(ma = new MouseAdapter()
      {
              MessageManager.getString("label.open_local_file"));
      chooser.setToolTipText(MessageManager.getString("action.open"));
  
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        File selectedFile = chooser.getSelectedFile();
 -        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 +    chooser.setResponseHandler(0, () -> {
 +      File selectedFile = chooser.getSelectedFile();
 +      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
  
 -        FileFormatI format = chooser.getSelectedFormat();
 +      FileFormatI format = chooser.getSelectedFormat();
  
 -        /*
 -         * Call IdentifyFile to verify the file contains what its extension implies.
 -         * Skip this step for dynamically added file formats, because IdentifyFile does
 -         * not know how to recognise them.
 -         */
 -        if (FileFormats.getInstance().isIdentifiable(format))
 +      /*
 +       * Call IdentifyFile to verify the file contains what its extension implies.
 +       * Skip this step for dynamically added file formats, because IdentifyFile does
 +       * not know how to recognise them.
 +       */
 +      if (FileFormats.getInstance().isIdentifiable(format))
 +      {
 +        try
          {
 -          try
 -          {
 -            format = new IdentifyFile().identify(selectedFile,
 -                    DataSourceType.FILE);
 -          } catch (FileFormatException e)
 -          {
 -            // format = null; //??
 -          }
 +          format = new IdentifyFile().identify(selectedFile,
 +                  DataSourceType.FILE);
 +        } catch (FileFormatException e)
 +        {
 +          // format = null; //??
          }
 -
 -        new FileLoader().LoadFile(viewport, selectedFile,
 -                DataSourceType.FILE, format);
        }
 +
 +      new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
 +              format);
 +      return null;
      });
      chooser.showOpenDialog(this);
    }
  
      Object[] options = new Object[] { MessageManager.getString("action.ok"),
          MessageManager.getString("action.cancel") };
 -    Runnable action = new Runnable()
 -    {
 -      @Override
 -      public void run()
 +    Callable<Void> action = () -> {
 +      @SuppressWarnings("unchecked")
 +      String url = (history instanceof JTextField
 +              ? ((JTextField) history).getText()
 +              : ((JComboBox<String>) history).getEditor().getItem()
 +                      .toString().trim());
 +
 +      if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
        {
 -        @SuppressWarnings("unchecked")
 -        String url = (history instanceof JTextField
 -                ? ((JTextField) history).getText()
 -                : ((JComboBox<String>) history).getEditor().getItem()
 -                        .toString().trim());
 -
 -        if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
 +        if (viewport != null)
          {
 -          if (viewport != null)
 -          {
 -            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                    FileFormat.Jalview);
 -          }
 -          else
 -          {
 -            new FileLoader().LoadFile(url, DataSourceType.URL,
 -                    FileFormat.Jalview);
 -          }
 +          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                  FileFormat.Jalview);
          }
          else
          {
 -          FileFormatI format = null;
 -          try
 -          {
 -            format = new IdentifyFile().identify(url, DataSourceType.URL);
 -          } catch (FileFormatException e)
 -          {
 -            // TODO revise error handling, distinguish between
 -            // URL not found and response not valid
 -          }
 +          new FileLoader().LoadFile(url, DataSourceType.URL,
 +                  FileFormat.Jalview);
 +        }
 +      }
 +      else
 +      {
 +        FileFormatI format = null;
 +        try
 +        {
 +          format = new IdentifyFile().identify(url, DataSourceType.URL);
 +        } catch (FileFormatException e)
 +        {
 +          // TODO revise error handling, distinguish between
 +          // URL not found and response not valid
 +        }
  
 -          if (format == null)
 -          {
 -            String msg = MessageManager
 -                    .formatMessage("label.couldnt_locate", url);
 -            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
 -                    MessageManager.getString("label.url_not_found"),
 -                    JvOptionPane.WARNING_MESSAGE);
 +        if (format == null)
 +        {
 +          String msg = MessageManager.formatMessage("label.couldnt_locate",
 +                  url);
 +          JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
 +                  MessageManager.getString("label.url_not_found"),
 +                  JvOptionPane.WARNING_MESSAGE);
  
 -            return;
 -          }
 +          return null; // Void
 +        }
  
 -          if (viewport != null)
 -          {
 -            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                    format);
 -          }
 -          else
 -          {
 -            new FileLoader().LoadFile(url, DataSourceType.URL, format);
 -          }
 +        if (viewport != null)
 +        {
 +          new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                  format);
 +        }
 +        else
 +        {
 +          new FileLoader().LoadFile(url, DataSourceType.URL, format);
          }
        }
 +      return null; // Void
      };
      String dialogOption = MessageManager
              .getString("label.input_alignment_from_url");
    }
  
    /*
 -   * Exit the program
 +   * Check with user and saving files before actually quitting
     */
 -  @Override
 -  public void quit()
 +  public void desktopQuit()
    {
 -    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
 -    Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
 -    Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
 -    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
 -            getWidth(), getHeight()));
 +    desktopQuit(true, false);
 +  }
  
 -    if (jconsole != null)
 -    {
 -      storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
 -      jconsole.stopConsole();
 -    }
 -    if (jvnews != null)
 -    {
 -      storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
 +  public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
 +  {
 +    final Callable<Void> doDesktopQuit = () -> {
 +      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
 +      Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
 +      Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
 +      storeLastKnownDimensions("", new Rectangle(getBounds().x,
 +              getBounds().y, getWidth(), getHeight()));
  
 -    }
 -    if (dialogExecutor != null)
 -    {
 -      dialogExecutor.shutdownNow();
 -    }
 -    closeAll_actionPerformed(null);
 +      if (jconsole != null)
 +      {
 +        storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
 +        jconsole.stopConsole();
 +      }
  
 -    if (groovyConsole != null)
 -    {
 -      // suppress a possible repeat prompt to save script
 -      groovyConsole.setDirty(false);
 -      groovyConsole.exit();
 -    }
 +      if (jvnews != null)
 +      {
 +        storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
 +
 +      }
 +
 +      if (dialogExecutor != null)
 +      {
 +        dialogExecutor.shutdownNow();
 +      }
 +
 +      closeAll_actionPerformed(null);
 +
 +      if (groovyConsole != null)
 +      {
 +        // suppress a possible repeat prompt to save script
 +        groovyConsole.setDirty(false);
 +        groovyConsole.exit();
 +      }
 +
 +      if (QuitHandler.gotQuitResponse() == QResponse.FORCE_QUIT)
 +      {
 +        // note that shutdown hook will not be run
 +        jalview.bin.Console.debug("Force Quit selected by user");
 +        Runtime.getRuntime().halt(0);
 +      }
 +
 +      jalview.bin.Console.debug("Quit selected by user");
 +      if (disposeFlag)
 +      {
 +        instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
 +        // instance.dispose();
 +      }
 +      instance.quit();
 +
 +      return null; // Void
 +    };
 +
 +    return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
 +            QuitHandler.defaultCancelQuit);
 +  }
 +
 +  /**
 +   * Don't call this directly, use desktopQuit() above. Exits the program.
 +   */
 +  @Override
 +  public void quit()
 +  {
 +    // this will run the shutdownHook but QuitHandler.getQuitResponse() should
 +    // not run a second time if gotQuitResponse flag has been set (i.e. user
 +    // confirmed quit of some kind).
      System.exit(0);
    }
  
      // allowBackupFiles
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 +    chooser.setResponseHandler(0, () -> {
 +      File selectedFile = chooser.getSelectedFile();
 +      setProjectFile(selectedFile);
 +      String choice = selectedFile.getAbsolutePath();
 +      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 +      new Thread(new Runnable()
        {
 -        File selectedFile = chooser.getSelectedFile();
 -        setProjectFile(selectedFile);
 -        String choice = selectedFile.getAbsolutePath();
 -        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 -        new Thread(new Runnable()
 +        @Override
 +        public void run()
          {
 -          @Override
 -          public void run()
 +          try
            {
 -            try
 -            {
 -              new Jalview2XML().loadJalviewAlign(selectedFile);
 -            } catch (OutOfMemoryError oom)
 -            {
 -              new OOMWarning("Whilst loading project from " + choice, oom);
 -            } catch (Exception ex)
 -            {
 -              jalview.bin.Console.error(
 -                      "Problems whilst loading project from " + choice, ex);
 -              JvOptionPane.showMessageDialog(Desktop.desktop,
 -                      MessageManager.formatMessage(
 -                              "label.error_whilst_loading_project_from",
 -                              new Object[]
 -                              { choice }),
 -                      MessageManager
 -                              .getString("label.couldnt_load_project"),
 -                      JvOptionPane.WARNING_MESSAGE);
 -            }
 +            new Jalview2XML().loadJalviewAlign(selectedFile);
 +          } catch (OutOfMemoryError oom)
 +          {
 +            new OOMWarning("Whilst loading project from " + choice, oom);
 +          } catch (Exception ex)
 +          {
 +            jalview.bin.Console.error(
 +                    "Problems whilst loading project from " + choice, ex);
 +            JvOptionPane.showMessageDialog(Desktop.desktop,
 +                    MessageManager.formatMessage(
 +                            "label.error_whilst_loading_project_from",
 +                            new Object[]
 +                            { choice }),
 +                    MessageManager.getString("label.couldnt_load_project"),
 +                    JvOptionPane.WARNING_MESSAGE);
            }
 -        }, "Project Loader").start();
 -      }
 +        }
 +      }, "Project Loader").start();
 +      return null;
      });
  
      chooser.showOpenDialog(this);
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        quit();
 +        desktopQuit();
        }
      });
    }
@@@ -411,6 -411,7 +411,7 @@@ public class FeatureSettings extends JP
      {
        frame = new JInternalFrame();
        frame.setContentPane(this);
+       frame.setFrameIcon(WindowIcons.featuresIcon);
        Rectangle bounds = af.getFeatureSettingsGeometry();
        String title;
        if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
      chooser.setDialogTitle(
              MessageManager.getString("label.load_feature_colours"));
      chooser.setToolTipText(MessageManager.getString("action.load"));
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        File file = chooser.getSelectedFile();
 -        load(file);
 -      }
 +    chooser.setResponseHandler(0, () -> {
 +      File file = chooser.getSelectedFile();
 +      load(file);
 +      return null;
      });
      chooser.showOpenDialog(this);
    }
  
  package jalview.gui;
  
+ import java.awt.AWTEvent;
+ import java.awt.ActiveEvent;
  import java.awt.Component;
+ import java.awt.Container;
  import java.awt.Dialog.ModalityType;
+ import java.awt.EventQueue;
  import java.awt.HeadlessException;
+ import java.awt.MenuComponent;
+ import java.awt.Toolkit;
  import java.awt.Window;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
+ import java.awt.event.MouseAdapter;
+ import java.awt.event.MouseMotionAdapter;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.util.ArrayList;
@@@ -34,18 -42,18 +42,21 @@@ import java.util.Arrays
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
 +import java.util.concurrent.Callable;
  import java.util.concurrent.Executors;
  
  import javax.swing.Icon;
  import javax.swing.JButton;
  import javax.swing.JDialog;
 +import javax.swing.JFrame;
  import javax.swing.JInternalFrame;
+ import javax.swing.JLayeredPane;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
 +import javax.swing.SwingUtilities;
  import javax.swing.UIManager;
+ import javax.swing.event.InternalFrameEvent;
+ import javax.swing.event.InternalFrameListener;
  
  import jalview.util.Platform;
  import jalview.util.dialogrunner.DialogRunnerI;
@@@ -55,13 -63,13 +66,13 @@@ public class JvOptionPane extends JOpti
  {
    private static final long serialVersionUID = -3019167117756785229L;
  
-   private static Object mockResponse = JOptionPane.CANCEL_OPTION;
+   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
  
    private static boolean interactiveMode = true;
  
    private Component parentComponent;
  
 -  private Map<Object, Runnable> callbacks = new HashMap<>();
 +  private Map<Object, Callable<Void>> callbacks = new HashMap<>();
  
    /*
     * JalviewJS reports user choice in the dialog as the selected
@@@ -72,6 -80,7 +83,6 @@@
    public JvOptionPane(final Component parent)
    {
      this.parentComponent = Platform.isJS() ? this : parent;
 -    this.setIcon(WindowIcons.logoIcon);
    }
  
    public static int showConfirmDialog(Component parentComponent,
      }
      switch (optionType)
      {
-     case JOptionPane.YES_NO_CANCEL_OPTION:
+     case JvOptionPane.YES_NO_CANCEL_OPTION:
        // FeatureRenderer amendFeatures ?? TODO ??
        // Chimera close
        // PromptUserConfig
        // $FALL-THROUGH$
      default:
-     case JOptionPane.YES_NO_OPTION:
+     case JvOptionPane.YES_NO_OPTION:
        // PromptUserConfig usage stats
        // for now treated as "OK CANCEL"
        // $FALL-THROUGH$
-     case JOptionPane.OK_CANCEL_OPTION:
+     case JvOptionPane.OK_CANCEL_OPTION:
        // will fall back to simple HTML
        return JOptionPane.showConfirmDialog(parentComponent, message, title,
                optionType);
      }
      switch (optionType)
      {
-     case JOptionPane.YES_NO_CANCEL_OPTION:
+     case JvOptionPane.YES_NO_CANCEL_OPTION:
        // ColourMenuHelper.addMenuItmers.offerRemoval TODO
-     case JOptionPane.YES_NO_OPTION:
+     case JvOptionPane.YES_NO_OPTION:
        // UserDefinedColoursSave -- relevant? TODO
        // $FALL-THROUGH$
      default:
-     case JOptionPane.OK_CANCEL_OPTION:
+     case JvOptionPane.OK_CANCEL_OPTION:
  
        // EditNameDialog --- uses panel for messsage TODO
  
      }
      switch (optionType)
      {
-     case JOptionPane.YES_NO_CANCEL_OPTION:
-     case JOptionPane.YES_NO_OPTION:
+     case JvOptionPane.YES_NO_CANCEL_OPTION:
+     case JvOptionPane.YES_NO_OPTION:
        // UserQuestionanaireCheck
        // VamsasApplication
        // $FALL-THROUGH$
      default:
-     case JOptionPane.OK_CANCEL_OPTION:
+     case JvOptionPane.OK_CANCEL_OPTION:
        // will fall back to simple HTML
        return JOptionPane.showConfirmDialog(parentComponent, message, title,
                optionType, messageType);
      }
      switch (optionType)
      {
-     case JOptionPane.YES_NO_CANCEL_OPTION:
-     case JOptionPane.YES_NO_OPTION:
+     case JvOptionPane.YES_NO_CANCEL_OPTION:
+     case JvOptionPane.YES_NO_OPTION:
        //$FALL-THROUGH$
      default:
-     case JOptionPane.OK_CANCEL_OPTION:
+     case JvOptionPane.OK_CANCEL_OPTION:
        // Preferences editLink/newLink
        return JOptionPane.showConfirmDialog(parentComponent, message, title,
                optionType, messageType, icon);
  
    public static void resetMock()
    {
-     setMockResponse(JOptionPane.CANCEL_OPTION);
+     setMockResponse(JvOptionPane.CANCEL_OPTION);
      setInteractiveMode(true);
    }
  
      {
        switch (messageType)
        {
-       case JOptionPane.WARNING_MESSAGE:
+       case JvOptionPane.WARNING_MESSAGE:
          prefix = "WARNING! ";
          break;
-       case JOptionPane.ERROR_MESSAGE:
+       case JvOptionPane.ERROR_MESSAGE:
          prefix = "ERROR! ";
          break;
        default:
     * @param string2
     * @return
     */
 +  public static JvOptionPane newOptionDialog()
 +  {
 +    return new JvOptionPane(null);
 +  }
 +
    public static JvOptionPane newOptionDialog(Component parentComponent)
    {
      return new JvOptionPane(parentComponent);
              initialValue, true);
    }
  
 -  public void showDialog(String message, String title, int optionType,
 +  public void showDialog(Object message, String title, int optionType,
            int messageType, Icon icon, Object[] options, Object initialValue,
            boolean modal)
    {
 +    showDialog(message, title, optionType, messageType, icon, options,
 +            initialValue, modal, null);
 +  }
 +
 +  public void showDialog(Object message, String title, int optionType,
 +          int messageType, Icon icon, Object[] options, Object initialValue,
 +          boolean modal, JButton[] buttons)
 +  {
      if (!isInteractiveMode())
      {
        handleResponse(getMockResponse());
  
      if (modal)
      {
 +      boolean useButtons = false;
 +      Object initialValueButton = null;
 +      NOTNULL: if (buttons != null)
 +      {
 +        if (buttons.length != options.length)
 +        {
 +          jalview.bin.Console.error(
 +                  "Supplied buttons array not the same length as supplied options array.");
 +          break NOTNULL;
 +        }
 +        int[] buttonActions = { JOptionPane.YES_OPTION,
 +            JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
 +        for (int i = 0; i < options.length; i++)
 +        {
 +          Object o = options[i];
 +          jalview.bin.Console.debug(
 +                  "Setting button " + i + " to '" + o.toString() + "'");
 +          JButton jb = buttons[i];
 +
 +          if (o.equals(initialValue))
 +            initialValueButton = jb;
 +
 +          int buttonAction = buttonActions[i];
 +          Callable<Void> action = callbacks.get(buttonAction);
 +          jb.setText((String) o);
 +          jb.addActionListener(new ActionListener()
 +          {
 +            @Override
 +            public void actionPerformed(ActionEvent e)
 +            {
 +
 +              Object obj = e.getSource();
 +              if (obj == null || !(obj instanceof Component))
 +              {
 +                jalview.bin.Console.debug(
 +                        "Could not find Component source of event object "
 +                                + obj);
 +                return;
 +              }
 +              Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
 +                      JOptionPane.class, (Component) obj);
 +              if (joptionpaneObject == null
 +                      || !(joptionpaneObject instanceof JOptionPane))
 +              {
 +                jalview.bin.Console.debug(
 +                        "Could not find JOptionPane ancestor of event object "
 +                                + obj);
 +                return;
 +              }
 +              JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
 +              joptionpane.setValue(buttonAction);
 +              if (action != null)
 +                Executors.newSingleThreadExecutor().submit(action);
 +              joptionpane.transferFocusBackward();
 +              joptionpane.setVisible(false);
 +              // put focus and raise parent window if possible, unless cancel or
 +              // no button pressed
 +              boolean raiseParent = (parentComponent != null);
 +              if (buttonAction == JOptionPane.CANCEL_OPTION)
 +                raiseParent = false;
 +              if (optionType == JOptionPane.YES_NO_OPTION
 +                      && buttonAction == JOptionPane.NO_OPTION)
 +                raiseParent = false;
 +              if (raiseParent)
 +              {
 +                parentComponent.requestFocus();
 +                if (parentComponent instanceof JInternalFrame)
 +                {
 +                  JInternalFrame jif = (JInternalFrame) parentComponent;
 +                  jif.show();
 +                  jif.moveToFront();
 +                  jif.grabFocus();
 +                }
 +                else if (parentComponent instanceof Window)
 +                {
 +                  Window w = (Window) parentComponent;
 +                  w.toFront();
 +                  w.requestFocus();
 +                }
 +              }
 +              joptionpane.setVisible(false);
 +            }
 +          });
 +
 +        }
 +        useButtons = true;
 +      }
        // use a JOptionPane as usual
        int response = JOptionPane.showOptionDialog(parentComponent, message,
 -              title, optionType, messageType, icon, options, initialValue);
 +              title, optionType, messageType, icon,
 +              useButtons ? buttons : options,
 +              useButtons ? initialValueButton : initialValue);
  
        /*
         * In Java, the response is returned to this thread and handled here;
         * attached to the button press of the dialog.  This means we can use
         * a non-modal JDialog for the confirmation without blocking the GUI.
         */
+       JOptionPane joptionpane = new JOptionPane();
+       // Make button options
+       int[] buttonActions = { JvOptionPane.YES_OPTION,
+           JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
  
-       JDialog dialog = createDialog(parentComponent, message, title,
-               optionType, messageType, icon, options, initialValue, modal,
-               buttons);
-       jalview.bin.Console.debug("About to setVisible(true)");
+       // we need the strings to make the buttons with actionEventListener
+       if (options == null)
+       {
+         ArrayList<String> options_default = new ArrayList<>();
+         options_default
+                 .add(UIManager.getString("OptionPane.yesButtonText"));
+         if (optionType == JvOptionPane.YES_NO_OPTION
+                 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
+         {
+           options_default
+                   .add(UIManager.getString("OptionPane.noButtonText"));
+         }
+         if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
+         {
+           options_default
+                   .add(UIManager.getString("OptionPane.cancelButtonText"));
+         }
+         options = options_default.toArray();
+       }
+       ArrayList<JButton> options_btns = new ArrayList<>();
+       Object initialValue_btn = null;
 -      if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
 -                            // add them here
++      if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here
+       {
+         for (int i = 0; i < options.length && i < 3; i++)
+         {
+           Object o = options[i];
+           int buttonAction = buttonActions[i];
+           Runnable action = callbacks.get(buttonAction);
+           JButton jb = new JButton();
+           jb.setText((String) o);
+           jb.addActionListener(new ActionListener()
+           {
+             @Override
+             public void actionPerformed(ActionEvent e)
+             {
+               joptionpane.setValue(buttonAction);
+               if (action != null)
+                 Executors.defaultThreadFactory().newThread(action).start();
+               // joptionpane.transferFocusBackward();
+               joptionpane.transferFocusBackward();
+               joptionpane.setVisible(false);
+               // put focus and raise parent window if possible, unless cancel
+               // button pressed
+               boolean raiseParent = (parentComponent != null);
+               if (buttonAction == JvOptionPane.CANCEL_OPTION)
+                 raiseParent = false;
+               if (optionType == JvOptionPane.YES_NO_OPTION
+                       && buttonAction == JvOptionPane.NO_OPTION)
+                 raiseParent = false;
+               if (raiseParent)
+               {
+                 parentComponent.requestFocus();
+                 if (parentComponent instanceof JInternalFrame)
+                 {
+                   JInternalFrame jif = (JInternalFrame) parentComponent;
+                   jif.show();
+                   jif.moveToFront();
+                   jif.grabFocus();
+                 }
+                 else if (parentComponent instanceof Window)
+                 {
+                   Window w = (Window) parentComponent;
+                   w.toFront();
+                   w.requestFocus();
+                 }
+               }
+               joptionpane.setVisible(false);
+             }
+           });
+           options_btns.add(jb);
+           if (o.equals(initialValue))
+             initialValue_btn = jb;
+         }
+       }
+       joptionpane.setMessage(message);
+       joptionpane.setMessageType(messageType);
+       joptionpane.setOptionType(optionType);
+       joptionpane.setIcon(icon);
+       joptionpane.setOptions(
+               Platform.isJS() ? options : options_btns.toArray());
+       joptionpane.setInitialValue(
+               Platform.isJS() ? initialValue : initialValue_btn);
+       JDialog dialog = joptionpane.createDialog(parentComponent, title);
+       dialog.setIconImage(WindowIcons.logoIcon.getImage());
+       dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
+               : ModalityType.MODELESS);
+       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setVisible(true);
-       jalview.bin.Console.debug("Just setVisible(true)");
      }
    }
  
        handleResponse(getMockResponse());
      }
  
+     // need to set these separately so we can set the title bar icon later
+     this.setOptionType(yesNoCancelOption);
+     this.setMessageType(questionMessage);
+     this.setIcon(icon);
+     this.setInitialValue(initresponse);
+     this.setOptions(options);
+     this.setMessage(mainPanel);
      ourOptions = Arrays.asList(options);
 +    int response;
      if (parentComponent != this)
      {
-       response = JOptionPane.showInternalOptionDialog(parentComponent,
-               mainPanel, title, yesNoCancelOption, questionMessage, icon,
-               options, initresponse);
+       JInternalFrame jif = this.createInternalFrame(parentComponent, title);
+       jif.setFrameIcon(WindowIcons.logoIcon);
+       jif.addInternalFrameListener(new InternalFrameListener()
+       {
+         @Override
+         public void internalFrameActivated(InternalFrameEvent arg0)
+         {
+         }
+         @Override
+         public void internalFrameClosed(InternalFrameEvent arg0)
+         {
+           JvOptionPane.this.internalDialogHandleResponse();
+         }
+         @Override
+         public void internalFrameClosing(InternalFrameEvent arg0)
+         {
+         }
+         @Override
+         public void internalFrameDeactivated(InternalFrameEvent arg0)
+         {
+         }
+         @Override
+         public void internalFrameDeiconified(InternalFrameEvent arg0)
+         {
+         }
+         @Override
+         public void internalFrameIconified(InternalFrameEvent arg0)
+         {
+         }
+         @Override
+         public void internalFrameOpened(InternalFrameEvent arg0)
+         {
+         }
+       });
+       jif.setVisible(true);
+       startModal(jif);
+       return;
      }
      else
      {
-       response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
-               title, yesNoCancelOption, questionMessage, icon, options,
-               initresponse);
+       JDialog dialog = this.createDialog(parentComponent, title);
+       dialog.setIconImage(WindowIcons.logoIcon.getImage());
+       dialog.setVisible(true); // blocking
+       this.internalDialogHandleResponse();
+       return;
      }
+   }
+   private void internalDialogHandleResponse()
+   {
+     String responseString = (String) this.getValue();
+     int response = ourOptions.indexOf(responseString);
      if (!Platform.isJS())
      /**
       * Java only
      }
    }
  
 +  /*
    @Override
    public JvOptionPane setResponseHandler(Object response, Runnable action)
    {
 +    callbacks.put(response, new Callable<Void>()
 +    {
 +      @Override
 +      public Void call()
 +      {
 +        action.run();
 +        return null;
 +      }
 +    });
 +    return this;
 +  }
 +  */
 +  @Override
 +  public JvOptionPane setResponseHandler(Object response,
 +          Callable<Void> action)
 +  {
      callbacks.put(response, action);
      return this;
    }
  
    /**
 +   * showDialogOnTop will create a dialog that (attempts to) come to top of OS
 +   * desktop windows
 +   */
 +  public static int showDialogOnTop(String label, String actionString,
 +          int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
 +  {
 +    // Ensure Jalview window is brought to front (primarily for Quit
 +    // confirmation window to be visible)
 +
 +    // This method of raising the Jalview window is broken in java
 +    // jalviewDesktop.setVisible(true);
 +    // jalviewDesktop.toFront();
 +
 +    // A better hack which works is to create a new JFrame parent with
 +    // setAlwaysOnTop(true)
 +    JFrame dialogParent = new JFrame();
 +    dialogParent.setAlwaysOnTop(true);
 +
 +    int answer = JOptionPane.showConfirmDialog(dialogParent, label,
 +            actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
 +
 +    dialogParent.setAlwaysOnTop(false);
 +    dialogParent.dispose();
 +
 +    return answer;
 +  }
 +
 +  public void showDialogOnTopAsync(String label, String actionString,
 +          int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
 +          Object[] options, Object initialValue, boolean modal)
 +  {
 +    showDialogOnTopAsync(new JFrame(), label, actionString,
 +            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
 +            initialValue, modal);
 +  }
 +
 +  public void showDialogOnTopAsync(JFrame dialogParent, Object label,
 +          String actionString, int JOPTIONPANE_OPTION,
 +          int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
 +          Object initialValue, boolean modal)
 +  {
 +    showDialogOnTopAsync(dialogParent, label, actionString,
 +            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
 +            initialValue, modal, null);
 +  }
 +
 +  public void showDialogOnTopAsync(JFrame dialogParent, Object label,
 +          String actionString, int JOPTIONPANE_OPTION,
 +          int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
 +          Object initialValue, boolean modal, JButton[] buttons)
 +  {
 +    // Ensure Jalview window is brought to front (primarily for Quit
 +    // confirmation window to be visible)
 +
 +    // This method of raising the Jalview window is broken in java
 +    // jalviewDesktop.setVisible(true);
 +    // jalviewDesktop.toFront();
 +
 +    // A better hack which works is to create a new JFrame parent with
 +    // setAlwaysOnTop(true)
 +    dialogParent.setAlwaysOnTop(true);
 +    parentComponent = dialogParent;
 +
 +    showDialog(label, actionString, JOPTIONPANE_OPTION,
 +            JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
 +            buttons);
 +
 +    dialogParent.setAlwaysOnTop(false);
 +    dialogParent.dispose();
 +  }
 +
 +  /**
     * JalviewJS signals option selection by a property change event for the
     * option e.g. "OK". This methods responds to that by running the response
     * action that corresponds to that option.
      {
        return;
      }
 -    Runnable action = callbacks.get(response);
 +    Callable<Void> action = callbacks.get(response);
      if (action != null)
      {
 -      action.run();
 -      parentComponent.requestFocus();
 +      try
 +      {
 +        action.call();
 +      } catch (Exception e)
 +      {
 +        e.printStackTrace();
 +      }
 +      if (parentComponent != null)
 +        parentComponent.requestFocus();
 +    }
 +  }
 +
 +  /**
 +   * Create a non-modal confirm dialog
 +   */
 +  public JDialog createDialog(Component parentComponent, Object message,
 +          String title, int optionType, int messageType, Icon icon,
 +          Object[] options, Object initialValue, boolean modal)
 +  {
 +    return createDialog(parentComponent, message, title, optionType,
 +            messageType, icon, options, initialValue, modal, null);
 +  }
 +
 +  public JDialog createDialog(Component parentComponent, Object message,
 +          String title, int optionType, int messageType, Icon icon,
 +          Object[] options, Object initialValue, boolean modal,
 +          JButton[] buttons)
 +  {
 +    JButton[] optionsButtons = null;
 +    Object initialValueButton = null;
 +    JOptionPane joptionpane = new JOptionPane();
 +    // Make button options
 +    int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
 +        JOptionPane.CANCEL_OPTION };
 +
 +    // we need the strings to make the buttons with actionEventListener
 +    if (options == null)
 +    {
 +      ArrayList<String> options_default = new ArrayList<>();
 +      options_default.add(UIManager.getString("OptionPane.yesButtonText"));
 +      if (optionType == JOptionPane.YES_NO_OPTION
 +              || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
 +      {
 +        options_default.add(UIManager.getString("OptionPane.noButtonText"));
 +      }
 +      if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
 +      {
 +        options_default
 +                .add(UIManager.getString("OptionPane.cancelButtonText"));
 +      }
 +      options = options_default.toArray();
      }
 +    if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
 +                          // add them here
 +    {
 +      if (((optionType == JOptionPane.YES_OPTION
 +              || optionType == JOptionPane.NO_OPTION
 +              || optionType == JOptionPane.CANCEL_OPTION
 +              || optionType == JOptionPane.OK_OPTION
 +              || optionType == JOptionPane.DEFAULT_OPTION)
 +              && options.length < 1)
 +              || ((optionType == JOptionPane.YES_NO_OPTION
 +                      || optionType == JOptionPane.OK_CANCEL_OPTION)
 +                      && options.length < 2)
 +              || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
 +                      && options.length < 3))
 +      {
 +        jalview.bin.Console
 +                .debug("JvOptionPane: not enough options for dialog type");
 +      }
 +      optionsButtons = new JButton[options.length];
 +      for (int i = 0; i < options.length && i < 3; i++)
 +      {
 +        Object o = options[i];
 +        int buttonAction = buttonActions[i];
 +        Callable<Void> action = callbacks.get(buttonAction);
 +        JButton jb;
 +        if (buttons != null && buttons.length > i && buttons[i] != null)
 +        {
 +          jb = buttons[i];
 +        }
 +        else
 +        {
 +          jb = new JButton();
 +        }
 +        jb.setText((String) o);
 +        jb.addActionListener(new ActionListener()
 +        {
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +            joptionpane.setValue(buttonAction);
 +            if (action != null)
 +              Executors.newSingleThreadExecutor().submit(action);
 +            // joptionpane.transferFocusBackward();
 +            joptionpane.transferFocusBackward();
 +            joptionpane.setVisible(false);
 +            // put focus and raise parent window if possible, unless cancel
 +            // button pressed
 +            boolean raiseParent = (parentComponent != null);
 +            if (buttonAction == JOptionPane.CANCEL_OPTION)
 +              raiseParent = false;
 +            if (optionType == JOptionPane.YES_NO_OPTION
 +                    && buttonAction == JOptionPane.NO_OPTION)
 +              raiseParent = false;
 +            if (raiseParent)
 +            {
 +              parentComponent.requestFocus();
 +              if (parentComponent instanceof JInternalFrame)
 +              {
 +                JInternalFrame jif = (JInternalFrame) parentComponent;
 +                jif.show();
 +                jif.moveToFront();
 +                jif.grabFocus();
 +              }
 +              else if (parentComponent instanceof Window)
 +              {
 +                Window w = (Window) parentComponent;
 +                w.toFront();
 +                w.requestFocus();
 +              }
 +            }
 +            joptionpane.setVisible(false);
 +          }
 +        });
 +        optionsButtons[i] = jb;
 +        if (o.equals(initialValue))
 +          initialValueButton = jb;
 +      }
 +    }
 +    joptionpane.setMessage(message);
 +    joptionpane.setMessageType(messageType);
 +    joptionpane.setOptionType(optionType);
 +    joptionpane.setIcon(icon);
 +    joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
 +    joptionpane.setInitialValue(
 +            Platform.isJS() ? initialValue : initialValueButton);
 +
 +    JDialog dialog = joptionpane.createDialog(parentComponent, title);
 +    dialog.setModalityType(
 +            modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
 +    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
 +    return dialog;
 +  }
 +
 +  /**
 +   * Utility to programmatically click a button on a JOptionPane (as a JFrame)
 +   * 
 +   * returns true if button was found
 +   */
 +  public static boolean clickButton(JFrame frame, int buttonType)
 +  {
 +
 +    return false;
    }
+   /**
+    * This helper method makes the JInternalFrame wait until it is notified by an
+    * InternalFrameClosing event. This method also adds the given JOptionPane to
+    * the JInternalFrame and sizes it according to the JInternalFrame's preferred
+    * size.
+    *
+    * @param f
+    *          The JInternalFrame to make modal.
+    */
+   private static void startModal(JInternalFrame f)
+   {
+     // We need to add an additional glasspane-like component directly
+     // below the frame, which intercepts all mouse events that are not
+     // directed at the frame itself.
+     JPanel modalInterceptor = new JPanel();
+     modalInterceptor.setOpaque(false);
+     JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
+     lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
+     modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
+     modalInterceptor.addMouseListener(new MouseAdapter()
+     {
+     });
+     modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
+     {
+     });
+     lp.add(modalInterceptor);
+     f.toFront();
+     // We need to explicitly dispatch events when we are blocking the event
+     // dispatch thread.
+     EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
+     try
+     {
+       while (!f.isClosed())
+       {
+         if (EventQueue.isDispatchThread())
+         {
+           // The getNextEventMethod() issues wait() when no
+           // event is available, so we don't need do explicitly wait().
+           AWTEvent ev = queue.getNextEvent();
+           // This mimics EventQueue.dispatchEvent(). We can't use
+           // EventQueue.dispatchEvent() directly, because it is
+           // protected, unfortunately.
+           if (ev instanceof ActiveEvent)
+             ((ActiveEvent) ev).dispatch();
+           else if (ev.getSource() instanceof Component)
+             ((Component) ev.getSource()).dispatchEvent(ev);
+           else if (ev.getSource() instanceof MenuComponent)
+             ((MenuComponent) ev.getSource()).dispatchEvent(ev);
+           // Other events are ignored as per spec in
+           // EventQueue.dispatchEvent
+         }
+         else
+         {
+           // Give other threads a chance to become active.
+           Thread.yield();
+         }
+       }
+     } catch (InterruptedException ex)
+     {
+       // If we get interrupted, then leave the modal state.
+     } finally
+     {
+       // Clean up the modal interceptor.
+       lp.remove(modalInterceptor);
+       // Remove the internal frame from its parent, so it is no longer
+       // lurking around and clogging memory.
+       Container parent = f.getParent();
+       if (parent != null)
+         parent.remove(f);
+     }
+   }
  }
@@@ -135,6 -135,7 +135,7 @@@ public abstract class StructureViewerBa
    public StructureViewerBase()
    {
      super();
+     setFrameIcon(WindowIcons.structureIcon);
    }
  
    /**
          if (confirm == JvOptionPane.CANCEL_OPTION
                  || confirm == JvOptionPane.CLOSED_OPTION)
          {
 +          // abort possible quit handling if CANCEL chosen
 +          if (confirm == JvOptionPane.CANCEL_OPTION)
 +            QuitHandler.abortQuit();
            return;
          }
          forceClose = confirm == JvOptionPane.YES_OPTION;
@@@ -148,6 -148,7 +148,7 @@@ public class UserDefinedColours extend
    {
      colorChooser.getSelectionModel().addChangeListener(this);
      frame = new JInternalFrame();
+     frame.setFrameIcon(WindowIcons.logoIcon);
      frame.setContentPane(this);
      Desktop.addInternalFrame(frame,
              MessageManager.getString("label.user_defined_colours"),
      chooser.setDialogTitle(
              MessageManager.getString("label.load_colour_scheme"));
      chooser.setToolTipText(MessageManager.getString("action.load"));
 -    chooser.setResponseHandler(0, new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        File choice = chooser.getSelectedFile();
 -        Cache.setProperty(LAST_DIRECTORY, choice.getParent());
 -
 -        UserColourScheme ucs = ColourSchemeLoader
 -                .loadColourScheme(choice.getAbsolutePath());
 -        Color[] colors = ucs.getColours();
 -        schemeName.setText(ucs.getSchemeName());
 +    chooser.setResponseHandler(0, () -> {
 +      File choice = chooser.getSelectedFile();
 +      Cache.setProperty(LAST_DIRECTORY, choice.getParent());
  
 -        if (ucs.getLowerCaseColours() != null)
 -        {
 -          caseSensitive.setSelected(true);
 -          lcaseColour.setEnabled(true);
 -          resetButtonPanel(true);
 -          for (int i = 0; i < lowerCaseButtons.size(); i++)
 -          {
 -            JButton button = lowerCaseButtons.get(i);
 -            button.setBackground(ucs.getLowerCaseColours()[i]);
 -          }
 -        }
 -        else
 -        {
 -          caseSensitive.setSelected(false);
 -          lcaseColour.setEnabled(false);
 -          resetButtonPanel(false);
 -        }
 +      UserColourScheme ucs = ColourSchemeLoader
 +              .loadColourScheme(choice.getAbsolutePath());
 +      Color[] colors = ucs.getColours();
 +      schemeName.setText(ucs.getSchemeName());
  
 -        for (int i = 0; i < upperCaseButtons.size(); i++)
 +      if (ucs.getLowerCaseColours() != null)
 +      {
 +        caseSensitive.setSelected(true);
 +        lcaseColour.setEnabled(true);
 +        resetButtonPanel(true);
 +        for (int i = 0; i < lowerCaseButtons.size(); i++)
          {
 -          JButton button = upperCaseButtons.get(i);
 -          button.setBackground(colors[i]);
 +          JButton button = lowerCaseButtons.get(i);
 +          button.setBackground(ucs.getLowerCaseColours()[i]);
          }
 +      }
 +      else
 +      {
 +        caseSensitive.setSelected(false);
 +        lcaseColour.setEnabled(false);
 +        resetButtonPanel(false);
 +      }
  
 -        addNewColourScheme(choice.getPath());
 +      for (int i = 0; i < upperCaseButtons.size(); i++)
 +      {
 +        JButton button = upperCaseButtons.get(i);
 +        button.setBackground(colors[i]);
        }
 +
 +      addNewColourScheme(choice.getPath());
 +      return null;
      });
  
      chooser.showOpenDialog(this);
  //////////////////////////////////////////////////////////////////
  package jalview.io;
  
++import jalview.bin.Cache;
++import jalview.gui.JvOptionPane;
++import jalview.util.MessageManager;
++import jalview.util.Platform;
++import jalview.util.dialogrunner.DialogRunnerI;
++
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.EventQueue;
@@@ -38,14 -38,13 +44,15 @@@ import java.util.List
  import java.util.Map;
  import java.util.StringTokenizer;
  import java.util.Vector;
 +import java.util.concurrent.Callable;
  
  import javax.swing.BoxLayout;
  import javax.swing.DefaultListCellRenderer;
  import javax.swing.JCheckBox;
+ import javax.swing.JDialog;
  import javax.swing.JFileChooser;
  import javax.swing.JList;
 +import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.SpringLayout;
@@@ -54,6 -53,7 +61,7 @@@ import javax.swing.plaf.basic.BasicFile
  
  import jalview.bin.Cache;
  import jalview.gui.JvOptionPane;
+ import jalview.gui.WindowIcons;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.util.dialogrunner.DialogRunnerI;
@@@ -72,7 -72,7 +80,7 @@@ public class JalviewFileChooser extend
  {
    private static final long serialVersionUID = 1L;
  
 -  private Map<Object, Runnable> callbacks = new HashMap<>();
 +  private Map<Object, Callable> callbacks = new HashMap<>();
  
    File selectedFile = null;
  
  
      if (selectedFile.exists())
      {
 -      int confirm = JvOptionPane.showConfirmDialog(this,
 -              MessageManager.getString("label.overwrite_existing_file"),
 -              MessageManager.getString("label.file_already_exists"),
 -              JvOptionPane.YES_NO_OPTION);
 +      int confirm = Cache.getDefault("CONFIRM_OVERWRITE_FILE", true)
 +              ? JvOptionPane.showConfirmDialog(this,
 +                      MessageManager
 +                              .getString("label.overwrite_existing_file"),
 +                      MessageManager.getString("label.file_already_exists"),
 +                      JvOptionPane.YES_NO_OPTION)
 +              : JOptionPane.YES_OPTION;
  
        if (confirm != JvOptionPane.YES_OPTION)
        {
  
    }
  
 +  /*
 +  @Override
 +  public JalviewFileChooser setResponseHandler(Object response,
 +          Runnable action)
 +  {
 +    callbacks.put(response, new Callable<Void>()
 +    {
 +      @Override
 +      public Void call()
 +      {
 +        action.run();
 +        return null;
 +      }
 +    });
 +    return this;
 +  }
 +  */
 +
    @Override
 -  public DialogRunnerI setResponseHandler(Object response, Runnable action)
 +  public DialogRunnerI setResponseHandler(Object response, Callable action)
    {
      callbacks.put(response, action);
      return this;
      {
        return;
      }
 -    Runnable action = callbacks.get(response);
 +    Callable action = callbacks.get(response);
      if (action != null)
      {
 -      action.run();
 +      try
 +      {
 +        action.call();
 +      } catch (Exception e)
 +      {
 +        e.printStackTrace();
 +      }
      }
    }
  
        break;
      }
    }
+   @Override
+   protected JDialog createDialog(Component parent) throws HeadlessException
+   {
+     JDialog dialog = super.createDialog(parent);
+     dialog.setIconImage(WindowIcons.logoIcon.getImage());
+     return dialog;
+   }
  }