JAL-3010 option to apply colour to all ontology sub-types
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.features.FeatureMatcherI;
28 import jalview.datamodel.features.FeatureMatcherSet;
29 import jalview.datamodel.features.FeatureMatcherSetI;
30 import jalview.datamodel.ontology.OntologyI;
31 import jalview.gui.Help.HelpId;
32 import jalview.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.io.gff.SequenceOntologyFactory;
35 import jalview.schemabinding.version2.Filter;
36 import jalview.schemabinding.version2.JalviewUserColours;
37 import jalview.schemabinding.version2.MatcherSet;
38 import jalview.schemes.FeatureColour;
39 import jalview.util.MessageManager;
40 import jalview.util.Platform;
41 import jalview.viewmodel.AlignmentViewport;
42 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
43 import jalview.ws.DasSequenceFeatureFetcher;
44 import jalview.ws.dbsources.das.api.jalviewSourceI;
45
46 import java.awt.BorderLayout;
47 import java.awt.Color;
48 import java.awt.Component;
49 import java.awt.Dimension;
50 import java.awt.Font;
51 import java.awt.Graphics;
52 import java.awt.GridLayout;
53 import java.awt.Point;
54 import java.awt.Rectangle;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.ActionListener;
57 import java.awt.event.ItemEvent;
58 import java.awt.event.ItemListener;
59 import java.awt.event.MouseAdapter;
60 import java.awt.event.MouseEvent;
61 import java.awt.event.MouseMotionAdapter;
62 import java.beans.PropertyChangeEvent;
63 import java.beans.PropertyChangeListener;
64 import java.io.File;
65 import java.io.FileInputStream;
66 import java.io.FileOutputStream;
67 import java.io.InputStreamReader;
68 import java.io.OutputStreamWriter;
69 import java.io.PrintWriter;
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Hashtable;
76 import java.util.Iterator;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Set;
80 import java.util.Vector;
81
82 import javax.help.HelpSetException;
83 import javax.swing.AbstractCellEditor;
84 import javax.swing.BorderFactory;
85 import javax.swing.Icon;
86 import javax.swing.JButton;
87 import javax.swing.JCheckBox;
88 import javax.swing.JCheckBoxMenuItem;
89 import javax.swing.JColorChooser;
90 import javax.swing.JDialog;
91 import javax.swing.JInternalFrame;
92 import javax.swing.JLabel;
93 import javax.swing.JLayeredPane;
94 import javax.swing.JMenuItem;
95 import javax.swing.JPanel;
96 import javax.swing.JPopupMenu;
97 import javax.swing.JScrollPane;
98 import javax.swing.JSlider;
99 import javax.swing.JTable;
100 import javax.swing.ListSelectionModel;
101 import javax.swing.RowFilter;
102 import javax.swing.SwingConstants;
103 import javax.swing.SwingUtilities;
104 import javax.swing.event.ChangeEvent;
105 import javax.swing.event.ChangeListener;
106 import javax.swing.table.AbstractTableModel;
107 import javax.swing.table.TableCellEditor;
108 import javax.swing.table.TableCellRenderer;
109 import javax.swing.table.TableColumn;
110 import javax.swing.table.TableRowSorter;
111
112 public class FeatureSettings extends JPanel
113         implements FeatureSettingsControllerI
114 {
115   private static final Font VERDANA_12 = new Font("Verdana", Font.PLAIN, 12);
116
117   private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
118           .getString("label.sequence_feature_colours");
119
120   /*
121    * column indices of fields in Feature Settings table
122    */
123   static final int TYPE_COLUMN = 0;
124
125   static final int COLOUR_COLUMN = 1;
126
127   static final int FILTER_COLUMN = 2;
128
129   static final int SHOW_COLUMN = 3;
130
131   private static final int COLUMN_COUNT = 4;
132
133   private static final int MIN_WIDTH = 400;
134
135   private static final int MIN_HEIGHT = 400;
136
137   DasSourceBrowser dassourceBrowser;
138
139   DasSequenceFeatureFetcher dasFeatureFetcher;
140
141   JPanel dasSettingsPane = new JPanel();
142
143   final FeatureRenderer fr;
144
145   public final AlignFrame af;
146
147   /*
148    * 'original' fields hold settings to restore on Cancel
149    */
150   Object[][] originalData;
151
152   private float originalTransparency;
153
154   private Map<String, FeatureMatcherSetI> originalFilters;
155
156   final JInternalFrame frame;
157
158   JScrollPane scrollPane = new JScrollPane();
159
160   JTable table;
161
162   JPanel groupPanel;
163
164   JSlider transparency = new JSlider();
165
166   /*
167    * when true, constructor is still executing - so ignore UI events
168    */
169   protected volatile boolean inConstruction = true;
170
171   int selectedRow = -1;
172
173   JButton fetchDAS = new JButton();
174
175   JButton saveDAS = new JButton();
176
177   JButton cancelDAS = new JButton();
178
179   boolean resettingTable = false;
180
181   /*
182    * true when Feature Settings are updating from feature renderer
183    */
184   private boolean handlingUpdate = false;
185
186   /*
187    * holds {featureCount, totalExtent} for each feature type
188    */
189   Map<String, float[]> typeWidth = null;
190
191   /*
192    * if true, 'child' feature types are not displayed
193    */
194   JCheckBox summaryView;
195
196   /*
197    * those feature types that do not have a parent feature type present
198    * (as determined by an Ontology relationship)
199    */
200   List<String> topLevelTypes;
201
202   /**
203    * Constructor
204    * 
205    * @param af
206    */
207   public FeatureSettings(AlignFrame alignFrame)
208   {
209     this.af = alignFrame;
210     fr = af.getFeatureRenderer();
211
212     // save transparency for restore on Cancel
213     originalTransparency = fr.getTransparency();
214     int originalTransparencyAsPercent = (int) (originalTransparency * 100);
215     transparency.setMaximum(100 - originalTransparencyAsPercent);
216
217     originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
218
219     topLevelTypes = new ArrayList<>();
220
221     try
222     {
223       jbInit();
224     } catch (Exception ex)
225     {
226       ex.printStackTrace();
227     }
228
229     initTable();
230
231     scrollPane.setViewportView(table);
232
233     dassourceBrowser = new DasSourceBrowser(this);
234     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
235
236     if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
237     {
238       fr.findAllFeatures(true); // display everything!
239     }
240
241     discoverAllFeatureData();
242     final PropertyChangeListener change;
243     final FeatureSettings fs = this;
244     fr.addPropertyChangeListener(change = new PropertyChangeListener()
245     {
246       @Override
247       public void propertyChange(PropertyChangeEvent evt)
248       {
249         if (!fs.resettingTable && !fs.handlingUpdate)
250         {
251           fs.handlingUpdate = true;
252           fs.resetTable(null);
253           // new groups may be added with new sequence feature types only
254           fs.handlingUpdate = false;
255         }
256       }
257     });
258
259     frame = new JInternalFrame();
260     frame.setContentPane(this);
261     if (Platform.isAMac())
262     {
263       Desktop.addInternalFrame(frame,
264               MessageManager.getString("label.sequence_feature_settings"),
265               600, 480);
266     }
267     else
268     {
269       Desktop.addInternalFrame(frame,
270               MessageManager.getString("label.sequence_feature_settings"),
271               600, 450);
272     }
273     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
274
275     frame.addInternalFrameListener(
276             new javax.swing.event.InternalFrameAdapter()
277             {
278               @Override
279               public void internalFrameClosed(
280                       javax.swing.event.InternalFrameEvent evt)
281               {
282                 fr.removePropertyChangeListener(change);
283                 dassourceBrowser.fs = null;
284               };
285             });
286     frame.setLayer(JLayeredPane.PALETTE_LAYER);
287     inConstruction = false;
288   }
289
290   /**
291    * Constructs and configures the JTable which displays columns of data for
292    * each feature type
293    */
294   protected void initTable()
295   {
296     table = new JTable()
297     {
298       @Override
299       public String getToolTipText(MouseEvent e)
300       {
301         String tip = null;
302         int column = table.columnAtPoint(e.getPoint());
303         switch (column)
304         {
305         case TYPE_COLUMN:
306           /*
307            * drag to reorder not enabled in Summary View
308            */
309           tip = summaryView.isSelected()
310                   ? MessageManager.getString(
311                           "label.feature_settings_select_columns")
312                   : JvSwingUtils.wrapTooltip(true, MessageManager
313                   .getString("label.feature_settings_click_drag"));
314           break;
315         case FILTER_COLUMN:
316           int row = table.rowAtPoint(e.getPoint());
317           FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
318                   column);
319           tip = o.isEmpty()
320                   ? MessageManager.getString("label.filters_tooltip")
321                   : o.toString();
322           break;
323         default:
324           break;
325         }
326         return tip;
327       }
328     };
329     table.getTableHeader().setFont(VERDANA_12);
330     table.setFont(VERDANA_12);
331
332     table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
333     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
334
335     table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
336     table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
337
338     TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
339             new ColorRenderer(), new ColorEditor(this));
340     table.addColumn(colourColumn);
341
342     TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
343             new FilterRenderer(), new FilterEditor(this));
344     table.addColumn(filterColumn);
345
346     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
347
348     table.addMouseListener(new MouseAdapter()
349     {
350       @Override
351       public void mousePressed(MouseEvent evt)
352       {
353         selectedRow = table.rowAtPoint(evt.getPoint());
354         String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
355         if (evt.isPopupTrigger())
356         {
357           Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
358           popupMenu(selectedRow, type, colour, evt.getX(), evt.getY());
359         }
360         else if (evt.getClickCount() == 2)
361         {
362           boolean invertSelection = evt.isAltDown();
363           boolean toggleSelection = Platform.isControlDown(evt);
364           boolean extendSelection = evt.isShiftDown();
365           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
366                   invertSelection, extendSelection, toggleSelection, type);
367         }
368       }
369
370       // isPopupTrigger fires on mouseReleased on Windows
371       @Override
372       public void mouseReleased(MouseEvent evt)
373       {
374         selectedRow = table.rowAtPoint(evt.getPoint());
375         if (evt.isPopupTrigger())
376         {
377           String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
378           Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
379           popupMenu(selectedRow, type, colour, evt.getX(), evt.getY());
380         }
381       }
382     });
383
384     table.addMouseMotionListener(new MouseMotionAdapter()
385     {
386       @Override
387       public void mouseDragged(MouseEvent evt)
388       {
389         int newRow = table.rowAtPoint(evt.getPoint());
390         dragRow(newRow);
391       }
392     });
393   }
394
395   protected void popupMenu(final int rowSelected, final String type,
396           final Object typeCol, int x, int y)
397   {
398     final FeatureColourI featureColour = (FeatureColourI) typeCol;
399
400     JPopupMenu men = new JPopupMenu(MessageManager
401             .formatMessage("label.settings_for_param", new String[]
402             { type }));
403     JMenuItem scr = new JMenuItem(
404             MessageManager.getString("label.sort_by_score"));
405     men.add(scr);
406     final FeatureSettings me = this;
407     scr.addActionListener(new ActionListener()
408     {
409
410       @Override
411       public void actionPerformed(ActionEvent e)
412       {
413         me.af.avc
414                 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
415                 { type }));
416       }
417
418     });
419     JMenuItem dens = new JMenuItem(
420             MessageManager.getString("label.sort_by_density"));
421     dens.addActionListener(new ActionListener()
422     {
423
424       @Override
425       public void actionPerformed(ActionEvent e)
426       {
427         me.af.avc
428                 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
429                 { type }));
430       }
431
432     });
433     men.add(dens);
434
435     /*
436      * variable colour options include colour by label, by score,
437      * by selected attribute text, or attribute value
438      */
439     final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
440             MessageManager.getString("label.variable_colour"));
441     mxcol.setSelected(!featureColour.isSimpleColour());
442     men.add(mxcol);
443     mxcol.addActionListener(new ActionListener()
444     {
445       JColorChooser colorChooser;
446
447       @Override
448       public void actionPerformed(ActionEvent e)
449       {
450         if (e.getSource() == mxcol)
451         {
452           if (featureColour.isSimpleColour())
453           {
454             FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
455             fc.addActionListener(this);
456           }
457           else
458           {
459             // bring up simple color chooser
460             colorChooser = new JColorChooser();
461             String title = MessageManager
462                     .getString("label.select_colour");
463             JDialog dialog = JColorChooser.createDialog(me,
464                     title, true, // modal
465                     colorChooser, this, // OK button handler
466                     null); // no CANCEL button handler
467             colorChooser.setColor(featureColour.getMaxColour());
468             dialog.setVisible(true);
469           }
470         }
471         else
472         {
473           if (e.getSource() instanceof FeatureTypeSettings)
474           {
475             /*
476              * update after OK in feature colour dialog; the updated
477              * colour will have already been set in the FeatureRenderer
478              */
479             FeatureColourI fci = fr.getFeatureColours().get(type);
480             table.setValueAt(fci, rowSelected, 1);
481             table.validate();
482           }
483           else
484           {
485             // probably the color chooser!
486             table.setValueAt(new FeatureColour(colorChooser.getColor()),
487                     rowSelected, 1);
488             table.validate();
489             me.updateFeatureRenderer(
490                     ((FeatureTableModel) table.getModel()).getData(),
491                     false);
492           }
493         }
494       }
495     });
496
497     JMenuItem selCols = new JMenuItem(
498             MessageManager.getString("label.select_columns_containing"));
499     selCols.addActionListener(new ActionListener()
500     {
501       @Override
502       public void actionPerformed(ActionEvent arg0)
503       {
504         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
505                 false, type);
506       }
507     });
508     JMenuItem clearCols = new JMenuItem(MessageManager
509             .getString("label.select_columns_not_containing"));
510     clearCols.addActionListener(new ActionListener()
511     {
512       @Override
513       public void actionPerformed(ActionEvent arg0)
514       {
515         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
516                 false, type);
517       }
518     });
519     JMenuItem hideCols = new JMenuItem(
520             MessageManager.getString("label.hide_columns_containing"));
521     hideCols.addActionListener(new ActionListener()
522     {
523       @Override
524       public void actionPerformed(ActionEvent arg0)
525       {
526         fr.ap.alignFrame.hideFeatureColumns(type, true);
527       }
528     });
529     JMenuItem hideOtherCols = new JMenuItem(
530             MessageManager.getString("label.hide_columns_not_containing"));
531     hideOtherCols.addActionListener(new ActionListener()
532     {
533       @Override
534       public void actionPerformed(ActionEvent arg0)
535       {
536         fr.ap.alignFrame.hideFeatureColumns(type, false);
537       }
538     });
539     men.add(selCols);
540     men.add(clearCols);
541     men.add(hideCols);
542     men.add(hideOtherCols);
543     men.show(table, x, y);
544   }
545
546   @Override
547   synchronized public void discoverAllFeatureData()
548   {
549     Set<String> allGroups = new HashSet<>();
550     AlignmentI alignment = af.getViewport().getAlignment();
551
552     for (int i = 0; i < alignment.getHeight(); i++)
553     {
554       SequenceI seq = alignment.getSequenceAt(i);
555       for (String group : seq.getFeatures().getFeatureGroups(true))
556       {
557         if (group != null && !allGroups.contains(group))
558         {
559           allGroups.add(group);
560           checkGroupState(group);
561         }
562       }
563     }
564
565     resetTable(null);
566
567     validate();
568   }
569
570   /**
571    * Synchronise gui group list and check visibility of group
572    * 
573    * @param group
574    * @return true if group is visible
575    */
576   private boolean checkGroupState(String group)
577   {
578     boolean visible = fr.checkGroupVisibility(group, true);
579
580     for (int g = 0; g < groupPanel.getComponentCount(); g++)
581     {
582       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
583       {
584         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
585         return visible;
586       }
587     }
588
589     final String grp = group;
590     final JCheckBox check = new JCheckBox(group, visible);
591     check.setFont(new Font("Serif", Font.BOLD, 12));
592     check.setToolTipText(group);
593     check.addItemListener(new ItemListener()
594     {
595       @Override
596       public void itemStateChanged(ItemEvent evt)
597       {
598         fr.setGroupVisibility(check.getText(), check.isSelected());
599         resetTable(new String[] { grp });
600         af.alignPanel.paintAlignment(true, true);
601       }
602     });
603     groupPanel.add(check);
604     return visible;
605   }
606
607   synchronized void resetTable(String[] groupChanged)
608   {
609     if (resettingTable)
610     {
611       return;
612     }
613     resettingTable = true;
614     typeWidth = new Hashtable<>();
615     // TODO: change avWidth calculation to 'per-sequence' average and use long
616     // rather than float
617
618     Set<String> displayableTypes = new HashSet<>();
619     Set<String> foundGroups = new HashSet<>();
620
621     /*
622      * determine which feature types may be visible depending on 
623      * which groups are selected, and recompute average width data
624      */
625     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
626     {
627
628       SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
629
630       /*
631        * get the sequence's groups for positional features
632        * and keep track of which groups are visible
633        */
634       Set<String> groups = seq.getFeatures().getFeatureGroups(true);
635       Set<String> visibleGroups = new HashSet<>();
636       for (String group : groups)
637       {
638         if (group == null || checkGroupState(group))
639         {
640           visibleGroups.add(group);
641         }
642       }
643       foundGroups.addAll(groups);
644
645       /*
646        * get distinct feature types for visible groups
647        * record distinct visible types, and their count and total length
648        */
649       Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
650               visibleGroups.toArray(new String[visibleGroups.size()]));
651
652       for (String type : types)
653       {
654         displayableTypes.add(type);
655         float[] avWidth = typeWidth.get(type);
656         if (avWidth == null)
657         {
658           avWidth = new float[2];
659           typeWidth.put(type, avWidth);
660         }
661         // todo this could include features with a non-visible group
662         // - do we greatly care?
663         // todo should we include non-displayable features here, and only
664         // update when features are added?
665         avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
666         avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
667       }
668     }
669
670     /*
671      * enable 'Summary View' if some types are sub-types of others
672      */
673     Set<String> parents = SequenceOntologyFactory.getInstance()
674             .getParentTerms(displayableTypes);
675     summaryView.setEnabled(parents.size() < displayableTypes.size());
676
677     Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
678     int dataIndex = 0;
679
680     if (fr.hasRenderOrder())
681     {
682       if (!handlingUpdate)
683       {
684         fr.findAllFeatures(groupChanged != null); // prod to update
685         // colourschemes. but don't
686         // affect display
687         // First add the checks in the previous render order,
688         // in case the window has been closed and reopened
689       }
690       List<String> frl = fr.getRenderOrder();
691       for (int ro = frl.size() - 1; ro > -1; ro--)
692       {
693         String type = frl.get(ro);
694
695         if (!displayableTypes.contains(type))
696         {
697           continue;
698         }
699         data[dataIndex][TYPE_COLUMN] = type;
700         data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
701         FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
702         data[dataIndex][FILTER_COLUMN] = featureFilter == null
703                 ? new FeatureMatcherSet()
704                 : featureFilter;
705         data[dataIndex][SHOW_COLUMN] = new Boolean(
706                 af.getViewport().getFeaturesDisplayed().isVisible(type));
707         dataIndex++;
708         displayableTypes.remove(type);
709       }
710     }
711
712     /*
713      * process any extra features belonging only to 
714      * a group which was just selected
715      */
716     while (!displayableTypes.isEmpty())
717     {
718       String type = displayableTypes.iterator().next();
719       data[dataIndex][TYPE_COLUMN] = type;
720
721       data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
722       if (data[dataIndex][COLOUR_COLUMN] == null)
723       {
724         // "Colour has been updated in another view!!"
725         fr.clearRenderOrder();
726         return;
727       }
728       FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
729       data[dataIndex][FILTER_COLUMN] = featureFilter == null
730               ? new FeatureMatcherSet()
731               : featureFilter;
732       data[dataIndex][SHOW_COLUMN] = new Boolean(true);
733       dataIndex++;
734       displayableTypes.remove(type);
735     }
736
737     if (originalData == null)
738     {
739       originalData = new Object[data.length][COLUMN_COUNT];
740       for (int i = 0; i < data.length; i++)
741       {
742         System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
743       }
744     }
745     else
746     {
747       updateOriginalData(data);
748     }
749
750     /*
751      * recreate the table model
752      */
753     FeatureTableModel dataModel = new FeatureTableModel(data);
754     table.setModel(dataModel);
755
756     /*
757      * we want to be able to filter out rows for sub-types, but not to sort 
758      * rows, so have to add a RowFilter to a disabled TableRowSorter (!)
759      */
760     final TableRowSorter<FeatureTableModel> sorter = new TableRowSorter<>(
761             dataModel);
762     for (int i = 0; i < table.getColumnCount(); i++)
763     {
764       sorter.setSortable(i, false);
765     }
766
767     /*
768      * filter rows to only top-level Ontology types if requested
769      */
770     sorter.setRowFilter(new RowFilter<FeatureTableModel, Integer>()
771     {
772       @Override
773       public boolean include(
774               Entry<? extends FeatureTableModel, ? extends Integer> entry)
775       {
776         if (!summaryView.isSelected())
777         {
778           return true;
779         }
780         int row = entry.getIdentifier(); // this is model, not view, row number
781         String featureType = (String) entry.getModel().getData()[row][TYPE_COLUMN];
782         return parents.contains(featureType);
783       }
784     });
785     table.setRowSorter(sorter);
786
787     table.getColumnModel().getColumn(0).setPreferredWidth(200);
788
789     groupPanel.setLayout(
790             new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
791     pruneGroups(foundGroups);
792     groupPanel.validate();
793
794     updateFeatureRenderer(data, groupChanged != null);
795     resettingTable = false;
796   }
797
798   /**
799    * Updates 'originalData' (used for restore on Cancel) if we detect that changes
800    * have been made outwith this dialog
801    * <ul>
802    * <li>a new feature type added (and made visible)</li>
803    * <li>a feature colour changed (in the Amend Features dialog)</li>
804    * </ul>
805    * 
806    * @param foundData
807    */
808   protected void updateOriginalData(Object[][] foundData)
809   {
810     // todo LinkedHashMap instead of Object[][] would be nice
811
812     Object[][] currentData = ((FeatureTableModel) table.getModel())
813             .getData();
814     for (Object[] row : foundData)
815     {
816       String type = (String) row[TYPE_COLUMN];
817       boolean found = false;
818       for (Object[] current : currentData)
819       {
820         if (type.equals(current[TYPE_COLUMN]))
821         {
822           found = true;
823           /*
824            * currently dependent on object equality here;
825            * really need an equals method on FeatureColour
826            */
827           if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
828           {
829             /*
830              * feature colour has changed externally - update originalData
831              */
832             for (Object[] original : originalData)
833             {
834               if (type.equals(original[TYPE_COLUMN]))
835               {
836                 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
837                 break;
838               }
839             }
840           }
841           break;
842         }
843       }
844       if (!found)
845       {
846         /*
847          * new feature detected - add to original data (on top)
848          */
849         Object[][] newData = new Object[originalData.length
850                 + 1][COLUMN_COUNT];
851         for (int i = 0; i < originalData.length; i++)
852         {
853           System.arraycopy(originalData[i], 0, newData[i + 1], 0,
854                   COLUMN_COUNT);
855         }
856         newData[0] = row;
857         originalData = newData;
858       }
859     }
860   }
861
862   /**
863    * Remove from the groups panel any checkboxes for groups that are not in the
864    * foundGroups set. This enables removing a group from the display when the last
865    * feature in that group is deleted.
866    * 
867    * @param foundGroups
868    */
869   protected void pruneGroups(Set<String> foundGroups)
870   {
871     for (int g = 0; g < groupPanel.getComponentCount(); g++)
872     {
873       JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
874       if (!foundGroups.contains(checkbox.getText()))
875       {
876         groupPanel.remove(checkbox);
877       }
878     }
879   }
880
881   /**
882    * reorder data based on the featureRenderers global priority list.
883    * 
884    * @param data
885    */
886   private void ensureOrder(Object[][] data)
887   {
888     boolean sort = false;
889     float[] order = new float[data.length];
890     for (int i = 0; i < order.length; i++)
891     {
892       order[i] = fr.getOrder(data[i][0].toString());
893       if (order[i] < 0)
894       {
895         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
896       }
897       if (i > 1)
898       {
899         sort = sort || order[i - 1] > order[i];
900       }
901     }
902     if (sort)
903     {
904       jalview.util.QuickSort.sort(order, data);
905     }
906   }
907
908   /**
909    * Offers a file chooser dialog, and then loads the feature colours and
910    * filters from file in XML format and unmarshals to Jalview feature settings
911    */
912   void load()
913   {
914     JalviewFileChooser chooser = new JalviewFileChooser("fc",
915             SEQUENCE_FEATURE_COLOURS);
916     chooser.setFileView(new JalviewFileView());
917     chooser.setDialogTitle(
918             MessageManager.getString("label.load_feature_colours"));
919     chooser.setToolTipText(MessageManager.getString("action.load"));
920
921     int value = chooser.showOpenDialog(this);
922
923     if (value == JalviewFileChooser.APPROVE_OPTION)
924     {
925       File file = chooser.getSelectedFile();
926       load(file);
927     }
928   }
929
930   /**
931    * Loads feature colours and filters from XML stored in the given file
932    * 
933    * @param file
934    */
935   void load(File file)
936   {
937     try
938     {
939       InputStreamReader in = new InputStreamReader(
940               new FileInputStream(file), "UTF-8");
941
942       JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
943
944       /*
945        * load feature colours
946        */
947       for (int i = jucs.getColourCount() - 1; i >= 0; i--)
948       {
949         jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
950         FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
951         fr.setColour(newcol.getName(), colour);
952         fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
953       }
954
955       /*
956        * load feature filters; loaded filters will replace any that are
957        * currently defined, other defined filters are left unchanged 
958        */
959       for (int i = 0; i < jucs.getFilterCount(); i++)
960       {
961         jalview.schemabinding.version2.Filter filterModel = jucs
962                 .getFilter(i);
963         String featureType = filterModel.getFeatureType();
964         FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
965                 filterModel.getMatcherSet());
966         if (!filter.isEmpty())
967         {
968           fr.setFeatureFilter(featureType, filter);
969         }
970       }
971
972       /*
973        * update feature settings table
974        */
975       if (table != null)
976       {
977         resetTable(null);
978         Object[][] data = ((FeatureTableModel) table.getModel())
979                 .getData();
980         ensureOrder(data);
981         updateFeatureRenderer(data, false);
982         table.repaint();
983       }
984     } catch (Exception ex)
985     {
986       System.out.println("Error loading User Colour File\n" + ex);
987     }
988   }
989
990   /**
991    * Offers a file chooser dialog, and then saves the current feature colours
992    * and any filters to the selected file in XML format
993    */
994   void save()
995   {
996     JalviewFileChooser chooser = new JalviewFileChooser("fc",
997             SEQUENCE_FEATURE_COLOURS);
998     chooser.setFileView(new JalviewFileView());
999     chooser.setDialogTitle(
1000             MessageManager.getString("label.save_feature_colours"));
1001     chooser.setToolTipText(MessageManager.getString("action.save"));
1002
1003     int value = chooser.showSaveDialog(this);
1004
1005     if (value == JalviewFileChooser.APPROVE_OPTION)
1006     {
1007       save(chooser.getSelectedFile());
1008     }
1009   }
1010
1011   /**
1012    * Saves feature colours and filters to the given file
1013    * 
1014    * @param file
1015    */
1016   void save(File file)
1017   {
1018     JalviewUserColours ucs = new JalviewUserColours();
1019     ucs.setSchemeName("Sequence Features");
1020     try
1021     {
1022       PrintWriter out = new PrintWriter(new OutputStreamWriter(
1023               new FileOutputStream(file), "UTF-8"));
1024
1025       /*
1026        * sort feature types by colour order, from 0 (highest)
1027        * to 1 (lowest)
1028        */
1029       Set<String> fr_colours = fr.getAllFeatureColours();
1030       String[] sortedTypes = fr_colours
1031               .toArray(new String[fr_colours.size()]);
1032       Arrays.sort(sortedTypes, new Comparator<String>()
1033       {
1034         @Override
1035         public int compare(String type1, String type2)
1036         {
1037           return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1038         }
1039       });
1040
1041       /*
1042        * save feature colours
1043        */
1044       for (String featureType : sortedTypes)
1045       {
1046         FeatureColourI fcol = fr.getFeatureStyle(featureType);
1047         jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1048                 featureType, fcol);
1049         ucs.addColour(col);
1050       }
1051
1052       /*
1053        * save any feature filters
1054        */
1055       for (String featureType : sortedTypes)
1056       {
1057         FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1058         if (filter != null && !filter.isEmpty())
1059         {
1060           Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1061           FeatureMatcherI firstMatcher = iterator.next();
1062           MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1063                   filter.isAnded());
1064           Filter filterModel = new Filter();
1065           filterModel.setFeatureType(featureType);
1066           filterModel.setMatcherSet(ms);
1067           ucs.addFilter(filterModel);
1068         }
1069       }
1070
1071       ucs.marshal(out);
1072       out.close();
1073     } catch (Exception ex)
1074     {
1075       ex.printStackTrace();
1076     }
1077   }
1078
1079   public void invertSelection()
1080   {
1081     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1082     for (int i = 0; i < data.length; i++)
1083     {
1084       data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1085     }
1086     updateFeatureRenderer(data, true);
1087     table.repaint();
1088   }
1089
1090   public void orderByAvWidth()
1091   {
1092     if (table == null || table.getModel() == null)
1093     {
1094       return;
1095     }
1096     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1097     float[] width = new float[data.length];
1098     float[] awidth;
1099     float max = 0;
1100
1101     for (int i = 0; i < data.length; i++)
1102     {
1103       awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1104       if (awidth[0] > 0)
1105       {
1106         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1107         // weight - but have to make per
1108         // sequence, too (awidth[2])
1109         // if (width[i]==1) // hack to distinguish single width sequences.
1110       }
1111       else
1112       {
1113         width[i] = 0;
1114       }
1115       if (max < width[i])
1116       {
1117         max = width[i];
1118       }
1119     }
1120     boolean sort = false;
1121     for (int i = 0; i < width.length; i++)
1122     {
1123       // awidth = (float[]) typeWidth.get(data[i][0]);
1124       if (width[i] == 0)
1125       {
1126         width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1127         if (width[i] < 0)
1128         {
1129           width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1130                   i / data.length);
1131         }
1132       }
1133       else
1134       {
1135         width[i] /= max; // normalize
1136         fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1137       }
1138       if (i > 0)
1139       {
1140         sort = sort || width[i - 1] > width[i];
1141       }
1142     }
1143     if (sort)
1144     {
1145       jalview.util.QuickSort.sort(width, data);
1146       // update global priority order
1147     }
1148
1149     updateFeatureRenderer(data, false);
1150     table.repaint();
1151   }
1152
1153   public void close()
1154   {
1155     try
1156     {
1157       frame.setClosed(true);
1158     } catch (Exception exe)
1159     {
1160     }
1161
1162   }
1163
1164   public void updateFeatureRenderer(Object[][] data)
1165   {
1166     updateFeatureRenderer(data, true);
1167   }
1168
1169   /**
1170    * Update the priority order of features; only repaint if this changed the order
1171    * of visible features
1172    * 
1173    * @param data
1174    * @param visibleNew
1175    */
1176   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1177   {
1178     FeatureSettingsBean[] rowData = getTableAsBeans(data);
1179
1180     if (fr.setFeaturePriority(rowData, visibleNew))
1181     {
1182       af.alignPanel.paintAlignment(true, true);
1183     }
1184   }
1185
1186   /**
1187    * Converts table data into an array of data beans
1188    */
1189   private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1190   {
1191     FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1192     for (int i = 0; i < data.length; i++)
1193     {
1194       String type = (String) data[i][TYPE_COLUMN];
1195       FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1196       FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1197       Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1198       rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1199               isShown);
1200     }
1201     return rowData;
1202   }
1203
1204   private void jbInit() throws Exception
1205   {
1206     this.setLayout(new BorderLayout());
1207
1208     JPanel settingsPane = new JPanel();
1209     settingsPane.setLayout(new BorderLayout());
1210
1211     dasSettingsPane.setLayout(new BorderLayout());
1212
1213     JPanel bigPanel = new JPanel();
1214     bigPanel.setLayout(new BorderLayout());
1215
1216     groupPanel = new JPanel();
1217     bigPanel.add(groupPanel, BorderLayout.NORTH);
1218
1219     JButton invert = new JButton(
1220             MessageManager.getString("label.invert_selection"));
1221     invert.setFont(JvSwingUtils.getLabelFont());
1222     invert.addActionListener(new ActionListener()
1223     {
1224       @Override
1225       public void actionPerformed(ActionEvent e)
1226       {
1227         invertSelection();
1228       }
1229     });
1230
1231     JButton optimizeOrder = new JButton(
1232             MessageManager.getString("label.optimise_order"));
1233     optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1234     optimizeOrder.addActionListener(new ActionListener()
1235     {
1236       @Override
1237       public void actionPerformed(ActionEvent e)
1238       {
1239         orderByAvWidth();
1240       }
1241     });
1242
1243     JButton sortByScore = new JButton(
1244             MessageManager.getString("label.seq_sort_by_score"));
1245     sortByScore.setFont(JvSwingUtils.getLabelFont());
1246     sortByScore.addActionListener(new ActionListener()
1247     {
1248       @Override
1249       public void actionPerformed(ActionEvent e)
1250       {
1251         af.avc.sortAlignmentByFeatureScore(null);
1252       }
1253     });
1254     JButton sortByDens = new JButton(
1255             MessageManager.getString("label.sequence_sort_by_density"));
1256     sortByDens.setFont(JvSwingUtils.getLabelFont());
1257     sortByDens.addActionListener(new ActionListener()
1258     {
1259       @Override
1260       public void actionPerformed(ActionEvent e)
1261       {
1262         af.avc.sortAlignmentByFeatureDensity(null);
1263       }
1264     });
1265
1266     JButton help = new JButton(MessageManager.getString("action.help"));
1267     help.setFont(JvSwingUtils.getLabelFont());
1268     help.addActionListener(new ActionListener()
1269     {
1270       @Override
1271       public void actionPerformed(ActionEvent e)
1272       {
1273         try
1274         {
1275           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1276         } catch (HelpSetException e1)
1277         {
1278           e1.printStackTrace();
1279         }
1280       }
1281     });
1282     help.setFont(JvSwingUtils.getLabelFont());
1283     help.setText(MessageManager.getString("action.help"));
1284     help.addActionListener(new ActionListener()
1285     {
1286       @Override
1287       public void actionPerformed(ActionEvent e)
1288       {
1289         try
1290         {
1291           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1292         } catch (HelpSetException e1)
1293         {
1294           e1.printStackTrace();
1295         }
1296       }
1297     });
1298
1299     JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1300     cancel.setFont(JvSwingUtils.getLabelFont());
1301     cancel.addActionListener(new ActionListener()
1302     {
1303       @Override
1304       public void actionPerformed(ActionEvent e)
1305       {
1306         fr.setTransparency(originalTransparency);
1307         fr.setFeatureFilters(originalFilters);
1308         updateFeatureRenderer(originalData);
1309         close();
1310       }
1311     });
1312
1313     JButton ok = new JButton(MessageManager.getString("action.ok"));
1314     ok.setFont(JvSwingUtils.getLabelFont());
1315     ok.addActionListener(new ActionListener()
1316     {
1317       @Override
1318       public void actionPerformed(ActionEvent e)
1319       {
1320         close();
1321       }
1322     });
1323
1324     JButton loadColours = new JButton(
1325             MessageManager.getString("label.load_colours"));
1326     loadColours.setFont(JvSwingUtils.getLabelFont());
1327     loadColours.setToolTipText(
1328             MessageManager.getString("label.load_colours_tooltip"));
1329     loadColours.addActionListener(new ActionListener()
1330     {
1331       @Override
1332       public void actionPerformed(ActionEvent e)
1333       {
1334         load();
1335       }
1336     });
1337
1338     JButton saveColours = new JButton(
1339             MessageManager.getString("label.save_colours"));
1340     saveColours.setFont(JvSwingUtils.getLabelFont());
1341     saveColours.setToolTipText(
1342             MessageManager.getString("label.save_colours_tooltip"));
1343     saveColours.addActionListener(new ActionListener()
1344     {
1345       @Override
1346       public void actionPerformed(ActionEvent e)
1347       {
1348         save();
1349       }
1350     });
1351     transparency.addChangeListener(new ChangeListener()
1352     {
1353       @Override
1354       public void stateChanged(ChangeEvent evt)
1355       {
1356         if (!inConstruction)
1357         {
1358           fr.setTransparency((100 - transparency.getValue()) / 100f);
1359           af.alignPanel.paintAlignment(true, true);
1360         }
1361       }
1362     });
1363
1364     summaryView = new JCheckBox(
1365             MessageManager.getString("label.summary_view"));
1366     summaryView
1367             .setToolTipText(
1368                     MessageManager.getString("label.summary_view_tip"));
1369     summaryView.addActionListener(new ActionListener()
1370     {
1371       @Override
1372       public void actionPerformed(ActionEvent e)
1373       {
1374         resetTable(null);
1375       }
1376     });
1377
1378     transparency.setMaximum(70);
1379     transparency.setToolTipText(
1380             MessageManager.getString("label.transparency_tip"));
1381     fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1382     fetchDAS.addActionListener(new ActionListener()
1383     {
1384       @Override
1385       public void actionPerformed(ActionEvent e)
1386       {
1387         fetchDAS_actionPerformed(e);
1388       }
1389     });
1390     saveDAS.setText(MessageManager.getString("action.save_as_default"));
1391     saveDAS.addActionListener(new ActionListener()
1392     {
1393       @Override
1394       public void actionPerformed(ActionEvent e)
1395       {
1396         saveDAS_actionPerformed(e);
1397       }
1398     });
1399
1400     JPanel dasButtonPanel = new JPanel();
1401     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1402     dasSettingsPane.setBorder(null);
1403     cancelDAS.setEnabled(false);
1404     cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1405     cancelDAS.addActionListener(new ActionListener()
1406     {
1407       @Override
1408       public void actionPerformed(ActionEvent e)
1409       {
1410         cancelDAS_actionPerformed(e);
1411       }
1412     });
1413
1414     JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1415     bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1416
1417     JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1418     transbuttons.add(optimizeOrder);
1419     transbuttons.add(invert);
1420     transbuttons.add(sortByScore);
1421     transbuttons.add(sortByDens);
1422     transbuttons.add(help);
1423     JPanel transPanel = new JPanel(new GridLayout(3, 1));
1424     transPanel.add(summaryView);
1425     transPanel.add(new JLabel(" Colour transparency" + ":"));
1426     transPanel.add(transparency);
1427     lowerPanel.add(transPanel);
1428     lowerPanel.add(transbuttons);
1429
1430     JPanel buttonPanel = new JPanel();
1431     buttonPanel.add(ok);
1432     buttonPanel.add(cancel);
1433     buttonPanel.add(loadColours);
1434     buttonPanel.add(saveColours);
1435     bigPanel.add(scrollPane, BorderLayout.CENTER);
1436     dasSettingsPane.add(dasButtonPanel, BorderLayout.SOUTH);
1437     dasButtonPanel.add(fetchDAS);
1438     dasButtonPanel.add(cancelDAS);
1439     dasButtonPanel.add(saveDAS);
1440     settingsPane.add(bigPanel, BorderLayout.CENTER);
1441     settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1442     this.add(settingsPane);
1443   }
1444
1445   public void fetchDAS_actionPerformed(ActionEvent e)
1446   {
1447     fetchDAS.setEnabled(false);
1448     cancelDAS.setEnabled(true);
1449     dassourceBrowser.setGuiEnabled(false);
1450     Vector<jalviewSourceI> selectedSources = dassourceBrowser
1451             .getSelectedSources();
1452     doDasFeatureFetch(selectedSources, true, true);
1453   }
1454
1455   /**
1456    * get the features from selectedSources for all or the current selection
1457    * 
1458    * @param selectedSources
1459    * @param checkDbRefs
1460    * @param promptFetchDbRefs
1461    */
1462   private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1463           boolean checkDbRefs, boolean promptFetchDbRefs)
1464   {
1465     SequenceI[] dataset, seqs;
1466     int iSize;
1467     AlignmentViewport vp = af.getViewport();
1468     if (vp.getSelectionGroup() != null
1469             && vp.getSelectionGroup().getSize() > 0)
1470     {
1471       iSize = vp.getSelectionGroup().getSize();
1472       dataset = new SequenceI[iSize];
1473       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1474     }
1475     else
1476     {
1477       iSize = vp.getAlignment().getHeight();
1478       seqs = vp.getAlignment().getSequencesArray();
1479     }
1480
1481     dataset = new SequenceI[iSize];
1482     for (int i = 0; i < iSize; i++)
1483     {
1484       dataset[i] = seqs[i].getDatasetSequence();
1485     }
1486
1487     cancelDAS.setEnabled(true);
1488     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1489             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1490     af.getViewport().setShowSequenceFeatures(true);
1491     af.showSeqFeatures.setSelected(true);
1492   }
1493
1494   /**
1495    * blocking call to initialise the das source browser
1496    */
1497   public void initDasSources()
1498   {
1499     dassourceBrowser.initDasSources();
1500   }
1501
1502   /**
1503    * examine the current list of das sources and return any matching the given
1504    * nicknames in sources
1505    * 
1506    * @param sources
1507    *          Vector of Strings to resolve to DAS source nicknames.
1508    * @return sources that are present in source list.
1509    */
1510   public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1511   {
1512     return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1513   }
1514
1515   /**
1516    * get currently selected das sources. ensure you have called initDasSources
1517    * before calling this.
1518    * 
1519    * @return vector of selected das source nicknames
1520    */
1521   public Vector<jalviewSourceI> getSelectedSources()
1522   {
1523     return dassourceBrowser.getSelectedSources();
1524   }
1525
1526   /**
1527    * properly initialise DAS fetcher and then initiate a new thread to fetch
1528    * features from the named sources (rather than any turned on by default)
1529    * 
1530    * @param sources
1531    * @param block
1532    *          if true then runs in same thread, otherwise passes to the Swing
1533    *          executor
1534    */
1535   public void fetchDasFeatures(Vector<String> sources, boolean block)
1536   {
1537     initDasSources();
1538     List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1539             .resolveSourceNicknames(sources);
1540     if (resolved.size() == 0)
1541     {
1542       resolved = dassourceBrowser.getSelectedSources();
1543     }
1544     if (resolved.size() > 0)
1545     {
1546       final List<jalviewSourceI> dassources = resolved;
1547       fetchDAS.setEnabled(false);
1548       // cancelDAS.setEnabled(true); doDasFetch does this.
1549       Runnable fetcher = new Runnable()
1550       {
1551
1552         @Override
1553         public void run()
1554         {
1555           doDasFeatureFetch(dassources, true, false);
1556
1557         }
1558       };
1559       if (block)
1560       {
1561         fetcher.run();
1562       }
1563       else
1564       {
1565         SwingUtilities.invokeLater(fetcher);
1566       }
1567     }
1568   }
1569
1570   public void saveDAS_actionPerformed(ActionEvent e)
1571   {
1572     dassourceBrowser
1573             .saveProperties(jalview.bin.Cache.applicationProperties);
1574   }
1575
1576   public void complete()
1577   {
1578     fetchDAS.setEnabled(true);
1579     cancelDAS.setEnabled(false);
1580     dassourceBrowser.setGuiEnabled(true);
1581
1582   }
1583
1584   public void cancelDAS_actionPerformed(ActionEvent e)
1585   {
1586     if (dasFeatureFetcher != null)
1587     {
1588       dasFeatureFetcher.cancel();
1589     }
1590     complete();
1591   }
1592
1593   public void noDasSourceActive()
1594   {
1595     complete();
1596     JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1597             MessageManager.getString("label.no_das_sources_selected_warn"),
1598             MessageManager.getString("label.no_das_sources_selected_title"),
1599             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1600   }
1601
1602   /**
1603    * Reorders features by 'dragging' selectedRow to 'newRow'
1604    * 
1605    * @param newRow
1606    */
1607   protected void dragRow(int newRow)
1608   {
1609     if (summaryView.isSelected())
1610     {
1611       // no drag while in summary view
1612       return;
1613     }
1614
1615     if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
1616     {
1617       /*
1618        * reposition 'selectedRow' to 'newRow' (the dragged to location)
1619        * this could be more than one row away for a very fast drag action
1620        * so just swap it with adjacent rows until we get it there
1621        */
1622       Object[][] data = ((FeatureTableModel) table.getModel())
1623               .getData();
1624       int direction = newRow < selectedRow ? -1 : 1;
1625       for (int i = selectedRow; i != newRow; i += direction)
1626       {
1627         Object[] temp = data[i];
1628         data[i] = data[i + direction];
1629         data[i + direction] = temp;
1630       }
1631       updateFeatureRenderer(data);
1632       table.repaint();
1633       selectedRow = newRow;
1634     }
1635   }
1636
1637   protected void refreshTable()
1638   {
1639     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1640     for (Object[] row : data)
1641     {
1642       String type = (String) row[TYPE_COLUMN];
1643       FeatureColourI colour = fr.getFeatureColours().get(type);
1644       FeatureMatcherSetI filter = fr.getFeatureFilter(type);
1645       if (filter == null)
1646       {
1647         filter = new FeatureMatcherSet();
1648       }
1649       row[COLOUR_COLUMN] = colour;
1650       row[FILTER_COLUMN] = filter;
1651     }
1652     repaint();
1653   }
1654
1655   // ///////////////////////////////////////////////////////////////////////
1656   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1657   // ///////////////////////////////////////////////////////////////////////
1658   class FeatureTableModel extends AbstractTableModel
1659   {
1660     private String[] columnNames = {
1661         MessageManager.getString("label.feature_type"),
1662         MessageManager.getString("action.colour"),
1663         MessageManager.getString("label.filter"),
1664         MessageManager.getString("label.show") };
1665
1666     private Object[][] data;
1667
1668     FeatureTableModel(Object[][] data)
1669     {
1670       this.data = data;
1671     }
1672
1673     public Object[][] getData()
1674     {
1675       return data;
1676     }
1677
1678     public void setData(Object[][] data)
1679     {
1680       this.data = data;
1681     }
1682
1683     @Override
1684     public int getColumnCount()
1685     {
1686       return columnNames.length;
1687     }
1688
1689     public Object[] getRow(int row)
1690     {
1691       return data[row];
1692     }
1693
1694     @Override
1695     public int getRowCount()
1696     {
1697       return data.length;
1698     }
1699
1700     @Override
1701     public String getColumnName(int col)
1702     {
1703       return columnNames[col];
1704     }
1705
1706     @Override
1707     public Object getValueAt(int row, int col)
1708     {
1709       return data[row][col];
1710     }
1711
1712     /**
1713      * Answers the class of the object in column c of the first row of the table
1714      */
1715     @Override
1716     public Class<?> getColumnClass(int c)
1717     {
1718       Object v = getValueAt(0, c);
1719       return v == null ? null : v.getClass();
1720     }
1721
1722     /**
1723      * Answers true for all columns except Feature Type
1724      */
1725     @Override
1726     public boolean isCellEditable(int row, int col)
1727     {
1728       return col != TYPE_COLUMN;
1729     }
1730
1731     /**
1732      * Sets the value in the model for a given row and column. If Visibility
1733      * (Show/Hide) is being set, and the table is in Summary View, then it is
1734      * set also on any sub-types of the row's feature type.
1735      */
1736     @Override
1737     public void setValueAt(Object value, int row, int col)
1738     {
1739       data[row][col] = value;
1740       fireTableCellUpdated(row, col);
1741       if (summaryView.isSelected() && col == SHOW_COLUMN)
1742       {
1743         setSubtypesVisibility(row, (Boolean) value);
1744       }
1745       updateFeatureRenderer(data);
1746     }
1747
1748     /**
1749      * Sets the visibility of any feature types which are sub-types of the type
1750      * in the given row of the table
1751      * 
1752      * @param row
1753      * @param value
1754      */
1755     protected void setSubtypesVisibility(int row, Boolean value)
1756     {
1757       String type = (String) data[row][TYPE_COLUMN];
1758       OntologyI so = SequenceOntologyFactory.getInstance();
1759
1760       for (int r = 0; r < data.length; r++)
1761       {
1762         if (r != row)
1763         {
1764           String type2 = (String) data[r][TYPE_COLUMN];
1765           if (so.isA(type2, type))
1766           {
1767             data[r][SHOW_COLUMN] = value;
1768             fireTableCellUpdated(r, SHOW_COLUMN);
1769           }
1770         }
1771       }
1772     }
1773
1774   }
1775
1776   class ColorRenderer extends JLabel implements TableCellRenderer
1777   {
1778     javax.swing.border.Border unselectedBorder = null;
1779
1780     javax.swing.border.Border selectedBorder = null;
1781
1782     final String baseTT = "Click to edit, right/apple click for menu.";
1783
1784     public ColorRenderer()
1785     {
1786       setOpaque(true); // MUST do this for background to show up.
1787       setHorizontalTextPosition(SwingConstants.CENTER);
1788       setVerticalTextPosition(SwingConstants.CENTER);
1789     }
1790
1791     @Override
1792     public Component getTableCellRendererComponent(JTable tbl, Object color,
1793             boolean isSelected, boolean hasFocus, int row, int column)
1794     {
1795       FeatureColourI cellColour = (FeatureColourI) color;
1796       setOpaque(true);
1797       setToolTipText(baseTT);
1798       setBackground(tbl.getBackground());
1799       if (!cellColour.isSimpleColour())
1800       {
1801         Rectangle cr = tbl.getCellRect(row, column, false);
1802         FeatureSettings.renderGraduatedColor(this, cellColour,
1803                 (int) cr.getWidth(), (int) cr.getHeight());
1804       }
1805       else
1806       {
1807         this.setText("");
1808         this.setIcon(null);
1809         setBackground(cellColour.getColour());
1810       }
1811       if (isSelected)
1812       {
1813         if (selectedBorder == null)
1814         {
1815           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1816                   tbl.getSelectionBackground());
1817         }
1818         setBorder(selectedBorder);
1819       }
1820       else
1821       {
1822         if (unselectedBorder == null)
1823         {
1824           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1825                   tbl.getBackground());
1826         }
1827         setBorder(unselectedBorder);
1828       }
1829
1830       return this;
1831     }
1832   }
1833
1834   class FilterRenderer extends JLabel implements TableCellRenderer
1835   {
1836     javax.swing.border.Border unselectedBorder = null;
1837
1838     javax.swing.border.Border selectedBorder = null;
1839
1840     public FilterRenderer()
1841     {
1842       setOpaque(true); // MUST do this for background to show up.
1843       setHorizontalTextPosition(SwingConstants.CENTER);
1844       setVerticalTextPosition(SwingConstants.CENTER);
1845     }
1846
1847     @Override
1848     public Component getTableCellRendererComponent(JTable tbl,
1849             Object filter, boolean isSelected, boolean hasFocus, int row,
1850             int column)
1851     {
1852       FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1853       setOpaque(true);
1854       String asText = theFilter.toString();
1855       setBackground(tbl.getBackground());
1856       this.setText(asText);
1857       this.setIcon(null);
1858
1859       if (isSelected)
1860       {
1861         if (selectedBorder == null)
1862         {
1863           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1864                   tbl.getSelectionBackground());
1865         }
1866         setBorder(selectedBorder);
1867       }
1868       else
1869       {
1870         if (unselectedBorder == null)
1871         {
1872           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1873                   tbl.getBackground());
1874         }
1875         setBorder(unselectedBorder);
1876       }
1877
1878       return this;
1879     }
1880   }
1881
1882   /**
1883    * update comp using rendering settings from gcol
1884    * 
1885    * @param comp
1886    * @param gcol
1887    */
1888   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1889   {
1890     int w = comp.getWidth(), h = comp.getHeight();
1891     if (w < 20)
1892     {
1893       w = (int) comp.getPreferredSize().getWidth();
1894       h = (int) comp.getPreferredSize().getHeight();
1895       if (w < 20)
1896       {
1897         w = 80;
1898         h = 12;
1899       }
1900     }
1901     renderGraduatedColor(comp, gcol, w, h);
1902   }
1903
1904   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1905           int w, int h)
1906   {
1907     boolean thr = false;
1908     StringBuilder tt = new StringBuilder();
1909     StringBuilder tx = new StringBuilder();
1910
1911     if (gcol.isColourByAttribute())
1912     {
1913       tx.append(String.join(":", gcol.getAttributeName()));
1914     }
1915     else if (!gcol.isColourByLabel())
1916     {
1917       tx.append(MessageManager.getString("label.score"));
1918     }
1919     tx.append(" ");
1920     if (gcol.isAboveThreshold())
1921     {
1922       thr = true;
1923       tx.append(">");
1924       tt.append("Thresholded (Above ").append(gcol.getThreshold())
1925               .append(") ");
1926     }
1927     if (gcol.isBelowThreshold())
1928     {
1929       thr = true;
1930       tx.append("<");
1931       tt.append("Thresholded (Below ").append(gcol.getThreshold())
1932               .append(") ");
1933     }
1934     if (gcol.isColourByLabel())
1935     {
1936       tt.append("Coloured by label text. ").append(tt);
1937       if (thr)
1938       {
1939         tx.append(" ");
1940       }
1941       if (!gcol.isColourByAttribute())
1942       {
1943         tx.append("Label");
1944       }
1945       comp.setIcon(null);
1946     }
1947     else
1948     {
1949       Color newColor = gcol.getMaxColour();
1950       comp.setBackground(newColor);
1951       // System.err.println("Width is " + w / 2);
1952       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1953       comp.setIcon(ficon);
1954       // tt+="RGB value: Max (" + newColor.getRed() + ", "
1955       // + newColor.getGreen() + ", " + newColor.getBlue()
1956       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1957       // + ", " + minCol.getBlue() + ")");
1958     }
1959     comp.setHorizontalAlignment(SwingConstants.CENTER);
1960     comp.setText(tx.toString());
1961     if (tt.length() > 0)
1962     {
1963       if (comp.getToolTipText() == null)
1964       {
1965         comp.setToolTipText(tt.toString());
1966       }
1967       else
1968       {
1969         comp.setToolTipText(
1970                 tt.append(" ").append(comp.getToolTipText()).toString());
1971       }
1972     }
1973   }
1974
1975   class ColorEditor extends AbstractCellEditor
1976           implements TableCellEditor, ActionListener
1977   {
1978     FeatureSettings me;
1979
1980     FeatureColourI currentColor;
1981
1982     FeatureTypeSettings chooser;
1983
1984     String type;
1985
1986     JButton colourButton;
1987
1988     JColorChooser colorChooser;
1989
1990     JDialog dialog;
1991
1992     protected static final String EDIT = "edit";
1993
1994     int rowSelected = 0;
1995
1996     public ColorEditor(FeatureSettings me)
1997     {
1998       this.me = me;
1999       // Set up the editor (from the table's point of view),
2000       // which is a button.
2001       // This button brings up the color chooser dialog,
2002       // which is the editor from the user's point of view.
2003       colourButton = new JButton();
2004       colourButton.setActionCommand(EDIT);
2005       colourButton.addActionListener(this);
2006       colourButton.setBorderPainted(false);
2007       // Set up the dialog that the button brings up.
2008       colorChooser = new JColorChooser();
2009       dialog = JColorChooser.createDialog(colourButton,
2010               MessageManager.getString("label.select_colour"), true, // modal
2011               colorChooser, this, // OK button handler
2012               null); // no CANCEL button handler
2013     }
2014
2015     /**
2016      * Handles events from the editor button and from the dialog's OK button.
2017      */
2018     @Override
2019     public void actionPerformed(ActionEvent e)
2020     {
2021       // todo test e.getSource() instead here
2022       if (EDIT.equals(e.getActionCommand()))
2023       {
2024         // The user has clicked the cell, so
2025         // bring up the dialog.
2026         if (currentColor.isSimpleColour())
2027         {
2028           // bring up simple color chooser
2029           colourButton.setBackground(currentColor.getColour());
2030           colorChooser.setColor(currentColor.getColour());
2031           dialog.setVisible(true);
2032         }
2033         else
2034         {
2035           // bring up graduated chooser.
2036           chooser = new FeatureTypeSettings(me.fr, type);
2037           chooser.setRequestFocusEnabled(true);
2038           chooser.requestFocus();
2039           chooser.addActionListener(this);
2040           chooser.showTab(true);
2041         }
2042         // Make the renderer reappear.
2043         fireEditingStopped();
2044       }
2045       else
2046       {
2047         if (currentColor.isSimpleColour())
2048         {
2049           /*
2050            * read off colour picked in colour chooser after OK pressed
2051            */
2052           currentColor = new FeatureColour(colorChooser.getColor());
2053           me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
2054         }
2055         else
2056         {
2057           /*
2058            * after OK in variable colour dialog, any changes to colour 
2059            * (or filters!) are already set in FeatureRenderer, so just
2060            * update table data without triggering updateFeatureRenderer
2061            */
2062           refreshTable();
2063         }
2064         fireEditingStopped();
2065         me.table.validate();
2066       }
2067     }
2068
2069     // Implement the one CellEditor method that AbstractCellEditor doesn't.
2070     @Override
2071     public Object getCellEditorValue()
2072     {
2073       return currentColor;
2074     }
2075
2076     // Implement the one method defined by TableCellEditor.
2077     @Override
2078     public Component getTableCellEditorComponent(JTable theTable, Object value,
2079             boolean isSelected, int row, int column)
2080     {
2081       currentColor = (FeatureColourI) value;
2082       this.rowSelected = row;
2083       type = me.table.getValueAt(row, TYPE_COLUMN).toString();
2084       colourButton.setOpaque(true);
2085       colourButton.setBackground(me.getBackground());
2086       if (!currentColor.isSimpleColour())
2087       {
2088         JLabel btn = new JLabel();
2089         btn.setSize(colourButton.getSize());
2090         FeatureSettings.renderGraduatedColor(btn, currentColor);
2091         colourButton.setBackground(btn.getBackground());
2092         colourButton.setIcon(btn.getIcon());
2093         colourButton.setText(btn.getText());
2094       }
2095       else
2096       {
2097         colourButton.setText("");
2098         colourButton.setIcon(null);
2099         colourButton.setBackground(currentColor.getColour());
2100       }
2101       return colourButton;
2102     }
2103   }
2104
2105   /**
2106    * The cell editor for the Filter column. It displays the text of any filters
2107    * for the feature type in that row (in full as a tooltip, possible abbreviated
2108    * as display text). On click in the cell, opens the Feature Display Settings
2109    * dialog at the Filters tab.
2110    */
2111   class FilterEditor extends AbstractCellEditor
2112           implements TableCellEditor, ActionListener
2113   {
2114     FeatureSettings me;
2115
2116     FeatureMatcherSetI currentFilter;
2117
2118     Point lastLocation;
2119
2120     String type;
2121
2122     JButton filterButton;
2123
2124     protected static final String EDIT = "edit";
2125
2126     int rowSelected = 0;
2127
2128     public FilterEditor(FeatureSettings me)
2129     {
2130       this.me = me;
2131       filterButton = new JButton();
2132       filterButton.setActionCommand(EDIT);
2133       filterButton.addActionListener(this);
2134       filterButton.setBorderPainted(false);
2135     }
2136
2137     /**
2138      * Handles events from the editor button
2139      */
2140     @Override
2141     public void actionPerformed(ActionEvent e)
2142     {
2143       if (filterButton == e.getSource())
2144       {
2145         FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
2146         chooser.addActionListener(this);
2147         chooser.setRequestFocusEnabled(true);
2148         chooser.requestFocus();
2149         if (lastLocation != null)
2150         {
2151           // todo open at its last position on screen
2152           chooser.setBounds(lastLocation.x, lastLocation.y,
2153                   chooser.getWidth(), chooser.getHeight());
2154           chooser.validate();
2155         }
2156         chooser.showTab(false);
2157         fireEditingStopped();
2158       }
2159       else if (e.getSource() instanceof Component)
2160       {
2161         /*
2162          * after OK in variable colour dialog, any changes to filter
2163          * (or colours!) are already set in FeatureRenderer, so just
2164          * update table data without triggering updateFeatureRenderer
2165          */
2166         refreshTable();
2167         fireEditingStopped();
2168         me.table.validate();
2169       }
2170     }
2171
2172     @Override
2173     public Object getCellEditorValue()
2174     {
2175       return currentFilter;
2176     }
2177
2178     @Override
2179     public Component getTableCellEditorComponent(JTable theTable, Object value,
2180             boolean isSelected, int row, int column)
2181     {
2182       currentFilter = (FeatureMatcherSetI) value;
2183       this.rowSelected = row;
2184       type = me.table.getValueAt(row, TYPE_COLUMN).toString();
2185       filterButton.setOpaque(true);
2186       filterButton.setBackground(me.getBackground());
2187       filterButton.setText(currentFilter.toString());
2188       filterButton.setToolTipText(currentFilter.toString());
2189       filterButton.setIcon(null);
2190       return filterButton;
2191     }
2192   }
2193 }
2194
2195 class FeatureIcon implements Icon
2196 {
2197   private static final Font VERDANA_9 = new Font("Verdana", Font.PLAIN, 9);
2198
2199   FeatureColourI gcol;
2200
2201   Color backg;
2202
2203   boolean midspace = false;
2204
2205   int width = 50, height = 20;
2206
2207   int s1, e1; // start and end of midpoint band for thresholded symbol
2208
2209   Color mpcolour = Color.white;
2210
2211   FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2212   {
2213     gcol = gfc;
2214     backg = bg;
2215     width = w;
2216     height = h;
2217     midspace = mspace;
2218     if (midspace)
2219     {
2220       s1 = width / 3;
2221       e1 = s1 * 2;
2222     }
2223     else
2224     {
2225       s1 = width / 2;
2226       e1 = s1;
2227     }
2228   }
2229
2230   @Override
2231   public int getIconWidth()
2232   {
2233     return width;
2234   }
2235
2236   @Override
2237   public int getIconHeight()
2238   {
2239     return height;
2240   }
2241
2242   @Override
2243   public void paintIcon(Component c, Graphics g, int x, int y)
2244   {
2245
2246     if (gcol.isColourByLabel())
2247     {
2248       g.setColor(backg);
2249       g.fillRect(0, 0, width, height);
2250       // need an icon here.
2251       g.setColor(gcol.getMaxColour());
2252
2253       g.setFont(VERDANA_9);
2254
2255       // g.setFont(g.getFont().deriveFont(
2256       // AffineTransform.getScaleInstance(
2257       // width/g.getFontMetrics().stringWidth("Label"),
2258       // height/g.getFontMetrics().getHeight())));
2259
2260       g.drawString(MessageManager.getString("label.label"), 0, 0);
2261
2262     }
2263     else
2264     {
2265       Color minCol = gcol.getMinColour();
2266       g.setColor(minCol);
2267       g.fillRect(0, 0, s1, height);
2268       if (midspace)
2269       {
2270         g.setColor(Color.white);
2271         g.fillRect(s1, 0, e1 - s1, height);
2272       }
2273       g.setColor(gcol.getMaxColour());
2274       g.fillRect(0, e1, width - e1, height);
2275     }
2276   }
2277 }