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