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