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