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