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