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