Merge branch 'develop' into features/r2_11_2_alphafold/JAL-2349_JAL-3855
authorJames Procter <j.procter@dundee.ac.uk>
Fri, 3 Feb 2023 14:14:49 +0000 (14:14 +0000)
committerJames Procter <j.procter@dundee.ac.uk>
Fri, 3 Feb 2023 14:14:49 +0000 (14:14 +0000)
1  2 
help/help/html/menus/alignmentMenu.html
resources/lang/Messages.properties
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/Finder.java
src/jalview/viewmodel/AlignmentViewport.java

              paste the clipboard contents to a text editor, you will see
              the format of the copied residues FASTA.
          </em></li>
 +        <li><strong>Copy Highlighted region (Control Shift
 +            C)</strong><br> <em>Copies each stretch of highlighted
 +            residues as a new sequence on the system clipboard - you can
 +            also do this by pressing &lt;CTRL&gt; &lt;SHIFT&gt; and C
 +            (&lt;APPLE&gt; &lt;SHIFT&gt; and C on MacOSX). <br>Use
 +            this when you want to extract sequence regions highlighted
 +            as a result of a Find operation, or due to mouseovers or
 +            selections made in other views such as an assocated 3D
 +            structure viewer.
 +        </em></li>
          <li><strong>Paste </strong>
            <ul>
              <li><strong>To New Alignment (Control Shift V)<br>
            Blosum62 Score, Percentage Identity, Zappo, Taylor,
          gecos:flower, gecos:blossom, gecos:sunset, gecos:ocean,
            Hydrophobicity, Helix Propensity, Strand Propensity, Turn
-           Propensity, Buried Index, Nucleotide, Purine/Pyrimidine, User
+           Propensity, Buried Index, Nucleotide, Nucleotide Ambiguity, Purine/Pyrimidine, User
            Defined<br>
        </strong> <em>See <a href="../colourSchemes/index.html">colours</a>
            for a description of all colour schemes.
@@@ -32,7 -32,17 +32,17 @@@ action.load_project = Load Projec
  action.save_project = Save Project
  action.save_project_as = Save Project as...
  action.quit = Quit
- label.quit_jalview = Quit Jalview?
+ action.force_quit = Force quit
+ label.quit_jalview = Are you sure you want to quit Jalview?
+ label.wait_for_save = Wait for save
+ label.unsaved_changes = There are unsaved changes.
+ label.save_in_progress = Some files are still saving:
+ label.unknown = Unknown
+ label.quit_after_saving = Jalview will quit after saving.
+ label.all_saved = All files saved.
+ label.quitting_bye = Quitting, bye!
+ action.wait = Wait
+ action.cancel_quit = Cancel quit
  action.expand_views = Expand Views
  action.gather_views = Gather Views
  action.page_setup = Page Setup...
@@@ -130,8 -140,6 +140,8 @@@ action.calculate = Calculat
  action.select_all = Select all
  action.select_highlighted_columns = Select Highlighted Columns
  tooltip.select_highlighted_columns = Press B to mark highlighted columns, Ctrl-(or Cmd)-B to toggle, and Alt-B to mark all but highlighted columns 
 +action.copy_highlighted_regions = Copy Highlighted Regions
 +tooltip.copy_highlighted_regions = Copies highlighted sequence regions to the clipboard for export or further analysis
  action.deselect_all = Deselect all
  action.invert_selection = Invert selection
  action.using_jmol = Using Jmol
@@@ -203,6 -211,7 +213,7 @@@ label.colourScheme_turnpropensity = Tur
  label.colourScheme_buriedindex = Buried Index
  label.colourScheme_purine/pyrimidine = Purine/Pyrimidine
  label.colourScheme_nucleotide = Nucleotide
+ label.colourScheme_nucleotideambiguity = Nucleotide Ambiguity
  label.colourScheme_t-coffeescores = T-Coffee Scores
  label.colourScheme_rnahelices = By RNA Helices
  label.colourScheme_sequenceid = Sequence ID Colour
@@@ -1400,8 -1409,6 +1411,8 @@@ label.click_to_edit = Click to edit, ri
  label.by_annotation_tooltip = Annotation Colour is configured from the main Colour menu
  label.show_linked_features = Show {0} features
  label.on_top = on top
 +label.include_features = Include Features
 +label.search_features = Search descriptions of displayed features
  label.include_linked_features = Include {0} features
  label.include_linked_tooltip = Include visible {0} features<br>converted to local sequence coordinates
  label.features_not_shown = {0} feature(s) not shown
@@@ -1410,7 -1417,6 +1421,7 @@@ label.ignore_hidden = Ignore hidden col
  label.ignore_hidden_tooltip = Ignore any characters in hidden columns when matching
  label.log_level = Log level
  label.log_level_tooltip = Temporarily set the log level for this console. The log level will revert to {0} when this Java console is closed.
 +label.copy = Copy
  label.copy_to_clipboard = Copy to clipboard
  label.copy_to_clipboard_tooltip = Copy all of the log text in this console to the system clipboard
  label.startup = Startup
@@@ -59,6 -59,7 +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;
@@@ -349,6 -350,8 +350,8 @@@ public class AlignFrame extends GAlignF
     */
    void init()
    {
+     setFrameIcon(null);
      // 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(null);
      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);
    }
                      | ActionEvent.CTRL_MASK)) != 0);
    }
  
 +  @Override
 +  protected void copyHighlightedColumns_actionPerformed(
 +          ActionEvent actionEvent)
 +  {
 +    avc.copyHighlightedRegionsToClipboard();
 +  }
 +
    /**
     * Rebuilds the Colour menu, including any user-defined colours which have
     * been loaded either on startup or during the session
      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);
  
@@@ -46,7 -46,6 +46,7 @@@ import jalview.datamodel.AlignedCodonFr
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.ContactMatrixI;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.SearchResults;
  import jalview.datamodel.SearchResultsI;
@@@ -720,10 -719,7 +720,10 @@@ public class AlignViewport extends Alig
          al.addSequence(seq);
        }
      }
 -
 +    for (ContactMatrixI cm : toAdd.getContactMaps())
 +    {
 +      al.addContactList(cm);
 +    }
      ranges.setEndSeq(getAlignment().getHeight() - 1); // BH 2019.04.18
      firePropertyChange("alignment", null, getAlignment().getSequences());
    }
       * in reverse order)
       */
      JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
-             .setResponseHandler(0, new Runnable()
-             {
-               @Override
-               public void run()
-               {
-                 addDataToAlignment(al);
-               }
-             }).setResponseHandler(1, new Runnable()
-             {
-               @Override
-               public void run()
-               {
-                 us.openLinkedAlignmentAs(al, title, true);
-               }
-             }).setResponseHandler(2, new Runnable()
-             {
-               @Override
-               public void run()
-               {
-                 us.openLinkedAlignmentAs(al, title, false);
-               }
+             .setResponseHandler(0, () -> {
+               addDataToAlignment(al);
+               return null;
+             }).setResponseHandler(1, () -> {
+               us.openLinkedAlignmentAs(al, title, true);
+               return null;
+             }).setResponseHandler(2, () -> {
+               us.openLinkedAlignmentAs(al, title, false);
+               return null;
              });
      dialog.showDialog(question,
              MessageManager.getString("label.open_split_window"),
@@@ -97,6 -97,7 +97,7 @@@ public class AnnotationColumnChooser ex
    {
      super(av, ap);
      frame = new JInternalFrame();
+     frame.setFrameIcon(null);
      frame.setContentPane(this);
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      Desktop.addInternalFrame(frame,
      // filterAnnotations, because showing hidden columns has the side effect of
      // adding them to the selection
      av.showAllHiddenColumns();
 -    av.getColumnSelection().filterAnnotations(currentAnnotation.annotations,
 +    av.getColumnSelection().filterAnnotations(currentAnnotation,
              filterParams);
  
      boolean hideCols = getActionOption() == ACTION_OPTION_HIDE;
@@@ -292,25 -292,21 +292,21 @@@ public class AnnotationLabels extends J
      EditNameDialog dialog = new EditNameDialog(annotation.label,
              annotation.description, name, description);
  
-     dialog.showDialog(ap.alignFrame, title, new Runnable()
-     {
-       @Override
-       public void run()
+     dialog.showDialog(ap.alignFrame, title, () -> {
+       annotation.label = dialog.getName();
+       String text = dialog.getDescription();
+       if (text != null && text.length() == 0)
        {
-         annotation.label = dialog.getName();
-         String text = dialog.getDescription();
-         if (text != null && text.length() == 0)
-         {
-           text = null;
-         }
-         annotation.description = text;
-         if (addNew)
-         {
-           ap.av.getAlignment().addAnnotation(annotation);
-           ap.av.getAlignment().setAnnotationIndex(annotation, 0);
-         }
-         ap.refresh(true);
+         text = null;
+       }
+       annotation.description = text;
+       if (addNew)
+       {
+         ap.av.getAlignment().addAnnotation(annotation);
+         ap.av.getAlignment().setAnnotationIndex(annotation, 0);
        }
+       ap.refresh(true);
+       return null;
      });
    }
  
  
      g.translate(0, getScrollOffset());
      g.setColor(Color.black);
 -
 +    SequenceI lastSeqRef = null;
      AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
      int fontHeight = g.getFont().getSize();
      int y = 0;
          {
            offset += fm.getDescent();
          }
 -
 -        x = width - fm.stringWidth(aa[i].label) - 3;
 +        String label = aa[i].label;
 +        boolean vertBar = false;
 +        if (aa[i].sequenceRef != null)
 +        {
 +          if (aa[i].sequenceRef != lastSeqRef)
 +          {
 +            label = aa[i].sequenceRef.getName() + " " + label;
 +            // TODO record relationship between sequence and this annotation and
 +            // display it here
 +          }
 +          else
 +          {
 +            vertBar = true;
 +          }
 +        }
 +        x = width - fm.stringWidth(label) - 3;
  
          if (aa[i].graphGroup > -1)
          {
          }
          else
          {
 -          g.drawString(aa[i].label, x, y + offset);
 +          if (vertBar)
 +          {
 +            g.drawLine(20, y + offset, 20, y - aa[i].height);
 +            g.drawLine(20, y + offset, x - 20, y + offset);
 +
 +          }
 +          g.drawString(label, x, y + offset);
          }
 +        lastSeqRef = aa[i].sequenceRef;
        }
      }
  
      if (!resizePanel && dragEvent != null && aa != null)
      {
        g.setColor(Color.lightGray);
 -      g.drawString(aa[selectedRow].label, dragEvent.getX(),
 -              dragEvent.getY() - getScrollOffset());
 +      g.drawString(
 +              (aa[selectedRow].sequenceRef == null ? ""
 +                      : aa[selectedRow].sequenceRef.getName())
 +                      + aa[selectedRow].label,
 +              dragEvent.getX(), dragEvent.getY() - getScrollOffset());
      }
  
      if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
@@@ -108,6 -108,7 +108,7 @@@ public class Finder extends GFinde
      focusFixed = fixedFocus;
      finders = new HashMap<>();
      frame = new JInternalFrame();
+     frame.setFrameIcon(null);
      frame.setContentPane(this);
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      frame.addInternalFrameListener(new InternalFrameAdapter()
      new FeatureEditor(ap, seqs, features, true).showDialog();
    }
  
 +  @Override
 +  protected void copyToClipboard_actionPerformed()
 +  {
 +    if (searchResults.isEmpty())
 +    {
 +      return; // shouldn't happen
 +    }
 +    // assume viewport controller has same searchResults as we do...
 +    ap.alignFrame.avc.copyHighlightedRegionsToClipboard();
 +  }
 +
    /**
     * Search the alignment for the next or all matches. If 'all matches', a
     * dialog is shown with the number of sequence ids and subsequences matched.
    void doSearch(boolean doFindAll)
    {
      createFeatures.setEnabled(false);
 +    copyToClipboard.setEnabled(false);
  
      String searchString = searchBox.getUserInput();
  
        finder = new jalview.analysis.Finder(av);
        finders.put(av, finder);
      }
 +    finder.setFeatureRenderer(ap.getFeatureRenderer());
  
      boolean isCaseSensitive = caseSensitive.isSelected();
      boolean doSearchDescription = searchDescription.isSelected();
 +    boolean doSearchfeatures = searchFeatures.isSelected();
      boolean skipHidden = ignoreHidden.isSelected();
      if (doFindAll)
      {
        finder.findAll(searchString, isCaseSensitive, doSearchDescription,
 -              skipHidden);
 +              doSearchfeatures, skipHidden);
      }
      else
      {
        finder.findNext(searchString, isCaseSensitive, doSearchDescription,
 -              skipHidden);
 +              doSearchfeatures, skipHidden);
      }
  
      searchResults = finder.getSearchResults();
      else
      {
        createFeatures.setEnabled(true);
 +      copyToClipboard.setEnabled(true);
      }
  
      searchBox.updateCache();
   */
  package jalview.viewmodel;
  
+ import java.awt.Color;
+ import java.beans.PropertyChangeSupport;
+ import java.util.ArrayDeque;
+ import java.util.ArrayList;
+ import java.util.BitSet;
+ import java.util.Deque;
+ import java.util.HashMap;
+ import java.util.Hashtable;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
  import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
  import jalview.analysis.Conservation;
  import jalview.analysis.TreeModel;
@@@ -29,6 -41,7 +41,7 @@@ import jalview.api.AlignViewportI
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeaturesDisplayedI;
  import jalview.api.ViewStyleI;
+ import jalview.bin.Console;
  import jalview.commands.CommandI;
  import jalview.datamodel.AlignedCodonFrame;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -37,7 -50,6 +50,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.ContactListI;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.ProfilesI;
@@@ -46,6 -58,7 +59,7 @@@ import jalview.datamodel.Sequence
  import jalview.datamodel.SequenceCollectionI;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
+ import jalview.project.Jalview2XML;
  import jalview.renderer.ResidueShader;
  import jalview.renderer.ResidueShaderI;
  import jalview.schemes.ColourSchemeI;
@@@ -62,18 -75,6 +76,6 @@@ import jalview.workers.ComplementConsen
  import jalview.workers.ConsensusThread;
  import jalview.workers.StrucConsensusThread;
  
- import java.awt.Color;
- import java.beans.PropertyChangeSupport;
- import java.util.ArrayDeque;
- import java.util.ArrayList;
- import java.util.BitSet;
- import java.util.Deque;
- import java.util.HashMap;
- import java.util.Hashtable;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
  /**
   * base class holding visualization and analysis attributes and common logic for
   * an active alignment view displayed in the GUI
@@@ -101,6 -102,11 +103,11 @@@ public abstract class AlignmentViewpor
    protected Deque<CommandI> redoList = new ArrayDeque<>();
  
    /**
+    * used to determine if quit should be confirmed
+    */
+   private boolean savedUpToDate = false;
+   /**
     * alignment displayed in the viewport. Please use get/setter
     */
    protected AlignmentI alignment;
      {
        this.historyList.push(command);
        broadcastCommand(command, false);
+       setSavedUpToDate(false);
+       Jalview2XML.setStateSavedUpToDate(false);
      }
    }
  
      return searchResults;
    }
  
 +  @Override
 +  public ContactListI getContactList(AlignmentAnnotation _aa, int column)
 +  {
 +    return alignment.getContactListFor(_aa, column);
 +  }
 +
    /**
     * get the consensus sequence as displayed under the PID consensus annotation
     * row.
      return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
              false));
    }
+   public void setSavedUpToDate(boolean s)
+   {
+     Console.debug(
+             "Setting " + this.getViewId() + " setSavedUpToDate to " + s);
+     savedUpToDate = s;
+   }
+   public boolean savedUpToDate()
+   {
+     Console.debug("Returning " + this.getViewId() + " savedUpToDate value: "
+             + savedUpToDate);
+     return savedUpToDate;
+   }
  }