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