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