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