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