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