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