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