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