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