JAL-2092 hide columns with/without feature
[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.FeatureSettingsControllerI;
24 import jalview.bin.Cache;
25 import jalview.datamodel.SequenceFeature;
26 import jalview.datamodel.SequenceI;
27 import jalview.gui.Help.HelpId;
28 import jalview.io.JalviewFileChooser;
29 import jalview.schemes.AnnotationColourGradient;
30 import jalview.schemes.GraduatedColor;
31 import jalview.util.MessageManager;
32 import jalview.viewmodel.AlignmentViewport;
33 import jalview.ws.dbsources.das.api.jalviewSourceI;
34
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.Component;
38 import java.awt.Font;
39 import java.awt.Graphics;
40 import java.awt.GridLayout;
41 import java.awt.Rectangle;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.ActionListener;
44 import java.awt.event.ItemEvent;
45 import java.awt.event.ItemListener;
46 import java.awt.event.MouseAdapter;
47 import java.awt.event.MouseEvent;
48 import java.awt.event.MouseMotionAdapter;
49 import java.beans.PropertyChangeEvent;
50 import java.beans.PropertyChangeListener;
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.FileOutputStream;
54 import java.io.InputStreamReader;
55 import java.io.OutputStreamWriter;
56 import java.io.PrintWriter;
57 import java.util.Arrays;
58 import java.util.Hashtable;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63 import java.util.Vector;
64
65 import javax.help.HelpSetException;
66 import javax.swing.AbstractCellEditor;
67 import javax.swing.BorderFactory;
68 import javax.swing.Icon;
69 import javax.swing.JButton;
70 import javax.swing.JCheckBox;
71 import javax.swing.JCheckBoxMenuItem;
72 import javax.swing.JColorChooser;
73 import javax.swing.JDialog;
74 import javax.swing.JInternalFrame;
75 import javax.swing.JLabel;
76 import javax.swing.JLayeredPane;
77 import javax.swing.JMenuItem;
78 import javax.swing.JOptionPane;
79 import javax.swing.JPanel;
80 import javax.swing.JPopupMenu;
81 import javax.swing.JScrollPane;
82 import javax.swing.JSlider;
83 import javax.swing.JTabbedPane;
84 import javax.swing.JTable;
85 import javax.swing.ListSelectionModel;
86 import javax.swing.SwingConstants;
87 import javax.swing.SwingUtilities;
88 import javax.swing.event.ChangeEvent;
89 import javax.swing.event.ChangeListener;
90 import javax.swing.table.AbstractTableModel;
91 import javax.swing.table.TableCellEditor;
92 import javax.swing.table.TableCellRenderer;
93
94 public class FeatureSettings extends JPanel implements
95         FeatureSettingsControllerI
96 {
97   DasSourceBrowser dassourceBrowser;
98
99   jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
100
101   JPanel settingsPane = new JPanel();
102
103   JPanel dasSettingsPane = new JPanel();
104
105   final FeatureRenderer fr;
106
107   public final AlignFrame af;
108
109   Object[][] originalData;
110
111   private float originalTransparency;
112
113   final JInternalFrame frame;
114
115   JScrollPane scrollPane = new JScrollPane();
116
117   JTable table;
118
119   JPanel groupPanel;
120
121   JSlider transparency = new JSlider();
122
123   JPanel transPanel = new JPanel(new GridLayout(1, 2));
124
125   public FeatureSettings(AlignFrame af)
126   {
127     this.af = af;
128     fr = af.getFeatureRenderer();
129     // allow transparency to be recovered
130     transparency.setMaximum(100 - (int) ((originalTransparency = fr
131             .getTransparency()) * 100));
132
133     try
134     {
135       jbInit();
136     } catch (Exception ex)
137     {
138       ex.printStackTrace();
139     }
140
141     table = new JTable()
142     {
143       @Override
144       public String getToolTipText(MouseEvent e)
145       {
146         if (table.columnAtPoint(e.getPoint()) == 0)
147         {
148           /*
149            * Tooltip for feature name only
150            */
151           return JvSwingUtils.wrapTooltip(true, MessageManager
152                   .getString("label.feature_settings_click_drag"));
153         }
154         return null;
155       }
156     };
157     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
158     table.setFont(new Font("Verdana", Font.PLAIN, 12));
159     table.setDefaultRenderer(Color.class, new ColorRenderer());
160
161     table.setDefaultEditor(Color.class, new ColorEditor(this));
162
163     table.setDefaultEditor(GraduatedColor.class, new ColorEditor(this));
164     table.setDefaultRenderer(GraduatedColor.class, new ColorRenderer());
165     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
166
167     table.addMouseListener(new MouseAdapter()
168     {
169       @Override
170       public void mousePressed(MouseEvent evt)
171       {
172         selectedRow = table.rowAtPoint(evt.getPoint());
173         if (evt.isPopupTrigger())
174         {
175           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
176                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
177                   evt.getX(), evt.getY());
178         }
179         else if (evt.getClickCount() == 2)
180         {
181           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
182                   evt.isAltDown(), evt.isShiftDown() || evt.isMetaDown(),
183                   evt.isMetaDown(),
184                   (String) table.getValueAt(selectedRow, 0));
185         }
186       }
187
188       // isPopupTrigger fires on mouseReleased on Mac
189       @Override
190       public void mouseReleased(MouseEvent evt)
191       {
192         selectedRow = table.rowAtPoint(evt.getPoint());
193         if (evt.isPopupTrigger())
194         {
195           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
196                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
197                   evt.getX(), evt.getY());
198         }
199       }
200     });
201
202     table.addMouseMotionListener(new MouseMotionAdapter()
203     {
204       @Override
205       public void mouseDragged(MouseEvent evt)
206       {
207         int newRow = table.rowAtPoint(evt.getPoint());
208         if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
209         {
210           Object[][] data = ((FeatureTableModel) table.getModel())
211                   .getData();
212           Object[] temp = data[selectedRow];
213           data[selectedRow] = data[newRow];
214           data[newRow] = temp;
215           updateFeatureRenderer(data);
216           table.repaint();
217           selectedRow = newRow;
218         }
219       }
220     });
221     // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
222     // MessageManager.getString("label.feature_settings_click_drag")));
223     scrollPane.setViewportView(table);
224
225     dassourceBrowser = new DasSourceBrowser(this);
226     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
227
228     if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
229     {
230       fr.findAllFeatures(true); // display everything!
231     }
232
233     discoverAllFeatureData();
234     final PropertyChangeListener change;
235     final FeatureSettings fs = this;
236     fr.addPropertyChangeListener(change = new PropertyChangeListener()
237     {
238       @Override
239       public void propertyChange(PropertyChangeEvent evt)
240       {
241         if (!fs.resettingTable && !fs.handlingUpdate)
242         {
243           fs.handlingUpdate = true;
244           fs.resetTable(null); // new groups may be added with new seuqence
245           // feature types only
246           fs.handlingUpdate = false;
247         }
248       }
249
250     });
251
252     frame = new JInternalFrame();
253     frame.setContentPane(this);
254     if (new jalview.util.Platform().isAMac())
255     {
256       Desktop.addInternalFrame(frame,
257               MessageManager.getString("label.sequence_feature_settings"),
258               475, 480);
259     }
260     else
261     {
262       Desktop.addInternalFrame(frame,
263               MessageManager.getString("label.sequence_feature_settings"),
264               400, 450);
265     }
266
267     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
268     {
269       @Override
270       public void internalFrameClosed(
271               javax.swing.event.InternalFrameEvent evt)
272       {
273         fr.removePropertyChangeListener(change);
274         dassourceBrowser.fs = null;
275       };
276     });
277     frame.setLayer(JLayeredPane.PALETTE_LAYER);
278   }
279
280   protected void popupSort(final int selectedRow, final String type,
281           final Object typeCol, final Map<String, float[][]> minmax, int x,
282           int y)
283   {
284     JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
285             "label.settings_for_param", new String[] { type }));
286     JMenuItem scr = new JMenuItem(
287             MessageManager.getString("label.sort_by_score"));
288     men.add(scr);
289     final FeatureSettings me = this;
290     scr.addActionListener(new ActionListener()
291     {
292
293       @Override
294       public void actionPerformed(ActionEvent e)
295       {
296         me.af.avc.sortAlignmentByFeatureScore(Arrays
297                 .asList(new String[] { type }));
298       }
299
300     });
301     JMenuItem dens = new JMenuItem(
302             MessageManager.getString("label.sort_by_density"));
303     dens.addActionListener(new ActionListener()
304     {
305
306       @Override
307       public void actionPerformed(ActionEvent e)
308       {
309         me.af.avc.sortAlignmentByFeatureDensity(Arrays
310                 .asList(new String[] { type }));
311       }
312
313     });
314     men.add(dens);
315     if (minmax != null)
316     {
317       final float[][] typeMinMax = minmax.get(type);
318       /*
319        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
320        * this is broken at the moment and isn't that useful anyway!
321        * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
322        * ActionListener() {
323        * 
324        * public void actionPerformed(ActionEvent e) {
325        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
326        * null); } else { minmax.put(type, typeMinMax); } }
327        * 
328        * });
329        * 
330        * men.add(chb);
331        */
332       if (typeMinMax != null && typeMinMax[0] != null)
333       {
334         // if (table.getValueAt(row, column));
335         // graduated colourschemes for those where minmax exists for the
336         // positional features
337         final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
338                 "Graduated Colour");
339         mxcol.setSelected(!(typeCol instanceof Color));
340         men.add(mxcol);
341         mxcol.addActionListener(new ActionListener()
342         {
343           JColorChooser colorChooser;
344
345           @Override
346           public void actionPerformed(ActionEvent e)
347           {
348             if (e.getSource() == mxcol)
349             {
350               if (typeCol instanceof Color)
351               {
352                 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
353                         type);
354                 fc.addActionListener(this);
355               }
356               else
357               {
358                 // bring up simple color chooser
359                 colorChooser = new JColorChooser();
360                 JDialog dialog = JColorChooser.createDialog(me,
361                         "Select new Colour", true, // modal
362                         colorChooser, this, // OK button handler
363                         null); // no CANCEL button handler
364                 colorChooser.setColor(((GraduatedColor) typeCol)
365                         .getMaxColor());
366                 dialog.setVisible(true);
367               }
368             }
369             else
370             {
371               if (e.getSource() instanceof FeatureColourChooser)
372               {
373                 FeatureColourChooser fc = (FeatureColourChooser) e
374                         .getSource();
375                 table.setValueAt(fc.getLastColour(), selectedRow, 1);
376                 table.validate();
377               }
378               else
379               {
380                 // probably the color chooser!
381                 table.setValueAt(colorChooser.getColor(), selectedRow, 1);
382                 table.validate();
383                 me.updateFeatureRenderer(
384                         ((FeatureTableModel) table.getModel()).getData(),
385                         false);
386               }
387             }
388           }
389
390         });
391       }
392     }
393     JMenuItem selCols = new JMenuItem(
394             MessageManager.getString("label.select_columns_containing"));
395     selCols.addActionListener(new ActionListener()
396     {
397       @Override
398       public void actionPerformed(ActionEvent arg0)
399       {
400         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
401                 false, type);
402       }
403     });
404     JMenuItem clearCols = new JMenuItem(
405             MessageManager.getString("label.select_columns_not_containing"));
406     clearCols.addActionListener(new ActionListener()
407     {
408       @Override
409       public void actionPerformed(ActionEvent arg0)
410       {
411         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
412                 false, type);
413       }
414     });
415     JMenuItem hideCols = new JMenuItem(
416             MessageManager.getString("label.hide_columns_containing"));
417     hideCols.addActionListener(new ActionListener()
418     {
419       @Override
420       public void actionPerformed(ActionEvent arg0)
421       {
422         fr.ap.alignFrame.hideFeatureColumns(type, true);
423       }
424     });
425     JMenuItem hideOtherCols = new JMenuItem(
426             MessageManager.getString("label.hide_columns_not_containing"));
427     hideOtherCols.addActionListener(new ActionListener()
428     {
429       @Override
430       public void actionPerformed(ActionEvent arg0)
431       {
432         fr.ap.alignFrame.hideFeatureColumns(type, false);
433       }
434     });
435     men.add(selCols);
436     men.add(clearCols);
437     men.add(hideCols);
438     men.add(hideOtherCols);
439     men.show(table, x, y);
440   }
441
442   /**
443    * true when Feature Settings are updating from feature renderer
444    */
445   private boolean handlingUpdate = false;
446
447   /**
448    * contains a float[3] for each feature type string. created by setTableData
449    */
450   Hashtable typeWidth = null;
451
452   @Override
453   synchronized public void discoverAllFeatureData()
454   {
455     Vector allFeatures = new Vector();
456     Vector allGroups = new Vector();
457     SequenceFeature[] tmpfeatures;
458     String group;
459     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
460     {
461       tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
462               .getSequenceFeatures();
463       if (tmpfeatures == null)
464       {
465         continue;
466       }
467
468       int index = 0;
469       while (index < tmpfeatures.length)
470       {
471         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
472         {
473           index++;
474           continue;
475         }
476
477         if (tmpfeatures[index].getFeatureGroup() != null)
478         {
479           group = tmpfeatures[index].featureGroup;
480           if (!allGroups.contains(group))
481           {
482             allGroups.addElement(group);
483             checkGroupState(group);
484           }
485         }
486
487         if (!allFeatures.contains(tmpfeatures[index].getType()))
488         {
489           allFeatures.addElement(tmpfeatures[index].getType());
490         }
491         index++;
492       }
493     }
494
495     resetTable(null);
496
497     validate();
498   }
499
500   /**
501    * Synchronise gui group list and check visibility of group
502    * 
503    * @param group
504    * @return true if group is visible
505    */
506   private boolean checkGroupState(String group)
507   {
508     boolean visible = fr.checkGroupVisibility(group, true);
509
510     if (groupPanel == null)
511     {
512       groupPanel = new JPanel();
513     }
514
515     boolean alreadyAdded = false;
516     for (int g = 0; g < groupPanel.getComponentCount(); g++)
517     {
518       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
519       {
520         alreadyAdded = true;
521         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
522         break;
523       }
524     }
525
526     if (alreadyAdded)
527     {
528
529       return visible;
530     }
531     final String grp = group;
532     final JCheckBox check = new JCheckBox(group, visible);
533     check.setFont(new Font("Serif", Font.BOLD, 12));
534     check.addItemListener(new ItemListener()
535     {
536       @Override
537       public void itemStateChanged(ItemEvent evt)
538       {
539         fr.setGroupVisibility(check.getText(), check.isSelected());
540         af.alignPanel.getSeqPanel().seqCanvas.repaint();
541         if (af.alignPanel.overviewPanel != null)
542         {
543           af.alignPanel.overviewPanel.updateOverviewImage();
544         }
545
546         resetTable(new String[] { grp });
547       }
548     });
549     groupPanel.add(check);
550     return visible;
551   }
552
553   boolean resettingTable = false;
554
555   synchronized void resetTable(String[] groupChanged)
556   {
557     if (resettingTable == true)
558     {
559       return;
560     }
561     resettingTable = true;
562     typeWidth = new Hashtable();
563     // TODO: change avWidth calculation to 'per-sequence' average and use long
564     // rather than float
565     float[] avWidth = null;
566     SequenceFeature[] tmpfeatures;
567     String group = null, type;
568     Vector visibleChecks = new Vector();
569
570     // Find out which features should be visible depending on which groups
571     // are selected / deselected
572     // and recompute average width ordering
573     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
574     {
575
576       tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
577               .getSequenceFeatures();
578       if (tmpfeatures == null)
579       {
580         continue;
581       }
582
583       int index = 0;
584       while (index < tmpfeatures.length)
585       {
586         group = tmpfeatures[index].featureGroup;
587
588         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
589         {
590           index++;
591           continue;
592         }
593
594         if (group == null || checkGroupState(group))
595         {
596           type = tmpfeatures[index].getType();
597           if (!visibleChecks.contains(type))
598           {
599             visibleChecks.addElement(type);
600           }
601         }
602         if (!typeWidth.containsKey(tmpfeatures[index].getType()))
603         {
604           typeWidth.put(tmpfeatures[index].getType(),
605                   avWidth = new float[3]);
606         }
607         else
608         {
609           avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
610         }
611         avWidth[0]++;
612         if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
613         {
614           avWidth[1] += 1 + tmpfeatures[index].getBegin()
615                   - tmpfeatures[index].getEnd();
616         }
617         else
618         {
619           avWidth[1] += 1 + tmpfeatures[index].getEnd()
620                   - tmpfeatures[index].getBegin();
621         }
622         index++;
623       }
624     }
625
626     int fSize = visibleChecks.size();
627     Object[][] data = new Object[fSize][3];
628     int dataIndex = 0;
629
630     if (fr.hasRenderOrder())
631     {
632       if (!handlingUpdate)
633       {
634         fr.findAllFeatures(groupChanged != null); // prod to update
635         // colourschemes. but don't
636         // affect display
637         // First add the checks in the previous render order,
638         // in case the window has been closed and reopened
639       }
640       List<String> frl = fr.getRenderOrder();
641       for (int ro = frl.size() - 1; ro > -1; ro--)
642       {
643         type = frl.get(ro);
644
645         if (!visibleChecks.contains(type))
646         {
647           continue;
648         }
649
650         data[dataIndex][0] = type;
651         data[dataIndex][1] = fr.getFeatureStyle(type);
652         data[dataIndex][2] = new Boolean(af.getViewport()
653                 .getFeaturesDisplayed().isVisible(type));
654         dataIndex++;
655         visibleChecks.removeElement(type);
656       }
657     }
658
659     fSize = visibleChecks.size();
660     for (int i = 0; i < fSize; i++)
661     {
662       // These must be extra features belonging to the group
663       // which was just selected
664       type = visibleChecks.elementAt(i).toString();
665       data[dataIndex][0] = type;
666
667       data[dataIndex][1] = fr.getFeatureStyle(type);
668       if (data[dataIndex][1] == null)
669       {
670         // "Colour has been updated in another view!!"
671         fr.clearRenderOrder();
672         return;
673       }
674
675       data[dataIndex][2] = new Boolean(true);
676       dataIndex++;
677     }
678
679     if (originalData == null)
680     {
681       originalData = new Object[data.length][3];
682       for (int i = 0; i < data.length; i++)
683       {
684         System.arraycopy(data[i], 0, originalData[i], 0, 3);
685       }
686     }
687
688     table.setModel(new FeatureTableModel(data));
689     table.getColumnModel().getColumn(0).setPreferredWidth(200);
690
691     if (groupPanel != null)
692     {
693       groupPanel.setLayout(new GridLayout(
694               fr.getFeatureGroupsSize() / 4 + 1, 4));
695
696       groupPanel.validate();
697       bigPanel.add(groupPanel, BorderLayout.NORTH);
698     }
699
700     updateFeatureRenderer(data, groupChanged != null);
701     resettingTable = false;
702   }
703
704   /**
705    * reorder data based on the featureRenderers global priority list.
706    * 
707    * @param data
708    */
709   private void ensureOrder(Object[][] data)
710   {
711     boolean sort = false;
712     float[] order = new float[data.length];
713     for (int i = 0; i < order.length; i++)
714     {
715       order[i] = fr.getOrder(data[i][0].toString());
716       if (order[i] < 0)
717       {
718         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
719       }
720       if (i > 1)
721       {
722         sort = sort || order[i - 1] > order[i];
723       }
724     }
725     if (sort)
726     {
727       jalview.util.QuickSort.sort(order, data);
728     }
729   }
730
731   void load()
732   {
733     JalviewFileChooser chooser = new JalviewFileChooser(
734             jalview.bin.Cache.getProperty("LAST_DIRECTORY"),
735             new String[] { "fc" },
736             new String[] { "Sequence Feature Colours" },
737             "Sequence Feature Colours");
738     chooser.setFileView(new jalview.io.JalviewFileView());
739     chooser.setDialogTitle(MessageManager
740             .getString("label.load_feature_colours"));
741     chooser.setToolTipText(MessageManager.getString("action.load"));
742
743     int value = chooser.showOpenDialog(this);
744
745     if (value == JalviewFileChooser.APPROVE_OPTION)
746     {
747       File file = chooser.getSelectedFile();
748
749       try
750       {
751         InputStreamReader in = new InputStreamReader(new FileInputStream(
752                 file), "UTF-8");
753
754         jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
755         jucs = jucs.unmarshal(in);
756
757         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
758         {
759           String name;
760           jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
761           if (newcol.hasMax())
762           {
763             Color mincol = null, maxcol = null;
764             try
765             {
766               mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
767               maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
768
769             } catch (Exception e)
770             {
771               Cache.log.warn("Couldn't parse out graduated feature color.",
772                       e);
773             }
774             GraduatedColor gcol = new GraduatedColor(mincol, maxcol,
775                     newcol.getMin(), newcol.getMax());
776             if (newcol.hasAutoScale())
777             {
778               gcol.setAutoScaled(newcol.getAutoScale());
779             }
780             if (newcol.hasColourByLabel())
781             {
782               gcol.setColourByLabel(newcol.getColourByLabel());
783             }
784             if (newcol.hasThreshold())
785             {
786               gcol.setThresh(newcol.getThreshold());
787               gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD); // default
788             }
789             if (newcol.getThreshType().length() > 0)
790             {
791               String ttyp = newcol.getThreshType();
792               if (ttyp.equalsIgnoreCase("NONE"))
793               {
794                 gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD);
795               }
796               if (ttyp.equalsIgnoreCase("ABOVE"))
797               {
798                 gcol.setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);
799               }
800               if (ttyp.equalsIgnoreCase("BELOW"))
801               {
802                 gcol.setThreshType(AnnotationColourGradient.BELOW_THRESHOLD);
803               }
804             }
805             fr.setColour(name = newcol.getName(), gcol);
806           }
807           else
808           {
809             fr.setColour(name = jucs.getColour(i).getName(), new Color(
810                     Integer.parseInt(jucs.getColour(i).getRGB(), 16)));
811           }
812           fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
813         }
814         if (table != null)
815         {
816           resetTable(null);
817           Object[][] data = ((FeatureTableModel) table.getModel())
818                   .getData();
819           ensureOrder(data);
820           updateFeatureRenderer(data, false);
821           table.repaint();
822         }
823       } catch (Exception ex)
824       {
825         System.out.println("Error loading User Colour File\n" + ex);
826       }
827     }
828   }
829
830   void save()
831   {
832     JalviewFileChooser chooser = new JalviewFileChooser(
833             jalview.bin.Cache.getProperty("LAST_DIRECTORY"),
834             new String[] { "fc" },
835             new String[] { "Sequence Feature Colours" },
836             "Sequence Feature Colours");
837     chooser.setFileView(new jalview.io.JalviewFileView());
838     chooser.setDialogTitle(MessageManager
839             .getString("label.save_feature_colours"));
840     chooser.setToolTipText(MessageManager.getString("action.save"));
841
842     int value = chooser.showSaveDialog(this);
843
844     if (value == JalviewFileChooser.APPROVE_OPTION)
845     {
846       String choice = chooser.getSelectedFile().getPath();
847       jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
848       ucs.setSchemeName("Sequence Features");
849       try
850       {
851         PrintWriter out = new PrintWriter(new OutputStreamWriter(
852                 new FileOutputStream(choice), "UTF-8"));
853
854         Set fr_colours = fr.getAllFeatureColours();
855         Iterator e = fr_colours.iterator();
856         float[] sortOrder = new float[fr_colours.size()];
857         String[] sortTypes = new String[fr_colours.size()];
858         int i = 0;
859         while (e.hasNext())
860         {
861           sortTypes[i] = e.next().toString();
862           sortOrder[i] = fr.getOrder(sortTypes[i]);
863           i++;
864         }
865         jalview.util.QuickSort.sort(sortOrder, sortTypes);
866         sortOrder = null;
867         Object fcol;
868         GraduatedColor gcol;
869         for (i = 0; i < sortTypes.length; i++)
870         {
871           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
872           col.setName(sortTypes[i]);
873           col.setRGB(jalview.util.Format.getHexString(fr.getColour(col
874                   .getName())));
875           fcol = fr.getFeatureStyle(sortTypes[i]);
876           if (fcol instanceof GraduatedColor)
877           {
878             gcol = (GraduatedColor) fcol;
879             col.setMin(gcol.getMin());
880             col.setMax(gcol.getMax());
881             col.setMinRGB(jalview.util.Format.getHexString(gcol
882                     .getMinColor()));
883             col.setAutoScale(gcol.isAutoScale());
884             col.setThreshold(gcol.getThresh());
885             col.setColourByLabel(gcol.isColourByLabel());
886             switch (gcol.getThreshType())
887             {
888             case AnnotationColourGradient.NO_THRESHOLD:
889               col.setThreshType("NONE");
890               break;
891             case AnnotationColourGradient.ABOVE_THRESHOLD:
892               col.setThreshType("ABOVE");
893               break;
894             case AnnotationColourGradient.BELOW_THRESHOLD:
895               col.setThreshType("BELOW");
896               break;
897             }
898           }
899           ucs.addColour(col);
900         }
901         ucs.marshal(out);
902         out.close();
903       } catch (Exception ex)
904       {
905         ex.printStackTrace();
906       }
907     }
908   }
909
910   public void invertSelection()
911   {
912     for (int i = 0; i < table.getRowCount(); i++)
913     {
914       Boolean value = (Boolean) table.getValueAt(i, 2);
915
916       table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
917     }
918   }
919
920   public void orderByAvWidth()
921   {
922     if (table == null || table.getModel() == null)
923     {
924       return;
925     }
926     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
927     float[] width = new float[data.length];
928     float[] awidth;
929     float max = 0;
930     int num = 0;
931     for (int i = 0; i < data.length; i++)
932     {
933       awidth = (float[]) typeWidth.get(data[i][0]);
934       if (awidth[0] > 0)
935       {
936         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
937         // weight - but have to make per
938         // sequence, too (awidth[2])
939         // if (width[i]==1) // hack to distinguish single width sequences.
940         num++;
941       }
942       else
943       {
944         width[i] = 0;
945       }
946       if (max < width[i])
947       {
948         max = width[i];
949       }
950     }
951     boolean sort = false;
952     for (int i = 0; i < width.length; i++)
953     {
954       // awidth = (float[]) typeWidth.get(data[i][0]);
955       if (width[i] == 0)
956       {
957         width[i] = fr.getOrder(data[i][0].toString());
958         if (width[i] < 0)
959         {
960           width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
961         }
962       }
963       else
964       {
965         width[i] /= max; // normalize
966         fr.setOrder(data[i][0].toString(), width[i]); // store for later
967       }
968       if (i > 0)
969       {
970         sort = sort || width[i - 1] > width[i];
971       }
972     }
973     if (sort)
974     {
975       jalview.util.QuickSort.sort(width, data);
976       // update global priority order
977     }
978
979     updateFeatureRenderer(data, false);
980     table.repaint();
981   }
982
983   public void close()
984   {
985     try
986     {
987       frame.setClosed(true);
988     } catch (Exception exe)
989     {
990     }
991
992   }
993
994   public void updateFeatureRenderer(Object[][] data)
995   {
996     updateFeatureRenderer(data, true);
997   }
998
999   /**
1000    * Update the priority order of features; only repaint if this changed the
1001    * order of visible features
1002    * 
1003    * @param data
1004    * @param visibleNew
1005    */
1006   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1007   {
1008     if (fr.setFeaturePriority(data, visibleNew))
1009     {
1010       af.alignPanel.paintAlignment(true);
1011     }
1012   }
1013
1014   int selectedRow = -1;
1015
1016   JTabbedPane tabbedPane = new JTabbedPane();
1017
1018   BorderLayout borderLayout1 = new BorderLayout();
1019
1020   BorderLayout borderLayout2 = new BorderLayout();
1021
1022   BorderLayout borderLayout3 = new BorderLayout();
1023
1024   JPanel bigPanel = new JPanel();
1025
1026   BorderLayout borderLayout4 = new BorderLayout();
1027
1028   JButton invert = new JButton();
1029
1030   JPanel buttonPanel = new JPanel();
1031
1032   JButton cancel = new JButton();
1033
1034   JButton ok = new JButton();
1035
1036   JButton loadColours = new JButton();
1037
1038   JButton saveColours = new JButton();
1039
1040   JPanel dasButtonPanel = new JPanel();
1041
1042   JButton fetchDAS = new JButton();
1043
1044   JButton saveDAS = new JButton();
1045
1046   JButton cancelDAS = new JButton();
1047
1048   JButton optimizeOrder = new JButton();
1049
1050   JButton sortByScore = new JButton();
1051
1052   JButton sortByDens = new JButton();
1053
1054   JButton help = new JButton();
1055
1056   JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1057
1058   private void jbInit() throws Exception
1059   {
1060     this.setLayout(borderLayout1);
1061     settingsPane.setLayout(borderLayout2);
1062     dasSettingsPane.setLayout(borderLayout3);
1063     bigPanel.setLayout(borderLayout4);
1064     invert.setFont(JvSwingUtils.getLabelFont());
1065     invert.setText(MessageManager.getString("label.invert_selection"));
1066     invert.addActionListener(new ActionListener()
1067     {
1068       @Override
1069       public void actionPerformed(ActionEvent e)
1070       {
1071         invertSelection();
1072       }
1073     });
1074     optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1075     optimizeOrder.setText(MessageManager.getString("label.optimise_order"));
1076     optimizeOrder.addActionListener(new ActionListener()
1077     {
1078       @Override
1079       public void actionPerformed(ActionEvent e)
1080       {
1081         orderByAvWidth();
1082       }
1083     });
1084     sortByScore.setFont(JvSwingUtils.getLabelFont());
1085     sortByScore
1086             .setText(MessageManager.getString("label.seq_sort_by_score"));
1087     sortByScore.addActionListener(new ActionListener()
1088     {
1089       @Override
1090       public void actionPerformed(ActionEvent e)
1091       {
1092         af.avc.sortAlignmentByFeatureScore(null);
1093       }
1094     });
1095     sortByDens.setFont(JvSwingUtils.getLabelFont());
1096     sortByDens.setText(MessageManager
1097             .getString("label.sequence_sort_by_density"));
1098     sortByDens.addActionListener(new ActionListener()
1099     {
1100       @Override
1101       public void actionPerformed(ActionEvent e)
1102       {
1103         af.avc.sortAlignmentByFeatureDensity(null);
1104       }
1105     });
1106     help.setFont(JvSwingUtils.getLabelFont());
1107     help.setText(MessageManager.getString("action.help"));
1108     help.addActionListener(new ActionListener()
1109     {
1110       @Override
1111       public void actionPerformed(ActionEvent e)
1112       {
1113         try
1114         {
1115           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1116         } catch (HelpSetException e1)
1117         {
1118           e1.printStackTrace();
1119         }
1120       }
1121     });
1122     help.setFont(JvSwingUtils.getLabelFont());
1123     help.setText(MessageManager.getString("action.help"));
1124     help.addActionListener(new ActionListener()
1125     {
1126       @Override
1127       public void actionPerformed(ActionEvent e)
1128       {
1129         try
1130         {
1131           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1132         } catch (HelpSetException e1)
1133         {
1134           e1.printStackTrace();
1135         }
1136       }
1137     });
1138     cancel.setFont(JvSwingUtils.getLabelFont());
1139     cancel.setText(MessageManager.getString("action.cancel"));
1140     cancel.addActionListener(new ActionListener()
1141     {
1142       @Override
1143       public void actionPerformed(ActionEvent e)
1144       {
1145         fr.setTransparency(originalTransparency);
1146         updateFeatureRenderer(originalData);
1147         close();
1148       }
1149     });
1150     ok.setFont(JvSwingUtils.getLabelFont());
1151     ok.setText(MessageManager.getString("action.ok"));
1152     ok.addActionListener(new ActionListener()
1153     {
1154       @Override
1155       public void actionPerformed(ActionEvent e)
1156       {
1157         close();
1158       }
1159     });
1160     loadColours.setFont(JvSwingUtils.getLabelFont());
1161     loadColours.setText(MessageManager.getString("label.load_colours"));
1162     loadColours.addActionListener(new ActionListener()
1163     {
1164       @Override
1165       public void actionPerformed(ActionEvent e)
1166       {
1167         load();
1168       }
1169     });
1170     saveColours.setFont(JvSwingUtils.getLabelFont());
1171     saveColours.setText(MessageManager.getString("label.save_colours"));
1172     saveColours.addActionListener(new ActionListener()
1173     {
1174       @Override
1175       public void actionPerformed(ActionEvent e)
1176       {
1177         save();
1178       }
1179     });
1180     transparency.addChangeListener(new ChangeListener()
1181     {
1182       @Override
1183       public void stateChanged(ChangeEvent evt)
1184       {
1185         fr.setTransparency((100 - transparency.getValue()) / 100f);
1186         af.alignPanel.paintAlignment(true);
1187       }
1188     });
1189
1190     transparency.setMaximum(70);
1191     transparency.setToolTipText(MessageManager
1192             .getString("label.transparency_tip"));
1193     fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1194     fetchDAS.addActionListener(new ActionListener()
1195     {
1196       @Override
1197       public void actionPerformed(ActionEvent e)
1198       {
1199         fetchDAS_actionPerformed(e);
1200       }
1201     });
1202     saveDAS.setText(MessageManager.getString("action.save_as_default"));
1203     saveDAS.addActionListener(new ActionListener()
1204     {
1205       @Override
1206       public void actionPerformed(ActionEvent e)
1207       {
1208         saveDAS_actionPerformed(e);
1209       }
1210     });
1211     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1212     dasSettingsPane.setBorder(null);
1213     cancelDAS.setEnabled(false);
1214     cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1215     cancelDAS.addActionListener(new ActionListener()
1216     {
1217       @Override
1218       public void actionPerformed(ActionEvent e)
1219       {
1220         cancelDAS_actionPerformed(e);
1221       }
1222     });
1223     this.add(tabbedPane, java.awt.BorderLayout.CENTER);
1224     tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
1225             settingsPane);
1226     tabbedPane.addTab(MessageManager.getString("label.das_settings"),
1227             dasSettingsPane);
1228     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
1229     transbuttons.add(optimizeOrder);
1230     transbuttons.add(invert);
1231     transbuttons.add(sortByScore);
1232     transbuttons.add(sortByDens);
1233     transbuttons.add(help);
1234     JPanel sliderPanel = new JPanel();
1235     sliderPanel.add(transparency);
1236     transPanel.add(transparency);
1237     transPanel.add(transbuttons);
1238     buttonPanel.add(ok);
1239     buttonPanel.add(cancel);
1240     buttonPanel.add(loadColours);
1241     buttonPanel.add(saveColours);
1242     bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
1243     dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
1244     dasButtonPanel.add(fetchDAS);
1245     dasButtonPanel.add(cancelDAS);
1246     dasButtonPanel.add(saveDAS);
1247     settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
1248     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
1249   }
1250
1251   public void fetchDAS_actionPerformed(ActionEvent e)
1252   {
1253     fetchDAS.setEnabled(false);
1254     cancelDAS.setEnabled(true);
1255     dassourceBrowser.setGuiEnabled(false);
1256     Vector selectedSources = dassourceBrowser.getSelectedSources();
1257     doDasFeatureFetch(selectedSources, true, true);
1258   }
1259
1260   /**
1261    * get the features from selectedSources for all or the current selection
1262    * 
1263    * @param selectedSources
1264    * @param checkDbRefs
1265    * @param promptFetchDbRefs
1266    */
1267   private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1268           boolean checkDbRefs, boolean promptFetchDbRefs)
1269   {
1270     SequenceI[] dataset, seqs;
1271     int iSize;
1272     AlignmentViewport vp = af.getViewport();
1273     if (vp.getSelectionGroup() != null
1274             && vp.getSelectionGroup().getSize() > 0)
1275     {
1276       iSize = vp.getSelectionGroup().getSize();
1277       dataset = new SequenceI[iSize];
1278       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1279     }
1280     else
1281     {
1282       iSize = vp.getAlignment().getHeight();
1283       seqs = vp.getAlignment().getSequencesArray();
1284     }
1285
1286     dataset = new SequenceI[iSize];
1287     for (int i = 0; i < iSize; i++)
1288     {
1289       dataset[i] = seqs[i].getDatasetSequence();
1290     }
1291
1292     cancelDAS.setEnabled(true);
1293     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1294             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1295     af.getViewport().setShowSequenceFeatures(true);
1296     af.showSeqFeatures.setSelected(true);
1297   }
1298
1299   /**
1300    * blocking call to initialise the das source browser
1301    */
1302   public void initDasSources()
1303   {
1304     dassourceBrowser.initDasSources();
1305   }
1306
1307   /**
1308    * examine the current list of das sources and return any matching the given
1309    * nicknames in sources
1310    * 
1311    * @param sources
1312    *          Vector of Strings to resolve to DAS source nicknames.
1313    * @return sources that are present in source list.
1314    */
1315   public List<jalviewSourceI> resolveSourceNicknames(Vector sources)
1316   {
1317     return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1318   }
1319
1320   /**
1321    * get currently selected das sources. ensure you have called initDasSources
1322    * before calling this.
1323    * 
1324    * @return vector of selected das source nicknames
1325    */
1326   public Vector getSelectedSources()
1327   {
1328     return dassourceBrowser.getSelectedSources();
1329   }
1330
1331   /**
1332    * properly initialise DAS fetcher and then initiate a new thread to fetch
1333    * features from the named sources (rather than any turned on by default)
1334    * 
1335    * @param sources
1336    * @param block
1337    *          if true then runs in same thread, otherwise passes to the Swing
1338    *          executor
1339    */
1340   public void fetchDasFeatures(Vector sources, boolean block)
1341   {
1342     initDasSources();
1343     List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1344             .resolveSourceNicknames(sources);
1345     if (resolved.size() == 0)
1346     {
1347       resolved = dassourceBrowser.getSelectedSources();
1348     }
1349     if (resolved.size() > 0)
1350     {
1351       final List<jalviewSourceI> dassources = resolved;
1352       fetchDAS.setEnabled(false);
1353       // cancelDAS.setEnabled(true); doDasFetch does this.
1354       Runnable fetcher = new Runnable()
1355       {
1356
1357         @Override
1358         public void run()
1359         {
1360           doDasFeatureFetch(dassources, true, false);
1361
1362         }
1363       };
1364       if (block)
1365       {
1366         fetcher.run();
1367       }
1368       else
1369       {
1370         SwingUtilities.invokeLater(fetcher);
1371       }
1372     }
1373   }
1374
1375   public void saveDAS_actionPerformed(ActionEvent e)
1376   {
1377     dassourceBrowser
1378             .saveProperties(jalview.bin.Cache.applicationProperties);
1379   }
1380
1381   public void complete()
1382   {
1383     fetchDAS.setEnabled(true);
1384     cancelDAS.setEnabled(false);
1385     dassourceBrowser.setGuiEnabled(true);
1386
1387   }
1388
1389   public void cancelDAS_actionPerformed(ActionEvent e)
1390   {
1391     if (dasFeatureFetcher != null)
1392     {
1393       dasFeatureFetcher.cancel();
1394     }
1395     complete();
1396   }
1397
1398   public void noDasSourceActive()
1399   {
1400     complete();
1401     JOptionPane
1402             .showInternalConfirmDialog(
1403                     Desktop.desktop,
1404                     MessageManager
1405                             .getString("label.no_das_sources_selected_warn"),
1406                     MessageManager
1407                             .getString("label.no_das_sources_selected_title"),
1408                     JOptionPane.DEFAULT_OPTION,
1409                     JOptionPane.INFORMATION_MESSAGE);
1410   }
1411
1412   // ///////////////////////////////////////////////////////////////////////
1413   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1414   // ///////////////////////////////////////////////////////////////////////
1415   class FeatureTableModel extends AbstractTableModel
1416   {
1417     FeatureTableModel(Object[][] data)
1418     {
1419       this.data = data;
1420     }
1421
1422     private String[] columnNames = {
1423         MessageManager.getString("label.feature_type"),
1424         MessageManager.getString("action.colour"),
1425         MessageManager.getString("label.display") };
1426
1427     private Object[][] data;
1428
1429     public Object[][] getData()
1430     {
1431       return data;
1432     }
1433
1434     public void setData(Object[][] data)
1435     {
1436       this.data = data;
1437     }
1438
1439     @Override
1440     public int getColumnCount()
1441     {
1442       return columnNames.length;
1443     }
1444
1445     public Object[] getRow(int row)
1446     {
1447       return data[row];
1448     }
1449
1450     @Override
1451     public int getRowCount()
1452     {
1453       return data.length;
1454     }
1455
1456     @Override
1457     public String getColumnName(int col)
1458     {
1459       return columnNames[col];
1460     }
1461
1462     @Override
1463     public Object getValueAt(int row, int col)
1464     {
1465       return data[row][col];
1466     }
1467
1468     @Override
1469     public Class getColumnClass(int c)
1470     {
1471       return getValueAt(0, c).getClass();
1472     }
1473
1474     @Override
1475     public boolean isCellEditable(int row, int col)
1476     {
1477       return col == 0 ? false : true;
1478     }
1479
1480     @Override
1481     public void setValueAt(Object value, int row, int col)
1482     {
1483       data[row][col] = value;
1484       fireTableCellUpdated(row, col);
1485       updateFeatureRenderer(data);
1486     }
1487
1488   }
1489
1490   class ColorRenderer extends JLabel implements TableCellRenderer
1491   {
1492     javax.swing.border.Border unselectedBorder = null;
1493
1494     javax.swing.border.Border selectedBorder = null;
1495
1496     final String baseTT = "Click to edit, right/apple click for menu.";
1497
1498     public ColorRenderer()
1499     {
1500       setOpaque(true); // MUST do this for background to show up.
1501       setHorizontalTextPosition(SwingConstants.CENTER);
1502       setVerticalTextPosition(SwingConstants.CENTER);
1503     }
1504
1505     @Override
1506     public Component getTableCellRendererComponent(JTable table,
1507             Object color, boolean isSelected, boolean hasFocus, int row,
1508             int column)
1509     {
1510       // JLabel comp = new JLabel();
1511       // comp.
1512       setOpaque(true);
1513       // comp.
1514       // setBounds(getBounds());
1515       Color newColor;
1516       setToolTipText(baseTT);
1517       setBackground(table.getBackground());
1518       if (color instanceof GraduatedColor)
1519       {
1520         Rectangle cr = table.getCellRect(row, column, false);
1521         FeatureSettings.renderGraduatedColor(this, (GraduatedColor) color,
1522                 (int) cr.getWidth(), (int) cr.getHeight());
1523
1524       }
1525       else
1526       {
1527         this.setText("");
1528         this.setIcon(null);
1529         newColor = (Color) color;
1530         // comp.
1531         setBackground(newColor);
1532         // comp.setToolTipText("RGB value: " + newColor.getRed() + ", "
1533         // + newColor.getGreen() + ", " + newColor.getBlue());
1534       }
1535       if (isSelected)
1536       {
1537         if (selectedBorder == null)
1538         {
1539           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1540                   table.getSelectionBackground());
1541         }
1542         // comp.
1543         setBorder(selectedBorder);
1544       }
1545       else
1546       {
1547         if (unselectedBorder == null)
1548         {
1549           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1550                   table.getBackground());
1551         }
1552         // comp.
1553         setBorder(unselectedBorder);
1554       }
1555
1556       return this;
1557     }
1558   }
1559
1560   /**
1561    * update comp using rendering settings from gcol
1562    * 
1563    * @param comp
1564    * @param gcol
1565    */
1566   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol)
1567   {
1568     int w = comp.getWidth(), h = comp.getHeight();
1569     if (w < 20)
1570     {
1571       w = (int) comp.getPreferredSize().getWidth();
1572       h = (int) comp.getPreferredSize().getHeight();
1573       if (w < 20)
1574       {
1575         w = 80;
1576         h = 12;
1577       }
1578     }
1579     renderGraduatedColor(comp, gcol, w, h);
1580   }
1581
1582   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol,
1583           int w, int h)
1584   {
1585     boolean thr = false;
1586     String tt = "";
1587     String tx = "";
1588     if (gcol.getThreshType() == AnnotationColourGradient.ABOVE_THRESHOLD)
1589     {
1590       thr = true;
1591       tx += ">";
1592       tt += "Thresholded (Above " + gcol.getThresh() + ") ";
1593     }
1594     if (gcol.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)
1595     {
1596       thr = true;
1597       tx += "<";
1598       tt += "Thresholded (Below " + gcol.getThresh() + ") ";
1599     }
1600     if (gcol.isColourByLabel())
1601     {
1602       tt = "Coloured by label text. " + tt;
1603       if (thr)
1604       {
1605         tx += " ";
1606       }
1607       tx += "Label";
1608       comp.setIcon(null);
1609     }
1610     else
1611     {
1612       Color newColor = gcol.getMaxColor();
1613       comp.setBackground(newColor);
1614       // System.err.println("Width is " + w / 2);
1615       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1616       comp.setIcon(ficon);
1617       // tt+="RGB value: Max (" + newColor.getRed() + ", "
1618       // + newColor.getGreen() + ", " + newColor.getBlue()
1619       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1620       // + ", " + minCol.getBlue() + ")");
1621     }
1622     comp.setHorizontalAlignment(SwingConstants.CENTER);
1623     comp.setText(tx);
1624     if (tt.length() > 0)
1625     {
1626       if (comp.getToolTipText() == null)
1627       {
1628         comp.setToolTipText(tt);
1629       }
1630       else
1631       {
1632         comp.setToolTipText(tt + " " + comp.getToolTipText());
1633       }
1634     }
1635   }
1636 }
1637
1638 class FeatureIcon implements Icon
1639 {
1640   GraduatedColor gcol;
1641
1642   Color backg;
1643
1644   boolean midspace = false;
1645
1646   int width = 50, height = 20;
1647
1648   int s1, e1; // start and end of midpoint band for thresholded symbol
1649
1650   Color mpcolour = Color.white;
1651
1652   FeatureIcon(GraduatedColor gfc, Color bg, int w, int h, boolean mspace)
1653   {
1654     gcol = gfc;
1655     backg = bg;
1656     width = w;
1657     height = h;
1658     midspace = mspace;
1659     if (midspace)
1660     {
1661       s1 = width / 3;
1662       e1 = s1 * 2;
1663     }
1664     else
1665     {
1666       s1 = width / 2;
1667       e1 = s1;
1668     }
1669   }
1670
1671   @Override
1672   public int getIconWidth()
1673   {
1674     return width;
1675   }
1676
1677   @Override
1678   public int getIconHeight()
1679   {
1680     return height;
1681   }
1682
1683   @Override
1684   public void paintIcon(Component c, Graphics g, int x, int y)
1685   {
1686
1687     if (gcol.isColourByLabel())
1688     {
1689       g.setColor(backg);
1690       g.fillRect(0, 0, width, height);
1691       // need an icon here.
1692       g.setColor(gcol.getMaxColor());
1693
1694       g.setFont(new Font("Verdana", Font.PLAIN, 9));
1695
1696       // g.setFont(g.getFont().deriveFont(
1697       // AffineTransform.getScaleInstance(
1698       // width/g.getFontMetrics().stringWidth("Label"),
1699       // height/g.getFontMetrics().getHeight())));
1700
1701       g.drawString(MessageManager.getString("label.label"), 0, 0);
1702
1703     }
1704     else
1705     {
1706       Color minCol = gcol.getMinColor();
1707       g.setColor(minCol);
1708       g.fillRect(0, 0, s1, height);
1709       if (midspace)
1710       {
1711         g.setColor(Color.white);
1712         g.fillRect(s1, 0, e1 - s1, height);
1713       }
1714       g.setColor(gcol.getMaxColor());
1715       g.fillRect(0, e1, width - e1, height);
1716     }
1717   }
1718 }
1719
1720 class ColorEditor extends AbstractCellEditor implements TableCellEditor,
1721         ActionListener
1722 {
1723   FeatureSettings me;
1724
1725   GraduatedColor currentGColor;
1726
1727   FeatureColourChooser chooser;
1728
1729   String type;
1730
1731   Color currentColor;
1732
1733   JButton button;
1734
1735   JColorChooser colorChooser;
1736
1737   JDialog dialog;
1738
1739   protected static final String EDIT = "edit";
1740
1741   int selectedRow = 0;
1742
1743   public ColorEditor(FeatureSettings me)
1744   {
1745     this.me = me;
1746     // Set up the editor (from the table's point of view),
1747     // which is a button.
1748     // This button brings up the color chooser dialog,
1749     // which is the editor from the user's point of view.
1750     button = new JButton();
1751     button.setActionCommand(EDIT);
1752     button.addActionListener(this);
1753     button.setBorderPainted(false);
1754     // Set up the dialog that the button brings up.
1755     colorChooser = new JColorChooser();
1756     dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
1757             colorChooser, this, // OK button handler
1758             null); // no CANCEL button handler
1759   }
1760
1761   /**
1762    * Handles events from the editor button and from the dialog's OK button.
1763    */
1764   @Override
1765   public void actionPerformed(ActionEvent e)
1766   {
1767
1768     if (EDIT.equals(e.getActionCommand()))
1769     {
1770       // The user has clicked the cell, so
1771       // bring up the dialog.
1772       if (currentColor != null)
1773       {
1774         // bring up simple color chooser
1775         button.setBackground(currentColor);
1776         colorChooser.setColor(currentColor);
1777         dialog.setVisible(true);
1778       }
1779       else
1780       {
1781         // bring up graduated chooser.
1782         chooser = new FeatureColourChooser(me.fr, type);
1783         chooser.setRequestFocusEnabled(true);
1784         chooser.requestFocus();
1785         chooser.addActionListener(this);
1786       }
1787       // Make the renderer reappear.
1788       fireEditingStopped();
1789
1790     }
1791     else
1792     { // User pressed dialog's "OK" button.
1793       if (currentColor != null)
1794       {
1795         currentColor = colorChooser.getColor();
1796       }
1797       else
1798       {
1799         // class cast exceptions may be raised if the chooser created on a
1800         // non-graduated color
1801         currentGColor = (GraduatedColor) chooser.getLastColour();
1802       }
1803       me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
1804       fireEditingStopped();
1805       me.table.validate();
1806     }
1807   }
1808
1809   // Implement the one CellEditor method that AbstractCellEditor doesn't.
1810   @Override
1811   public Object getCellEditorValue()
1812   {
1813     if (currentColor == null)
1814     {
1815       return currentGColor;
1816     }
1817     return currentColor;
1818   }
1819
1820   // Implement the one method defined by TableCellEditor.
1821   @Override
1822   public Component getTableCellEditorComponent(JTable table, Object value,
1823           boolean isSelected, int row, int column)
1824   {
1825     currentGColor = null;
1826     currentColor = null;
1827     this.selectedRow = row;
1828     type = me.table.getValueAt(row, 0).toString();
1829     button.setOpaque(true);
1830     button.setBackground(me.getBackground());
1831     if (value instanceof GraduatedColor)
1832     {
1833       currentGColor = (GraduatedColor) value;
1834       JLabel btn = new JLabel();
1835       btn.setSize(button.getSize());
1836       FeatureSettings.renderGraduatedColor(btn, currentGColor);
1837       button.setBackground(btn.getBackground());
1838       button.setIcon(btn.getIcon());
1839       button.setText(btn.getText());
1840     }
1841     else
1842     {
1843       button.setText("");
1844       button.setIcon(null);
1845       currentColor = (Color) value;
1846       button.setBackground(currentColor);
1847     }
1848     return button;
1849   }
1850 }