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