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