Merge branch 'improvement/JAL-3416_default_to_LIVE_DRAG_MODE_for_flatlaf' into merge...
authorBen Soares <b.soares@dundee.ac.uk>
Wed, 9 Nov 2022 18:10:20 +0000 (18:10 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Wed, 9 Nov 2022 18:10:20 +0000 (18:10 +0000)
1  2 
src/jalview/gui/JvOptionPane.java
src/jalview/io/JalviewFileChooser.java

@@@ -18,6 -18,7 +18,6 @@@
   * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
   * The Jalview Authors are detailed in the 'AUTHORS' file.
   */
 -
  package jalview.gui;
  
  import java.awt.AWTEvent;
@@@ -41,22 -42,20 +41,23 @@@ 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.ChannelProperties;
  import jalview.util.Platform;
  import jalview.util.dialogrunner.DialogRunnerI;
  
@@@ -71,11 -70,11 +72,11 @@@ public class JvOptionPane extends JOpti
  
    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
 -   * option (text); this list allows conversion to index (int)
 +   * JalviewJS reports user choice in the dialog as the selected option (text);
 +   * this list allows conversion to index (int)
     */
    List<Object> ourOptions;
  
     * @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());
 +      return;
      }
      // two uses:
      //
  
      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;
 -       * (for Javascript, see propertyChange)
 +       * In Java, the response is returned to this thread and handled here; (for
 +       * Javascript, see propertyChange)
         */
        if (!Platform.isJS())
        /**
      else
      {
        /*
 -       * This is java similar to the swingjs handling, with the callbacks
 -       * attached to the button press of the dialog.  This means we can use
 -       * a non-modal JDialog for the confirmation without blocking the GUI.
 +       * This is java similar to the swingjs handling, with the callbacks 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
          {
            Object o = options[i];
            int buttonAction = buttonActions[i];
 -          Runnable action = callbacks.get(buttonAction);
 +          Callable<Void> action = callbacks.get(buttonAction);
            JButton jb = new JButton();
            jb.setText((String) o);
            jb.addActionListener(new ActionListener()
              {
                joptionpane.setValue(buttonAction);
                if (action != null)
 -                Executors.defaultThreadFactory().newThread(action).start();
 +                Executors.newSingleThreadExecutor().submit(action);
                // joptionpane.transferFocusBackward();
                joptionpane.transferFocusBackward();
                joptionpane.setVisible(false);
                Platform.isJS() ? initialValue : initialValue_btn);
  
        JDialog dialog = joptionpane.createDialog(parentComponent, title);
-       dialog.setIconImage(null);
+       dialog.setIconImages(ChannelProperties.getIconList());
        dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
                : ModalityType.MODELESS);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
      this.setMessage(mainPanel);
  
      ourOptions = Arrays.asList(options);
 +    int response;
      if (parentComponent != this)
      {
        JInternalFrame jif = this.createInternalFrame(parentComponent, title);
      else
      {
        JDialog dialog = this.createDialog(parentComponent, title);
-       dialog.setIconImage(null);
+       dialog.setIconImages(ChannelProperties.getIconList());
        dialog.setVisible(true); // blocking
        this.internalDialogHandleResponse();
        return;
      }
    }
  
 +  /*
 +   * @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, Runnable action)
 +  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)
 +  {
 +    if (!isInteractiveMode())
 +    {
 +      return (int) getMockResponse();
 +    }
 +    // 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.setIconImage(null);
 +    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)
 +  {
 +    JFrame frame = new JFrame();
 +    frame.setIconImage(null);
 +    showDialogOnTopAsync(frame, 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)
 +  {
 +    if (!isInteractiveMode())
 +    {
 +      handleResponse(getMockResponse());
 +      return;
 +    }
 +    // 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.
    public void handleResponse(Object response)
    {
      /*
 -    * this test is for NaN in Chrome
 -    */
 +     * this test is for NaN in Chrome
 +     */
      if (response != null && !response.equals(response))
      {
        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)
 +  {
 +    if (!isInteractiveMode())
 +    {
 +      handleResponse(getMockResponse());
 +      return null;
 +    }
 +    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.setIconImage(null);
 +    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;
    }
  
    /**
  //////////////////////////////////////////////////////////////////
  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;
@@@ -44,7 -38,6 +44,7 @@@ 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;
@@@ -52,7 -45,6 +52,7 @@@ 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;
@@@ -61,6 -53,7 +61,7 @@@ import javax.swing.plaf.basic.BasicFile
  
  import jalview.bin.Cache;
  import jalview.gui.JvOptionPane;
+ import jalview.util.ChannelProperties;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.util.dialogrunner.DialogRunnerI;
@@@ -79,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();
 +      }
      }
    }
  
    protected JDialog createDialog(Component parent) throws HeadlessException
    {
      JDialog dialog = super.createDialog(parent);
-     dialog.setIconImage(null);
+     dialog.setIconImages(ChannelProperties.getIconList());
      return dialog;
    }