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