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