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