Merge branch 'patch/JAL-4345_pae_epas1_doubleclick' into develop
[jalview.git] / src / jalview / gui / Preferences.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.Font;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.io.File;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.concurrent.CompletableFuture;
34
35 import javax.help.HelpSetException;
36 import javax.swing.JComboBox;
37 import javax.swing.JInternalFrame;
38 import javax.swing.JPanel;
39 import javax.swing.ListSelectionModel;
40 import javax.swing.RowFilter;
41 import javax.swing.RowSorter;
42 import javax.swing.SortOrder;
43 import javax.swing.event.DocumentEvent;
44 import javax.swing.event.DocumentListener;
45 import javax.swing.event.ListSelectionEvent;
46 import javax.swing.event.ListSelectionListener;
47 import javax.swing.table.TableCellRenderer;
48 import javax.swing.table.TableColumn;
49 import javax.swing.table.TableModel;
50 import javax.swing.table.TableRowSorter;
51
52 //import edu.stanford.ejalbert.launching.IBrowserLaunching;
53 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
54 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
55 import jalview.bin.Cache;
56 import jalview.bin.Console;
57 import jalview.bin.MemorySetting;
58 import jalview.ext.pymol.PymolManager;
59 import jalview.gui.Help.HelpId;
60 import jalview.gui.StructureViewer.ViewerType;
61 import jalview.io.BackupFiles;
62 import jalview.io.BackupFilesPresetEntry;
63 import jalview.io.FileFormatI;
64 import jalview.io.JalviewFileChooser;
65 import jalview.io.JalviewFileView;
66 import jalview.jbgui.GPreferences;
67 import jalview.jbgui.GSequenceLink;
68 import jalview.schemes.ColourSchemeI;
69 import jalview.schemes.ColourSchemes;
70 import jalview.schemes.ResidueColourScheme;
71 import jalview.urls.UrlLinkTableModel;
72 import jalview.urls.api.UrlProviderFactoryI;
73 import jalview.urls.api.UrlProviderI;
74 import jalview.urls.desktop.DesktopUrlProviderFactory;
75 import jalview.util.MessageManager;
76 import jalview.util.Platform;
77 import jalview.util.UrlConstants;
78 import jalview.ws.sifts.SiftsSettings;
79
80 /**
81  * DOCUMENT ME!
82  * 
83  * @author $author$
84  * @version $Revision$
85  */
86 /*
87  * for merge with Jalview-JS
88  public class Preferences extends GPreferences implements ApplicationSingletonI
89  */
90 public class Preferences extends GPreferences
91 {
92   public static final String ENABLE_SPLIT_FRAME = "ENABLE_SPLIT_FRAME";
93
94   public static final String SCALE_PROTEIN_TO_CDNA = "SCALE_PROTEIN_TO_CDNA";
95
96   public static final String DEFAULT_COLOUR = "DEFAULT_COLOUR";
97
98   public static final String DEFAULT_COLOUR_PROT = "DEFAULT_COLOUR_PROT";
99
100   public static final String DEFAULT_COLOUR_NUC = "DEFAULT_COLOUR_NUC";
101
102   public static final String ADD_TEMPFACT_ANN = "ADD_TEMPFACT_ANN";
103
104   public static final String ADD_SS_ANN = "ADD_SS_ANN";
105
106   public static final String USE_RNAVIEW = "USE_RNAVIEW";
107
108   public static final String STRUCT_FROM_PDB = "STRUCT_FROM_PDB";
109
110   public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
111
112   public static final String CHIMERA_PATH = "CHIMERA_PATH";
113
114   public static final String CHIMERAX_PATH = "CHIMERAX_PATH";
115
116   public static final String PYMOL_PATH = "PYMOL_PATH";
117
118   public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
119
120   public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE";
121
122   public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY";
123
124   public static final String SHOW_OV_HIDDEN_AT_START = "SHOW_OV_HIDDEN_AT_START";
125
126   public static final String USE_LEGACY_GAP = "USE_LEGACY_GAP";
127
128   public static final String GAP_COLOUR = "GAP_COLOUR";
129
130   public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
131
132   private static final int MIN_FONT_SIZE = 1;
133
134   private static final int MAX_FONT_SIZE = 30;
135
136   private String previousProxyType;
137
138   private static Preferences INSTANCE = null; // add "final"
139
140   /**
141    * Holds name and link separated with | character. Sequence ID must be
142    * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
143    */
144   public static UrlProviderI sequenceUrlLinks;
145
146   public static UrlLinkTableModel dataModel;
147
148   /**
149    * Holds name and link separated with | character. Sequence IDS and Sequences
150    * must be $SEQUENCEIDS$ or $SEQUENCEIDS=/.possible | chars ./=$ and
151    * $SEQUENCES$ or $SEQUENCES=/.possible | chars ./=$ and separation character
152    * for first and second token specified after a pipe character at end |,|.
153    * (TODO: proper escape for using | to separate ids or sequences
154    */
155
156   public static List<String> groupURLLinks;
157   static
158   {
159     // don't populate with session properties
160     Cache.disableSessionProperties();
161
162     // get links selected to be in the menu (SEQUENCE_LINKS)
163     // and links entered by the user but not selected (STORED_LINKS)
164     String inMenuString = Cache.getDefault("SEQUENCE_LINKS", "");
165     String notInMenuString = Cache.getDefault("STORED_LINKS", "");
166     String defaultUrl = Cache.getDefault("DEFAULT_URL",
167             UrlConstants.DEFAULT_LABEL);
168
169     // if both links lists are empty, add the DEFAULT_URL link
170     // otherwise we assume the default link is in one of the lists
171     if (inMenuString.isEmpty() && notInMenuString.isEmpty())
172     {
173       inMenuString = UrlConstants.DEFAULT_STRING;
174     }
175     UrlProviderFactoryI factory = new DesktopUrlProviderFactory(defaultUrl,
176             inMenuString, notInMenuString);
177     sequenceUrlLinks = factory.createUrlProvider();
178     dataModel = new UrlLinkTableModel(sequenceUrlLinks);
179
180     /**
181      * TODO: reformulate groupURL encoding so two or more can be stored in the
182      * .properties file as '|' separated strings
183      */
184
185     groupURLLinks = new ArrayList<>();
186
187     // reenable
188     Cache.enableSessionProperties();
189   }
190
191   JInternalFrame frame;
192
193   private WsPreferences wsPrefs;
194
195   private OptionsParam promptEachTimeOpt = new OptionsParam(
196           MessageManager.getString("label.prompt_each_time"),
197           LineartOptions.PROMPT_EACH_TIME);
198
199   private OptionsParam lineArtOpt = new OptionsParam(
200           MessageManager.getString("label.lineart"), "Lineart");
201
202   private OptionsParam textOpt = new OptionsParam(
203           MessageManager.getString("action.text"), "Text");
204
205   // get singleton Preferences instance
206   public static Preferences getInstance()
207   {
208     if (INSTANCE == null || INSTANCE.frame == null
209             || INSTANCE.frame.isClosed())
210     {
211       INSTANCE = new Preferences();
212     }
213     return INSTANCE;
214
215     /*
216      * Replace code with the following for Jalvew-JS
217     Preferences INSTANCE = ApplicationSingletonProvider.getInstance(Preferences.class);
218     if (INSTANCE == null || INSTANCE.frame == null
219             || INSTANCE.frame.isClosed())
220     {
221       ApplicationSingletonProvider.remove(Preferences.class);
222       INSTANCE = ApplicationSingletonProvider.getInstance(Preferences.class);
223     }
224     return INSTANCE;
225     */
226   }
227
228   public static void openPreferences()
229   {
230     openPreferences(null, null);
231   }
232
233   public static void openPreferences(TabRef selectTab, String message)
234   {
235     Preferences p = getInstance();
236     if (selectTab != null)
237       p.selectTab(selectTab, message);
238     p.frame.show();
239     p.frame.moveToFront();
240     p.frame.grabFocus();
241   }
242
243   public void selectTab(TabRef selectTab, String message)
244   {
245     this.selectTab(selectTab);
246     if (message != null)
247       this.setMessage(message);
248     this.frame.show();
249   }
250
251   /**
252    * Creates a new Preferences object.
253    */
254   private Preferences()
255   {
256     super();
257     // don't populate with session properties
258     Cache.disableSessionProperties();
259
260     frame = new JInternalFrame();
261     frame.setFrameIcon(null);
262     frame.setContentPane(this);
263     if (!Platform.isJS())
264     /**
265      * Java only
266      * 
267      * @j2sIgnore
268      */
269     {
270       wsPrefs = new WsPreferences();
271       wsTab.add(wsPrefs, BorderLayout.CENTER);
272     }
273     int width = 500, height = 450;
274     if (Platform.isAMacAndNotJS())
275     {
276       width = 570;
277       height = 480;
278     }
279
280     Desktop.addInternalFrame(frame,
281             MessageManager.getString("label.preferences"), width, height);
282     frame.setMinimumSize(new Dimension(width, height));
283
284     /*
285      * Set Visual tab defaults
286      */
287     seqLimit.setSelected(Cache.getDefault("SHOW_JVSUFFIX", true));
288     rightAlign.setSelected(Cache.getDefault("RIGHT_ALIGN_IDS", false));
289     fullScreen.setSelected(Cache.getDefault("SHOW_FULLSCREEN", false));
290     annotations.setSelected(Cache.getDefault("SHOW_ANNOTATIONS", true));
291
292     conservation.setSelected(Cache.getDefault("SHOW_CONSERVATION", true));
293     quality.setSelected(Cache.getDefault("SHOW_QUALITY", true));
294     identity.setSelected(Cache.getDefault("SHOW_IDENTITY", true));
295     openoverv.setSelected(Cache.getDefault("SHOW_OVERVIEW", false));
296     showUnconserved
297             .setSelected(Cache.getDefault("SHOW_UNCONSERVED", false));
298     showOccupancy.setSelected(Cache.getDefault(SHOW_OCCUPANCY, false));
299     showGroupConsensus
300             .setSelected(Cache.getDefault("SHOW_GROUP_CONSENSUS", false));
301     showGroupConservation.setSelected(
302             Cache.getDefault("SHOW_GROUP_CONSERVATION", false));
303     showConsensHistogram.setSelected(
304             Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM", true));
305     showConsensLogo
306             .setSelected(Cache.getDefault("SHOW_CONSENSUS_LOGO", false));
307     showNpTooltip
308             .setSelected(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
309     showDbRefTooltip
310             .setSelected(Cache.getDefault("SHOW_DBREFS_TOOLTIP", true));
311
312     String[] fonts = java.awt.GraphicsEnvironment
313             .getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
314     for (int i = 0; i < fonts.length; i++)
315     {
316       fontNameCB.addItem(fonts[i]);
317     }
318
319     for (int i = MIN_FONT_SIZE; i <= MAX_FONT_SIZE; i++)
320     {
321       fontSizeCB.addItem(i + "");
322     }
323
324     fontStyleCB.addItem("plain");
325     fontStyleCB.addItem("bold");
326     fontStyleCB.addItem("italic");
327
328     fontNameCB.setSelectedItem(Cache.getDefault("FONT_NAME", "SansSerif"));
329     fontSizeCB.setSelectedItem(Cache.getDefault("FONT_SIZE", "10"));
330     fontStyleCB.setSelectedItem(
331             Cache.getDefault("FONT_STYLE", Font.PLAIN + ""));
332
333     smoothFont.setSelected(Cache.getDefault("ANTI_ALIAS", true));
334     scaleProteinToCdna
335             .setSelected(Cache.getDefault(SCALE_PROTEIN_TO_CDNA, false));
336
337     idItalics.setSelected(Cache.getDefault("ID_ITALICS", true));
338
339     wrap.setSelected(Cache.getDefault("WRAP_ALIGNMENT", false));
340
341     gapSymbolCB.addItem("-");
342     gapSymbolCB.addItem(".");
343
344     gapSymbolCB.setSelectedItem(Cache.getDefault("GAP_SYMBOL", "-"));
345
346     sortby.addItem("No sort");
347     sortby.addItem("Id");
348     sortby.addItem("Pairwise Identity");
349     sortby.setSelectedItem(Cache.getDefault("SORT_ALIGNMENT", "No sort"));
350
351     sortAnnBy.addItem(SequenceAnnotationOrder.NONE.toString());
352     sortAnnBy
353             .addItem(SequenceAnnotationOrder.SEQUENCE_AND_LABEL.toString());
354     sortAnnBy
355             .addItem(SequenceAnnotationOrder.LABEL_AND_SEQUENCE.toString());
356     SequenceAnnotationOrder savedSort = SequenceAnnotationOrder
357             .valueOf(Cache.getDefault(SORT_ANNOTATIONS,
358                     SequenceAnnotationOrder.NONE.name()));
359     sortAnnBy.setSelectedItem(savedSort.toString());
360
361     sortAutocalc.addItem("Autocalculated first");
362     sortAutocalc.addItem("Autocalculated last");
363     final boolean showAbove = Cache.getDefault(SHOW_AUTOCALC_ABOVE, true);
364     sortAutocalc.setSelectedItem(showAbove ? sortAutocalc.getItemAt(0)
365             : sortAutocalc.getItemAt(1));
366     startupCheckbox
367             .setSelected(Cache.getDefault("SHOW_STARTUP_FILE", true));
368     startupFileTextfield.setText(Cache.getDefault("STARTUP_FILE",
369             Cache.getDefault("www.jalview.org", "https://www.jalview.org")
370                     + "/examples/exampleFile_2_7.jvp"));
371
372     /*
373      * Set Colours tab defaults
374      */
375     protColour.addItem(ResidueColourScheme.NONE);
376     nucColour.addItem(ResidueColourScheme.NONE);
377     for (ColourSchemeI cs : ColourSchemes.getInstance().getColourSchemes())
378     {
379       String name = cs.getSchemeName();
380       protColour.addItem(name);
381       nucColour.addItem(name);
382     }
383     String oldProp = Cache.getDefault(DEFAULT_COLOUR,
384             ResidueColourScheme.NONE);
385     String newProp = Cache.getDefault(DEFAULT_COLOUR_PROT, null);
386     protColour.setSelectedItem(newProp != null ? newProp : oldProp);
387     newProp = Cache.getDefault(DEFAULT_COLOUR_NUC, null);
388     nucColour.setSelectedItem(newProp != null ? newProp : oldProp);
389     minColour.setBackground(
390             Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN", Color.orange));
391     maxColour.setBackground(
392             Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX", Color.red));
393
394     /*
395      * Set overview panel defaults
396      */
397     gapColour.setBackground(Cache.getDefaultColour(GAP_COLOUR,
398             OverviewCanvas.OVERVIEW_DEFAULT_GAP));
399     hiddenColour.setBackground(Cache.getDefaultColour(HIDDEN_COLOUR,
400             OverviewCanvas.OVERVIEW_DEFAULT_HIDDEN));
401     useLegacyGap.setSelected(Cache.getDefault(USE_LEGACY_GAP, false));
402     gapLabel.setEnabled(!useLegacyGap.isSelected());
403     gapColour.setEnabled(!useLegacyGap.isSelected());
404     showHiddenAtStart
405             .setSelected(Cache.getDefault(SHOW_OV_HIDDEN_AT_START, false));
406
407     /*
408      * Set Structure tab defaults
409      */
410     final boolean structSelected = Cache.getDefault(STRUCT_FROM_PDB, true);
411     structFromPdb.setSelected(structSelected);
412     addSecondaryStructure.setSelected(Cache.getDefault(ADD_SS_ANN, true));
413     addSecondaryStructure.setEnabled(structSelected);
414     addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, true));
415     addTempFactor.setEnabled(structSelected);
416
417     /*
418      * set choice of structure viewer, and path if saved as a preference;
419      * default to Jmol (first choice) if an unexpected value is found
420      */
421     String viewerType = Cache.getDefault(STRUCTURE_DISPLAY,
422             ViewerType.JMOL.name());
423     structViewer.setSelectedItem(viewerType);
424     String viewerPath = "";
425     ViewerType type = null;
426     try
427     {
428       type = ViewerType.valueOf(viewerType);
429       switch (type)
430       {
431       case JMOL:
432         break;
433       case CHIMERA:
434         viewerPath = Cache.getDefault(CHIMERA_PATH, "");
435         break;
436       case CHIMERAX:
437         viewerPath = Cache.getDefault(CHIMERAX_PATH, "");
438         break;
439       case PYMOL:
440         viewerPath = Cache.getDefault(PYMOL_PATH, "");
441         break;
442       }
443     } catch (IllegalArgumentException e)
444     {
445       Console.error("Unknown structure viewer type: " + viewerType
446               + ", defaulting to Jmol");
447       type = ViewerType.JMOL;
448     }
449     structureViewerPath.setText(viewerPath);
450
451     structureViewerPath.addActionListener(new ActionListener()
452     {
453       @Override
454       public void actionPerformed(ActionEvent e)
455       {
456         if (validateViewerPath())
457         {
458           String path = structureViewerPath.getText();
459           try
460           {
461             ViewerType type = ViewerType.valueOf(viewerType);
462             switch (type)
463             {
464             case JMOL:
465               break;
466             case CHIMERA:
467               Cache.setProperty(CHIMERA_PATH, path);
468               break;
469             case CHIMERAX:
470               Cache.setProperty(CHIMERAX_PATH, path);
471               break;
472             case PYMOL:
473               Cache.setProperty(PYMOL_PATH, path);
474               break;
475             }
476           } catch (IllegalArgumentException x)
477           {
478             Console.error("Failed to set path - unknown viewer type", x);
479           }
480         }
481       }
482     });
483
484     if (Cache.getDefault("MAP_WITH_SIFTS", false))
485     {
486       siftsMapping.setSelected(true);
487     }
488     else
489     {
490       nwMapping.setSelected(true);
491     }
492
493     SiftsSettings
494             .setMapWithSifts(Cache.getDefault("MAP_WITH_SIFTS", false));
495
496     /*
497      * Set Connections tab defaults
498      */
499
500     // set up sorting
501     linkUrlTable.setModel(dataModel);
502     final TableRowSorter<TableModel> sorter = new TableRowSorter<>(
503             linkUrlTable.getModel());
504     linkUrlTable.setRowSorter(sorter);
505     List<RowSorter.SortKey> sortKeys = new ArrayList<>();
506
507     UrlLinkTableModel m = (UrlLinkTableModel) linkUrlTable.getModel();
508     sortKeys.add(new RowSorter.SortKey(m.getPrimaryColumn(),
509             SortOrder.DESCENDING));
510     sortKeys.add(new RowSorter.SortKey(m.getSelectedColumn(),
511             SortOrder.DESCENDING));
512     sortKeys.add(
513             new RowSorter.SortKey(m.getNameColumn(), SortOrder.ASCENDING));
514
515     sorter.setSortKeys(sortKeys);
516     // BH 2018 setSortKeys will do the sort
517     // sorter.sort();
518
519     // set up filtering
520     ActionListener onReset;
521     onReset = new ActionListener()
522     {
523       @Override
524       public void actionPerformed(ActionEvent e)
525       {
526         filterTB.setText("");
527         sorter.setRowFilter(RowFilter.regexFilter(""));
528       }
529
530     };
531     doReset.addActionListener(onReset);
532
533     // filter to display only custom urls
534     final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
535     {
536       @Override
537       public boolean include(
538               Entry<? extends TableModel, ? extends Object> entry)
539       {
540         return ((UrlLinkTableModel) entry.getModel()).isUserEntry(entry);
541       }
542     };
543
544     final TableRowSorter<TableModel> customSorter = new TableRowSorter<>(
545             linkUrlTable.getModel());
546     customSorter.setRowFilter(customUrlFilter);
547
548     ActionListener onCustomOnly;
549     onCustomOnly = new ActionListener()
550     {
551       @Override
552       public void actionPerformed(ActionEvent e)
553       {
554         filterTB.setText("");
555         sorter.setRowFilter(customUrlFilter);
556       }
557     };
558     userOnly.addActionListener(onCustomOnly);
559
560     filterTB.getDocument().addDocumentListener(new DocumentListener()
561     {
562       String caseInsensitiveFlag = "(?i)";
563
564       @Override
565       public void changedUpdate(DocumentEvent e)
566       {
567         sorter.setRowFilter(RowFilter
568                 .regexFilter(caseInsensitiveFlag + filterTB.getText()));
569       }
570
571       @Override
572       public void removeUpdate(DocumentEvent e)
573       {
574         sorter.setRowFilter(RowFilter
575                 .regexFilter(caseInsensitiveFlag + filterTB.getText()));
576       }
577
578       @Override
579       public void insertUpdate(DocumentEvent e)
580       {
581         sorter.setRowFilter(RowFilter
582                 .regexFilter(caseInsensitiveFlag + filterTB.getText()));
583       }
584     });
585
586     // set up list selection functionality
587     linkUrlTable.getSelectionModel()
588             .addListSelectionListener(new UrlListSelectionHandler());
589
590     // set up radio buttons
591     int onClickCol = ((UrlLinkTableModel) linkUrlTable.getModel())
592             .getPrimaryColumn();
593     String onClickName = linkUrlTable.getColumnName(onClickCol);
594     linkUrlTable.getColumn(onClickName)
595             .setCellRenderer(new RadioButtonRenderer());
596     linkUrlTable.getColumn(onClickName)
597             .setCellEditor(new RadioButtonEditor());
598
599     // get boolean columns and resize those to min possible
600     for (int column = 0; column < linkUrlTable.getColumnCount(); column++)
601     {
602       if (linkUrlTable.getModel().getColumnClass(column)
603               .equals(Boolean.class))
604       {
605         TableColumn tableColumn = linkUrlTable.getColumnModel()
606                 .getColumn(column);
607         int preferredWidth = tableColumn.getMinWidth();
608
609         TableCellRenderer cellRenderer = linkUrlTable.getCellRenderer(0,
610                 column);
611         Component c = linkUrlTable.prepareRenderer(cellRenderer, 0, column);
612         int cwidth = c.getPreferredSize().width
613                 + linkUrlTable.getIntercellSpacing().width;
614         preferredWidth = Math.max(preferredWidth, cwidth);
615
616         tableColumn.setPreferredWidth(preferredWidth);
617       }
618     }
619
620     String proxyTypeString = Cache.getDefault("USE_PROXY", "false");
621     previousProxyType = proxyTypeString;
622     switch (proxyTypeString)
623     {
624     case Cache.PROXYTYPE_NONE:
625       proxyType.setSelected(noProxy.getModel(), true);
626       break;
627     case Cache.PROXYTYPE_SYSTEM:
628       proxyType.setSelected(systemProxy.getModel(), true);
629       break;
630     case Cache.PROXYTYPE_CUSTOM:
631       proxyType.setSelected(customProxy.getModel(), true);
632       break;
633     default:
634       Console.warn(
635               "Incorrect PROXY_TYPE - should be 'none' (clear proxy properties), 'false' (system settings), 'true' (custom settings): "
636                       + proxyTypeString);
637     }
638     proxyServerHttpTB.setText(Cache.getDefault("PROXY_SERVER", ""));
639     proxyPortHttpTB.setText(Cache.getDefault("PROXY_PORT", ""));
640     proxyServerHttpsTB.setText(Cache.getDefault("PROXY_SERVER_HTTPS", ""));
641     proxyPortHttpsTB.setText(Cache.getDefault("PROXY_PORT_HTTPS", ""));
642     proxyAuth.setSelected(Cache.getDefault("PROXY_AUTH", false));
643     proxyAuthUsernameTB
644             .setText(Cache.getDefault("PROXY_AUTH_USERNAME", ""));
645     // we are not storing or retrieving proxy password from .jalview_properties
646     proxyAuthPasswordPB.setText(Cache.proxyAuthPassword == null ? ""
647             : new String(Cache.proxyAuthPassword));
648     setCustomProxyEnabled();
649     applyProxyButtonEnabled(false);
650
651     usagestats.setSelected(Cache.getDefault("USAGESTATS", false));
652     // note antisense here: default is true
653     questionnaire
654             .setSelected(Cache.getProperty("NOQUESTIONNAIRES") == null);
655     versioncheck.setSelected(Cache.getDefault("VERSION_CHECK", true));
656
657     /*
658      * Set Output tab defaults
659      */
660     setupOutputCombo(epsRendering, "EPS_RENDERING");
661     setupOutputCombo(htmlRendering, "HTML_RENDERING");
662     setupOutputCombo(svgRendering, "SVG_RENDERING");
663     autoIdWidth.setSelected(Cache.getDefault("FIGURE_AUTOIDWIDTH", false));
664     userIdWidth.setEnabled(!autoIdWidth.isSelected());
665     userIdWidthlabel.setEnabled(!autoIdWidth.isSelected());
666     Integer wi = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH");
667     userIdWidth.setText(wi == null ? "" : wi.toString());
668     // TODO: refactor to use common enum via FormatAdapter and allow extension
669     // for new flat file formats
670     blcjv.setSelected(Cache.getDefault("BLC_JVSUFFIX", true));
671     clustaljv.setSelected(Cache.getDefault("CLUSTAL_JVSUFFIX", true));
672     fastajv.setSelected(Cache.getDefault("FASTA_JVSUFFIX", true));
673     msfjv.setSelected(Cache.getDefault("MSF_JVSUFFIX", true));
674     pfamjv.setSelected(Cache.getDefault("PFAM_JVSUFFIX", true));
675     pileupjv.setSelected(Cache.getDefault("PILEUP_JVSUFFIX", true));
676     pirjv.setSelected(Cache.getDefault("PIR_JVSUFFIX", true));
677     modellerOutput.setSelected(Cache.getDefault("PIR_MODELLER", false));
678     embbedBioJSON
679             .setSelected(Cache.getDefault("EXPORT_EMBBED_BIOJSON", true));
680
681     /*
682      * Set Editing tab defaults
683      */
684     autoCalculateConsCheck
685             .setSelected(Cache.getDefault("AUTO_CALC_CONSENSUS", true));
686     padGaps.setSelected(Cache.getDefault("PAD_GAPS", false));
687     sortByTree.setSelected(Cache.getDefault("SORT_BY_TREE", false));
688
689     annotations_actionPerformed(null); // update the display of the annotation
690                                        // settings
691
692     /*
693      * Set Backups tab defaults
694      */
695     loadLastSavedBackupsOptions();
696
697     /*
698      * Set Startup tab defaults
699      */
700
701     // re-enable
702     Cache.enableSessionProperties();
703   }
704
705   /**
706    * A helper method that sets the items and initial selection in a character
707    * rendering option list (Prompt each time/Lineart/Text)
708    * 
709    * @param comboBox
710    * @param propertyKey
711    */
712   protected void setupOutputCombo(JComboBox<Object> comboBox,
713           String propertyKey)
714   {
715     Cache.disableSessionProperties();
716
717     comboBox.addItem(promptEachTimeOpt);
718     comboBox.addItem(lineArtOpt);
719     comboBox.addItem(textOpt);
720
721     /*
722      * JalviewJS doesn't support Lineart so force it to Text
723      */
724     String defaultOption = Platform.isJS() ? "Text"
725             : Cache.getDefault(propertyKey, "Prompt each time");
726     if (defaultOption.equalsIgnoreCase("Text"))
727     {
728       comboBox.setSelectedItem(textOpt);
729     }
730     else if (defaultOption.equalsIgnoreCase("Lineart"))
731     {
732       comboBox.setSelectedItem(lineArtOpt);
733     }
734     else
735     {
736       comboBox.setSelectedItem(promptEachTimeOpt);
737     }
738
739     Cache.enableSessionProperties();
740   }
741
742   /**
743    * Save user selections on the Preferences tabs to the Cache and write out to
744    * file.
745    * 
746    * @param e
747    */
748   @Override
749   public void ok_actionPerformed(ActionEvent e)
750   {
751     Cache.disableSessionProperties();
752
753     if (!validateSettings())
754     {
755       return;
756     }
757
758     /* 
759      * Set proxy settings first (to be before web services refresh)
760      */
761     saveProxySettings();
762
763     /*
764      * Save Visual settings
765      */
766
767     Cache.applicationProperties.setProperty("SHOW_JVSUFFIX",
768             Boolean.toString(seqLimit.isSelected()));
769     Cache.applicationProperties.setProperty("RIGHT_ALIGN_IDS",
770             Boolean.toString(rightAlign.isSelected()));
771     Cache.applicationProperties.setProperty("SHOW_FULLSCREEN",
772             Boolean.toString(fullScreen.isSelected()));
773     Cache.applicationProperties.setProperty("SHOW_OVERVIEW",
774             Boolean.toString(openoverv.isSelected()));
775     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
776             Boolean.toString(annotations.isSelected()));
777     Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
778             Boolean.toString(conservation.isSelected()));
779     Cache.applicationProperties.setProperty("SHOW_QUALITY",
780             Boolean.toString(quality.isSelected()));
781     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
782             Boolean.toString(identity.isSelected()));
783
784     Cache.applicationProperties.setProperty("GAP_SYMBOL",
785             gapSymbolCB.getSelectedItem().toString());
786
787     Cache.applicationProperties.setProperty("FONT_NAME",
788             fontNameCB.getSelectedItem().toString());
789     Cache.applicationProperties.setProperty("FONT_STYLE",
790             fontStyleCB.getSelectedItem().toString());
791     Cache.applicationProperties.setProperty("FONT_SIZE",
792             fontSizeCB.getSelectedItem().toString());
793
794     Cache.applicationProperties.setProperty("ID_ITALICS",
795             Boolean.toString(idItalics.isSelected()));
796     Cache.applicationProperties.setProperty("SHOW_UNCONSERVED",
797             Boolean.toString(showUnconserved.isSelected()));
798     Cache.applicationProperties.setProperty(SHOW_OCCUPANCY,
799             Boolean.toString(showOccupancy.isSelected()));
800     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSENSUS",
801             Boolean.toString(showGroupConsensus.isSelected()));
802     Cache.applicationProperties.setProperty("SHOW_GROUP_CONSERVATION",
803             Boolean.toString(showGroupConservation.isSelected()));
804     Cache.applicationProperties.setProperty("SHOW_CONSENSUS_HISTOGRAM",
805             Boolean.toString(showConsensHistogram.isSelected()));
806     Cache.applicationProperties.setProperty("SHOW_CONSENSUS_LOGO",
807             Boolean.toString(showConsensLogo.isSelected()));
808     Cache.applicationProperties.setProperty("ANTI_ALIAS",
809             Boolean.toString(smoothFont.isSelected()));
810     Cache.applicationProperties.setProperty(SCALE_PROTEIN_TO_CDNA,
811             Boolean.toString(scaleProteinToCdna.isSelected()));
812     Cache.applicationProperties.setProperty("SHOW_NPFEATS_TOOLTIP",
813             Boolean.toString(showNpTooltip.isSelected()));
814     Cache.applicationProperties.setProperty("SHOW_DBREFS_TOOLTIP",
815             Boolean.toString(showDbRefTooltip.isSelected()));
816
817     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT",
818             Boolean.toString(wrap.isSelected()));
819
820     Cache.applicationProperties.setProperty("STARTUP_FILE",
821             startupFileTextfield.getText());
822     Cache.applicationProperties.setProperty("SHOW_STARTUP_FILE",
823             Boolean.toString(startupCheckbox.isSelected()));
824
825     Cache.applicationProperties.setProperty("SORT_ALIGNMENT",
826             sortby.getSelectedItem().toString());
827
828     // convert description of sort order to enum name for save
829     SequenceAnnotationOrder annSortOrder = SequenceAnnotationOrder
830             .forDescription(sortAnnBy.getSelectedItem().toString());
831     if (annSortOrder != null)
832     {
833       Cache.applicationProperties.setProperty(SORT_ANNOTATIONS,
834               annSortOrder.name());
835     }
836
837     final boolean showAutocalcFirst = sortAutocalc.getSelectedIndex() == 0;
838     Cache.applicationProperties.setProperty(SHOW_AUTOCALC_ABOVE,
839             Boolean.valueOf(showAutocalcFirst).toString());
840
841     /*
842      * Save Colours settings
843      */
844     Cache.applicationProperties.setProperty(DEFAULT_COLOUR_PROT,
845             protColour.getSelectedItem().toString());
846     Cache.applicationProperties.setProperty(DEFAULT_COLOUR_NUC,
847             nucColour.getSelectedItem().toString());
848     Cache.setColourProperty("ANNOTATIONCOLOUR_MIN",
849             minColour.getBackground());
850     Cache.setColourProperty("ANNOTATIONCOLOUR_MAX",
851             maxColour.getBackground());
852
853     /*
854      * Save Overview settings
855      */
856     Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground());
857     Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground());
858     Cache.applicationProperties.setProperty(USE_LEGACY_GAP,
859             Boolean.toString(useLegacyGap.isSelected()));
860     Cache.applicationProperties.setProperty(SHOW_OV_HIDDEN_AT_START,
861             Boolean.toString(showHiddenAtStart.isSelected()));
862
863     /*
864      * Save Structure settings
865      */
866     Cache.applicationProperties.setProperty(ADD_TEMPFACT_ANN,
867             Boolean.toString(addTempFactor.isSelected()));
868     Cache.applicationProperties.setProperty(ADD_SS_ANN,
869             Boolean.toString(addSecondaryStructure.isSelected()));
870     Cache.applicationProperties.setProperty(STRUCT_FROM_PDB,
871             Boolean.toString(structFromPdb.isSelected()));
872     String viewer = structViewer.getSelectedItem().toString();
873     String viewerPath = structureViewerPath.getText();
874     Cache.applicationProperties.setProperty(STRUCTURE_DISPLAY, viewer);
875     if (viewer.equals(ViewerType.CHIMERA.name()))
876     {
877       Cache.setOrRemove(CHIMERA_PATH, viewerPath);
878     }
879     else if (viewer.equals(ViewerType.CHIMERAX.name()))
880     {
881       Cache.setOrRemove(CHIMERAX_PATH, viewerPath);
882     }
883     else if (viewer.equals(ViewerType.PYMOL.name()))
884     {
885       Cache.setOrRemove(PYMOL_PATH, viewerPath);
886     }
887     Cache.applicationProperties.setProperty("MAP_WITH_SIFTS",
888             Boolean.toString(siftsMapping.isSelected()));
889     SiftsSettings.setMapWithSifts(siftsMapping.isSelected());
890
891     /*
892      * Save Output settings
893      */
894     Cache.applicationProperties.setProperty("EPS_RENDERING",
895             ((OptionsParam) epsRendering.getSelectedItem()).getCode());
896     Cache.applicationProperties.setProperty("HTML_RENDERING",
897             ((OptionsParam) htmlRendering.getSelectedItem()).getCode());
898     Cache.applicationProperties.setProperty("SVG_RENDERING",
899             ((OptionsParam) svgRendering.getSelectedItem()).getCode());
900
901     /*
902      * Save Connections settings
903      */
904     // Proxy settings set first (to catch web services)
905
906     // save user-defined and selected links
907     String menuLinks = sequenceUrlLinks.writeUrlsAsString(true);
908     if (menuLinks.isEmpty())
909     {
910       Cache.applicationProperties.remove("SEQUENCE_LINKS");
911     }
912     else
913     {
914       Cache.applicationProperties.setProperty("SEQUENCE_LINKS",
915               menuLinks.toString());
916     }
917
918     String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false);
919     if (nonMenuLinks.isEmpty())
920     {
921       Cache.applicationProperties.remove("STORED_LINKS");
922     }
923     else
924     {
925       Cache.applicationProperties.setProperty("STORED_LINKS",
926               nonMenuLinks.toString());
927     }
928
929     Cache.applicationProperties.setProperty("DEFAULT_URL",
930             sequenceUrlLinks.getPrimaryUrlId());
931
932     Cache.setProperty("VERSION_CHECK",
933             Boolean.toString(versioncheck.isSelected()));
934     if (Cache.getProperty("USAGESTATS") != null || usagestats.isSelected())
935     {
936       // default is false - we only set this if the user has actively agreed
937       Cache.setProperty("USAGESTATS",
938               Boolean.toString(usagestats.isSelected()));
939     }
940     if (!questionnaire.isSelected())
941     {
942       Cache.setProperty("NOQUESTIONNAIRES", "true");
943     }
944     else
945     {
946       // special - made easy to edit a property file to disable questionnaires
947       // by just adding the given line
948       Cache.removeProperty("NOQUESTIONNAIRES");
949     }
950
951     /*
952      * Save Output settings
953      */
954     Cache.applicationProperties.setProperty("BLC_JVSUFFIX",
955             Boolean.toString(blcjv.isSelected()));
956     Cache.applicationProperties.setProperty("CLUSTAL_JVSUFFIX",
957             Boolean.toString(clustaljv.isSelected()));
958     Cache.applicationProperties.setProperty("FASTA_JVSUFFIX",
959             Boolean.toString(fastajv.isSelected()));
960     Cache.applicationProperties.setProperty("MSF_JVSUFFIX",
961             Boolean.toString(msfjv.isSelected()));
962     Cache.applicationProperties.setProperty("PFAM_JVSUFFIX",
963             Boolean.toString(pfamjv.isSelected()));
964     Cache.applicationProperties.setProperty("PILEUP_JVSUFFIX",
965             Boolean.toString(pileupjv.isSelected()));
966     Cache.applicationProperties.setProperty("PIR_JVSUFFIX",
967             Boolean.toString(pirjv.isSelected()));
968     Cache.applicationProperties.setProperty("PIR_MODELLER",
969             Boolean.toString(modellerOutput.isSelected()));
970     Cache.applicationProperties.setProperty("EXPORT_EMBBED_BIOJSON",
971             Boolean.toString(embbedBioJSON.isSelected()));
972     jalview.io.PIRFile.useModellerOutput = modellerOutput.isSelected();
973
974     Cache.applicationProperties.setProperty("FIGURE_AUTOIDWIDTH",
975             Boolean.toString(autoIdWidth.isSelected()));
976     userIdWidth_actionPerformed();
977     Cache.applicationProperties.setProperty("FIGURE_FIXEDIDWIDTH",
978             userIdWidth.getText());
979
980     /*
981      * Save Editing settings
982      */
983     Cache.applicationProperties.setProperty("AUTO_CALC_CONSENSUS",
984             Boolean.toString(autoCalculateConsCheck.isSelected()));
985     Cache.applicationProperties.setProperty("SORT_BY_TREE",
986             Boolean.toString(sortByTree.isSelected()));
987     Cache.applicationProperties.setProperty("PAD_GAPS",
988             Boolean.toString(padGaps.isSelected()));
989
990     if (!Platform.isJS())
991     {
992       wsPrefs.updateAndRefreshWsMenuConfig(false);
993     }
994
995     /*
996      * Save Backups settings
997      */
998     Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
999             Boolean.toString(enableBackupFiles.isSelected()));
1000     int preset = getComboIntStringKey(backupfilesPresetsCombo);
1001     Cache.applicationProperties.setProperty(BackupFiles.NS + "_PRESET",
1002             Integer.toString(preset));
1003
1004     if (preset == BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM)
1005     {
1006       BackupFilesPresetEntry customBFPE = getBackupfilesCurrentEntry();
1007       BackupFilesPresetEntry.backupfilesPresetEntriesValues.put(
1008               BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM, customBFPE);
1009       Cache.applicationProperties.setProperty(
1010               BackupFilesPresetEntry.CUSTOMCONFIG, customBFPE.toString());
1011     }
1012
1013     BackupFilesPresetEntry savedBFPE = BackupFilesPresetEntry.backupfilesPresetEntriesValues
1014             .get(preset);
1015     Cache.applicationProperties.setProperty(
1016             BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString());
1017
1018     /*
1019      * Save Memory Settings
1020      */
1021     Cache.applicationProperties.setProperty(
1022             MemorySetting.CUSTOMISED_SETTINGS,
1023             Boolean.toString(customiseMemorySetting.isSelected()));
1024     Cache.applicationProperties.setProperty(MemorySetting.MEMORY_JVMMEMPC,
1025             Integer.toString(jvmMemoryPercentSlider.getValue()));
1026     Cache.applicationProperties.setProperty(MemorySetting.MEMORY_JVMMEMMAX,
1027             jvmMemoryMaxTextField.getText());
1028
1029     /*
1030      * save and close Preferences
1031      */
1032
1033     Cache.saveProperties();
1034     Desktop.instance.doConfigureStructurePrefs();
1035     try
1036     {
1037       frame.setClosed(true);
1038     } catch (Exception ex)
1039     {
1040     }
1041
1042     Cache.enableSessionProperties();
1043   }
1044
1045   public void saveProxySettings()
1046   {
1047     Cache.disableSessionProperties();
1048
1049     String newProxyType = customProxy.isSelected() ? Cache.PROXYTYPE_CUSTOM
1050             : noProxy.isSelected() ? Cache.PROXYTYPE_NONE
1051                     : Cache.PROXYTYPE_SYSTEM;
1052     Cache.applicationProperties.setProperty("USE_PROXY", newProxyType);
1053     Cache.setOrRemove("PROXY_SERVER", proxyServerHttpTB.getText());
1054     Cache.setOrRemove("PROXY_PORT", proxyPortHttpTB.getText());
1055     Cache.setOrRemove("PROXY_SERVER_HTTPS", proxyServerHttpsTB.getText());
1056     Cache.setOrRemove("PROXY_PORT_HTTPS", proxyPortHttpsTB.getText());
1057     Cache.setOrRemove("PROXY_AUTH",
1058             Boolean.toString(proxyAuth.isSelected()));
1059     Cache.setOrRemove("PROXY_AUTH_USERNAME", proxyAuthUsernameTB.getText());
1060     Cache.proxyAuthPassword = proxyAuthPasswordPB.getPassword();
1061     Cache.setProxyPropertiesFromPreferences(previousProxyType);
1062     if (newProxyType.equals(Cache.PROXYTYPE_CUSTOM)
1063             || !newProxyType.equals(previousProxyType))
1064     {
1065       // force a re-lookup of ws if proxytype is custom or has changed
1066       wsPrefs.update++;
1067     }
1068     previousProxyType = newProxyType;
1069
1070     Cache.enableSessionProperties();
1071   }
1072
1073   /**
1074    * Do any necessary validation before saving settings. Return focus to the
1075    * first tab which fails validation.
1076    * 
1077    * @return
1078    */
1079   private boolean validateSettings()
1080   {
1081     if (!validateStructure())
1082     {
1083       structureTab.requestFocusInWindow();
1084       return false;
1085     }
1086     return true;
1087   }
1088
1089   @Override
1090   protected boolean validateStructure()
1091   {
1092     return validateViewerPath();
1093
1094   }
1095
1096   /**
1097    * DOCUMENT ME!
1098    */
1099   @Override
1100   public void startupFileTextfield_mouseClicked()
1101   {
1102     Cache.disableSessionProperties();
1103
1104     // TODO: JAL-3048 not needed for Jalview-JS
1105     String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
1106     JalviewFileChooser chooser = JalviewFileChooser
1107             .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
1108     chooser.setFileView(new JalviewFileView());
1109     chooser.setDialogTitle(
1110             MessageManager.getString("label.select_startup_file"));
1111
1112     int value = chooser.showOpenDialog(this);
1113
1114     if (value == JalviewFileChooser.APPROVE_OPTION)
1115     {
1116       FileFormatI format = chooser.getSelectedFormat();
1117       if (format != null)
1118       {
1119         Cache.applicationProperties.setProperty("DEFAULT_FILE_FORMAT",
1120                 format.getName());
1121       }
1122       startupFileTextfield
1123               .setText(chooser.getSelectedFile().getAbsolutePath());
1124     }
1125
1126     Cache.enableSessionProperties();
1127   }
1128
1129   /**
1130    * DOCUMENT ME!
1131    * 
1132    * @param e
1133    *          DOCUMENT ME!
1134    */
1135   @Override
1136   public void cancel_actionPerformed(ActionEvent e)
1137   {
1138     try
1139     {
1140       if (!Platform.isJS())
1141       {
1142         wsPrefs.updateWsMenuConfig(true);
1143         wsPrefs.refreshWs_actionPerformed(e);
1144       }
1145       frame.setClosed(true);
1146     } catch (Exception ex)
1147     {
1148     }
1149   }
1150
1151   /**
1152    * DOCUMENT ME!
1153    * 
1154    * @param e
1155    *          DOCUMENT ME!
1156    */
1157   @Override
1158   public void annotations_actionPerformed(ActionEvent e)
1159   {
1160     conservation.setEnabled(annotations.isSelected());
1161     quality.setEnabled(annotations.isSelected());
1162     identity.setEnabled(annotations.isSelected());
1163     showOccupancy.setEnabled(annotations.isSelected());
1164     showGroupConsensus.setEnabled(annotations.isSelected());
1165     showGroupConservation.setEnabled(annotations.isSelected());
1166     showConsensHistogram.setEnabled(annotations.isSelected()
1167             && (identity.isSelected() || showGroupConsensus.isSelected()));
1168     showConsensLogo.setEnabled(annotations.isSelected()
1169             && (identity.isSelected() || showGroupConsensus.isSelected()));
1170   }
1171
1172   @Override
1173   public void newLink_actionPerformed(ActionEvent e)
1174   {
1175     GSequenceLink link = new GSequenceLink();
1176     boolean valid = false;
1177     while (!valid)
1178     {
1179       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
1180               MessageManager.getString("label.new_sequence_url_link"),
1181               JvOptionPane.OK_CANCEL_OPTION, -1,
1182               null) == JvOptionPane.OK_OPTION)
1183       {
1184         if (link.checkValid())
1185         {
1186           if (((UrlLinkTableModel) linkUrlTable.getModel())
1187                   .isUniqueName(link.getName()))
1188           {
1189             ((UrlLinkTableModel) linkUrlTable.getModel())
1190                     .insertRow(link.getName(), link.getURL());
1191             valid = true;
1192           }
1193           else
1194           {
1195             link.notifyDuplicate();
1196             continue;
1197           }
1198         }
1199       }
1200       else
1201       {
1202         break;
1203       }
1204     }
1205   }
1206
1207   @Override
1208   public void editLink_actionPerformed(ActionEvent e)
1209   {
1210     GSequenceLink link = new GSequenceLink();
1211
1212     int index = linkUrlTable.getSelectedRow();
1213     if (index == -1)
1214     {
1215       // button no longer enabled if row is not selected
1216       Console.debug("Edit with no row selected in linkUrlTable");
1217       return;
1218     }
1219
1220     int nameCol = ((UrlLinkTableModel) linkUrlTable.getModel())
1221             .getNameColumn();
1222     int urlCol = ((UrlLinkTableModel) linkUrlTable.getModel())
1223             .getUrlColumn();
1224     String oldName = linkUrlTable.getValueAt(index, nameCol).toString();
1225     link.setName(oldName);
1226     link.setURL(linkUrlTable.getValueAt(index, urlCol).toString());
1227
1228     boolean valid = false;
1229     while (!valid)
1230     {
1231       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
1232               MessageManager.getString("label.edit_sequence_url_link"),
1233               JvOptionPane.OK_CANCEL_OPTION, -1,
1234               null) == JvOptionPane.OK_OPTION)
1235       {
1236         if (link.checkValid())
1237         {
1238           if ((oldName.equals(link.getName()))
1239                   || (((UrlLinkTableModel) linkUrlTable.getModel())
1240                           .isUniqueName(link.getName())))
1241           {
1242             linkUrlTable.setValueAt(link.getName(), index, nameCol);
1243             linkUrlTable.setValueAt(link.getURL(), index, urlCol);
1244             valid = true;
1245           }
1246           else
1247           {
1248             link.notifyDuplicate();
1249             continue;
1250           }
1251         }
1252       }
1253       else
1254       {
1255         break;
1256       }
1257     }
1258   }
1259
1260   @Override
1261   public void deleteLink_actionPerformed(ActionEvent e)
1262   {
1263     int index = linkUrlTable.getSelectedRow();
1264     int modelIndex = -1;
1265     if (index == -1)
1266     {
1267       // button no longer enabled if row is not selected
1268       Console.debug("Delete with no row selected in linkUrlTable");
1269       return;
1270     }
1271     else
1272     {
1273       modelIndex = linkUrlTable.convertRowIndexToModel(index);
1274     }
1275
1276     // make sure we use the model index to delete, and not the table index
1277     ((UrlLinkTableModel) linkUrlTable.getModel()).removeRow(modelIndex);
1278   }
1279
1280   /*
1281    * (non-Javadoc)
1282    * 
1283    * @see
1284    * jalview.jbgui.GPreferences#showunconserved_actionPerformed(java.awt.event
1285    * .ActionEvent)
1286    */
1287   @Override
1288   protected void showunconserved_actionPerformed(ActionEvent e)
1289   {
1290     // TODO Auto-generated method stub
1291     super.showunconserved_actionPerformed(e);
1292   }
1293
1294   public static List<String> getGroupURLLinks()
1295   {
1296     return groupURLLinks;
1297   }
1298
1299   @Override
1300   public void minColour_actionPerformed(JPanel panel)
1301   {
1302     JalviewColourChooser.showColourChooser(this,
1303             MessageManager.getString("label.select_colour_minimum_value"),
1304             panel);
1305   }
1306
1307   @Override
1308   public void maxColour_actionPerformed(JPanel panel)
1309   {
1310     JalviewColourChooser.showColourChooser(this,
1311             MessageManager.getString("label.select_colour_maximum_value"),
1312             panel);
1313   }
1314
1315   @Override
1316   public void gapColour_actionPerformed(JPanel gap)
1317   {
1318     if (!useLegacyGap.isSelected())
1319     {
1320       JalviewColourChooser.showColourChooser(this,
1321               MessageManager.getString("label.select_gap_colour"), gap);
1322     }
1323   }
1324
1325   @Override
1326   public void hiddenColour_actionPerformed(JPanel hidden)
1327   {
1328     JalviewColourChooser.showColourChooser(this,
1329             MessageManager.getString("label.select_hidden_colour"), hidden);
1330   }
1331
1332   @Override
1333   protected void useLegacyGaps_actionPerformed(ActionEvent e)
1334   {
1335     boolean enabled = useLegacyGap.isSelected();
1336     if (enabled)
1337     {
1338       gapColour.setBackground(OverviewCanvas.OVERVIEW_DEFAULT_LEGACY_GAP);
1339     }
1340     else
1341     {
1342       gapColour.setBackground(OverviewCanvas.OVERVIEW_DEFAULT_GAP);
1343     }
1344     gapColour.setEnabled(!enabled);
1345     gapLabel.setEnabled(!enabled);
1346   }
1347
1348   @Override
1349   protected void resetOvDefaults_actionPerformed(ActionEvent e)
1350   {
1351     useLegacyGap.setSelected(false);
1352     useLegacyGaps_actionPerformed(null);
1353     showHiddenAtStart.setSelected(false);
1354     hiddenColour.setBackground(OverviewCanvas.OVERVIEW_DEFAULT_HIDDEN);
1355   }
1356
1357   @Override
1358   protected void userIdWidth_actionPerformed()
1359   {
1360     try
1361     {
1362       String val = userIdWidth.getText().trim();
1363       if (val.length() > 0)
1364       {
1365         Integer iw = Integer.parseInt(val);
1366         if (iw.intValue() < 12)
1367         {
1368           throw new NumberFormatException();
1369         }
1370         userIdWidth.setText(iw.toString());
1371       }
1372     } catch (NumberFormatException x)
1373     {
1374       userIdWidth.setText("");
1375       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1376               MessageManager
1377                       .getString("warn.user_defined_width_requirements"),
1378               MessageManager.getString("label.invalid_id_column_width"),
1379               JvOptionPane.WARNING_MESSAGE);
1380     }
1381   }
1382
1383   @Override
1384   protected void autoIdWidth_actionPerformed()
1385   {
1386     userIdWidth.setEnabled(!autoIdWidth.isSelected());
1387     userIdWidthlabel.setEnabled(!autoIdWidth.isSelected());
1388   }
1389
1390   /**
1391    * Returns true if structure viewer path is to a valid executable, else shows
1392    * an error dialog. Does nothing if the path is empty, as is the case for Jmol
1393    * (built in to Jalview) or when Jalview is left to try default paths.
1394    */
1395   private boolean validateViewerPath()
1396   {
1397     if (structureViewerPath.getText().trim().length() > 0)
1398     {
1399       File f = new File(structureViewerPath.getText());
1400       if (!f.canExecute())
1401       {
1402         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
1403                 MessageManager.getString("label.invalid_viewer_path"),
1404                 MessageManager.getString("label.invalid_viewer_path"),
1405                 JvOptionPane.ERROR_MESSAGE);
1406         return false;
1407       }
1408     }
1409     return true;
1410   }
1411
1412   /**
1413    * If Chimera or ChimeraX or Pymol is selected, check it can be found on
1414    * default or user-specified path, if not show a warning/help dialog
1415    */
1416   @Override
1417   protected void structureViewer_actionPerformed(String selectedItem)
1418   {
1419     if (selectedItem.equals(ViewerType.JMOL.name()))
1420     {
1421       structureViewerPath.setEnabled(false);
1422       structureViewerPathLabel.setEnabled(false);
1423       return;
1424     }
1425     boolean found = false;
1426     structureViewerPath.setEnabled(true);
1427     structureViewerPathLabel.setEnabled(true);
1428     structureViewerPathLabel.setText(MessageManager
1429             .formatMessage("label.viewer_path", selectedItem));
1430
1431     /*
1432      * Try user-specified and standard paths for structure viewer executable
1433      */
1434     String viewerPath = "";
1435     List<String> paths = null;
1436     Cache.disableSessionProperties();
1437     try
1438     {
1439       ViewerType viewerType = ViewerType.valueOf(selectedItem);
1440       switch (viewerType)
1441       {
1442       case JMOL:
1443         // dealt with above
1444         break;
1445       case CHIMERA:
1446         viewerPath = Cache.getDefault(CHIMERA_PATH, "");
1447         paths = StructureManager.getChimeraPaths(false);
1448         break;
1449       case CHIMERAX:
1450         viewerPath = Cache.getDefault(CHIMERAX_PATH, "");
1451         paths = StructureManager.getChimeraPaths(true);
1452         break;
1453       case PYMOL:
1454         viewerPath = Cache.getDefault(PYMOL_PATH, "");
1455         paths = PymolManager.getPymolPaths();
1456         break;
1457       }
1458     } catch (IllegalArgumentException e)
1459     {
1460       // only valid entries should be in the drop-down
1461     }
1462     Cache.enableSessionProperties();
1463     structureViewerPath.setText(viewerPath);
1464
1465     paths.add(0, structureViewerPath.getText());
1466     for (String path : paths)
1467     {
1468       if (new File(path.trim()).canExecute())
1469       {
1470         found = true;
1471         break;
1472       }
1473     }
1474
1475     if (!found)
1476     {
1477       String[] options = { "OK", "Help" };
1478       int showHelp = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
1479               JvSwingUtils.wrapTooltip(true,
1480                       MessageManager.getString("label.viewer_missing")),
1481               "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
1482               null, options, options[0]);
1483
1484       if (showHelp == JvOptionPane.NO_OPTION)
1485       {
1486         this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
1487         try
1488         {
1489           Help.showHelpWindow(HelpId.StructureViewer);
1490         } catch (HelpSetException e)
1491         {
1492           e.printStackTrace();
1493         }
1494       }
1495       else if (showHelp == JvOptionPane.OK_OPTION)
1496       {
1497         this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
1498         CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
1499           try
1500           {
1501             for (int i = 0; i < 3; i++)
1502             {
1503               structureViewerPath.setBackground(Color.PINK);
1504               Thread.sleep(500);
1505               structureViewerPath.setBackground(Color.WHITE);
1506               Thread.sleep(500);
1507             }
1508           } catch (InterruptedException e)
1509           {
1510           }
1511         });
1512       }
1513     }
1514   }
1515
1516   public class OptionsParam
1517   {
1518     private String name;
1519
1520     private String code;
1521
1522     public OptionsParam(String name, String code)
1523     {
1524       this.name = name;
1525       this.code = code;
1526     }
1527
1528     public String getName()
1529     {
1530       return name;
1531     }
1532
1533     public void setName(String name)
1534     {
1535       this.name = name;
1536     }
1537
1538     public String getCode()
1539     {
1540       return code;
1541     }
1542
1543     public void setCode(String code)
1544     {
1545       this.code = code;
1546     }
1547
1548     @Override
1549     public String toString()
1550     {
1551       return name;
1552     }
1553
1554     @Override
1555     public boolean equals(Object that)
1556     {
1557       if (!(that instanceof OptionsParam))
1558       {
1559         return false;
1560       }
1561       return this.code.equalsIgnoreCase(((OptionsParam) that).code);
1562     }
1563
1564     @Override
1565     public int hashCode()
1566     {
1567       return name.hashCode() + code.hashCode();
1568     }
1569   }
1570
1571   private class UrlListSelectionHandler implements ListSelectionListener
1572   {
1573
1574     @Override
1575     public void valueChanged(ListSelectionEvent e)
1576     {
1577       ListSelectionModel lsm = (ListSelectionModel) e.getSource();
1578
1579       int index = lsm.getMinSelectionIndex();
1580       if (index == -1)
1581       {
1582         // no selection, so disable delete/edit buttons
1583         editLink.setEnabled(false);
1584         deleteLink.setEnabled(false);
1585         return;
1586       }
1587       int modelIndex = linkUrlTable.convertRowIndexToModel(index);
1588
1589       // enable/disable edit and delete link buttons
1590       if (((UrlLinkTableModel) linkUrlTable.getModel())
1591               .isRowDeletable(modelIndex))
1592       {
1593         deleteLink.setEnabled(true);
1594       }
1595       else
1596       {
1597         deleteLink.setEnabled(false);
1598       }
1599
1600       if (((UrlLinkTableModel) linkUrlTable.getModel())
1601               .isRowEditable(modelIndex))
1602       {
1603         editLink.setEnabled(true);
1604       }
1605       else
1606       {
1607         editLink.setEnabled(false);
1608       }
1609     }
1610   }
1611 }