JAL-2069 update spike branch with latest
[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     chooseTypePanel.setBorder(BorderFactory
1345             .createTitledBorder(MessageManager
1346                     .getString("label.feature_type")));
1347     filteredFeatureChoice = new JComboBox<>();
1348     filteredFeatureChoice.addItemListener(new ItemListener()
1349     {
1350       @Override
1351       public void itemStateChanged(ItemEvent e)
1352       {
1353         refreshFiltersDisplay();
1354       }
1355     });
1356     chooseTypePanel.add(new JLabel(MessageManager
1357             .getString("label.feature_to_filter")));
1358     chooseTypePanel.add(filteredFeatureChoice);
1359     populateFilterableFeatures();
1360
1361     /*
1362      * the panel with the filters for the selected feature type
1363      */
1364     JPanel filtersPanel = new JPanel();
1365     filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
1366     filtersPanel.setBackground(Color.white);
1367     filtersPanel.setBorder(BorderFactory
1368             .createTitledBorder(MessageManager.getString("label.filters")));
1369
1370     /*
1371      * add AND or OR radio buttons
1372      */
1373     JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1374     andOrPanel.setBackground(Color.white);
1375     andFilters = new JRadioButton("And");
1376     orFilters = new JRadioButton("Or");
1377     ActionListener actionListener = new ActionListener()
1378     {
1379       @Override
1380       public void actionPerformed(ActionEvent e)
1381       {
1382         filtersChanged();
1383       }
1384     };
1385     andFilters.addActionListener(actionListener);
1386     orFilters.addActionListener(actionListener);
1387     ButtonGroup andOr = new ButtonGroup();
1388     andOr.add(andFilters);
1389     andOr.add(orFilters);
1390     andFilters.setSelected(true);
1391     andOrPanel.add(new JLabel(MessageManager
1392             .getString("label.join_conditions")));
1393     andOrPanel.add(andFilters);
1394     andOrPanel.add(orFilters);
1395     filtersPanel.add(andOrPanel);
1396
1397     /*
1398      * panel with filters - populated by refreshFiltersDisplay
1399      */
1400     chooseFiltersPanel = new JPanel();
1401     chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel,
1402             BoxLayout.Y_AXIS));
1403     filtersPanel.add(chooseFiltersPanel);
1404
1405     /*
1406      * a read-only text view of the current filters
1407      */
1408     JPanel showFiltersPanel = new JPanel(new BorderLayout(5, 5));
1409     showFiltersPanel.setBackground(Color.white);
1410     showFiltersPanel.setBorder(BorderFactory
1411             .createTitledBorder(MessageManager
1412                     .getString("label.match_condition")));
1413     filtersAsText = new JTextArea();
1414     filtersAsText.setLineWrap(true);
1415     filtersAsText.setWrapStyleWord(true);
1416     showFiltersPanel.add(filtersAsText);
1417
1418     filtersPane.setLayout(new BorderLayout());
1419     filtersPane.add(chooseTypePanel, BorderLayout.NORTH);
1420     filtersPane.add(filtersPanel, BorderLayout.CENTER);
1421     filtersPane.add(showFiltersPanel, BorderLayout.SOUTH);
1422
1423     /*
1424      * update display for initial feature type selection
1425      */
1426     refreshFiltersDisplay();
1427   }
1428
1429   /**
1430    * Adds entries to the 'choose feature to filter' drop-down choice. Only
1431    * feature types which have known attributes (so can be filtered) are
1432    * included, so recall this method to update the list (check for newly added
1433    * attributes).
1434    */
1435   protected void populateFilterableFeatures()
1436   {
1437     /*
1438      * suppress action handler while updating the list
1439      */
1440     ItemListener listener = filteredFeatureChoice.getItemListeners()[0];
1441     filteredFeatureChoice.removeItemListener(listener);
1442
1443     filteredFeatureChoice.removeAllItems();
1444     ReverseListIterator<String> types = new ReverseListIterator<>(
1445             fr.getRenderOrder());
1446
1447     boolean found = false;
1448     while (types.hasNext())
1449     {
1450       String type = types.next();
1451       if (FeatureAttributes.getInstance().hasAttributes(type))
1452       {
1453         filteredFeatureChoice.addItem(type);
1454         found = true;
1455       }
1456     }
1457     if (!found)
1458     {
1459       filteredFeatureChoice // todo i18n
1460               .addItem("No filterable feature attributes known");
1461     }
1462
1463     filteredFeatureChoice.addItemListener(listener);
1464
1465   }
1466
1467   /**
1468    * Refreshes the display to show any filters currently configured for the
1469    * selected feature type (editable, with 'remove' option), plus one extra row
1470    * for adding a condition. This should be called on change of selected feature
1471    * type, or after a filter has been removed, added or amended.
1472    */
1473   protected void refreshFiltersDisplay()
1474   {
1475     /*
1476      * clear the panel and list of filter conditions
1477      */
1478     chooseFiltersPanel.removeAll();
1479     filters.clear();
1480
1481     /*
1482      * look up attributes known for feature type
1483      */
1484     String selectedType = (String) filteredFeatureChoice.getSelectedItem();
1485     List<String> attNames = FeatureAttributes.getInstance().getAttributes(
1486             selectedType);
1487
1488     /*
1489      * if this feature type has filters set, load them first
1490      */
1491     KeyedMatcherSetI featureFilters = fr.getFeatureFilter(selectedType);
1492     filtersAsText.setText("");
1493     if (featureFilters != null)
1494     {
1495       filtersAsText.setText(featureFilters.toString());
1496       if (!featureFilters.isAnded())
1497       {
1498         orFilters.setSelected(true);
1499       }
1500       featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
1501     }
1502
1503     /*
1504      * and an empty filter for the user to populate (add)
1505      */
1506     KeyedMatcherI noFilter = new KeyedMatcher("", Condition.values()[0], "");
1507     filters.add(noFilter);
1508
1509     /*
1510      * render the conditions in rows, each in its own JPanel
1511      */
1512     int filterIndex = 0;
1513     for (KeyedMatcherI filter : filters)
1514     {
1515       String key = filter.getKey();
1516       Condition condition = filter.getMatcher()
1517               .getCondition();
1518       String pattern = filter.getMatcher().getPattern();
1519       JPanel row = addFilter(key, attNames, condition, pattern, filterIndex);
1520       chooseFiltersPanel.add(row);
1521       filterIndex++;
1522     }
1523
1524     filtersPane.validate();
1525     filtersPane.repaint();
1526   }
1527
1528   /**
1529    * A helper method that constructs a panel with one filter condition:
1530    * <ul>
1531    * <li>a drop-down list of attribute names to choose from</li>
1532    * <li>a drop-down list of conditions to choose from</li>
1533    * <li>a text field for input of a match pattern</li>
1534    * <li>optionally, a 'remove' button</li>
1535    * </ul>
1536    * If attribute, condition or pattern are not null, they are set as defaults
1537    * for the input fields. The 'remove' button is added unless the pattern is
1538    * null or empty (incomplete filter condition).
1539    * 
1540    * @param attribute
1541    * @param attNames
1542    * @param cond
1543    * @param pattern
1544    * @param filterIndex
1545    * @return
1546    */
1547   protected JPanel addFilter(String attribute, List<String> attNames,
1548           Condition cond, String pattern, int filterIndex)
1549   {
1550     JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
1551     filterRow.setBackground(Color.white);
1552
1553     /*
1554      * inputs for attribute, condition, pattern
1555      */
1556     /*
1557      * drop-down choice of attribute, with description as a tooltip 
1558      * if we can obtain it
1559      */
1560     String featureType = (String) filteredFeatureChoice.getSelectedItem();
1561     final JComboBox<String> attCombo = populateAttributesDropdown(
1562             featureType, attNames);
1563     JComboBox<Condition> condCombo = new JComboBox<>();
1564     JTextField patternField = new JTextField(8);
1565
1566     /*
1567      * action handlers that validate and (if valid) apply changes
1568      */
1569     ActionListener actionListener = new ActionListener()
1570     {
1571       @Override
1572       public void actionPerformed(ActionEvent e)
1573       {
1574         if (attCombo.getSelectedItem() != null)
1575         {
1576           if (validateFilter(patternField, condCombo))
1577           {
1578             updateFilter(attCombo, condCombo, patternField, filterIndex);
1579             filtersChanged();
1580           }
1581         }
1582       }
1583     };
1584     ItemListener itemListener = new ItemListener()
1585     {
1586       @Override
1587       public void itemStateChanged(ItemEvent e)
1588       {
1589         actionListener.actionPerformed(null);
1590       }
1591     };
1592
1593     if ("".equals(attribute))
1594     {
1595       attCombo.setSelectedItem(null);
1596     }
1597     else
1598     {
1599       attCombo.setSelectedItem(attribute);
1600     }
1601     attCombo.addItemListener(itemListener);
1602
1603     filterRow.add(attCombo);
1604
1605     /*
1606      * drop-down choice of test condition
1607      */
1608     for (Condition c : Condition.values())
1609     {
1610       condCombo.addItem(c);
1611     }
1612     if (cond != null)
1613     {
1614       condCombo.setSelectedItem(cond);
1615     }
1616     condCombo.addItemListener(itemListener);
1617     filterRow.add(condCombo);
1618
1619     /*
1620      * pattern to match against
1621      */
1622     patternField.setText(pattern);
1623     patternField.addActionListener(actionListener);
1624     patternField.addFocusListener(new FocusAdapter()
1625     {
1626       @Override
1627       public void focusLost(FocusEvent e)
1628       {
1629         actionListener.actionPerformed(null);
1630       }
1631     });
1632     filterRow.add(patternField);
1633
1634     /*
1635      * add remove button if filter is populated (non-empty pattern)
1636      */
1637     if (pattern != null && pattern.trim().length() > 0)
1638     {
1639       // todo: gif for - button
1640       JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
1641       removeCondition.setToolTipText(MessageManager
1642               .getString("label.delete_row"));
1643       removeCondition.addActionListener(new ActionListener()
1644       {
1645         @Override
1646         public void actionPerformed(ActionEvent e)
1647         {
1648           filters.remove(filterIndex);
1649           filtersChanged();
1650         }
1651       });
1652       filterRow.add(removeCondition);
1653     }
1654
1655     return filterRow;
1656   }
1657
1658   /**
1659    * A helper method to build the drop-down choice of attributes for a feature.
1660    * Where metadata is available with a description for an attribute, that is
1661    * added as a tooltip.
1662    * 
1663    * @param featureType
1664    * @param attNames
1665    */
1666   protected JComboBox<String> populateAttributesDropdown(
1667           String featureType, List<String> attNames)
1668   {
1669     List<String> tooltips = new ArrayList<>();
1670     FeatureAttributes fa = FeatureAttributes.getInstance();
1671     for (String attName : attNames)
1672     {
1673       String desc = fa.getDescription(featureType, attName);
1674       if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
1675       {
1676         desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
1677       }
1678       tooltips.add(desc == null ? "" : desc);
1679     }
1680
1681     JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
1682             attNames, tooltips);
1683     if (attNames.isEmpty())
1684     {
1685       attCombo.setToolTipText(MessageManager
1686               .getString("label.no_attributes"));
1687     }
1688     return attCombo;
1689   }
1690
1691   /**
1692    * Action on any change to feature filtering, namely
1693    * <ul>
1694    * <li>change of selected attribute</li>
1695    * <li>change of selected condition</li>
1696    * <li>change of match pattern</li>
1697    * <li>removal of a condition</li>
1698    * </ul>
1699    * The action should be to
1700    * <ul>
1701    * <li>parse and validate the filters</li>
1702    * <li>if valid, update the filter text box</li>
1703    * <li>and apply the filters to the viewport</li>
1704    * </ul>
1705    */
1706   protected void filtersChanged()
1707   {
1708     /*
1709      * update the filter conditions for the feature type
1710      */
1711     String featureType = (String) filteredFeatureChoice.getSelectedItem();
1712     boolean anded = andFilters.isSelected();
1713     KeyedMatcherSetI combined = new KeyedMatcherSet();
1714
1715     for (KeyedMatcherI filter : filters)
1716     {
1717       String pattern = filter.getMatcher().getPattern();
1718       if (pattern.trim().length() > 0)
1719       {
1720         if (anded)
1721         {
1722           combined.and(filter);
1723         }
1724         else
1725         {
1726           combined.or(filter);
1727         }
1728       }
1729     }
1730
1731     /*
1732      * save the filter conditions in the FeatureRenderer
1733      * (note this might now be an empty filter with no conditions)
1734      */
1735     fr.setFeatureFilter(featureType, combined);
1736
1737     filtersAsText.setText(combined.toString());
1738
1739     refreshFiltersDisplay();
1740
1741     af.alignPanel.paintAlignment(true, true);
1742   }
1743
1744   /**
1745    * Constructs a filter condition from the given input fields, and replaces the
1746    * condition at filterIndex with the new one
1747    * 
1748    * @param attCombo
1749    * @param condCombo
1750    * @param valueField
1751    * @param filterIndex
1752    */
1753   protected void updateFilter(JComboBox<String> attCombo,
1754           JComboBox<Condition> condCombo, JTextField valueField,
1755           int filterIndex)
1756   {
1757     String attName = (String) attCombo.getSelectedItem();
1758     Condition cond = (Condition) condCombo.getSelectedItem();
1759     String pattern = valueField.getText();
1760     KeyedMatcherI km = new KeyedMatcher(attName, cond, pattern);
1761
1762     filters.set(filterIndex, km);
1763   }
1764
1765   public void fetchDAS_actionPerformed(ActionEvent e)
1766   {
1767     fetchDAS.setEnabled(false);
1768     cancelDAS.setEnabled(true);
1769     dassourceBrowser.setGuiEnabled(false);
1770     Vector<jalviewSourceI> selectedSources = dassourceBrowser
1771             .getSelectedSources();
1772     doDasFeatureFetch(selectedSources, true, true);
1773   }
1774
1775   /**
1776    * get the features from selectedSources for all or the current selection
1777    * 
1778    * @param selectedSources
1779    * @param checkDbRefs
1780    * @param promptFetchDbRefs
1781    */
1782   private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1783           boolean checkDbRefs, boolean promptFetchDbRefs)
1784   {
1785     SequenceI[] dataset, seqs;
1786     int iSize;
1787     AlignmentViewport vp = af.getViewport();
1788     if (vp.getSelectionGroup() != null
1789             && vp.getSelectionGroup().getSize() > 0)
1790     {
1791       iSize = vp.getSelectionGroup().getSize();
1792       dataset = new SequenceI[iSize];
1793       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1794     }
1795     else
1796     {
1797       iSize = vp.getAlignment().getHeight();
1798       seqs = vp.getAlignment().getSequencesArray();
1799     }
1800
1801     dataset = new SequenceI[iSize];
1802     for (int i = 0; i < iSize; i++)
1803     {
1804       dataset[i] = seqs[i].getDatasetSequence();
1805     }
1806
1807     cancelDAS.setEnabled(true);
1808     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1809             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1810     af.getViewport().setShowSequenceFeatures(true);
1811     af.showSeqFeatures.setSelected(true);
1812   }
1813
1814   /**
1815    * blocking call to initialise the das source browser
1816    */
1817   public void initDasSources()
1818   {
1819     dassourceBrowser.initDasSources();
1820   }
1821
1822   /**
1823    * examine the current list of das sources and return any matching the given
1824    * nicknames in sources
1825    * 
1826    * @param sources
1827    *          Vector of Strings to resolve to DAS source nicknames.
1828    * @return sources that are present in source list.
1829    */
1830   public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1831   {
1832     return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1833   }
1834
1835   /**
1836    * get currently selected das sources. ensure you have called initDasSources
1837    * before calling this.
1838    * 
1839    * @return vector of selected das source nicknames
1840    */
1841   public Vector<jalviewSourceI> getSelectedSources()
1842   {
1843     return dassourceBrowser.getSelectedSources();
1844   }
1845
1846   /**
1847    * properly initialise DAS fetcher and then initiate a new thread to fetch
1848    * features from the named sources (rather than any turned on by default)
1849    * 
1850    * @param sources
1851    * @param block
1852    *          if true then runs in same thread, otherwise passes to the Swing
1853    *          executor
1854    */
1855   public void fetchDasFeatures(Vector<String> sources, boolean block)
1856   {
1857     initDasSources();
1858     List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1859             .resolveSourceNicknames(sources);
1860     if (resolved.size() == 0)
1861     {
1862       resolved = dassourceBrowser.getSelectedSources();
1863     }
1864     if (resolved.size() > 0)
1865     {
1866       final List<jalviewSourceI> dassources = resolved;
1867       fetchDAS.setEnabled(false);
1868       // cancelDAS.setEnabled(true); doDasFetch does this.
1869       Runnable fetcher = new Runnable()
1870       {
1871
1872         @Override
1873         public void run()
1874         {
1875           doDasFeatureFetch(dassources, true, false);
1876
1877         }
1878       };
1879       if (block)
1880       {
1881         fetcher.run();
1882       }
1883       else
1884       {
1885         SwingUtilities.invokeLater(fetcher);
1886       }
1887     }
1888   }
1889
1890   public void saveDAS_actionPerformed(ActionEvent e)
1891   {
1892     dassourceBrowser
1893             .saveProperties(jalview.bin.Cache.applicationProperties);
1894   }
1895
1896   public void complete()
1897   {
1898     fetchDAS.setEnabled(true);
1899     cancelDAS.setEnabled(false);
1900     dassourceBrowser.setGuiEnabled(true);
1901
1902   }
1903
1904   public void cancelDAS_actionPerformed(ActionEvent e)
1905   {
1906     if (dasFeatureFetcher != null)
1907     {
1908       dasFeatureFetcher.cancel();
1909     }
1910     complete();
1911   }
1912
1913   public void noDasSourceActive()
1914   {
1915     complete();
1916     JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1917             MessageManager.getString("label.no_das_sources_selected_warn"),
1918             MessageManager.getString("label.no_das_sources_selected_title"),
1919             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1920   }
1921
1922   /**
1923    * Answers true unless a numeric condition has been selected with a
1924    * non-numeric value. Sets the value field to RED with a tooltip if in error.
1925    * <p>
1926    * If the pattern entered is empty, this method returns false, but does not
1927    * mark the field as invalid. This supports selecting an attribute for a new
1928    * condition before a match pattern has been entered.
1929    * 
1930    * @param value
1931    * @param condCombo
1932    */
1933   protected boolean validateFilter(JTextField value,
1934           JComboBox<Condition> condCombo)
1935   {
1936     if (value == null || condCombo == null)
1937     {
1938       return true; // fields not populated
1939     }
1940   
1941     Condition cond = (Condition) condCombo.getSelectedItem();
1942     value.setBackground(Color.white);
1943     value.setToolTipText("");
1944     String v1 = value.getText().trim();
1945     if (v1.length() == 0)
1946     {
1947       return false;
1948     }
1949
1950     if (cond.isNumeric())
1951     {
1952       try
1953       {
1954         Float.valueOf(v1);
1955       } catch (NumberFormatException e)
1956       {
1957         value.setBackground(Color.red);
1958         value.setToolTipText(MessageManager
1959                 .getString("label.numeric_required"));
1960         return false;
1961       }
1962     }
1963   
1964     return true;
1965   }
1966
1967   // ///////////////////////////////////////////////////////////////////////
1968   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1969   // ///////////////////////////////////////////////////////////////////////
1970   class FeatureTableModel extends AbstractTableModel
1971   {
1972     FeatureTableModel(Object[][] data)
1973     {
1974       this.data = data;
1975     }
1976
1977     private String[] columnNames = {
1978         MessageManager.getString("label.feature_type"),
1979         MessageManager.getString("action.colour"),
1980         MessageManager.getString("label.display") };
1981
1982     private Object[][] data;
1983
1984     public Object[][] getData()
1985     {
1986       return data;
1987     }
1988
1989     public void setData(Object[][] data)
1990     {
1991       this.data = data;
1992     }
1993
1994     @Override
1995     public int getColumnCount()
1996     {
1997       return columnNames.length;
1998     }
1999
2000     public Object[] getRow(int row)
2001     {
2002       return data[row];
2003     }
2004
2005     @Override
2006     public int getRowCount()
2007     {
2008       return data.length;
2009     }
2010
2011     @Override
2012     public String getColumnName(int col)
2013     {
2014       return columnNames[col];
2015     }
2016
2017     @Override
2018     public Object getValueAt(int row, int col)
2019     {
2020       return data[row][col];
2021     }
2022
2023     @Override
2024     public Class getColumnClass(int c)
2025     {
2026       return getValueAt(0, c).getClass();
2027     }
2028
2029     @Override
2030     public boolean isCellEditable(int row, int col)
2031     {
2032       return col == 0 ? false : true;
2033     }
2034
2035     @Override
2036     public void setValueAt(Object value, int row, int col)
2037     {
2038       data[row][col] = value;
2039       fireTableCellUpdated(row, col);
2040       updateFeatureRenderer(data);
2041     }
2042
2043   }
2044
2045   class ColorRenderer extends JLabel implements TableCellRenderer
2046   {
2047     javax.swing.border.Border unselectedBorder = null;
2048
2049     javax.swing.border.Border selectedBorder = null;
2050
2051     final String baseTT = "Click to edit, right/apple click for menu.";
2052
2053     public ColorRenderer()
2054     {
2055       setOpaque(true); // MUST do this for background to show up.
2056       setHorizontalTextPosition(SwingConstants.CENTER);
2057       setVerticalTextPosition(SwingConstants.CENTER);
2058     }
2059
2060     @Override
2061     public Component getTableCellRendererComponent(JTable tbl, Object color,
2062             boolean isSelected, boolean hasFocus, int row, int column)
2063     {
2064       FeatureColourI cellColour = (FeatureColourI) color;
2065       setOpaque(true);
2066       setToolTipText(baseTT);
2067       setBackground(tbl.getBackground());
2068       if (!cellColour.isSimpleColour())
2069       {
2070         Rectangle cr = tbl.getCellRect(row, column, false);
2071         FeatureSettings.renderGraduatedColor(this, cellColour,
2072                 (int) cr.getWidth(), (int) cr.getHeight());
2073       }
2074       else
2075       {
2076         this.setText("");
2077         this.setIcon(null);
2078         setBackground(cellColour.getColour());
2079       }
2080       if (isSelected)
2081       {
2082         if (selectedBorder == null)
2083         {
2084           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2085                   tbl.getSelectionBackground());
2086         }
2087         setBorder(selectedBorder);
2088       }
2089       else
2090       {
2091         if (unselectedBorder == null)
2092         {
2093           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
2094                   tbl.getBackground());
2095         }
2096         setBorder(unselectedBorder);
2097       }
2098
2099       return this;
2100     }
2101   }
2102
2103   /**
2104    * update comp using rendering settings from gcol
2105    * 
2106    * @param comp
2107    * @param gcol
2108    */
2109   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
2110   {
2111     int w = comp.getWidth(), h = comp.getHeight();
2112     if (w < 20)
2113     {
2114       w = (int) comp.getPreferredSize().getWidth();
2115       h = (int) comp.getPreferredSize().getHeight();
2116       if (w < 20)
2117       {
2118         w = 80;
2119         h = 12;
2120       }
2121     }
2122     renderGraduatedColor(comp, gcol, w, h);
2123   }
2124
2125   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
2126           int w, int h)
2127   {
2128     boolean thr = false;
2129     StringBuilder tt = new StringBuilder();
2130     StringBuilder tx = new StringBuilder();
2131
2132     if (gcol.isColourByAttribute())
2133     {
2134       tx.append(gcol.getAttributeName());
2135     }
2136     else if (!gcol.isColourByLabel())
2137     {
2138       tx.append(MessageManager.getString("label.score"));
2139     }
2140     tx.append(" ");
2141     if (gcol.isAboveThreshold())
2142     {
2143       thr = true;
2144       tx.append(">");
2145       tt.append("Thresholded (Above ").append(gcol.getThreshold())
2146               .append(") ");
2147     }
2148     if (gcol.isBelowThreshold())
2149     {
2150       thr = true;
2151       tx.append("<");
2152       tt.append("Thresholded (Below ").append(gcol.getThreshold())
2153               .append(") ");
2154     }
2155     if (gcol.isColourByLabel())
2156     {
2157       tt.append("Coloured by label text. ").append(tt);
2158       if (thr)
2159       {
2160         tx.append(" ");
2161       }
2162       if (!gcol.isColourByAttribute())
2163       {
2164         tx.append("Label");
2165       }
2166       comp.setIcon(null);
2167     }
2168     else
2169     {
2170       Color newColor = gcol.getMaxColour();
2171       comp.setBackground(newColor);
2172       // System.err.println("Width is " + w / 2);
2173       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
2174       comp.setIcon(ficon);
2175       // tt+="RGB value: Max (" + newColor.getRed() + ", "
2176       // + newColor.getGreen() + ", " + newColor.getBlue()
2177       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
2178       // + ", " + minCol.getBlue() + ")");
2179     }
2180     comp.setHorizontalAlignment(SwingConstants.CENTER);
2181     comp.setText(tx.toString());
2182     if (tt.length() > 0)
2183     {
2184       if (comp.getToolTipText() == null)
2185       {
2186         comp.setToolTipText(tt.toString());
2187       }
2188       else
2189       {
2190         comp.setToolTipText(tt.append(" ").append(comp.getToolTipText())
2191                 .toString());
2192       }
2193     }
2194   }
2195 }
2196
2197 class FeatureIcon implements Icon
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(new Font("Verdana", Font.PLAIN, 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 }
2278
2279 class ColorEditor extends AbstractCellEditor
2280         implements TableCellEditor, ActionListener
2281 {
2282   FeatureSettings me;
2283
2284   FeatureColourI currentColor;
2285
2286   FeatureColourChooser chooser;
2287
2288   String type;
2289
2290   JButton button;
2291
2292   JColorChooser colorChooser;
2293
2294   JDialog dialog;
2295
2296   protected static final String EDIT = "edit";
2297
2298   int selectedRow = 0;
2299
2300   public ColorEditor(FeatureSettings me)
2301   {
2302     this.me = me;
2303     // Set up the editor (from the table's point of view),
2304     // which is a button.
2305     // This button brings up the color chooser dialog,
2306     // which is the editor from the user's point of view.
2307     button = new JButton();
2308     button.setActionCommand(EDIT);
2309     button.addActionListener(this);
2310     button.setBorderPainted(false);
2311     // Set up the dialog that the button brings up.
2312     colorChooser = new JColorChooser();
2313     dialog = JColorChooser.createDialog(button,
2314             MessageManager.getString("label.select_new_colour"), true, // modal
2315             colorChooser, this, // OK button handler
2316             null); // no CANCEL button handler
2317   }
2318
2319   /**
2320    * Handles events from the editor button and from the dialog's OK button.
2321    */
2322   @Override
2323   public void actionPerformed(ActionEvent e)
2324   {
2325
2326     if (EDIT.equals(e.getActionCommand()))
2327     {
2328       // The user has clicked the cell, so
2329       // bring up the dialog.
2330       if (currentColor.isSimpleColour())
2331       {
2332         // bring up simple color chooser
2333         button.setBackground(currentColor.getColour());
2334         colorChooser.setColor(currentColor.getColour());
2335         dialog.setVisible(true);
2336       }
2337       else
2338       {
2339         // bring up graduated chooser.
2340         chooser = new FeatureColourChooser(me.fr, type);
2341         chooser.setRequestFocusEnabled(true);
2342         chooser.requestFocus();
2343         chooser.addActionListener(this);
2344       }
2345       // Make the renderer reappear.
2346       fireEditingStopped();
2347
2348     }
2349     else
2350     { // User pressed dialog's "OK" button.
2351       if (currentColor.isSimpleColour())
2352       {
2353         currentColor = new FeatureColour(colorChooser.getColor());
2354       }
2355       else
2356       {
2357         currentColor = chooser.getLastColour();
2358       }
2359       me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
2360       fireEditingStopped();
2361       me.table.validate();
2362     }
2363   }
2364
2365   // Implement the one CellEditor method that AbstractCellEditor doesn't.
2366   @Override
2367   public Object getCellEditorValue()
2368   {
2369     return currentColor;
2370   }
2371
2372   // Implement the one method defined by TableCellEditor.
2373   @Override
2374   public Component getTableCellEditorComponent(JTable table, Object value,
2375           boolean isSelected, int row, int column)
2376   {
2377     currentColor = (FeatureColourI) value;
2378     this.selectedRow = row;
2379     type = me.table.getValueAt(row, 0).toString();
2380     button.setOpaque(true);
2381     button.setBackground(me.getBackground());
2382     if (!currentColor.isSimpleColour())
2383     {
2384       JLabel btn = new JLabel();
2385       btn.setSize(button.getSize());
2386       FeatureSettings.renderGraduatedColor(btn, currentColor);
2387       button.setBackground(btn.getBackground());
2388       button.setIcon(btn.getIcon());
2389       button.setText(btn.getText());
2390     }
2391     else
2392     {
2393       button.setText("");
2394       button.setIcon(null);
2395       button.setBackground(currentColor.getColour());
2396     }
2397     return button;
2398   }
2399 }