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