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