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