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