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