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