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