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