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