6130ef8e7cf5fafaddd99aaa2e5fe4f0c5703bf9
[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<>();
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         resetTable(new String[] { grp });
545         af.alignPanel.paintAlignment(true, true);
546       }
547     });
548     groupPanel.add(check);
549     return visible;
550   }
551
552   boolean resettingTable = false;
553
554   synchronized void resetTable(String[] groupChanged)
555   {
556     if (resettingTable)
557     {
558       return;
559     }
560     resettingTable = true;
561     typeWidth = new Hashtable<>();
562     // TODO: change avWidth calculation to 'per-sequence' average and use long
563     // rather than float
564
565     Set<String> displayableTypes = new HashSet<>();
566     Set<String> foundGroups = new HashSet<>();
567
568     /*
569      * determine which feature types may be visible depending on 
570      * which groups are selected, and recompute average width data
571      */
572     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
573     {
574
575       SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
576
577       /*
578        * get the sequence's groups for positional features
579        * and keep track of which groups are visible
580        */
581       Set<String> groups = seq.getFeatures().getFeatureGroups(true);
582       Set<String> visibleGroups = new HashSet<>();
583       for (String group : groups)
584       {
585         if (group == null || checkGroupState(group))
586         {
587           visibleGroups.add(group);
588         }
589       }
590       foundGroups.addAll(groups);
591
592       /*
593        * get distinct feature types for visible groups
594        * record distinct visible types, and their count and total length
595        */
596       Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
597               visibleGroups.toArray(new String[visibleGroups.size()]));
598       for (String type : types)
599       {
600         displayableTypes.add(type);
601         float[] avWidth = typeWidth.get(type);
602         if (avWidth == null)
603         {
604           avWidth = new float[2];
605           typeWidth.put(type, avWidth);
606         }
607         // todo this could include features with a non-visible group
608         // - do we greatly care?
609         // todo should we include non-displayable features here, and only
610         // update when features are added?
611         avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
612         avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
613       }
614     }
615
616     Object[][] data = new Object[displayableTypes.size()][3];
617     int dataIndex = 0;
618
619     if (fr.hasRenderOrder())
620     {
621       if (!handlingUpdate)
622       {
623         fr.findAllFeatures(groupChanged != null); // prod to update
624         // colourschemes. but don't
625         // affect display
626         // First add the checks in the previous render order,
627         // in case the window has been closed and reopened
628       }
629       List<String> frl = fr.getRenderOrder();
630       for (int ro = frl.size() - 1; ro > -1; ro--)
631       {
632         String type = frl.get(ro);
633
634         if (!displayableTypes.contains(type))
635         {
636           continue;
637         }
638
639         data[dataIndex][0] = type;
640         data[dataIndex][1] = fr.getFeatureStyle(type);
641         data[dataIndex][2] = new Boolean(
642                 af.getViewport().getFeaturesDisplayed().isVisible(type));
643         dataIndex++;
644         displayableTypes.remove(type);
645       }
646     }
647
648     /*
649      * process any extra features belonging only to 
650      * a group which was just selected
651      */
652     while (!displayableTypes.isEmpty())
653     {
654       String type = displayableTypes.iterator().next();
655       data[dataIndex][0] = type;
656
657       data[dataIndex][1] = fr.getFeatureStyle(type);
658       if (data[dataIndex][1] == null)
659       {
660         // "Colour has been updated in another view!!"
661         fr.clearRenderOrder();
662         return;
663       }
664
665       data[dataIndex][2] = new Boolean(true);
666       dataIndex++;
667       displayableTypes.remove(type);
668     }
669
670     if (originalData == null)
671     {
672       originalData = new Object[data.length][3];
673       for (int i = 0; i < data.length; i++)
674       {
675         System.arraycopy(data[i], 0, originalData[i], 0, 3);
676       }
677     }
678     else
679     {
680       updateOriginalData(data);
681     }
682
683     table.setModel(new FeatureTableModel(data));
684     table.getColumnModel().getColumn(0).setPreferredWidth(200);
685
686     groupPanel.setLayout(
687             new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
688     pruneGroups(foundGroups);
689     groupPanel.validate();
690
691     updateFeatureRenderer(data, groupChanged != null);
692     resettingTable = false;
693   }
694
695   /**
696    * Updates 'originalData' (used for restore on Cancel) if we detect that
697    * changes have been made outwith this dialog
698    * <ul>
699    * <li>a new feature type added (and made visible)</li>
700    * <li>a feature colour changed (in the Amend Features dialog)</li>
701    * </ul>
702    * 
703    * @param foundData
704    */
705   protected void updateOriginalData(Object[][] foundData)
706   {
707     // todo LinkedHashMap instead of Object[][] would be nice
708
709     Object[][] currentData = ((FeatureTableModel) table.getModel())
710             .getData();
711     for (Object[] row : foundData)
712     {
713       String type = (String) row[0];
714       boolean found = false;
715       for (Object[] current : currentData)
716       {
717         if (type.equals(current[0]))
718         {
719           found = true;
720           /*
721            * currently dependent on object equality here;
722            * really need an equals method on FeatureColour
723            */
724           if (!row[1].equals(current[1]))
725           {
726             /*
727              * feature colour has changed externally - update originalData
728              */
729             for (Object[] original : originalData)
730             {
731               if (type.equals(original[0]))
732               {
733                 original[1] = row[1];
734                 break;
735               }
736             }
737           }
738           break;
739         }
740       }
741       if (!found)
742       {
743         /*
744          * new feature detected - add to original data (on top)
745          */
746         Object[][] newData = new Object[originalData.length + 1][3];
747         for (int i = 0; i < originalData.length; i++)
748         {
749           System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
750         }
751         newData[0] = row;
752         originalData = newData;
753       }
754     }
755   }
756
757   /**
758    * Remove from the groups panel any checkboxes for groups that are not in the
759    * foundGroups set. This enables removing a group from the display when the
760    * last feature in that group is deleted.
761    * 
762    * @param foundGroups
763    */
764   protected void pruneGroups(Set<String> foundGroups)
765   {
766     for (int g = 0; g < groupPanel.getComponentCount(); g++)
767     {
768       JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
769       if (!foundGroups.contains(checkbox.getText()))
770       {
771         groupPanel.remove(checkbox);
772       }
773     }
774   }
775
776   /**
777    * reorder data based on the featureRenderers global priority list.
778    * 
779    * @param data
780    */
781   private void ensureOrder(Object[][] data)
782   {
783     boolean sort = false;
784     float[] order = new float[data.length];
785     for (int i = 0; i < order.length; i++)
786     {
787       order[i] = fr.getOrder(data[i][0].toString());
788       if (order[i] < 0)
789       {
790         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
791       }
792       if (i > 1)
793       {
794         sort = sort || order[i - 1] > order[i];
795       }
796     }
797     if (sort)
798     {
799       jalview.util.QuickSort.sort(order, data);
800     }
801   }
802
803   void load()
804   {
805     JalviewFileChooser chooser = new JalviewFileChooser("fc",
806             "Sequence Feature Colours");
807     chooser.setFileView(new JalviewFileView());
808     chooser.setDialogTitle(
809             MessageManager.getString("label.load_feature_colours"));
810     chooser.setToolTipText(MessageManager.getString("action.load"));
811
812     int value = chooser.showOpenDialog(this);
813
814     if (value == JalviewFileChooser.APPROVE_OPTION)
815     {
816       File file = chooser.getSelectedFile();
817
818       try
819       {
820         InputStreamReader in = new InputStreamReader(
821                 new FileInputStream(file), "UTF-8");
822
823         JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
824
825         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
826         {
827           String name;
828           jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
829           if (newcol.hasMax())
830           {
831             Color mincol = null, maxcol = null;
832             try
833             {
834               mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
835               maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
836
837             } catch (Exception e)
838             {
839               Cache.log.warn("Couldn't parse out graduated feature color.",
840                       e);
841             }
842             FeatureColourI gcol = new FeatureColour(mincol, maxcol,
843                     newcol.getMin(), newcol.getMax());
844             if (newcol.hasAutoScale())
845             {
846               gcol.setAutoScaled(newcol.getAutoScale());
847             }
848             if (newcol.hasColourByLabel())
849             {
850               gcol.setColourByLabel(newcol.getColourByLabel());
851             }
852             if (newcol.hasThreshold())
853             {
854               gcol.setThreshold(newcol.getThreshold());
855             }
856             if (newcol.getThreshType().length() > 0)
857             {
858               String ttyp = newcol.getThreshType();
859               if (ttyp.equalsIgnoreCase("ABOVE"))
860               {
861                 gcol.setAboveThreshold(true);
862               }
863               if (ttyp.equalsIgnoreCase("BELOW"))
864               {
865                 gcol.setBelowThreshold(true);
866               }
867             }
868             fr.setColour(name = newcol.getName(), gcol);
869           }
870           else
871           {
872             Color color = new Color(
873                     Integer.parseInt(jucs.getColour(i).getRGB(), 16));
874             fr.setColour(name = jucs.getColour(i).getName(),
875                     new FeatureColour(color));
876           }
877           fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
878         }
879         if (table != null)
880         {
881           resetTable(null);
882           Object[][] data = ((FeatureTableModel) table.getModel())
883                   .getData();
884           ensureOrder(data);
885           updateFeatureRenderer(data, false);
886           table.repaint();
887         }
888       } catch (Exception ex)
889       {
890         System.out.println("Error loading User Colour File\n" + ex);
891       }
892     }
893   }
894
895   void save()
896   {
897     JalviewFileChooser chooser = new JalviewFileChooser("fc",
898             "Sequence Feature Colours");
899     chooser.setFileView(new JalviewFileView());
900     chooser.setDialogTitle(
901             MessageManager.getString("label.save_feature_colours"));
902     chooser.setToolTipText(MessageManager.getString("action.save"));
903
904     int value = chooser.showSaveDialog(this);
905
906     if (value == JalviewFileChooser.APPROVE_OPTION)
907     {
908       String choice = chooser.getSelectedFile().getPath();
909       jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
910       ucs.setSchemeName("Sequence Features");
911       try
912       {
913         PrintWriter out = new PrintWriter(new OutputStreamWriter(
914                 new FileOutputStream(choice), "UTF-8"));
915
916         Set<String> fr_colours = fr.getAllFeatureColours();
917         Iterator<String> e = fr_colours.iterator();
918         float[] sortOrder = new float[fr_colours.size()];
919         String[] sortTypes = new String[fr_colours.size()];
920         int i = 0;
921         while (e.hasNext())
922         {
923           sortTypes[i] = e.next();
924           sortOrder[i] = fr.getOrder(sortTypes[i]);
925           i++;
926         }
927         QuickSort.sort(sortOrder, sortTypes);
928         sortOrder = null;
929         for (i = 0; i < sortTypes.length; i++)
930         {
931           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
932           col.setName(sortTypes[i]);
933           FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]);
934           if (fcol.isSimpleColour())
935           {
936             col.setRGB(Format.getHexString(fcol.getColour()));
937           }
938           else
939           {
940             col.setRGB(Format.getHexString(fcol.getMaxColour()));
941             col.setMin(fcol.getMin());
942             col.setMax(fcol.getMax());
943             col.setMinRGB(
944                     jalview.util.Format.getHexString(fcol.getMinColour()));
945             col.setAutoScale(fcol.isAutoScaled());
946             col.setThreshold(fcol.getThreshold());
947             col.setColourByLabel(fcol.isColourByLabel());
948             col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
949                     : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
950           }
951           ucs.addColour(col);
952         }
953         ucs.marshal(out);
954         out.close();
955       } catch (Exception ex)
956       {
957         ex.printStackTrace();
958       }
959     }
960   }
961
962   public void invertSelection()
963   {
964     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
965     for (int i = 0; i < data.length; i++)
966     {
967       data[i][1] = !(Boolean) data[i][1];
968     }
969     af.alignPanel.paintAlignment(true, true);
970   }
971
972   public void orderByAvWidth()
973   {
974     if (table == null || table.getModel() == null)
975     {
976       return;
977     }
978     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
979     float[] width = new float[data.length];
980     float[] awidth;
981     float max = 0;
982     int num = 0;
983     for (int i = 0; i < data.length; i++)
984     {
985       awidth = typeWidth.get(data[i][0]);
986       if (awidth[0] > 0)
987       {
988         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
989         // weight - but have to make per
990         // sequence, too (awidth[2])
991         // if (width[i]==1) // hack to distinguish single width sequences.
992         num++;
993       }
994       else
995       {
996         width[i] = 0;
997       }
998       if (max < width[i])
999       {
1000         max = width[i];
1001       }
1002     }
1003     boolean sort = false;
1004     for (int i = 0; i < width.length; i++)
1005     {
1006       // awidth = (float[]) typeWidth.get(data[i][0]);
1007       if (width[i] == 0)
1008       {
1009         width[i] = fr.getOrder(data[i][0].toString());
1010         if (width[i] < 0)
1011         {
1012           width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
1013         }
1014       }
1015       else
1016       {
1017         width[i] /= max; // normalize
1018         fr.setOrder(data[i][0].toString(), width[i]); // store for later
1019       }
1020       if (i > 0)
1021       {
1022         sort = sort || width[i - 1] > width[i];
1023       }
1024     }
1025     if (sort)
1026     {
1027       jalview.util.QuickSort.sort(width, data);
1028       // update global priority order
1029     }
1030
1031     updateFeatureRenderer(data, false);
1032     table.repaint();
1033   }
1034
1035   public void close()
1036   {
1037     try
1038     {
1039       frame.setClosed(true);
1040     } catch (Exception exe)
1041     {
1042     }
1043
1044   }
1045
1046   public void updateFeatureRenderer(Object[][] data)
1047   {
1048     updateFeatureRenderer(data, true);
1049   }
1050
1051   /**
1052    * Update the priority order of features; only repaint if this changed the
1053    * order of visible features
1054    * 
1055    * @param data
1056    * @param visibleNew
1057    */
1058   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1059   {
1060     if (fr.setFeaturePriority(data, visibleNew))
1061     {
1062       af.alignPanel.paintAlignment(true, true);
1063     }
1064   }
1065
1066   int selectedRow = -1;
1067
1068   JTabbedPane tabbedPane = new JTabbedPane();
1069
1070   BorderLayout borderLayout1 = new BorderLayout();
1071
1072   BorderLayout borderLayout2 = new BorderLayout();
1073
1074   BorderLayout borderLayout3 = new BorderLayout();
1075
1076   JPanel bigPanel = new JPanel();
1077
1078   BorderLayout borderLayout4 = new BorderLayout();
1079
1080   JButton invert = new JButton();
1081
1082   JPanel buttonPanel = new JPanel();
1083
1084   JButton cancel = new JButton();
1085
1086   JButton ok = new JButton();
1087
1088   JButton loadColours = new JButton();
1089
1090   JButton saveColours = new JButton();
1091
1092   JPanel dasButtonPanel = new JPanel();
1093
1094   JButton fetchDAS = new JButton();
1095
1096   JButton saveDAS = new JButton();
1097
1098   JButton cancelDAS = new JButton();
1099
1100   JButton optimizeOrder = new JButton();
1101
1102   JButton sortByScore = new JButton();
1103
1104   JButton sortByDens = new JButton();
1105
1106   JButton help = new JButton();
1107
1108   JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1109
1110   private void jbInit() throws Exception
1111   {
1112     this.setLayout(borderLayout1);
1113     settingsPane.setLayout(borderLayout2);
1114     dasSettingsPane.setLayout(borderLayout3);
1115     bigPanel.setLayout(borderLayout4);
1116
1117     groupPanel = new JPanel();
1118     bigPanel.add(groupPanel, BorderLayout.NORTH);
1119
1120     invert.setFont(JvSwingUtils.getLabelFont());
1121     invert.setText(MessageManager.getString("label.invert_selection"));
1122     invert.addActionListener(new ActionListener()
1123     {
1124       @Override
1125       public void actionPerformed(ActionEvent e)
1126       {
1127         invertSelection();
1128       }
1129     });
1130     optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1131     optimizeOrder.setText(MessageManager.getString("label.optimise_order"));
1132     optimizeOrder.addActionListener(new ActionListener()
1133     {
1134       @Override
1135       public void actionPerformed(ActionEvent e)
1136       {
1137         orderByAvWidth();
1138       }
1139     });
1140     sortByScore.setFont(JvSwingUtils.getLabelFont());
1141     sortByScore
1142             .setText(MessageManager.getString("label.seq_sort_by_score"));
1143     sortByScore.addActionListener(new ActionListener()
1144     {
1145       @Override
1146       public void actionPerformed(ActionEvent e)
1147       {
1148         af.avc.sortAlignmentByFeatureScore(null);
1149       }
1150     });
1151     sortByDens.setFont(JvSwingUtils.getLabelFont());
1152     sortByDens.setText(
1153             MessageManager.getString("label.sequence_sort_by_density"));
1154     sortByDens.addActionListener(new ActionListener()
1155     {
1156       @Override
1157       public void actionPerformed(ActionEvent e)
1158       {
1159         af.avc.sortAlignmentByFeatureDensity(null);
1160       }
1161     });
1162     help.setFont(JvSwingUtils.getLabelFont());
1163     help.setText(MessageManager.getString("action.help"));
1164     help.addActionListener(new ActionListener()
1165     {
1166       @Override
1167       public void actionPerformed(ActionEvent e)
1168       {
1169         try
1170         {
1171           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1172         } catch (HelpSetException e1)
1173         {
1174           e1.printStackTrace();
1175         }
1176       }
1177     });
1178     help.setFont(JvSwingUtils.getLabelFont());
1179     help.setText(MessageManager.getString("action.help"));
1180     help.addActionListener(new ActionListener()
1181     {
1182       @Override
1183       public void actionPerformed(ActionEvent e)
1184       {
1185         try
1186         {
1187           Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1188         } catch (HelpSetException e1)
1189         {
1190           e1.printStackTrace();
1191         }
1192       }
1193     });
1194     cancel.setFont(JvSwingUtils.getLabelFont());
1195     cancel.setText(MessageManager.getString("action.cancel"));
1196     cancel.addActionListener(new ActionListener()
1197     {
1198       @Override
1199       public void actionPerformed(ActionEvent e)
1200       {
1201         fr.setTransparency(originalTransparency);
1202         updateFeatureRenderer(originalData);
1203         close();
1204       }
1205     });
1206     ok.setFont(JvSwingUtils.getLabelFont());
1207     ok.setText(MessageManager.getString("action.ok"));
1208     ok.addActionListener(new ActionListener()
1209     {
1210       @Override
1211       public void actionPerformed(ActionEvent e)
1212       {
1213         close();
1214       }
1215     });
1216     loadColours.setFont(JvSwingUtils.getLabelFont());
1217     loadColours.setText(MessageManager.getString("label.load_colours"));
1218     loadColours.addActionListener(new ActionListener()
1219     {
1220       @Override
1221       public void actionPerformed(ActionEvent e)
1222       {
1223         load();
1224       }
1225     });
1226     saveColours.setFont(JvSwingUtils.getLabelFont());
1227     saveColours.setText(MessageManager.getString("label.save_colours"));
1228     saveColours.addActionListener(new ActionListener()
1229     {
1230       @Override
1231       public void actionPerformed(ActionEvent e)
1232       {
1233         save();
1234       }
1235     });
1236     transparency.addChangeListener(new ChangeListener()
1237     {
1238       @Override
1239       public void stateChanged(ChangeEvent evt)
1240       {
1241         if (!inConstruction)
1242         {
1243           fr.setTransparency((100 - transparency.getValue()) / 100f);
1244           af.alignPanel.paintAlignment(true,true);
1245         }
1246       }
1247     });
1248
1249     transparency.setMaximum(70);
1250     transparency.setToolTipText(
1251             MessageManager.getString("label.transparency_tip"));
1252     fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1253     fetchDAS.addActionListener(new ActionListener()
1254     {
1255       @Override
1256       public void actionPerformed(ActionEvent e)
1257       {
1258         fetchDAS_actionPerformed(e);
1259       }
1260     });
1261     saveDAS.setText(MessageManager.getString("action.save_as_default"));
1262     saveDAS.addActionListener(new ActionListener()
1263     {
1264       @Override
1265       public void actionPerformed(ActionEvent e)
1266       {
1267         saveDAS_actionPerformed(e);
1268       }
1269     });
1270     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1271     dasSettingsPane.setBorder(null);
1272     cancelDAS.setEnabled(false);
1273     cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1274     cancelDAS.addActionListener(new ActionListener()
1275     {
1276       @Override
1277       public void actionPerformed(ActionEvent e)
1278       {
1279         cancelDAS_actionPerformed(e);
1280       }
1281     });
1282     this.add(tabbedPane, java.awt.BorderLayout.CENTER);
1283     tabbedPane.addTab(MessageManager.getString("label.feature_settings"),
1284             settingsPane);
1285     tabbedPane.addTab(MessageManager.getString("label.das_settings"),
1286             dasSettingsPane);
1287     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
1288     transbuttons.add(optimizeOrder);
1289     transbuttons.add(invert);
1290     transbuttons.add(sortByScore);
1291     transbuttons.add(sortByDens);
1292     transbuttons.add(help);
1293     JPanel sliderPanel = new JPanel();
1294     sliderPanel.add(transparency);
1295     transPanel.add(transparency);
1296     transPanel.add(transbuttons);
1297     buttonPanel.add(ok);
1298     buttonPanel.add(cancel);
1299     buttonPanel.add(loadColours);
1300     buttonPanel.add(saveColours);
1301     bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
1302     dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
1303     dasButtonPanel.add(fetchDAS);
1304     dasButtonPanel.add(cancelDAS);
1305     dasButtonPanel.add(saveDAS);
1306     settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
1307     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
1308   }
1309
1310   public void fetchDAS_actionPerformed(ActionEvent e)
1311   {
1312     fetchDAS.setEnabled(false);
1313     cancelDAS.setEnabled(true);
1314     dassourceBrowser.setGuiEnabled(false);
1315     Vector<jalviewSourceI> selectedSources = dassourceBrowser
1316             .getSelectedSources();
1317     doDasFeatureFetch(selectedSources, true, true);
1318   }
1319
1320   /**
1321    * get the features from selectedSources for all or the current selection
1322    * 
1323    * @param selectedSources
1324    * @param checkDbRefs
1325    * @param promptFetchDbRefs
1326    */
1327   private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1328           boolean checkDbRefs, boolean promptFetchDbRefs)
1329   {
1330     SequenceI[] dataset, seqs;
1331     int iSize;
1332     AlignmentViewport vp = af.getViewport();
1333     if (vp.getSelectionGroup() != null
1334             && vp.getSelectionGroup().getSize() > 0)
1335     {
1336       iSize = vp.getSelectionGroup().getSize();
1337       dataset = new SequenceI[iSize];
1338       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1339     }
1340     else
1341     {
1342       iSize = vp.getAlignment().getHeight();
1343       seqs = vp.getAlignment().getSequencesArray();
1344     }
1345
1346     dataset = new SequenceI[iSize];
1347     for (int i = 0; i < iSize; i++)
1348     {
1349       dataset[i] = seqs[i].getDatasetSequence();
1350     }
1351
1352     cancelDAS.setEnabled(true);
1353     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1354             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1355     af.getViewport().setShowSequenceFeatures(true);
1356     af.showSeqFeatures.setSelected(true);
1357   }
1358
1359   /**
1360    * blocking call to initialise the das source browser
1361    */
1362   public void initDasSources()
1363   {
1364     dassourceBrowser.initDasSources();
1365   }
1366
1367   /**
1368    * examine the current list of das sources and return any matching the given
1369    * nicknames in sources
1370    * 
1371    * @param sources
1372    *          Vector of Strings to resolve to DAS source nicknames.
1373    * @return sources that are present in source list.
1374    */
1375   public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1376   {
1377     return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1378   }
1379
1380   /**
1381    * get currently selected das sources. ensure you have called initDasSources
1382    * before calling this.
1383    * 
1384    * @return vector of selected das source nicknames
1385    */
1386   public Vector<jalviewSourceI> getSelectedSources()
1387   {
1388     return dassourceBrowser.getSelectedSources();
1389   }
1390
1391   /**
1392    * properly initialise DAS fetcher and then initiate a new thread to fetch
1393    * features from the named sources (rather than any turned on by default)
1394    * 
1395    * @param sources
1396    * @param block
1397    *          if true then runs in same thread, otherwise passes to the Swing
1398    *          executor
1399    */
1400   public void fetchDasFeatures(Vector<String> sources, boolean block)
1401   {
1402     initDasSources();
1403     List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1404             .resolveSourceNicknames(sources);
1405     if (resolved.size() == 0)
1406     {
1407       resolved = dassourceBrowser.getSelectedSources();
1408     }
1409     if (resolved.size() > 0)
1410     {
1411       final List<jalviewSourceI> dassources = resolved;
1412       fetchDAS.setEnabled(false);
1413       // cancelDAS.setEnabled(true); doDasFetch does this.
1414       Runnable fetcher = new Runnable()
1415       {
1416
1417         @Override
1418         public void run()
1419         {
1420           doDasFeatureFetch(dassources, true, false);
1421
1422         }
1423       };
1424       if (block)
1425       {
1426         fetcher.run();
1427       }
1428       else
1429       {
1430         SwingUtilities.invokeLater(fetcher);
1431       }
1432     }
1433   }
1434
1435   public void saveDAS_actionPerformed(ActionEvent e)
1436   {
1437     dassourceBrowser
1438             .saveProperties(jalview.bin.Cache.applicationProperties);
1439   }
1440
1441   public void complete()
1442   {
1443     fetchDAS.setEnabled(true);
1444     cancelDAS.setEnabled(false);
1445     dassourceBrowser.setGuiEnabled(true);
1446
1447   }
1448
1449   public void cancelDAS_actionPerformed(ActionEvent e)
1450   {
1451     if (dasFeatureFetcher != null)
1452     {
1453       dasFeatureFetcher.cancel();
1454     }
1455     complete();
1456   }
1457
1458   public void noDasSourceActive()
1459   {
1460     complete();
1461     JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1462             MessageManager.getString("label.no_das_sources_selected_warn"),
1463             MessageManager.getString("label.no_das_sources_selected_title"),
1464             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1465   }
1466
1467   // ///////////////////////////////////////////////////////////////////////
1468   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1469   // ///////////////////////////////////////////////////////////////////////
1470   class FeatureTableModel extends AbstractTableModel
1471   {
1472     FeatureTableModel(Object[][] data)
1473     {
1474       this.data = data;
1475     }
1476
1477     private String[] columnNames = {
1478         MessageManager.getString("label.feature_type"),
1479         MessageManager.getString("action.colour"),
1480         MessageManager.getString("label.display") };
1481
1482     private Object[][] data;
1483
1484     public Object[][] getData()
1485     {
1486       return data;
1487     }
1488
1489     public void setData(Object[][] data)
1490     {
1491       this.data = data;
1492     }
1493
1494     @Override
1495     public int getColumnCount()
1496     {
1497       return columnNames.length;
1498     }
1499
1500     public Object[] getRow(int row)
1501     {
1502       return data[row];
1503     }
1504
1505     @Override
1506     public int getRowCount()
1507     {
1508       return data.length;
1509     }
1510
1511     @Override
1512     public String getColumnName(int col)
1513     {
1514       return columnNames[col];
1515     }
1516
1517     @Override
1518     public Object getValueAt(int row, int col)
1519     {
1520       return data[row][col];
1521     }
1522
1523     @Override
1524     public Class getColumnClass(int c)
1525     {
1526       return getValueAt(0, c).getClass();
1527     }
1528
1529     @Override
1530     public boolean isCellEditable(int row, int col)
1531     {
1532       return col == 0 ? false : true;
1533     }
1534
1535     @Override
1536     public void setValueAt(Object value, int row, int col)
1537     {
1538       data[row][col] = value;
1539       fireTableCellUpdated(row, col);
1540       updateFeatureRenderer(data);
1541     }
1542
1543   }
1544
1545   class ColorRenderer extends JLabel implements TableCellRenderer
1546   {
1547     javax.swing.border.Border unselectedBorder = null;
1548
1549     javax.swing.border.Border selectedBorder = null;
1550
1551     final String baseTT = "Click to edit, right/apple click for menu.";
1552
1553     public ColorRenderer()
1554     {
1555       setOpaque(true); // MUST do this for background to show up.
1556       setHorizontalTextPosition(SwingConstants.CENTER);
1557       setVerticalTextPosition(SwingConstants.CENTER);
1558     }
1559
1560     @Override
1561     public Component getTableCellRendererComponent(JTable tbl, Object color,
1562             boolean isSelected, boolean hasFocus, int row, int column)
1563     {
1564       FeatureColourI cellColour = (FeatureColourI) color;
1565       // JLabel comp = new JLabel();
1566       // comp.
1567       setOpaque(true);
1568       // comp.
1569       // setBounds(getBounds());
1570       Color newColor;
1571       setToolTipText(baseTT);
1572       setBackground(tbl.getBackground());
1573       if (!cellColour.isSimpleColour())
1574       {
1575         Rectangle cr = tbl.getCellRect(row, column, false);
1576         FeatureSettings.renderGraduatedColor(this, cellColour,
1577                 (int) cr.getWidth(), (int) cr.getHeight());
1578
1579       }
1580       else
1581       {
1582         this.setText("");
1583         this.setIcon(null);
1584         newColor = cellColour.getColour();
1585         setBackground(newColor);
1586       }
1587       if (isSelected)
1588       {
1589         if (selectedBorder == null)
1590         {
1591           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1592                   tbl.getSelectionBackground());
1593         }
1594         setBorder(selectedBorder);
1595       }
1596       else
1597       {
1598         if (unselectedBorder == null)
1599         {
1600           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1601                   tbl.getBackground());
1602         }
1603         setBorder(unselectedBorder);
1604       }
1605
1606       return this;
1607     }
1608   }
1609
1610   /**
1611    * update comp using rendering settings from gcol
1612    * 
1613    * @param comp
1614    * @param gcol
1615    */
1616   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1617   {
1618     int w = comp.getWidth(), h = comp.getHeight();
1619     if (w < 20)
1620     {
1621       w = (int) comp.getPreferredSize().getWidth();
1622       h = (int) comp.getPreferredSize().getHeight();
1623       if (w < 20)
1624       {
1625         w = 80;
1626         h = 12;
1627       }
1628     }
1629     renderGraduatedColor(comp, gcol, w, h);
1630   }
1631
1632   public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1633           int w, int h)
1634   {
1635     boolean thr = false;
1636     String tt = "";
1637     String tx = "";
1638     if (gcol.isAboveThreshold())
1639     {
1640       thr = true;
1641       tx += ">";
1642       tt += "Thresholded (Above " + gcol.getThreshold() + ") ";
1643     }
1644     if (gcol.isBelowThreshold())
1645     {
1646       thr = true;
1647       tx += "<";
1648       tt += "Thresholded (Below " + gcol.getThreshold() + ") ";
1649     }
1650     if (gcol.isColourByLabel())
1651     {
1652       tt = "Coloured by label text. " + tt;
1653       if (thr)
1654       {
1655         tx += " ";
1656       }
1657       tx += "Label";
1658       comp.setIcon(null);
1659     }
1660     else
1661     {
1662       Color newColor = gcol.getMaxColour();
1663       comp.setBackground(newColor);
1664       // System.err.println("Width is " + w / 2);
1665       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1666       comp.setIcon(ficon);
1667       // tt+="RGB value: Max (" + newColor.getRed() + ", "
1668       // + newColor.getGreen() + ", " + newColor.getBlue()
1669       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1670       // + ", " + minCol.getBlue() + ")");
1671     }
1672     comp.setHorizontalAlignment(SwingConstants.CENTER);
1673     comp.setText(tx);
1674     if (tt.length() > 0)
1675     {
1676       if (comp.getToolTipText() == null)
1677       {
1678         comp.setToolTipText(tt);
1679       }
1680       else
1681       {
1682         comp.setToolTipText(tt + " " + comp.getToolTipText());
1683       }
1684     }
1685   }
1686 }
1687
1688 class FeatureIcon implements Icon
1689 {
1690   FeatureColourI gcol;
1691
1692   Color backg;
1693
1694   boolean midspace = false;
1695
1696   int width = 50, height = 20;
1697
1698   int s1, e1; // start and end of midpoint band for thresholded symbol
1699
1700   Color mpcolour = Color.white;
1701
1702   FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1703   {
1704     gcol = gfc;
1705     backg = bg;
1706     width = w;
1707     height = h;
1708     midspace = mspace;
1709     if (midspace)
1710     {
1711       s1 = width / 3;
1712       e1 = s1 * 2;
1713     }
1714     else
1715     {
1716       s1 = width / 2;
1717       e1 = s1;
1718     }
1719   }
1720
1721   @Override
1722   public int getIconWidth()
1723   {
1724     return width;
1725   }
1726
1727   @Override
1728   public int getIconHeight()
1729   {
1730     return height;
1731   }
1732
1733   @Override
1734   public void paintIcon(Component c, Graphics g, int x, int y)
1735   {
1736
1737     if (gcol.isColourByLabel())
1738     {
1739       g.setColor(backg);
1740       g.fillRect(0, 0, width, height);
1741       // need an icon here.
1742       g.setColor(gcol.getMaxColour());
1743
1744       g.setFont(new Font("Verdana", Font.PLAIN, 9));
1745
1746       // g.setFont(g.getFont().deriveFont(
1747       // AffineTransform.getScaleInstance(
1748       // width/g.getFontMetrics().stringWidth("Label"),
1749       // height/g.getFontMetrics().getHeight())));
1750
1751       g.drawString(MessageManager.getString("label.label"), 0, 0);
1752
1753     }
1754     else
1755     {
1756       Color minCol = gcol.getMinColour();
1757       g.setColor(minCol);
1758       g.fillRect(0, 0, s1, height);
1759       if (midspace)
1760       {
1761         g.setColor(Color.white);
1762         g.fillRect(s1, 0, e1 - s1, height);
1763       }
1764       g.setColor(gcol.getMaxColour());
1765       g.fillRect(0, e1, width - e1, height);
1766     }
1767   }
1768 }
1769
1770 class ColorEditor extends AbstractCellEditor
1771         implements TableCellEditor, ActionListener
1772 {
1773   FeatureSettings me;
1774
1775   FeatureColourI currentColor;
1776
1777   FeatureColourChooser chooser;
1778
1779   String type;
1780
1781   JButton button;
1782
1783   JColorChooser colorChooser;
1784
1785   JDialog dialog;
1786
1787   protected static final String EDIT = "edit";
1788
1789   int selectedRow = 0;
1790
1791   public ColorEditor(FeatureSettings me)
1792   {
1793     this.me = me;
1794     // Set up the editor (from the table's point of view),
1795     // which is a button.
1796     // This button brings up the color chooser dialog,
1797     // which is the editor from the user's point of view.
1798     button = new JButton();
1799     button.setActionCommand(EDIT);
1800     button.addActionListener(this);
1801     button.setBorderPainted(false);
1802     // Set up the dialog that the button brings up.
1803     colorChooser = new JColorChooser();
1804     dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
1805             colorChooser, this, // OK button handler
1806             null); // no CANCEL button handler
1807   }
1808
1809   /**
1810    * Handles events from the editor button and from the dialog's OK button.
1811    */
1812   @Override
1813   public void actionPerformed(ActionEvent e)
1814   {
1815
1816     if (EDIT.equals(e.getActionCommand()))
1817     {
1818       // The user has clicked the cell, so
1819       // bring up the dialog.
1820       if (currentColor.isSimpleColour())
1821       {
1822         // bring up simple color chooser
1823         button.setBackground(currentColor.getColour());
1824         colorChooser.setColor(currentColor.getColour());
1825         dialog.setVisible(true);
1826       }
1827       else
1828       {
1829         // bring up graduated chooser.
1830         chooser = new FeatureColourChooser(me.fr, type);
1831         chooser.setRequestFocusEnabled(true);
1832         chooser.requestFocus();
1833         chooser.addActionListener(this);
1834       }
1835       // Make the renderer reappear.
1836       fireEditingStopped();
1837
1838     }
1839     else
1840     { // User pressed dialog's "OK" button.
1841       if (currentColor.isSimpleColour())
1842       {
1843         currentColor = new FeatureColour(colorChooser.getColor());
1844       }
1845       else
1846       {
1847         currentColor = chooser.getLastColour();
1848       }
1849       me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
1850       fireEditingStopped();
1851       me.table.validate();
1852     }
1853   }
1854
1855   // Implement the one CellEditor method that AbstractCellEditor doesn't.
1856   @Override
1857   public Object getCellEditorValue()
1858   {
1859     return currentColor;
1860   }
1861
1862   // Implement the one method defined by TableCellEditor.
1863   @Override
1864   public Component getTableCellEditorComponent(JTable table, Object value,
1865           boolean isSelected, int row, int column)
1866   {
1867     currentColor = (FeatureColourI) value;
1868     this.selectedRow = row;
1869     type = me.table.getValueAt(row, 0).toString();
1870     button.setOpaque(true);
1871     button.setBackground(me.getBackground());
1872     if (!currentColor.isSimpleColour())
1873     {
1874       JLabel btn = new JLabel();
1875       btn.setSize(button.getSize());
1876       FeatureSettings.renderGraduatedColor(btn, currentColor);
1877       button.setBackground(btn.getBackground());
1878       button.setIcon(btn.getIcon());
1879       button.setText(btn.getText());
1880     }
1881     else
1882     {
1883       button.setText("");
1884       button.setIcon(null);
1885       button.setBackground(currentColor.getColour());
1886     }
1887     return button;
1888   }
1889 }