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