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