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