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