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