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