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