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