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