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