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