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