Merge branch 'bug/JAL-4214_system_freeze_when_opening_file_using_gui_in_linux' into...
authorBen Soares <b.soares@dundee.ac.uk>
Thu, 24 Aug 2023 17:43:19 +0000 (18:43 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Thu, 24 Aug 2023 17:43:19 +0000 (18:43 +0100)
1  2 
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/JvOptionPane.java

@@@ -49,7 -49,6 +49,7 @@@ import java.beans.PropertyChangeEvent
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
 +import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.net.URL;
  import java.util.ArrayList;
@@@ -624,12 -623,12 +624,12 @@@ public class AlignFrame extends GAlignF
          // if (viewport.cursorMode)
          // {
          // alignPanel.seqPanel.insertNucAtCursor(false,"A");
 -        // //System.out.println("A");
 +        // //jalview.bin.Console.outPrintln("A");
          // }
          // break;
          /*
           * case KeyEvent.VK_CLOSE_BRACKET: if (viewport.cursorMode) {
 -         * System.out.println("closing bracket"); } break;
 +         * jalview.bin.Console.outPrintln("closing bracket"); } break;
           */
          case KeyEvent.VK_DELETE:
          case KeyEvent.VK_BACK_SPACE:
                @Override
                public void propertyChange(PropertyChangeEvent evt)
                {
 -                // // System.out.println("Discoverer property change.");
 +                // // jalview.bin.Console.outPrintln("Discoverer property
 +                // change.");
                  // if (evt.getPropertyName().equals("services"))
                  {
                    SwingUtilities.invokeLater(new Runnable()
                      @Override
                      public void run()
                      {
 -                      System.err.println(
 +                      jalview.bin.Console.errPrintln(
                                "Rebuild WS Menu for service change");
                        BuildWebServiceMenu();
                      }
        public void internalFrameClosed(
                javax.swing.event.InternalFrameEvent evt)
        {
 -        // System.out.println("deregistering discoverer listener");
 +        // jalview.bin.Console.outPrintln("deregistering discoverer listener");
          Desktop.instance.removeJalviewPropertyChangeListener("services",
                  thisListener);
          closeMenuItem_actionPerformed(true);
     */
    public void saveAlignment(String file, FileFormatI format)
    {
 +    saveAlignment(file, format, false);
 +  }
 +
 +  public void saveAlignment(String file, FileFormatI format, boolean stdout)
 +  {
      lastSaveSuccessful = true;
 -    lastFilenameSaved = file;
 +    if (!stdout)
 +    {
 +      lastFilenameSaved = file;
 +    }
      lastFormatSaved = format;
  
      if (FileFormat.Jalview.equals(format))
          shortName = shortName
                  .substring(shortName.lastIndexOf(File.separatorChar) + 1);
        }
 +      // TODO deal with stdout=true
        lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
                shortName);
  
        else
        {
          // create backupfiles object and get new temp filename destination
 -        boolean doBackup = BackupFiles.getEnabled();
 +        boolean doBackup = BackupFiles.getEnabled() && !stdout;
          BackupFiles backupfiles = null;
          if (doBackup)
          {
            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
                    : file;
            Console.trace("ALIGNFRAME setting PrintWriter");
 -          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 +          PrintWriter out = stdout
 +                  ? new PrintWriter(new OutputStreamWriter(System.out))
 +                  : new PrintWriter(new FileWriter(tempFilePath));
  
            if (backupfiles != null)
            {
            }
  
            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() }));
 +          out.flush();
 +          if (!stdout)
 +          {
 +            Console.trace("ALIGNFRAME about to close file");
 +            out.close();
 +            Console.trace("ALIGNFRAME closed file");
 +          }
 +          AlignFrame.this.setTitle(stdout ? "STDOUT" : file);
 +          if (stdout)
 +          {
 +            statusBar.setText(MessageManager.formatMessage(
 +                    "label.successfully_printed_to_stdout_in_format",
 +                    new Object[]
 +                    { format.getName() }));
 +          }
 +          else
 +          {
 +            statusBar.setText(MessageManager.formatMessage(
 +                    "label.successfully_saved_to_file_in_format",
 +                    new Object[]
 +                    { fileName, format.getName() }));
 +          }
            lastSaveSuccessful = true;
          } catch (IOException e)
          {
        closeAllTabs = true;
      }
  
+     Desktop.closeModal(this);
      try
      {
        if (alignPanels != null)
            featureSettings.close();
            featureSettings = null;
          }
          /*
           * this will raise an INTERNAL_FRAME_CLOSED event and this method will
           * be called recursively, with the frame now in 'closed' state
      } catch (Exception ex)
      {
        ex.printStackTrace();
 -      System.out.println("Exception whilst pasting: " + ex);
 +      jalview.bin.Console.outPrintln("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      }
  
      } catch (Exception ex)
      {
        ex.printStackTrace();
 -      System.out.println("Exception whilst pasting: " + ex);
 +      jalview.bin.Console.outPrintln("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      } catch (OutOfMemoryError oom)
      {
    @Override
    public void wrapMenuItem_actionPerformed(ActionEvent e)
    {
 -    scaleAbove.setVisible(wrapMenuItem.isSelected());
 -    scaleLeft.setVisible(wrapMenuItem.isSelected());
 -    scaleRight.setVisible(wrapMenuItem.isSelected());
 -    viewport.setWrapAlignment(wrapMenuItem.isSelected());
 +    setWrapFormat(wrapMenuItem.isSelected(), false);
 +  }
 +
 +  public void setWrapFormat(boolean b, boolean setMenuItem)
 +  {
 +    scaleAbove.setVisible(b);
 +    scaleLeft.setVisible(b);
 +    scaleRight.setVisible(b);
 +    viewport.setWrapAlignment(b);
      alignPanel.updateLayout();
 +    if (setMenuItem)
 +    {
 +      wrapMenuItem.setSelected(b);
 +    }
    }
  
    @Override
      }
  
      JInternalFrame frame = new JInternalFrame();
 -
 +    frame.setFrameIcon(null);
      frame.getContentPane().add(new JScrollPane(pane));
  
      Desktop.addInternalFrame(frame, MessageManager
        return alignPanel.overviewPanel;
      }
      JInternalFrame frame = new JInternalFrame();
 +    frame.setFrameIcon(null);
      final OverviewPanel overview = new OverviewPanel(alignPanel, frame,
              showHidden);
      frame.setContentPane(overview);
      Desktop.addInternalFrame(frame, "", true, frame.getWidth(),
              frame.getHeight(), true, true);
 -    frame.setFrameIcon(null);
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      final AlignmentPanel thePanel = this.alignPanel;
      else
      {
        JInternalFrame frame = new JInternalFrame();
 +      frame.setFrameIcon(null);
        frame.setContentPane(new PairwiseAlignPanel(viewport));
        Desktop.addInternalFrame(frame,
                MessageManager.getString("action.pairwise_alignment"), 600,
      {
        NewickFile fin = new NewickFile(
                new FileParse(cm.getNewick(), DataSourceType.PASTE));
 -      String title = cm.getAnnotLabel() + " " + cm.getTreeMethod() + " tree"
 -              + aa.sequenceRef != null
 +      String title = aa.label + " " + cm.getTreeMethod() + " tree"
 +              + (aa.sequenceRef != null
                        ? (" for " + aa.sequenceRef.getDisplayId(false))
 -                      : "";
 +                      : "");
  
        showColumnWiseTree(fin, aa, title, w, h, x, y);
      } catch (Throwable xx)
        {
          return null;
        }
 -      TreePanel tp = new TreePanel(alignPanel, nf, aa, title);
 +      TreePanel tp = new TreePanel(alignPanel, nf, aa, treeTitle);
  
        tp.setSize(w, h);
  
          tp.setLocation(x, y);
        }
  
 -      Desktop.addInternalFrame(tp, title, w, h);
 +      Desktop.addInternalFrame(tp, treeTitle, w, h);
        return tp;
      } catch (Throwable xx)
      {
      {
        try
        {
 -        System.err.println("Waiting for building menu to finish.");
 +        jalview.bin.Console
 +                .errPrintln("Waiting for building menu to finish.");
          Thread.sleep(10);
        } catch (Exception e)
        {
          final List<JMenuItem> legacyItems = new ArrayList<>();
          try
          {
 -          // System.err.println("Building ws menu again "
 +          // jalview.bin.Console.errPrintln("Building ws menu again "
            // + Thread.currentThread());
            // TODO: add support for context dependent disabling of services based
            // on
                                      Desktop.instance);
                      if (pe != null)
                      {
 -                      System.err.println("Associated file : "
 +                      jalview.bin.Console.errPrintln("Associated file : "
                                + (fm[0].toString()) + " with "
                                + toassoc.getDisplayId(true));
                        assocfiles++;
                viewport.getAlignment()));
      } catch (Exception ex)
      {
 -      System.err.println(ex.getMessage());
 +      jalview.bin.Console.errPrintln(ex.getMessage());
        return;
      }
    }
          console.runScript();
        } catch (Exception ex)
        {
 -        System.err.println((ex.toString()));
 +        jalview.bin.Console.errPrintln((ex.toString()));
          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                  MessageManager.getString("label.couldnt_run_groovy_script"),
                  MessageManager.getString("label.groovy_support_failed"),
      }
      else
      {
 -      System.err.println("Can't run Groovy script as console not found");
 +      jalview.bin.Console
 +              .errPrintln("Can't run Groovy script as console not found");
      }
    }
  
@@@ -51,6 -51,7 +51,7 @@@ import java.awt.event.WindowEvent
  import java.awt.geom.AffineTransform;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
+ import java.beans.PropertyVetoException;
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
@@@ -63,6 -64,7 +64,7 @@@ import java.util.Hashtable
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Locale;
+ import java.util.Map;
  import java.util.Vector;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
@@@ -439,7 -441,7 +441,7 @@@ public class Desktop extends jalview.jb
           * Send this message to stderr as the warning that follows (due to
           * reflection) also goes to stderr.
           */
 -        System.err.println(
 +        jalview.bin.Console.errPrintln(
                  "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
        }
        final String awtAppClassName = "awtAppClassName";
        }
  
        // Thread off a new instance of the file chooser - this reduces the time
 -      // it
 -      // takes to open it later on.
 +      // it takes to open it later on.
        new Thread(new Runnable()
        {
          @Override
          public void run()
          {
            jalview.bin.Console.debug("Filechooser init thread started.");
 -          String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 +          String fileFormat = FileLoader.getUseDefaultFileFormat()
 +                  ? Cache.getProperty("DEFAULT_FILE_FORMAT")
 +                  : null;
            JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
                    fileFormat);
            jalview.bin.Console.debug("Filechooser init thread finished.");
          }
        } catch (Exception ex)
        {
 -        System.out.println(
 +        jalview.bin.Console.outPrintln(
                  "Unable to paste alignment from system clipboard:\n" + ex);
        }
      }
  
      setKeyBindings(frame);
  
 -    desktop.add(frame);
 +    // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
 +    int tries=3;
 +    boolean shown=false;
 +    Exception last=null;
 +    do
 +    {
 +      try
 +      {
 +        desktop.add(frame);
 +        shown=true;
 +      } catch (IllegalArgumentException iaex)
 +      {
 +        last=iaex;
 +        tries--;
 +        jalview.bin.Console.info(
 +                "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
 +                iaex);
 +        try
 +        {
 +          Thread.sleep(5);
 +        } catch (InterruptedException iex)
 +        {
 +        }
 +        ;
 +      }
 +    } while (!shown && tries > 0);
 +    if (!shown)
 +    {
 +      jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
 +    }
  
      windowMenu.add(menuItem);
  
    @Override
    public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
    {
 -    String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 +    String fileFormat = FileLoader.getUseDefaultFileFormat()
 +            ? Cache.getProperty("DEFAULT_FILE_FORMAT")
 +            : null;
      JalviewFileChooser chooser = JalviewFileChooser.forRead(
              Cache.getProperty("LAST_DIRECTORY"), fileFormat,
              BackupFiles.getEnabled());
        }
      } catch (Exception ex)
      {
 -      System.err.println("Error opening help: " + ex.getMessage());
 +      jalview.bin.Console.errPrintln("Error opening help: " + ex.getMessage());
      }
    }
  
      boolean autoSave = projectFile != null && !saveAs
              && BackupFiles.getEnabled();
  
 -    // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
 +    // jalview.bin.Console.outPrintln("autoSave="+autoSave+", projectFile='"+projectFile+"',
      // saveAs="+saveAs+", Backups
      // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
  
          {
            if (Platform.isAMacAndNotJS())
            {
 -            System.err.println(
 +            jalview.bin.Console.errPrintln(
                      "Please ignore plist error - occurs due to problem with java 8 on OSX");
            }
          }
      {
        Desktop.instance.closeAll_actionPerformed(null);
        Desktop.instance.setVisible(false);
 -      Desktop.instance.dispose();
 +      Desktop us = Desktop.instance;
        Desktop.instance = null;
 +      // call dispose in a separate thread - try to avoid indirect deadlocks
 +      new Thread(new Runnable() {
 +        @Override
 +        public void run()
 +        {
 +          ExecutorService dex = us.dialogExecutor;
 +          if (dex!=null) {
 +            dex.shutdownNow();
 +            us.dialogExecutor=null;
 +            us.block.drainPermits();
 +          }
 +          us.dispose();
 +        }
 +      }).start();
      }
    }
  
    /**
-    * checks if any progress bars are being displayed in any of the windows managed by the desktop
+    * checks if any progress bars are being displayed in any of the windows
+    * managed by the desktop
+    * 
     * @return
     */
    public boolean operationsAreInProgress()
    {
      JInternalFrame[] frames = getAllFrames();
-     for (JInternalFrame frame:frames)
+     for (JInternalFrame frame : frames)
      {
        if (frame instanceof IProgressIndicator)
        {
-         if (((IProgressIndicator)frame).operationInProgress())
+         if (((IProgressIndicator) frame).operationInProgress())
          {
            return true;
          }
      }
      return operationInProgress();
    }
+   /**
+    * keep track of modal JvOptionPanes open as modal dialogs for AlignFrames.
+    * The way the modal JInternalFrame is made means it cannot be a child of an
+    * AlignFrame, so closing the AlignFrame might leave the modal open :(
+    */
+   private static Map<AlignFrame, JInternalFrame> alignFrameModalMap = new HashMap<>();
+   protected static void addModal(AlignFrame af, JInternalFrame jif)
+   {
+     alignFrameModalMap.put(af, jif);
+   }
+   protected static void closeModal(AlignFrame af)
+   {
+     if (!alignFrameModalMap.containsKey(af))
+     {
+       return;
+     }
+     JInternalFrame jif = alignFrameModalMap.get(af);
+     if (jif != null)
+     {
+       try
+       {
+         jif.setClosed(true);
+       } catch (PropertyVetoException e)
+       {
+         e.printStackTrace();
+       }
+     }
+     alignFrameModalMap.remove(af);
+   }
  }
@@@ -32,6 -32,7 +32,7 @@@ import java.awt.Toolkit
  import java.awt.Window;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
+ import java.awt.event.KeyEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseMotionAdapter;
  import java.beans.PropertyChangeEvent;
@@@ -51,8 -52,11 +52,11 @@@ import javax.swing.JDialog
  import javax.swing.JFrame;
  import javax.swing.JInternalFrame;
  import javax.swing.JLayeredPane;
+ import javax.swing.JMenu;
+ import javax.swing.JMenuBar;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
+ import javax.swing.JRootPane;
  import javax.swing.SwingUtilities;
  import javax.swing.UIManager;
  import javax.swing.event.InternalFrameEvent;
@@@ -718,7 -722,7 +722,7 @@@ public class JvOptionPane extends JOpti
  
    private static void outputMessage(Object message)
    {
 -    System.out.println(">>> JOption Message : " + message.toString());
 +    jalview.bin.Console.outPrintln(">>> JOption Message : " + message.toString());
    }
  
    public static Object getMockResponse()
      if (parentComponent != this
              && !(parentComponent == null && Desktop.instance == null))
      {
+       // note the parent goes back to a JRootPane so is probably
+       // Desktop.getDesktop()
        JInternalFrame jif = this.createInternalFrame(
                parentComponent != null ? parentComponent : Desktop.instance,
                title);
+       // connect to the alignFrame using a map in Desktop
+       if (parentComponent instanceof AlignFrame)
+       {
+         Desktop.addModal((AlignFrame) parentComponent, jif);
+       }
        jif.setFrameIcon(null);
        jif.addInternalFrameListener(new InternalFrameListener()
        {
          @Override
          public void internalFrameActivated(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameActivated");
          }
  
          @Override
          public void internalFrameClosed(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameClosed");
            JvOptionPane.this.internalDialogHandleResponse();
          }
  
          @Override
          public void internalFrameClosing(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameClosing");
          }
  
          @Override
          public void internalFrameDeactivated(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameDeactivated");
          }
  
          @Override
          public void internalFrameDeiconified(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameDeiconified");
          }
  
          @Override
          public void internalFrameIconified(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameIconified");
          }
  
          @Override
          public void internalFrameOpened(InternalFrameEvent arg0)
          {
-           System.err.println("##### internalFrameOpened");
          }
        });
        jif.setVisible(true);
  
    private void internalDialogHandleResponse()
    {
 -    String responseString = (String) this.getValue();
 +    Object value = this.getValue();
 +    if (value == null
 +            || (value instanceof Integer && (Integer) value == -1))
 +    {
 +      return;
 +    }
 +    String responseString = value.toString();
      int response = ourOptions.indexOf(responseString);
  
      if (!Platform.isJS())
      lp.add(modalInterceptor);
      f.toFront();
  
+     // disable the main menu bar if in Linux
+     JMenuBar menubar = null;
+     if (Platform.isLinux())
+     {
+       JRootPane rootpane = Desktop.getDesktop().getRootPane();
+       menubar = rootpane.getJMenuBar();
+     }
      // We need to explicitly dispatch events when we are blocking the event
      // dispatch thread.
      EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
      try
      {
-       boolean stillModal = true;
-       while (!f.isClosed() && stillModal)
+       if (menubar != null)
+       {
+         // don't allow clicks on main menu on linux due to a hanging bug.
+         // see JAL-4214.
+         setMenusEnabled(menubar, false);
+       }
+       while (!f.isClosed())
        {
          if (EventQueue.isDispatchThread())
          {
            // This mimics EventQueue.dispatchEvent(). We can't use
            // EventQueue.dispatchEvent() directly, because it is
            // protected, unfortunately.
-           System.out.println(
-                   "##### ev source=" + ev.getSource().getClass() + "");
            if (ev instanceof ActiveEvent)
            {
-             System.err.println("##### 1");
              ((ActiveEvent) ev).dispatch();
            }
+           else if (ev instanceof KeyEvent && ((KeyEvent) ev).isControlDown()
+                   && menubar != null)
+           {
+             // temporarily enable menus to send Ctrl+? KeyEvents
+             setMenusEnabled(menubar, true);
+             ((Component) ev.getSource()).dispatchEvent(ev);
+             setMenusEnabled(menubar, false);
+           }
            else if (ev.getSource() instanceof MenuComponent)
            {
-             System.err.println("##### 2");
              ((MenuComponent) ev.getSource()).dispatchEvent(ev);
            }
            else if (ev.getSource() instanceof Component)
            {
-             System.err.println("##### 3");
-             if (ev.getSource().equals(Desktop.getDesktop().getRootPane()))
-             {
-               stillModal = false;
-             }
-             else
-             {
-               ((Component) ev.getSource()).dispatchEvent(ev);
-             }
+             ((Component) ev.getSource()).dispatchEvent(ev);
            }
            // Other events are ignored as per spec in
            // EventQueue.dispatchEvent
-           System.err.println("##### 4");
          }
          else
          {
            // Give other threads a chance to become active.
-           System.err.println("##### 5");
            Thread.yield();
          }
        }
      } catch (InterruptedException ex)
      {
        // If we get interrupted, then leave the modal state.
-       System.err.println("##### 6");
      } finally
      {
-       System.err.println("##### 7");
+       // re-enable the main menu bar
+       if (menubar != null)
+       {
+         setMenusEnabled(menubar, true);
+       }
        // Clean up the modal interceptor.
        lp.remove(modalInterceptor);
  
+       // unpaint the frame
        f.setVisible(false);
  
+       // close the frame
        try
        {
          f.setClosed(true);
  
      return jvop;
    }
+   private static void setMenusEnabled(JMenuBar menubar, boolean b)
+   {
+     for (int i = 0; i < menubar.getMenuCount(); i++)
+     {
+       JMenu menu = menubar.getMenu(i);
+       menu.setEnabled(b);
+     }
+   }
  }