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