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