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