Jalview 2.6 source licence
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
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     if (new jalview.util.Platform().isAMac())
167     {
168         Desktop.addInternalFrame(frame, "Sequence Feature Settings", 475, 480);
169     } else {
170         Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 450);
171     }
172     
173     frame
174             .addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
175             {
176               public void internalFrameClosed(
177                       javax.swing.event.InternalFrameEvent evt)
178               {
179                 fr.removePropertyChangeListener(change);
180                 dassourceBrowser.fs = null;
181               };
182             });
183     frame.setLayer(JLayeredPane.PALETTE_LAYER);
184   }
185
186   protected void popupSort(final int selectedRow, final String type,
187           final Object typeCol, final Hashtable minmax, int x, int y)
188   {
189     JPopupMenu men = new JPopupMenu("Settings for " + type);
190     JMenuItem scr = new JMenuItem("Sort by Score");
191     men.add(scr);
192     final FeatureSettings me = this;
193     scr.addActionListener(new ActionListener()
194     {
195
196       public void actionPerformed(ActionEvent e)
197       {
198         me.sortByScore(new String[]
199         { type });
200       }
201
202     });
203     JMenuItem dens = new JMenuItem("Sort by Density");
204     dens.addActionListener(new ActionListener()
205     {
206
207       public void actionPerformed(ActionEvent e)
208       {
209         me.sortByDens(new String[]
210         { type });
211       }
212
213     });
214     men.add(dens);
215     if (minmax != null)
216     {
217       final Object typeMinMax = minmax.get(type);
218       /*
219        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
220        * this is broken at the moment and isn't that useful anyway!
221        * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
222        * ActionListener() {
223        * 
224        * public void actionPerformed(ActionEvent e) {
225        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
226        * null); } else { minmax.put(type, typeMinMax); } }
227        * 
228        * });
229        * 
230        * men.add(chb);
231        */
232       if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
233       {
234         // if (table.getValueAt(row, column));
235         // graduated colourschemes for those where minmax exists for the
236         // positional features
237         final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
238                 "Graduated Colour");
239         mxcol.setSelected(!(typeCol instanceof Color));
240         men.add(mxcol);
241         mxcol.addActionListener(new ActionListener()
242         {
243           JColorChooser colorChooser;
244
245           public void actionPerformed(ActionEvent e)
246           {
247             if (e.getSource() == mxcol)
248             {
249               if (typeCol instanceof Color)
250               {
251                 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
252                         type);
253                 fc.addActionListener(this);
254               }
255               else
256               {
257                 // bring up simple color chooser
258                 colorChooser = new JColorChooser();
259                 JDialog dialog = JColorChooser.createDialog(me,
260                         "Select new Colour", true, // modal
261                         colorChooser, this, // OK button handler
262                         null); // no CANCEL button handler
263                 colorChooser.setColor(((GraduatedColor) typeCol)
264                         .getMaxColor());
265                 dialog.setVisible(true);
266               }
267             }
268             else
269             {
270               if (e.getSource() instanceof FeatureColourChooser)
271               {
272                 FeatureColourChooser fc = (FeatureColourChooser) e
273                         .getSource();
274                 table.setValueAt(fc.getLastColour(), selectedRow, 1);
275                 table.validate();
276               }
277               else
278               {
279                 // probably the color chooser!
280                 table.setValueAt(colorChooser.getColor(), selectedRow, 1);
281                 table.validate();
282                 me.updateFeatureRenderer(((FeatureTableModel) table
283                         .getModel()).getData(), false);
284               }
285             }
286           }
287
288         });
289       }
290     }
291     men.show(table, x, y);
292   }
293
294   /**
295    * true when Feature Settings are updating from feature renderer
296    */
297   private boolean handlingUpdate = false;
298
299   /**
300    * contains a float[3] for each feature type string. created by setTableData
301    */
302   Hashtable typeWidth = null;
303
304   synchronized public void setTableData()
305   {
306     if (fr.featureGroups == null)
307     {
308       fr.featureGroups = new Hashtable();
309     }
310     Vector allFeatures = new Vector();
311     Vector allGroups = new Vector();
312     SequenceFeature[] tmpfeatures;
313     String group;
314     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
315     {
316       if (af.getViewport().alignment.getSequenceAt(i).getDatasetSequence()
317               .getSequenceFeatures() == null)
318       {
319         continue;
320       }
321
322       tmpfeatures = af.getViewport().alignment.getSequenceAt(i)
323               .getDatasetSequence().getSequenceFeatures();
324
325       int index = 0;
326       while (index < tmpfeatures.length)
327       {
328         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
329         {
330           index++;
331           continue;
332         }
333
334         if (tmpfeatures[index].getFeatureGroup() != null)
335         {
336           group = tmpfeatures[index].featureGroup;
337           if (!allGroups.contains(group))
338           {
339             allGroups.addElement(group);
340             if (group != null)
341             {
342               checkGroupState(group);
343             }
344           }
345         }
346
347         if (!allFeatures.contains(tmpfeatures[index].getType()))
348         {
349           allFeatures.addElement(tmpfeatures[index].getType());
350         }
351         index++;
352       }
353     }
354
355     resetTable(null);
356
357     validate();
358   }
359
360   /**
361    * 
362    * @param group
363    * @return true if group has been seen before and is already added to set.
364    */
365   private boolean checkGroupState(String group)
366   {
367     boolean visible;
368     if (fr.featureGroups.containsKey(group))
369     {
370       visible = ((Boolean) fr.featureGroups.get(group)).booleanValue();
371     }
372     else
373     {
374       visible = true; // new group is always made visible
375     }
376
377     if (groupPanel == null)
378     {
379       groupPanel = new JPanel();
380     }
381
382     boolean alreadyAdded = false;
383     for (int g = 0; g < groupPanel.getComponentCount(); g++)
384     {
385       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
386       {
387         alreadyAdded = true;
388         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
389         break;
390       }
391     }
392
393     if (alreadyAdded)
394     {
395
396       return true;
397     }
398
399     fr.featureGroups.put(group, new Boolean(visible));
400     final String grp = group;
401     final JCheckBox check = new JCheckBox(group, visible);
402     check.setFont(new Font("Serif", Font.BOLD, 12));
403     check.addItemListener(new ItemListener()
404     {
405       public void itemStateChanged(ItemEvent evt)
406       {
407         fr.featureGroups.put(check.getText(), new Boolean(check
408                 .isSelected()));
409         af.alignPanel.seqPanel.seqCanvas.repaint();
410         if (af.alignPanel.overviewPanel != null)
411         {
412           af.alignPanel.overviewPanel.updateOverviewImage();
413         }
414
415         resetTable(new String[]
416         { grp });
417       }
418     });
419     groupPanel.add(check);
420     return false;
421   }
422
423   boolean resettingTable = false;
424
425   synchronized void resetTable(String[] groupChanged)
426   {
427     if (resettingTable == true)
428     {
429       return;
430     }
431     resettingTable = true;
432     typeWidth = new Hashtable();
433     // TODO: change avWidth calculation to 'per-sequence' average and use long
434     // rather than float
435     float[] avWidth = null;
436     SequenceFeature[] tmpfeatures;
437     String group = null, type;
438     Vector visibleChecks = new Vector();
439
440     // Find out which features should be visible depending on which groups
441     // are selected / deselected
442     // and recompute average width ordering
443     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
444     {
445
446       tmpfeatures = af.getViewport().alignment.getSequenceAt(i)
447               .getDatasetSequence().getSequenceFeatures();
448       if (tmpfeatures == null)
449       {
450         continue;
451       }
452
453       int index = 0;
454       while (index < tmpfeatures.length)
455       {
456         group = tmpfeatures[index].featureGroup;
457
458         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
459         {
460           index++;
461           continue;
462         }
463
464         if (group == null || fr.featureGroups.get(group) == null
465                 || ((Boolean) fr.featureGroups.get(group)).booleanValue())
466         {
467           if (group != null)
468             checkGroupState(group);
469           type = tmpfeatures[index].getType();
470           if (!visibleChecks.contains(type))
471           {
472             visibleChecks.addElement(type);
473           }
474         }
475         if (!typeWidth.containsKey(tmpfeatures[index].getType()))
476         {
477           typeWidth.put(tmpfeatures[index].getType(),
478                   avWidth = new float[3]);
479         }
480         else
481         {
482           avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
483         }
484         avWidth[0]++;
485         if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
486         {
487           avWidth[1] += 1 + tmpfeatures[index].getBegin()
488                   - tmpfeatures[index].getEnd();
489         }
490         else
491         {
492           avWidth[1] += 1 + tmpfeatures[index].getEnd()
493                   - tmpfeatures[index].getBegin();
494         }
495         index++;
496       }
497     }
498
499     int fSize = visibleChecks.size();
500     Object[][] data = new Object[fSize][3];
501     int dataIndex = 0;
502
503     if (fr.renderOrder != null)
504     {
505       if (!handlingUpdate)
506         fr.findAllFeatures(groupChanged != null); // prod to update
507       // colourschemes. but don't
508       // affect display
509       // First add the checks in the previous render order,
510       // in case the window has been closed and reopened
511       for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
512       {
513         type = fr.renderOrder[ro];
514
515         if (!visibleChecks.contains(type))
516         {
517           continue;
518         }
519
520         data[dataIndex][0] = type;
521         data[dataIndex][1] = fr.getFeatureStyle(type);
522         data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed
523                 .containsKey(type));
524         dataIndex++;
525         visibleChecks.removeElement(type);
526       }
527     }
528
529     fSize = visibleChecks.size();
530     for (int i = 0; i < fSize; i++)
531     {
532       // These must be extra features belonging to the group
533       // which was just selected
534       type = visibleChecks.elementAt(i).toString();
535       data[dataIndex][0] = type;
536
537       data[dataIndex][1] = fr.getFeatureStyle(type);
538       if (data[dataIndex][1] == null)
539       {
540         // "Colour has been updated in another view!!"
541         fr.renderOrder = null;
542         return;
543       }
544
545       data[dataIndex][2] = new Boolean(true);
546       dataIndex++;
547     }
548
549     if (originalData == null)
550     {
551       originalData = new Object[data.length][3];
552       for (int i = 0; i < data.length; i++)
553       {
554         System.arraycopy(data[i], 0, originalData[i], 0, 3);
555       }
556     }
557
558     table.setModel(new FeatureTableModel(data));
559     table.getColumnModel().getColumn(0).setPreferredWidth(200);
560
561     if (groupPanel != null)
562     {
563       groupPanel.setLayout(new GridLayout(fr.featureGroups.size() / 4 + 1,
564               4));
565
566       groupPanel.validate();
567       bigPanel.add(groupPanel, BorderLayout.NORTH);
568     }
569
570     updateFeatureRenderer(data, groupChanged != null);
571     resettingTable = false;
572   }
573
574   /**
575    * reorder data based on the featureRenderers global priority list.
576    * 
577    * @param data
578    */
579   private void ensureOrder(Object[][] data)
580   {
581     boolean sort = false;
582     float[] order = new float[data.length];
583     for (int i = 0; i < order.length; i++)
584     {
585       order[i] = fr.getOrder(data[i][0].toString());
586       if (order[i] < 0)
587         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
588       if (i > 1)
589         sort = sort || order[i - 1] > order[i];
590     }
591     if (sort)
592       jalview.util.QuickSort.sort(order, data);
593   }
594
595   void load()
596   {
597     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
598             .getProperty("LAST_DIRECTORY"), new String[]
599     { "fc" }, new String[]
600     { "Sequence Feature Colours" }, "Sequence Feature Colours");
601     chooser.setFileView(new jalview.io.JalviewFileView());
602     chooser.setDialogTitle("Load Feature Colours");
603     chooser.setToolTipText("Load");
604
605     int value = chooser.showOpenDialog(this);
606
607     if (value == JalviewFileChooser.APPROVE_OPTION)
608     {
609       File file = chooser.getSelectedFile();
610
611       try
612       {
613         InputStreamReader in = new InputStreamReader(new FileInputStream(
614                 file), "UTF-8");
615
616         jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
617         jucs = (jalview.schemabinding.version2.JalviewUserColours) jucs
618                 .unmarshal(in);
619
620         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
621         {
622           String name;
623           jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
624           if (newcol.hasMax())
625           {
626             Color mincol = null, maxcol = null;
627             try
628             {
629               mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
630               maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
631
632             } catch (Exception e)
633             {
634               Cache.log.warn("Couldn't parse out graduated feature color.",
635                       e);
636             }
637             GraduatedColor gcol = new GraduatedColor(mincol, maxcol, newcol
638                     .getMin(), newcol.getMax());
639             if (newcol.hasAutoScale())
640             {
641               gcol.setAutoScaled(newcol.getAutoScale());
642             }
643             if (newcol.hasColourByLabel())
644             {
645               gcol.setColourByLabel(newcol.getColourByLabel());
646             }
647             if (newcol.hasThreshold())
648             {
649               gcol.setThresh(newcol.getThreshold());
650               gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD); // default
651             }
652             if (newcol.getThreshType().length() > 0)
653             {
654               String ttyp = newcol.getThreshType();
655               if (ttyp.equalsIgnoreCase("NONE"))
656               {
657                 gcol.setThreshType(AnnotationColourGradient.NO_THRESHOLD);
658               }
659               if (ttyp.equalsIgnoreCase("ABOVE"))
660               {
661                 gcol
662                         .setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);
663               }
664               if (ttyp.equalsIgnoreCase("BELOW"))
665               {
666                 gcol
667                         .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(jalview.bin.Cache
698             .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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
954     ok.setText("OK");
955     ok.addActionListener(new ActionListener()
956     {
957       public void actionPerformed(ActionEvent e)
958       {
959         close();
960       }
961     });
962     loadColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
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(new java.awt.Font("Verdana", Font.PLAIN, 11));
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 }