JAL-2483 use SequenceFeatures to look up groups, types, feature lengths
[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.AlignmentI;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.Help.HelpId;
29 import jalview.io.JalviewFileChooser;
30 import jalview.io.JalviewFileView;
31 import jalview.schemabinding.version2.JalviewUserColours;
32 import jalview.schemes.FeatureColour;
33 import jalview.util.Format;
34 import jalview.util.MessageManager;
35 import jalview.util.Platform;
36 import jalview.util.QuickSort;
37 import jalview.viewmodel.AlignmentViewport;
38 import jalview.ws.dbsources.das.api.jalviewSourceI;
39
40 import java.awt.BorderLayout;
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Font;
44 import java.awt.Graphics;
45 import java.awt.GridLayout;
46 import java.awt.Rectangle;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.ItemEvent;
50 import java.awt.event.ItemListener;
51 import java.awt.event.MouseAdapter;
52 import java.awt.event.MouseEvent;
53 import java.awt.event.MouseMotionAdapter;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.InputStreamReader;
60 import java.io.OutputStreamWriter;
61 import java.io.PrintWriter;
62 import java.util.Arrays;
63 import java.util.HashSet;
64 import java.util.Hashtable;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Set;
69 import java.util.Vector;
70
71 import javax.help.HelpSetException;
72 import javax.swing.AbstractCellEditor;
73 import javax.swing.BorderFactory;
74 import javax.swing.Icon;
75 import javax.swing.JButton;
76 import javax.swing.JCheckBox;
77 import javax.swing.JCheckBoxMenuItem;
78 import javax.swing.JColorChooser;
79 import javax.swing.JDialog;
80 import javax.swing.JInternalFrame;
81 import javax.swing.JLabel;
82 import javax.swing.JLayeredPane;
83 import javax.swing.JMenuItem;
84 import javax.swing.JPanel;
85 import javax.swing.JPopupMenu;
86 import javax.swing.JScrollPane;
87 import javax.swing.JSlider;
88 import javax.swing.JTabbedPane;
89 import javax.swing.JTable;
90 import javax.swing.ListSelectionModel;
91 import javax.swing.SwingConstants;
92 import javax.swing.SwingUtilities;
93 import javax.swing.event.ChangeEvent;
94 import javax.swing.event.ChangeListener;
95 import javax.swing.table.AbstractTableModel;
96 import javax.swing.table.TableCellEditor;
97 import javax.swing.table.TableCellRenderer;
98
99 public class FeatureSettings extends JPanel implements
100         FeatureSettingsControllerI
101 {
102   DasSourceBrowser dassourceBrowser;
103
104   jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
105
106   JPanel settingsPane = new JPanel();
107
108   JPanel dasSettingsPane = new JPanel();
109
110   final FeatureRenderer fr;
111
112   public final AlignFrame af;
113
114   Object[][] originalData;
115
116   private float originalTransparency;
117
118   final JInternalFrame frame;
119
120   JScrollPane scrollPane = new JScrollPane();
121
122   JTable table;
123
124   JPanel groupPanel;
125
126   JSlider transparency = new JSlider();
127
128   JPanel transPanel = new JPanel(new GridLayout(1, 2));
129
130   public FeatureSettings(AlignFrame af)
131   {
132     this.af = af;
133     fr = af.getFeatureRenderer();
134     // allow transparency to be recovered
135     transparency.setMaximum(100 - (int) ((originalTransparency = fr
136             .getTransparency()) * 100));
137
138     try
139     {
140       jbInit();
141     } catch (Exception ex)
142     {
143       ex.printStackTrace();
144     }
145
146     table = new JTable()
147     {
148       @Override
149       public String getToolTipText(MouseEvent e)
150       {
151         if (table.columnAtPoint(e.getPoint()) == 0)
152         {
153           /*
154            * Tooltip for feature name only
155            */
156           return JvSwingUtils.wrapTooltip(true, MessageManager
157                   .getString("label.feature_settings_click_drag"));
158         }
159         return null;
160       }
161     };
162     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
163     table.setFont(new Font("Verdana", Font.PLAIN, 12));
164     table.setDefaultRenderer(Color.class, new ColorRenderer());
165
166     table.setDefaultEditor(Color.class, new ColorEditor(this));
167
168     table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
169     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
170     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
171
172     table.addMouseListener(new MouseAdapter()
173     {
174       @Override
175       public void mousePressed(MouseEvent evt)
176       {
177         selectedRow = table.rowAtPoint(evt.getPoint());
178         if (evt.isPopupTrigger())
179         {
180           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
181                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
182                   evt.getX(), evt.getY());
183         }
184         else if (evt.getClickCount() == 2)
185         {
186           boolean invertSelection = evt.isAltDown();
187           boolean toggleSelection = Platform.isControlDown(evt);
188           boolean extendSelection = evt.isShiftDown();
189           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
190                   invertSelection, extendSelection, toggleSelection,
191                   (String) table.getValueAt(selectedRow, 0));
192         }
193       }
194
195       // isPopupTrigger fires on mouseReleased on Windows
196       @Override
197       public void mouseReleased(MouseEvent evt)
198       {
199         selectedRow = table.rowAtPoint(evt.getPoint());
200         if (evt.isPopupTrigger())
201         {
202           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
203                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
204                   evt.getX(), evt.getY());
205         }
206       }
207     });
208
209     table.addMouseMotionListener(new MouseMotionAdapter()
210     {
211       @Override
212       public void mouseDragged(MouseEvent evt)
213       {
214         int newRow = table.rowAtPoint(evt.getPoint());
215         if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
216         {
217           /*
218            * reposition 'selectedRow' to 'newRow' (the dragged to location)
219            * this could be more than one row away for a very fast drag action
220            * so just swap it with adjacent rows until we get it there
221            */
222           Object[][] data = ((FeatureTableModel) table.getModel())
223                   .getData();
224           int direction = newRow < selectedRow ? -1 : 1;
225           for (int i = selectedRow; i != newRow; i += direction)
226           {
227             Object[] temp = data[i];
228             data[i] = data[i + direction];
229             data[i + direction] = temp;
230           }
231           updateFeatureRenderer(data);
232           table.repaint();
233           selectedRow = newRow;
234         }
235       }
236     });
237     // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
238     // MessageManager.getString("label.feature_settings_click_drag")));
239     scrollPane.setViewportView(table);
240
241     dassourceBrowser = new DasSourceBrowser(this);
242     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
243
244     if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
245     {
246       fr.findAllFeatures(true); // display everything!
247     }
248
249     discoverAllFeatureData();
250     final PropertyChangeListener change;
251     final FeatureSettings fs = this;
252     fr.addPropertyChangeListener(change = new PropertyChangeListener()
253     {
254       @Override
255       public void propertyChange(PropertyChangeEvent evt)
256       {
257         if (!fs.resettingTable && !fs.handlingUpdate)
258         {
259           fs.handlingUpdate = true;
260           fs.resetTable(null); // new groups may be added with new seuqence
261           // feature types only
262           fs.handlingUpdate = false;
263         }
264       }
265
266     });
267
268     frame = new JInternalFrame();
269     frame.setContentPane(this);
270     if (Platform.isAMac())
271     {
272       Desktop.addInternalFrame(frame,
273               MessageManager.getString("label.sequence_feature_settings"),
274               475, 480);
275     }
276     else
277     {
278       Desktop.addInternalFrame(frame,
279               MessageManager.getString("label.sequence_feature_settings"),
280               400, 450);
281     }
282
283     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
284     {
285       @Override
286       public void internalFrameClosed(
287               javax.swing.event.InternalFrameEvent evt)
288       {
289         fr.removePropertyChangeListener(change);
290         dassourceBrowser.fs = null;
291       };
292     });
293     frame.setLayer(JLayeredPane.PALETTE_LAYER);
294   }
295
296   protected void popupSort(final int selectedRow, final String type,
297           final Object typeCol, final Map<String, float[][]> minmax, int x,
298           int y)
299   {
300     final FeatureColourI featureColour = (FeatureColourI) typeCol;
301
302     JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
303             "label.settings_for_param", new String[] { type }));
304     JMenuItem scr = new JMenuItem(
305             MessageManager.getString("label.sort_by_score"));
306     men.add(scr);
307     final FeatureSettings me = this;
308     scr.addActionListener(new ActionListener()
309     {
310
311       @Override
312       public void actionPerformed(ActionEvent e)
313       {
314         me.af.avc.sortAlignmentByFeatureScore(Arrays
315                 .asList(new String[] { type }));
316       }
317
318     });
319     JMenuItem dens = new JMenuItem(
320             MessageManager.getString("label.sort_by_density"));
321     dens.addActionListener(new ActionListener()
322     {
323
324       @Override
325       public void actionPerformed(ActionEvent e)
326       {
327         me.af.avc.sortAlignmentByFeatureDensity(Arrays
328                 .asList(new String[] { type }));
329       }
330
331     });
332     men.add(dens);
333     if (minmax != null)
334     {
335       final float[][] typeMinMax = minmax.get(type);
336       /*
337        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
338        * this is broken at the moment and isn't that useful anyway!
339        * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
340        * ActionListener() {
341        * 
342        * public void actionPerformed(ActionEvent e) {
343        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
344        * null); } else { minmax.put(type, typeMinMax); } }
345        * 
346        * });
347        * 
348        * men.add(chb);
349        */
350       if (typeMinMax != null && typeMinMax[0] != null)
351       {
352         // if (table.getValueAt(row, column));
353         // graduated colourschemes for those where minmax exists for the
354         // positional features
355         final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
356                 "Graduated Colour");
357         mxcol.setSelected(!featureColour.isSimpleColour());
358         men.add(mxcol);
359         mxcol.addActionListener(new ActionListener()
360         {
361           JColorChooser colorChooser;
362
363           @Override
364           public void actionPerformed(ActionEvent e)
365           {
366             if (e.getSource() == mxcol)
367             {
368               if (featureColour.isSimpleColour())
369               {
370                 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
371                         type);
372                 fc.addActionListener(this);
373               }
374               else
375               {
376                 // bring up simple color chooser
377                 colorChooser = new JColorChooser();
378                 JDialog dialog = JColorChooser.createDialog(me,
379                         "Select new Colour", true, // modal
380                         colorChooser, this, // OK button handler
381                         null); // no CANCEL button handler
382                 colorChooser.setColor(featureColour.getMaxColour());
383                 dialog.setVisible(true);
384               }
385             }
386             else
387             {
388               if (e.getSource() instanceof FeatureColourChooser)
389               {
390                 FeatureColourChooser fc = (FeatureColourChooser) e
391                         .getSource();
392                 table.setValueAt(fc.getLastColour(), selectedRow, 1);
393                 table.validate();
394               }
395               else
396               {
397                 // probably the color chooser!
398                 table.setValueAt(
399                         new FeatureColour(colorChooser.getColor()),
400                         selectedRow, 1);
401                 table.validate();
402                 me.updateFeatureRenderer(
403                         ((FeatureTableModel) table.getModel()).getData(),
404                         false);
405               }
406             }
407           }
408
409         });
410       }
411     }
412     JMenuItem selCols = new JMenuItem(
413             MessageManager.getString("label.select_columns_containing"));
414     selCols.addActionListener(new ActionListener()
415     {
416       @Override
417       public void actionPerformed(ActionEvent arg0)
418       {
419         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
420                 false, type);
421       }
422     });
423     JMenuItem clearCols = new JMenuItem(
424             MessageManager.getString("label.select_columns_not_containing"));
425     clearCols.addActionListener(new ActionListener()
426     {
427       @Override
428       public void actionPerformed(ActionEvent arg0)
429       {
430         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
431                 false, type);
432       }
433     });
434     JMenuItem hideCols = new JMenuItem(
435             MessageManager.getString("label.hide_columns_containing"));
436     hideCols.addActionListener(new ActionListener()
437     {
438       @Override
439       public void actionPerformed(ActionEvent arg0)
440       {
441         fr.ap.alignFrame.hideFeatureColumns(type, true);
442       }
443     });
444     JMenuItem hideOtherCols = new JMenuItem(
445             MessageManager.getString("label.hide_columns_not_containing"));
446     hideOtherCols.addActionListener(new ActionListener()
447     {
448       @Override
449       public void actionPerformed(ActionEvent arg0)
450       {
451         fr.ap.alignFrame.hideFeatureColumns(type, false);
452       }
453     });
454     men.add(selCols);
455     men.add(clearCols);
456     men.add(hideCols);
457     men.add(hideOtherCols);
458     men.show(table, x, y);
459   }
460
461   /**
462    * true when Feature Settings are updating from feature renderer
463    */
464   private boolean handlingUpdate = false;
465
466   /**
467    * holds {featureCount, totalExtent} for each feature type
468    */
469   Map<String, float[]> typeWidth = null;
470
471   @Override
472   synchronized public void discoverAllFeatureData()
473   {
474     Set<String> allGroups = new HashSet<String>();
475     AlignmentI alignment = af.getViewport().getAlignment();
476
477     for (int i = 0; i < alignment.getHeight(); i++)
478     {
479       SequenceI seq = alignment.getSequenceAt(i);
480       for (String group : seq.getFeatures().getFeatureGroups(true))
481       {
482         if (group != null && !allGroups.contains(group))
483         {
484           allGroups.add(group);
485           checkGroupState(group);
486         }
487       }
488     }
489
490     resetTable(null);
491
492     validate();
493   }
494
495   /**
496    * Synchronise gui group list and check visibility of group
497    * 
498    * @param group
499    * @return true if group is visible
500    */
501   private boolean checkGroupState(String group)
502   {
503     boolean visible = fr.checkGroupVisibility(group, true);
504
505     if (groupPanel == null)
506     {
507       groupPanel = new JPanel();
508     }
509
510     boolean alreadyAdded = false;
511     for (int g = 0; g < groupPanel.getComponentCount(); g++)
512     {
513       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
514       {
515         alreadyAdded = true;
516         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
517         break;
518       }
519     }
520
521     if (alreadyAdded)
522     {
523
524       return visible;
525     }
526     final String grp = group;
527     final JCheckBox check = new JCheckBox(group, visible);
528     check.setFont(new Font("Serif", Font.BOLD, 12));
529     check.addItemListener(new ItemListener()
530     {
531       @Override
532       public void itemStateChanged(ItemEvent evt)
533       {
534         fr.setGroupVisibility(check.getText(), check.isSelected());
535         af.alignPanel.getSeqPanel().seqCanvas.repaint();
536         if (af.alignPanel.overviewPanel != null)
537         {
538           af.alignPanel.overviewPanel.updateOverviewImage();
539         }
540
541         resetTable(new String[] { grp });
542       }
543     });
544     groupPanel.add(check);
545     return visible;
546   }
547
548   boolean resettingTable = false;
549
550   synchronized void resetTable(String[] groupChanged)
551   {
552     if (resettingTable)
553     {
554       return;
555     }
556     resettingTable = true;
557     typeWidth = new Hashtable<String, float[]>();
558     // TODO: change avWidth calculation to 'per-sequence' average and use long
559     // rather than float
560
561     Set<String> displayableTypes = new HashSet<String>();
562
563     /*
564      * determine which feature types may be visible depending on 
565      * which groups are selected, and recompute average width data
566      */
567     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
568     {
569
570       SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
571
572       /*
573        * get the sequence's groups for positional features
574        * and keep track of which groups are visible
575        */
576       Set<String> groups = seq.getFeatures().getFeatureGroups(true);
577       Set<String> visibleGroups = new HashSet<String>();
578       for (String group : groups)
579       {
580         if (group == null || checkGroupState(group))
581         {
582           visibleGroups.add(group);
583         }
584       }
585
586       /*
587        * get distinct feature types for visible groups
588        * record distinct visible types, and their count and total length
589        */
590       Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
591               visibleGroups.toArray(new String[visibleGroups.size()]));
592       for (String type : types)
593       {
594         displayableTypes.add(type);
595         float[] avWidth = typeWidth.get(type);
596         if (avWidth == null)
597         {
598           avWidth = new float[2];
599           typeWidth.put(type, avWidth);
600         }
601         // todo this could include features with a non-visible group
602         // - do we greatly care?
603         // todo should we include non-displayable features here, and only
604         // update when features are added?
605         avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
606         avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
607       }
608     }
609
610     Object[][] data = new Object[displayableTypes.size()][3];
611     int dataIndex = 0;
612
613     if (fr.hasRenderOrder())
614     {
615       if (!handlingUpdate)
616       {
617         fr.findAllFeatures(groupChanged != null); // prod to update
618         // colourschemes. but don't
619         // affect display
620         // First add the checks in the previous render order,
621         // in case the window has been closed and reopened
622       }
623       List<String> frl = fr.getRenderOrder();
624       for (int ro = frl.size() - 1; ro > -1; ro--)
625       {
626         String type = frl.get(ro);
627
628         if (!displayableTypes.contains(type))
629         {
630           continue;
631         }
632
633         data[dataIndex][0] = type;
634         data[dataIndex][1] = fr.getFeatureStyle(type);
635         data[dataIndex][2] = new Boolean(af.getViewport()
636                 .getFeaturesDisplayed().isVisible(type));
637         dataIndex++;
638         displayableTypes.remove(type);
639       }
640     }
641
642     /*
643      * process any extra features belonging only to 
644      * a group which was just selected
645      */
646     while (!displayableTypes.isEmpty())
647     {
648       String type = displayableTypes.iterator().next();
649       data[dataIndex][0] = type;
650
651       data[dataIndex][1] = fr.getFeatureStyle(type);
652       if (data[dataIndex][1] == null)
653       {
654         // "Colour has been updated in another view!!"
655         fr.clearRenderOrder();
656         return;
657       }
658
659       data[dataIndex][2] = new Boolean(true);
660       dataIndex++;
661       displayableTypes.remove(type);
662     }
663
664     if (originalData == null)
665     {
666       originalData = new Object[data.length][3];
667       for (int i = 0; i < data.length; i++)
668       {
669         System.arraycopy(data[i], 0, originalData[i], 0, 3);
670       }
671     }
672
673     table.setModel(new FeatureTableModel(data));
674     table.getColumnModel().getColumn(0).setPreferredWidth(200);
675
676     if (groupPanel != null)
677     {
678       groupPanel.setLayout(new GridLayout(
679               fr.getFeatureGroupsSize() / 4 + 1, 4));
680
681       groupPanel.validate();
682       bigPanel.add(groupPanel, BorderLayout.NORTH);
683     }
684
685     updateFeatureRenderer(data, groupChanged != null);
686     resettingTable = false;
687   }
688
689   /**
690    * reorder data based on the featureRenderers global priority list.
691    * 
692    * @param data
693    */
694   private void ensureOrder(Object[][] data)
695   {
696     boolean sort = false;
697     float[] order = new float[data.length];
698     for (int i = 0; i < order.length; i++)
699     {
700       order[i] = fr.getOrder(data[i][0].toString());
701       if (order[i] < 0)
702       {
703         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
704       }
705       if (i > 1)
706       {
707         sort = sort || order[i - 1] > order[i];
708       }
709     }
710     if (sort)
711     {
712       jalview.util.QuickSort.sort(order, data);
713     }
714   }
715
716   void load()
717   {
718     JalviewFileChooser chooser = new JalviewFileChooser("fc",
719             "Sequence Feature Colours");
720     chooser.setFileView(new JalviewFileView());
721     chooser.setDialogTitle(MessageManager
722             .getString("label.load_feature_colours"));
723     chooser.setToolTipText(MessageManager.getString("action.load"));
724
725     int value = chooser.showOpenDialog(this);
726
727     if (value == JalviewFileChooser.APPROVE_OPTION)
728     {
729       File file = chooser.getSelectedFile();
730
731       try
732       {
733         InputStreamReader in = new InputStreamReader(new FileInputStream(
734                 file), "UTF-8");
735
736         JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
737
738         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
739         {
740           String name;
741           jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
742           if (newcol.hasMax())
743           {
744             Color mincol = null, maxcol = null;
745             try
746             {
747               mincol = new Color(Integer.parseInt(newcol.getMinRGB(), 16));
748               maxcol = new Color(Integer.parseInt(newcol.getRGB(), 16));
749
750             } catch (Exception e)
751             {
752               Cache.log.warn("Couldn't parse out graduated feature color.",
753                       e);
754             }
755             FeatureColourI gcol = new FeatureColour(mincol, maxcol,
756                     newcol.getMin(), newcol.getMax());
757             if (newcol.hasAutoScale())
758             {
759               gcol.setAutoScaled(newcol.getAutoScale());
760             }
761             if (newcol.hasColourByLabel())
762             {
763               gcol.setColourByLabel(newcol.getColourByLabel());
764             }
765             if (newcol.hasThreshold())
766             {
767               gcol.setThreshold(newcol.getThreshold());
768             }
769             if (newcol.getThreshType().length() > 0)
770             {
771               String ttyp = newcol.getThreshType();
772               if (ttyp.equalsIgnoreCase("ABOVE"))
773               {
774                 gcol.setAboveThreshold(true);
775               }
776               if (ttyp.equalsIgnoreCase("BELOW"))
777               {
778                 gcol.setBelowThreshold(true);
779               }
780             }
781             fr.setColour(name = newcol.getName(), gcol);
782           }
783           else
784           {
785             Color color = new Color(
786                     Integer.parseInt(jucs.getColour(i).getRGB(), 16));
787             fr.setColour(name = jucs.getColour(i).getName(),
788                     new FeatureColour(color));
789           }
790           fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
791         }
792         if (table != null)
793         {
794           resetTable(null);
795           Object[][] data = ((FeatureTableModel) table.getModel())
796                   .getData();
797           ensureOrder(data);
798           updateFeatureRenderer(data, false);
799           table.repaint();
800         }
801       } catch (Exception ex)
802       {
803         System.out.println("Error loading User Colour File\n" + ex);
804       }
805     }
806   }
807
808   void save()
809   {
810     JalviewFileChooser chooser = new JalviewFileChooser("fc",
811             "Sequence Feature Colours");
812     chooser.setFileView(new JalviewFileView());
813     chooser.setDialogTitle(MessageManager
814             .getString("label.save_feature_colours"));
815     chooser.setToolTipText(MessageManager.getString("action.save"));
816
817     int value = chooser.showSaveDialog(this);
818
819     if (value == JalviewFileChooser.APPROVE_OPTION)
820     {
821       String choice = chooser.getSelectedFile().getPath();
822       jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
823       ucs.setSchemeName("Sequence Features");
824       try
825       {
826         PrintWriter out = new PrintWriter(new OutputStreamWriter(
827                 new FileOutputStream(choice), "UTF-8"));
828
829         Set<String> fr_colours = fr.getAllFeatureColours();
830         Iterator<String> e = fr_colours.iterator();
831         float[] sortOrder = new float[fr_colours.size()];
832         String[] sortTypes = new String[fr_colours.size()];
833         int i = 0;
834         while (e.hasNext())
835         {
836           sortTypes[i] = e.next();
837           sortOrder[i] = fr.getOrder(sortTypes[i]);
838           i++;
839         }
840         QuickSort.sort(sortOrder, sortTypes);
841         sortOrder = null;
842         for (i = 0; i < sortTypes.length; i++)
843         {
844           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
845           col.setName(sortTypes[i]);
846           FeatureColourI fcol = fr.getFeatureStyle(sortTypes[i]);
847           if (fcol.isSimpleColour())
848           {
849             col.setRGB(Format.getHexString(fcol.getColour()));
850           }
851           else
852           {
853             col.setRGB(Format.getHexString(fcol.getMaxColour()));
854             col.setMin(fcol.getMin());
855             col.setMax(fcol.getMax());
856             col.setMinRGB(jalview.util.Format.getHexString(fcol
857                     .getMinColour()));
858             col.setAutoScale(fcol.isAutoScaled());
859             col.setThreshold(fcol.getThreshold());
860             col.setColourByLabel(fcol.isColourByLabel());
861             col.setThreshType(fcol.isAboveThreshold() ? "ABOVE" : (fcol
862                     .isBelowThreshold() ? "BELOW" : "NONE"));
863           }
864           ucs.addColour(col);
865         }
866         ucs.marshal(out);
867         out.close();
868       } catch (Exception ex)
869       {
870         ex.printStackTrace();
871       }
872     }
873   }
874
875   public void invertSelection()
876   {
877     for (int i = 0; i < table.getRowCount(); i++)
878     {
879       Boolean value = (Boolean) table.getValueAt(i, 2);
880
881       table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
882     }
883   }
884
885   public void orderByAvWidth()
886   {
887     if (table == null || table.getModel() == null)
888     {
889       return;
890     }
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 = 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       {
913         max = width[i];
914       }
915     }
916     boolean sort = false;
917     for (int i = 0; i < width.length; i++)
918     {
919       // awidth = (float[]) typeWidth.get(data[i][0]);
920       if (width[i] == 0)
921       {
922         width[i] = fr.getOrder(data[i][0].toString());
923         if (width[i] < 0)
924         {
925           width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
926         }
927       }
928       else
929       {
930         width[i] /= max; // normalize
931         fr.setOrder(data[i][0].toString(), width[i]); // store for later
932       }
933       if (i > 0)
934       {
935         sort = sort || width[i - 1] > width[i];
936       }
937     }
938     if (sort)
939     {
940       jalview.util.QuickSort.sort(width, data);
941       // update global priority order
942     }
943
944     updateFeatureRenderer(data, false);
945     table.repaint();
946   }
947
948   public void close()
949   {
950     try
951     {
952       frame.setClosed(true);
953     } catch (Exception exe)
954     {
955     }
956
957   }
958
959   public void updateFeatureRenderer(Object[][] data)
960   {
961     updateFeatureRenderer(data, true);
962   }
963
964   /**
965    * Update the priority order of features; only repaint if this changed the
966    * order of visible features
967    * 
968    * @param data
969    * @param visibleNew
970    */
971   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
972   {
973     if (fr.setFeaturePriority(data, visibleNew))
974     {
975       af.alignPanel.paintAlignment(true);
976     }
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     JvOptionPane
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                     JvOptionPane.DEFAULT_OPTION,
1375                     JvOptionPane.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 }