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