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