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