Merge branch 'develop' into spike/JAL-4047/JAL-4048_columns_in_sequenceID
[jalview.git] / src / jalview / gui / Desktop.java
index 1521d0a..035da25 100644 (file)
@@ -51,6 +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,8 +64,8 @@ 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.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
@@ -122,6 +123,7 @@ import jalview.io.FormatAdapter;
 import jalview.io.IdentifyFile;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
+import jalview.io.exceptions.ImageOutputException;
 import jalview.jbgui.GSplitFrame;
 import jalview.jbgui.GStructureViewer;
 import jalview.project.Jalview2XML;
@@ -435,9 +437,14 @@ public class Desktop extends jalview.jbgui.GDesktop
     {
       if (LaunchUtils.getJavaVersion() >= 11)
       {
-        jalview.bin.Console.info(
+        /*
+         * Send this message to stderr as the warning that follows (due to
+         * reflection) also goes to stderr.
+         */
+        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";
       try
       {
         Toolkit xToolkit = Toolkit.getDefaultToolkit();
@@ -445,10 +452,10 @@ public class Desktop extends jalview.jbgui.GDesktop
         Field awtAppClassNameField = null;
 
         if (Arrays.stream(declaredFields)
-                .anyMatch(f -> f.getName().equals("awtAppClassName")))
+                .anyMatch(f -> f.getName().equals(awtAppClassName)))
         {
           awtAppClassNameField = xToolkit.getClass()
-                  .getDeclaredField("awtAppClassName");
+                  .getDeclaredField(awtAppClassName);
         }
 
         String title = ChannelProperties.getProperty("app_name");
@@ -459,11 +466,12 @@ public class Desktop extends jalview.jbgui.GDesktop
         }
         else
         {
-          jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
+          jalview.bin.Console
+                  .debug("XToolkit: " + awtAppClassName + " not found");
         }
       } catch (Exception e)
       {
-        jalview.bin.Console.debug("Error setting awtAppClassName");
+        jalview.bin.Console.debug("Error setting " + awtAppClassName);
         jalview.bin.Console.trace(Cache.getStackTraceString(e));
       }
     }
@@ -530,6 +538,9 @@ public class Desktop extends jalview.jbgui.GDesktop
       setBounds(xPos, yPos, 900, 650);
     }
 
+    // start dialogue queue for single dialogues
+    startDialogQueue();
+
     if (!Platform.isJS())
     /**
      * Java only
@@ -563,15 +574,16 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
 
       // 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.");
@@ -617,6 +629,12 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     });
     desktop.addMouseListener(ma);
+
+    if (Platform.isJS())
+    {
+      // used for jalviewjsTest
+      jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
+    }
   }
 
   /**
@@ -808,7 +826,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // TODO - write tests and fix AlignFrame.paste() which doesn't track if
     // clipboard has come from a different alignment window than the one where
     // paste has been called! JAL-4151
-    
+
     if (Desktop.jalviewClipboard != null)
     {
       // The clipboard was filled from within Jalview, we must use the
@@ -835,30 +853,32 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
 
       Desktop.addInternalFrame(af, newtitle, AlignFrame.DEFAULT_WIDTH,
-            AlignFrame.DEFAULT_HEIGHT);
+              AlignFrame.DEFAULT_HEIGHT);
 
-    } else {
-    try
+    }
+    else
     {
-      Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
-      Transferable contents = c.getContents(this);
-
-      if (contents != null)
+      try
       {
-        String file = (String) contents
-                .getTransferData(DataFlavor.stringFlavor);
+        Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
+        Transferable contents = c.getContents(this);
+
+        if (contents != null)
+        {
+          String file = (String) contents
+                  .getTransferData(DataFlavor.stringFlavor);
 
-        FileFormatI format = new IdentifyFile().identify(file,
-                DataSourceType.PASTE);
+          FileFormatI format = new IdentifyFile().identify(file,
+                  DataSourceType.PASTE);
 
-        new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
+          new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
 
+        }
+      } catch (Exception ex)
+      {
+        jalview.bin.Console.outPrintln(
+                "Unable to paste alignment from system clipboard:\n" + ex);
       }
-    } catch (Exception ex)
-    {
-      System.out.println(
-              "Unable to paste alignment from system clipboard:\n" + ex);
-    }
     }
   }
 
@@ -1061,7 +1081,36 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     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);
 
@@ -1221,7 +1270,9 @@ public class Desktop extends jalview.jbgui.GDesktop
   @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());
@@ -1256,7 +1307,6 @@ public class Desktop extends jalview.jbgui.GDesktop
 
       new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
               format);
-      return null;
     });
     chooser.showOpenDialog(this);
   }
@@ -1312,7 +1362,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     Object[] options = new Object[] { MessageManager.getString("action.ok"),
         MessageManager.getString("action.cancel") };
-    Callable<Void> action = () -> {
+    Runnable action = () -> {
       @SuppressWarnings("unchecked")
       String url = (history instanceof JTextField
               ? ((JTextField) history).getText()
@@ -1351,8 +1401,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
                   MessageManager.getString("label.url_not_found"),
                   JvOptionPane.WARNING_MESSAGE);
-
-          return null; // Void
+          return;
         }
 
         if (viewport != null)
@@ -1365,7 +1414,6 @@ public class Desktop extends jalview.jbgui.GDesktop
           new FileLoader().LoadFile(url, DataSourceType.URL, format);
         }
       }
-      return null; // Void
     };
     String dialogOption = MessageManager
             .getString("label.input_alignment_from_url");
@@ -1404,7 +1452,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public QuitHandler.QResponse desktopQuit(boolean ui, boolean disposeFlag)
   {
-    final Callable<Void> doDesktopQuit = () -> {
+    final Runnable doDesktopQuit = () -> {
       Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
       Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
       Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
@@ -1430,7 +1478,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       if (QuitHandler.quitCancelled())
       {
         jalview.bin.Console.debug("Desktop aborting quit");
-        return null;
+        return;
       }
 
       if (dialogExecutor != null)
@@ -1459,8 +1507,6 @@ public class Desktop extends jalview.jbgui.GDesktop
         // instance.dispose();
       }
       instance.quit();
-
-      return null; // Void
     };
 
     return QuitHandler.getQuitResponse(ui, doDesktopQuit, doDesktopQuit,
@@ -1476,7 +1522,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // 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);
+    Jalview.exit("Desktop exiting.", 0);
   }
 
   private void storeLastKnownDimensions(String string, Rectangle jc)
@@ -1589,7 +1635,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     } catch (Exception ex)
     {
-      System.err.println("Error opening help: " + ex.getMessage());
+      jalview.bin.Console.errPrintln("Error opening help: " + ex.getMessage());
     }
   }
 
@@ -1608,7 +1654,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     }
     Jalview.setCurrentAlignFrame(null);
-    System.out.println("ALL CLOSED");
+    jalview.bin.Console.info("ALL CLOSED");
 
     /*
      * reset state of singleton objects as appropriate (clear down session state
@@ -1820,7 +1866,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     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"));
 
@@ -1959,7 +2005,6 @@ public class Desktop extends jalview.jbgui.GDesktop
           }
         }
       }, "Project Loader").start();
-      return null;
     });
 
     chooser.showOpenDialog(this);
@@ -3049,7 +3094,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   /**
    * pause the queue
    */
-  private java.util.concurrent.Semaphore block = new Semaphore(0);
+  private Semaphore block = new Semaphore(0);
 
   private static groovy.ui.Console groovyConsole;
 
@@ -3067,12 +3112,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       {
         if (dialogPause)
         {
-          try
-          {
-            block.acquire();
-          } catch (InterruptedException x)
-          {
-          }
+          acquireDialogQueue();
         }
         if (instance == null)
         {
@@ -3090,12 +3130,41 @@ public class Desktop extends jalview.jbgui.GDesktop
     });
   }
 
+  private boolean dialogQueueStarted = false;
+
   public void startDialogQueue()
   {
+    if (dialogQueueStarted)
+    {
+      return;
+    }
     // set the flag so we don't pause waiting for another permit and semaphore
     // the current task to begin
-    dialogPause = false;
+    releaseDialogQueue();
+    dialogQueueStarted = true;
+  }
+
+  public void acquireDialogQueue()
+  {
+    try
+    {
+      block.acquire();
+      dialogPause = true;
+    } catch (InterruptedException e)
+    {
+      jalview.bin.Console.debug("Interruption when acquiring DialogueQueue",
+              e);
+    }
+  }
+
+  public void releaseDialogQueue()
+  {
+    if (!dialogPause)
+    {
+      return;
+    }
     block.release();
+    dialogPause = false;
   }
 
   /**
@@ -3130,7 +3199,15 @@ public class Desktop extends jalview.jbgui.GDesktop
     String title = "View of desktop";
     ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
             title);
-    exporter.doExport(of, this, width, height, title);
+    try
+    {
+      exporter.doExport(of, this, width, height, title);
+    } catch (ImageOutputException ioex)
+    {
+      jalview.bin.Console.error(
+              "Unexpected error whilst writing Jalview desktop snapshot as EPS",
+              ioex);
+    }
   }
 
   /**
@@ -3338,7 +3415,7 @@ public class Desktop extends jalview.jbgui.GDesktop
         {
           if (Platform.isAMacAndNotJS())
           {
-            System.err.println(
+            jalview.bin.Console.errPrintln(
                     "Please ignore plist error - occurs due to problem with java 8 on OSX");
           }
         }
@@ -3570,4 +3647,87 @@ public class Desktop extends jalview.jbgui.GDesktop
       jalview.bin.Console.debug(Cache.getStackTraceString(e));
     }
   }
+
+  /**
+   * closes the current instance window, disposes and forgets about it.
+   */
+  public static void closeDesktop()
+  {
+    if (Desktop.instance != null)
+    {
+      Desktop.instance.closeAll_actionPerformed(null);
+      Desktop.instance.setVisible(false);
+      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
+   * 
+   * @return
+   */
+  public boolean operationsAreInProgress()
+  {
+    JInternalFrame[] frames = getAllFrames();
+    for (JInternalFrame frame : frames)
+    {
+      if (frame instanceof IProgressIndicator)
+      {
+        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);
+  }
+
 }