gui/debug: integrate graduated feature colour editor into amend feature and feature...
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.gui;
20
21 import java.io.*;
22 import java.util.*;
23
24 import java.awt.*;
25 import java.awt.event.*;
26 import java.awt.geom.AffineTransform;
27 import java.awt.image.BufferedImage;
28 import java.beans.PropertyChangeEvent;
29 import java.beans.PropertyChangeListener;
30
31 import javax.swing.*;
32 import javax.swing.event.*;
33 import javax.swing.table.*;
34
35 import jalview.analysis.AlignmentSorter;
36 import jalview.commands.OrderCommand;
37 import jalview.datamodel.*;
38 import jalview.io.*;
39 import jalview.schemes.AnnotationColourGradient;
40 import jalview.schemes.GraduatedColor;
41
42 public class FeatureSettings extends JPanel
43 {
44   DasSourceBrowser dassourceBrowser;
45
46   jalview.ws.DasSequenceFeatureFetcher dasFeatureFetcher;
47
48   JPanel settingsPane = new JPanel();
49
50   JPanel dasSettingsPane = new JPanel();
51
52   final FeatureRenderer fr;
53
54   public final AlignFrame af;
55
56   Object[][] originalData;
57
58   final JInternalFrame frame;
59
60   JScrollPane scrollPane = new JScrollPane();
61
62   JTable table;
63
64   JPanel groupPanel;
65
66   JSlider transparency = new JSlider();
67
68   JPanel transPanel = new JPanel(new GridLayout(1, 2));
69
70   public FeatureSettings(AlignFrame af)
71   {
72     this.af = af;
73     fr = af.getFeatureRenderer();
74
75     transparency.setMaximum(100 - (int) (fr.transparency * 100));
76
77     try
78     {
79       jbInit();
80     } catch (Exception ex)
81     {
82       ex.printStackTrace();
83     }
84
85     table = new JTable();
86     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
87     table.setFont(new Font("Verdana", Font.PLAIN, 12));
88     table.setDefaultRenderer(Color.class, new ColorRenderer());
89
90     table.setDefaultEditor(Color.class, new ColorEditor(this));
91
92     table.setDefaultEditor(GraduatedColor.class, new ColorEditor(this));
93     table.setDefaultRenderer(GraduatedColor.class, new ColorRenderer());
94     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
95
96     table.addMouseListener(new MouseAdapter()
97     {
98       public void mousePressed(MouseEvent evt)
99       {
100         selectedRow = table.rowAtPoint(evt.getPoint());
101         if (javax.swing.SwingUtilities.isRightMouseButton(evt))
102         {
103           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
104                   table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(),
105                   evt.getY());
106         }
107       }
108     });
109
110     table.addMouseMotionListener(new MouseMotionAdapter()
111     {
112       public void mouseDragged(MouseEvent evt)
113       {
114         int newRow = table.rowAtPoint(evt.getPoint());
115         if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
116         {
117           Object[] temp = new Object[3];
118           temp[0] = table.getValueAt(selectedRow, 0);
119           temp[1] = table.getValueAt(selectedRow, 1);
120           temp[2] = table.getValueAt(selectedRow, 2);
121
122           table.setValueAt(table.getValueAt(newRow, 0), selectedRow, 0);
123           table.setValueAt(table.getValueAt(newRow, 1), selectedRow, 1);
124           table.setValueAt(table.getValueAt(newRow, 2), selectedRow, 2);
125
126           table.setValueAt(temp[0], newRow, 0);
127           table.setValueAt(temp[1], newRow, 1);
128           table.setValueAt(temp[2], newRow, 2);
129
130           selectedRow = newRow;
131         }
132       }
133     });
134
135     scrollPane.setViewportView(table);
136
137     dassourceBrowser = new DasSourceBrowser();
138     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
139
140     if (af.getViewport().featuresDisplayed == null
141             || fr.renderOrder == null)
142     {
143       fr.findAllFeatures(true); // display everything!
144     }
145
146     setTableData();
147     final PropertyChangeListener change;
148     final FeatureSettings fs = this;
149     fr.addPropertyChangeListener(change = new PropertyChangeListener()
150     {
151       public void propertyChange(PropertyChangeEvent evt)
152       {
153         if (!fs.resettingTable && !fs.handlingUpdate)
154         {
155           fs.handlingUpdate = true;
156           fs.resetTable(null); // new groups may be added with new seuqence
157           // feature types only
158           fs.handlingUpdate = false;
159         }
160       }
161
162     });
163
164     frame = new JInternalFrame();
165     frame.setContentPane(this);
166     Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 450);
167     frame
168             .addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
169             {
170               public void internalFrameClosed(
171                       javax.swing.event.InternalFrameEvent evt)
172               {
173                 fr.removePropertyChangeListener(change);
174               };
175             });
176     frame.setLayer(JLayeredPane.PALETTE_LAYER);
177   }
178
179   protected void popupSort(final int selectedRow, final String type,
180           final Object typeCol, final Hashtable minmax, int x, int y)
181   {
182     JPopupMenu men = new JPopupMenu("Settings for " + type);
183     JMenuItem scr = new JMenuItem("Sort by Score");
184     men.add(scr);
185     final FeatureSettings me = this;
186     scr.addActionListener(new ActionListener()
187     {
188
189       public void actionPerformed(ActionEvent e)
190       {
191         me.sortByScore(new String[]
192         { type });
193       }
194
195     });
196     JMenuItem dens = new JMenuItem("Sort by Density");
197     dens.addActionListener(new ActionListener()
198     {
199
200       public void actionPerformed(ActionEvent e)
201       {
202         me.sortByDens(new String[]
203         { type });
204       }
205
206     });
207     men.add(dens);
208     if (minmax != null)
209     {
210       final Object typeMinMax = minmax.get(type);
211       /*
212        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
213        * this is broken at the moment and isn't that useful anyway!
214        * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
215        * ActionListener() {
216        * 
217        * public void actionPerformed(ActionEvent e) {
218        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
219        * null); } else { minmax.put(type, typeMinMax); } }
220        * 
221        * });
222        * 
223        * men.add(chb);
224        */
225       if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
226       {
227         // if (table.getValueAt(row, column));
228         // graduated colourschemes for those where minmax exists for the
229         // positional features
230         final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
231                 "Graduated Colour");
232         mxcol.setSelected(!(typeCol instanceof Color));
233         men.add(mxcol);
234         mxcol.addActionListener(new ActionListener()
235         {
236           JColorChooser colorChooser;
237
238           public void actionPerformed(ActionEvent e)
239           {
240             if (e.getSource() == mxcol)
241             {
242               if (typeCol instanceof Color)
243               {
244                 FeatureColourChooser fc = new FeatureColourChooser(me.fr,
245                         type);
246                 fc.addActionListener(this);
247               }
248               else
249               {
250                 // bring up simple color chooser
251                 colorChooser = new JColorChooser();
252                 JDialog dialog = JColorChooser.createDialog(me,
253                         "Select new Colour", true, // modal
254                         colorChooser, this, // OK button handler
255                         null); // no CANCEL button handler
256                 colorChooser.setColor(((GraduatedColor) typeCol)
257                         .getMaxColor());
258                 dialog.setVisible(true);
259               }
260             }
261             else
262             {
263               if (e.getSource() instanceof FeatureColourChooser)
264               {
265                 FeatureColourChooser fc = (FeatureColourChooser) e
266                         .getSource();
267                 table.setValueAt(fc.getLastColour(), selectedRow, 1);
268                 table.validate();
269               }
270               else
271               {
272                 // probably the color chooser!
273                 table.setValueAt(colorChooser.getColor(), selectedRow, 1);
274                 table.validate();
275                 me.updateFeatureRenderer(((FeatureTableModel) table
276                         .getModel()).getData(), false);
277               }
278             }
279           }
280
281         });
282       }
283     }
284     men.show(table, x, y);
285   }
286
287   /**
288    * true when Feature Settings are updating from feature renderer
289    */
290   private boolean handlingUpdate = false;
291
292   /**
293    * contains a float[3] for each feature type string. created by setTableData
294    */
295   Hashtable typeWidth = null;
296
297   synchronized public void setTableData()
298   {
299     if (fr.featureGroups == null)
300     {
301       fr.featureGroups = new Hashtable();
302     }
303     Vector allFeatures = new Vector();
304     Vector allGroups = new Vector();
305     SequenceFeature[] tmpfeatures;
306     String group;
307     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
308     {
309       if (af.getViewport().alignment.getSequenceAt(i).getDatasetSequence()
310               .getSequenceFeatures() == null)
311       {
312         continue;
313       }
314
315       tmpfeatures = af.getViewport().alignment.getSequenceAt(i)
316               .getDatasetSequence().getSequenceFeatures();
317
318       int index = 0;
319       while (index < tmpfeatures.length)
320       {
321         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
322         {
323           index++;
324           continue;
325         }
326
327         if (tmpfeatures[index].getFeatureGroup() != null)
328         {
329           group = tmpfeatures[index].featureGroup;
330           if (!allGroups.contains(group))
331           {
332             allGroups.addElement(group);
333             if (group != null)
334             {
335               checkGroupState(group);
336             }
337           }
338         }
339
340         if (!allFeatures.contains(tmpfeatures[index].getType()))
341         {
342           allFeatures.addElement(tmpfeatures[index].getType());
343         }
344         index++;
345       }
346     }
347
348     resetTable(null);
349
350     validate();
351   }
352
353   /**
354    * 
355    * @param group
356    * @return true if group has been seen before and is already added to set.
357    */
358   private boolean checkGroupState(String group)
359   {
360     boolean visible;
361     if (fr.featureGroups.containsKey(group))
362     {
363       visible = ((Boolean) fr.featureGroups.get(group)).booleanValue();
364     }
365     else
366     {
367       visible = true; // new group is always made visible
368     }
369
370     if (groupPanel == null)
371     {
372       groupPanel = new JPanel();
373     }
374
375     boolean alreadyAdded = false;
376     for (int g = 0; g < groupPanel.getComponentCount(); g++)
377     {
378       if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
379       {
380         alreadyAdded = true;
381         ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
382         break;
383       }
384     }
385
386     if (alreadyAdded)
387     {
388
389       return true;
390     }
391
392     fr.featureGroups.put(group, new Boolean(visible));
393     final String grp = group;
394     final JCheckBox check = new JCheckBox(group, visible);
395     check.setFont(new Font("Serif", Font.BOLD, 12));
396     check.addItemListener(new ItemListener()
397     {
398       public void itemStateChanged(ItemEvent evt)
399       {
400         fr.featureGroups.put(check.getText(), new Boolean(check
401                 .isSelected()));
402         af.alignPanel.seqPanel.seqCanvas.repaint();
403         if (af.alignPanel.overviewPanel != null)
404         {
405           af.alignPanel.overviewPanel.updateOverviewImage();
406         }
407
408         resetTable(new String[]
409         { grp });
410       }
411     });
412     groupPanel.add(check);
413     return false;
414   }
415
416   boolean resettingTable = false;
417
418   synchronized void resetTable(String[] groupChanged)
419   {
420     if (resettingTable == true)
421     {
422       return;
423     }
424     resettingTable = true;
425     typeWidth = new Hashtable();
426     // TODO: change avWidth calculation to 'per-sequence' average and use long
427     // rather than float
428     float[] avWidth = null;
429     SequenceFeature[] tmpfeatures;
430     String group = null, type;
431     Vector visibleChecks = new Vector();
432
433     // Find out which features should be visible depending on which groups
434     // are selected / deselected
435     // and recompute average width ordering
436     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
437     {
438
439       tmpfeatures = af.getViewport().alignment.getSequenceAt(i)
440               .getDatasetSequence().getSequenceFeatures();
441       if (tmpfeatures == null)
442       {
443         continue;
444       }
445
446       int index = 0;
447       while (index < tmpfeatures.length)
448       {
449         group = tmpfeatures[index].featureGroup;
450
451         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
452         {
453           index++;
454           continue;
455         }
456
457         if (group == null || fr.featureGroups.get(group) == null
458                 || ((Boolean) fr.featureGroups.get(group)).booleanValue())
459         {
460           if (group != null)
461             checkGroupState(group);
462           type = tmpfeatures[index].getType();
463           if (!visibleChecks.contains(type))
464           {
465             visibleChecks.addElement(type);
466           }
467         }
468         if (!typeWidth.containsKey(tmpfeatures[index].getType()))
469         {
470           typeWidth.put(tmpfeatures[index].getType(),
471                   avWidth = new float[3]);
472         }
473         else
474         {
475           avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
476         }
477         avWidth[0]++;
478         if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
479         {
480           avWidth[1] += 1 + tmpfeatures[index].getBegin()
481                   - tmpfeatures[index].getEnd();
482         }
483         else
484         {
485           avWidth[1] += 1 + tmpfeatures[index].getEnd()
486                   - tmpfeatures[index].getBegin();
487         }
488         index++;
489       }
490     }
491
492     int fSize = visibleChecks.size();
493     Object[][] data = new Object[fSize][3];
494     int dataIndex = 0;
495
496     if (fr.renderOrder != null)
497     {
498       if (!handlingUpdate)
499         fr.findAllFeatures(groupChanged != null); // prod to update
500       // colourschemes. but don't
501       // affect display
502       // First add the checks in the previous render order,
503       // in case the window has been closed and reopened
504       for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
505       {
506         type = fr.renderOrder[ro];
507
508         if (!visibleChecks.contains(type))
509         {
510           continue;
511         }
512
513         data[dataIndex][0] = type;
514         data[dataIndex][1] = fr.getFeatureStyle(type);
515         data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed
516                 .containsKey(type));
517         dataIndex++;
518         visibleChecks.removeElement(type);
519       }
520     }
521
522     fSize = visibleChecks.size();
523     for (int i = 0; i < fSize; i++)
524     {
525       // These must be extra features belonging to the group
526       // which was just selected
527       type = visibleChecks.elementAt(i).toString();
528       data[dataIndex][0] = type;
529
530       data[dataIndex][1] = fr.getFeatureStyle(type);
531       if (data[dataIndex][1] == null)
532       {
533         // "Colour has been updated in another view!!"
534         fr.renderOrder = null;
535         return;
536       }
537
538       data[dataIndex][2] = new Boolean(true);
539       dataIndex++;
540     }
541
542     if (originalData == null)
543     {
544       originalData = new Object[data.length][3];
545       for (int i = 0; i < data.length; i++)
546       {
547         System.arraycopy(data[i], 0, originalData[i], 0, 3);
548       }
549     }
550
551     table.setModel(new FeatureTableModel(data));
552     table.getColumnModel().getColumn(0).setPreferredWidth(200);
553
554     if (groupPanel != null)
555     {
556       groupPanel.setLayout(new GridLayout(fr.featureGroups.size() / 4 + 1,
557               4));
558
559       groupPanel.validate();
560       bigPanel.add(groupPanel, BorderLayout.NORTH);
561     }
562
563     updateFeatureRenderer(data, groupChanged != null);
564     resettingTable = false;
565   }
566
567   /**
568    * reorder data based on the featureRenderers global priority list.
569    * 
570    * @param data
571    */
572   private void ensureOrder(Object[][] data)
573   {
574     boolean sort = false;
575     float[] order = new float[data.length];
576     for (int i = 0; i < order.length; i++)
577     {
578       order[i] = fr.getOrder(data[i][0].toString());
579       if (order[i] < 0)
580         order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
581       if (i > 1)
582         sort = sort || order[i - 1] > order[i];
583     }
584     if (sort)
585       jalview.util.QuickSort.sort(order, data);
586   }
587
588   void load()
589   {
590     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
591             .getProperty("LAST_DIRECTORY"), new String[]
592     { "fc" }, new String[]
593     { "Sequence Feature Colours" }, "Sequence Feature Colours");
594     chooser.setFileView(new jalview.io.JalviewFileView());
595     chooser.setDialogTitle("Load Feature Colours");
596     chooser.setToolTipText("Load");
597
598     int value = chooser.showOpenDialog(this);
599
600     if (value == JalviewFileChooser.APPROVE_OPTION)
601     {
602       File file = chooser.getSelectedFile();
603
604       try
605       {
606         InputStreamReader in = new InputStreamReader(new FileInputStream(
607                 file), "UTF-8");
608
609         jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
610         jucs = (jalview.binding.JalviewUserColours) jucs.unmarshal(in);
611
612         for (int i = jucs.getColourCount() - 1; i >= 0; i--)
613         {
614           String name;
615           // TODO: update Colour XML
616           fr.setColour(name = jucs.getColour(i).getName(), new Color(
617                   Integer.parseInt(jucs.getColour(i).getRGB(), 16)));
618           fr.setOrder(name, (i == 0) ? 0 : i / jucs.getColourCount());
619         }
620         if (table != null)
621         {
622           resetTable(null);
623           Object[][] data = ((FeatureTableModel) table.getModel())
624                   .getData();
625           ensureOrder(data);
626           updateFeatureRenderer(data, false);
627           table.repaint();
628         }
629       } catch (Exception ex)
630       {
631         System.out.println("Error loading User Colour File\n" + ex);
632       }
633     }
634   }
635
636   void save()
637   {
638     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
639             .getProperty("LAST_DIRECTORY"), new String[]
640     { "fc" }, new String[]
641     { "Sequence Feature Colours" }, "Sequence Feature Colours");
642     chooser.setFileView(new jalview.io.JalviewFileView());
643     chooser.setDialogTitle("Save Feature Colour Scheme");
644     chooser.setToolTipText("Save");
645
646     int value = chooser.showSaveDialog(this);
647
648     if (value == JalviewFileChooser.APPROVE_OPTION)
649     {
650       String choice = chooser.getSelectedFile().getPath();
651       jalview.binding.JalviewUserColours ucs = new jalview.binding.JalviewUserColours();
652       ucs.setSchemeName("Sequence Features");
653       try
654       {
655         PrintWriter out = new PrintWriter(new OutputStreamWriter(
656                 new FileOutputStream(choice), "UTF-8"));
657
658         Enumeration e = fr.featureColours.keys();
659         float[] sortOrder = new float[fr.featureColours.size()];
660         String[] sortTypes = new String[fr.featureColours.size()];
661         int i = 0;
662         while (e.hasMoreElements())
663         {
664           sortTypes[i] = e.nextElement().toString();
665           sortOrder[i] = fr.getOrder(sortTypes[i]);
666           i++;
667         }
668         jalview.util.QuickSort.sort(sortOrder, sortTypes);
669         sortOrder = null;
670         for (i = 0; i < sortTypes.length; i++)
671         {
672           jalview.binding.Colour col = new jalview.binding.Colour();
673           col.setName(sortTypes[i]);
674           col.setRGB(jalview.util.Format.getHexString(fr.getColour(col
675                   .getName())));
676           ucs.addColour(col);
677         }
678         ucs.marshal(out);
679         out.close();
680       } catch (Exception ex)
681       {
682         ex.printStackTrace();
683       }
684     }
685   }
686
687   public void invertSelection()
688   {
689     for (int i = 0; i < table.getRowCount(); i++)
690     {
691       Boolean value = (Boolean) table.getValueAt(i, 2);
692
693       table.setValueAt(new Boolean(!value.booleanValue()), i, 2);
694     }
695   }
696
697   public void orderByAvWidth()
698   {
699     if (table == null || table.getModel() == null)
700       return;
701     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
702     float[] width = new float[data.length];
703     float[] awidth;
704     float max = 0;
705     int num = 0;
706     for (int i = 0; i < data.length; i++)
707     {
708       awidth = (float[]) typeWidth.get(data[i][0]);
709       if (awidth[0] > 0)
710       {
711         width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
712         // weight - but have to make per
713         // sequence, too (awidth[2])
714         // if (width[i]==1) // hack to distinguish single width sequences.
715         num++;
716       }
717       else
718       {
719         width[i] = 0;
720       }
721       if (max < width[i])
722         max = width[i];
723     }
724     boolean sort = false;
725     for (int i = 0; i < width.length; i++)
726     {
727       // awidth = (float[]) typeWidth.get(data[i][0]);
728       if (width[i] == 0)
729       {
730         width[i] = fr.getOrder(data[i][0].toString());
731         if (width[i] < 0)
732         {
733           width[i] = fr.setOrder(data[i][0].toString(), i / data.length);
734         }
735       }
736       else
737       {
738         width[i] /= max; // normalize
739         fr.setOrder(data[i][0].toString(), width[i]); // store for later
740       }
741       if (i > 0)
742         sort = sort || width[i - 1] > width[i];
743     }
744     if (sort)
745       jalview.util.QuickSort.sort(width, data);
746     // update global priority order
747
748     updateFeatureRenderer(data, false);
749     table.repaint();
750   }
751
752   public void close()
753   {
754     try
755     {
756       frame.setClosed(true);
757     } catch (Exception exe)
758     {
759     }
760
761   }
762
763   public void updateFeatureRenderer(Object[][] data)
764   {
765     updateFeatureRenderer(data, true);
766   }
767
768   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
769   {
770     fr.setFeaturePriority(data, visibleNew);
771     af.alignPanel.paintAlignment(true);
772   }
773
774   int selectedRow = -1;
775
776   JTabbedPane tabbedPane = new JTabbedPane();
777
778   BorderLayout borderLayout1 = new BorderLayout();
779
780   BorderLayout borderLayout2 = new BorderLayout();
781
782   BorderLayout borderLayout3 = new BorderLayout();
783
784   JPanel bigPanel = new JPanel();
785
786   BorderLayout borderLayout4 = new BorderLayout();
787
788   JButton invert = new JButton();
789
790   JPanel buttonPanel = new JPanel();
791
792   JButton cancel = new JButton();
793
794   JButton ok = new JButton();
795
796   JButton loadColours = new JButton();
797
798   JButton saveColours = new JButton();
799
800   JPanel dasButtonPanel = new JPanel();
801
802   JButton fetchDAS = new JButton();
803
804   JButton saveDAS = new JButton();
805
806   JButton cancelDAS = new JButton();
807
808   JButton optimizeOrder = new JButton();
809
810   JButton sortByScore = new JButton();
811
812   JButton sortByDens = new JButton();
813
814   JPanel transbuttons = new JPanel(new GridLayout(4, 1));
815
816   private void jbInit() throws Exception
817   {
818     this.setLayout(borderLayout1);
819     settingsPane.setLayout(borderLayout2);
820     dasSettingsPane.setLayout(borderLayout3);
821     bigPanel.setLayout(borderLayout4);
822     invert.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
823     invert.setText("Invert Selection");
824     invert.addActionListener(new ActionListener()
825     {
826       public void actionPerformed(ActionEvent e)
827       {
828         invertSelection();
829       }
830     });
831     optimizeOrder.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
832     optimizeOrder.setText("Optimise Order");
833     optimizeOrder.addActionListener(new ActionListener()
834     {
835       public void actionPerformed(ActionEvent e)
836       {
837         orderByAvWidth();
838       }
839     });
840     sortByScore.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
841     sortByScore.setText("Seq sort by Score");
842     sortByScore.addActionListener(new ActionListener()
843     {
844       public void actionPerformed(ActionEvent e)
845       {
846         sortByScore(null);
847       }
848     });
849     sortByDens.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
850     sortByDens.setText("Seq Sort by density");
851     sortByDens.addActionListener(new ActionListener()
852     {
853       public void actionPerformed(ActionEvent e)
854       {
855         sortByDens(null);
856       }
857     });
858     cancel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
859     cancel.setText("Cancel");
860     cancel.addActionListener(new ActionListener()
861     {
862       public void actionPerformed(ActionEvent e)
863       {
864         updateFeatureRenderer(originalData);
865         close();
866       }
867     });
868     ok.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
869     ok.setText("OK");
870     ok.addActionListener(new ActionListener()
871     {
872       public void actionPerformed(ActionEvent e)
873       {
874         close();
875       }
876     });
877     loadColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
878     loadColours.setText("Load Colours");
879     loadColours.addActionListener(new ActionListener()
880     {
881       public void actionPerformed(ActionEvent e)
882       {
883         load();
884       }
885     });
886     saveColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
887     saveColours.setText("Save Colours");
888     saveColours.addActionListener(new ActionListener()
889     {
890       public void actionPerformed(ActionEvent e)
891       {
892         save();
893       }
894     });
895     transparency.addChangeListener(new ChangeListener()
896     {
897       public void stateChanged(ChangeEvent evt)
898       {
899         fr.setTransparency((float) (100 - transparency.getValue()) / 100f);
900         af.alignPanel.paintAlignment(true);
901       }
902     });
903
904     transparency.setMaximum(70);
905     fetchDAS.setText("Fetch DAS Features");
906     fetchDAS.addActionListener(new ActionListener()
907     {
908       public void actionPerformed(ActionEvent e)
909       {
910         fetchDAS_actionPerformed(e);
911       }
912     });
913     saveDAS.setText("Save as default");
914     saveDAS.addActionListener(new ActionListener()
915     {
916       public void actionPerformed(ActionEvent e)
917       {
918         saveDAS_actionPerformed(e);
919       }
920     });
921     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
922     dasSettingsPane.setBorder(null);
923     cancelDAS.setEnabled(false);
924     cancelDAS.setText("Cancel Fetch");
925     cancelDAS.addActionListener(new ActionListener()
926     {
927       public void actionPerformed(ActionEvent e)
928       {
929         cancelDAS_actionPerformed(e);
930       }
931     });
932     this.add(tabbedPane, java.awt.BorderLayout.CENTER);
933     tabbedPane.addTab("Feature Settings", settingsPane);
934     tabbedPane.addTab("DAS Settings", dasSettingsPane);
935     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
936     transbuttons.add(optimizeOrder);
937     transbuttons.add(invert);
938     transbuttons.add(sortByScore);
939     transbuttons.add(sortByDens);
940     transPanel.add(transparency);
941     transPanel.add(transbuttons);
942     buttonPanel.add(ok);
943     buttonPanel.add(cancel);
944     buttonPanel.add(loadColours);
945     buttonPanel.add(saveColours);
946     bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
947     dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
948     dasButtonPanel.add(fetchDAS);
949     dasButtonPanel.add(cancelDAS);
950     dasButtonPanel.add(saveDAS);
951     settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
952     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
953   }
954
955   protected void sortByDens(String[] typ)
956   {
957     sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY);
958   }
959
960   protected void sortBy(String[] typ, String methodText, final String method)
961   {
962     if (typ == null)
963     {
964       typ = getDisplayedFeatureTypes();
965     }
966     String gps[] = null;
967     gps = getDisplayedFeatureGroups();
968     if (typ != null)
969     {
970       for (int i = 0; i < typ.length; i++)
971       {
972         System.err.println("Sorting on Types:" + typ[i]);
973       }
974     }
975     if (gps != null)
976     {
977
978       for (int i = 0; i < gps.length; i++)
979       {
980         System.err.println("Sorting on groups:" + gps[i]);
981       }
982     }
983     AlignmentPanel alignPanel = af.alignPanel;
984     AlignmentI al = alignPanel.av.getAlignment();
985
986     int start, stop;
987     SequenceGroup sg = alignPanel.av.getSelectionGroup();
988     if (sg != null)
989     {
990       start = sg.getStartRes();
991       stop = sg.getEndRes();
992     }
993     else
994     {
995       start = 0;
996       stop = al.getWidth();
997     }
998     SequenceI[] oldOrder = al.getSequencesArray();
999     AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
1000     af.addHistoryItem(new OrderCommand(methodText, oldOrder, alignPanel.av
1001             .getAlignment()));
1002     alignPanel.paintAlignment(true);
1003
1004   }
1005
1006   protected void sortByScore(String[] typ)
1007   {
1008     sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE);
1009   }
1010
1011   private String[] getDisplayedFeatureTypes()
1012   {
1013     String[] typ = null;
1014     if (fr != null)
1015     {
1016       synchronized (fr.renderOrder)
1017       {
1018         typ = new String[fr.renderOrder.length];
1019         System.arraycopy(fr.renderOrder, 0, typ, 0, typ.length);
1020         for (int i = 0; i < typ.length; i++)
1021         {
1022           if (af.viewport.featuresDisplayed.get(typ[i]) == null)
1023           {
1024             typ[i] = null;
1025           }
1026         }
1027       }
1028     }
1029     return typ;
1030   }
1031
1032   private String[] getDisplayedFeatureGroups()
1033   {
1034     String[] gps = null;
1035     if (fr != null)
1036     {
1037
1038       if (fr.featureGroups != null)
1039       {
1040         Enumeration en = fr.featureGroups.keys();
1041         gps = new String[fr.featureColours.size()];
1042         int g = 0;
1043         boolean valid = false;
1044         while (en.hasMoreElements())
1045         {
1046           String gp = (String) en.nextElement();
1047           Boolean on = (Boolean) fr.featureGroups.get(gp);
1048           if (on != null && on.booleanValue())
1049           {
1050             valid = true;
1051             gps[g++] = gp;
1052           }
1053         }
1054         while (g < gps.length)
1055         {
1056           gps[g++] = null;
1057         }
1058         if (!valid)
1059         {
1060           return null;
1061         }
1062       }
1063     }
1064     return gps;
1065   }
1066
1067   public void fetchDAS_actionPerformed(ActionEvent e)
1068   {
1069     fetchDAS.setEnabled(false);
1070     cancelDAS.setEnabled(true);
1071     Vector selectedSources = dassourceBrowser.getSelectedSources();
1072     doDasFeatureFetch(selectedSources, true, true);
1073   }
1074
1075   /**
1076    * get the features from selectedSources for all or the current selection
1077    * 
1078    * @param selectedSources
1079    * @param checkDbRefs
1080    * @param promptFetchDbRefs
1081    */
1082   private void doDasFeatureFetch(Vector selectedSources,
1083           boolean checkDbRefs, boolean promptFetchDbRefs)
1084   {
1085     SequenceI[] dataset, seqs;
1086     int iSize;
1087     AlignViewport vp = af.getViewport();
1088     if (vp.getSelectionGroup() != null
1089             && vp.getSelectionGroup().getSize() > 0)
1090     {
1091       iSize = vp.getSelectionGroup().getSize();
1092       dataset = new SequenceI[iSize];
1093       seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1094     }
1095     else
1096     {
1097       iSize = vp.getAlignment().getHeight();
1098       seqs = vp.getAlignment().getSequencesArray();
1099     }
1100
1101     dataset = new SequenceI[iSize];
1102     for (int i = 0; i < iSize; i++)
1103     {
1104       dataset[i] = seqs[i].getDatasetSequence();
1105     }
1106
1107     cancelDAS.setEnabled(true);
1108     dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1109             this, selectedSources, checkDbRefs, promptFetchDbRefs);
1110     af.getViewport().setShowSequenceFeatures(true);
1111     af.showSeqFeatures.setSelected(true);
1112   }
1113
1114   /**
1115    * blocking call to initialise the das source browser
1116    */
1117   public void initDasSources()
1118   {
1119     dassourceBrowser.initDasSources();
1120   }
1121
1122   /**
1123    * examine the current list of das sources and return any matching the given
1124    * nicknames in sources
1125    * 
1126    * @param sources
1127    *          Vector of Strings to resolve to DAS source nicknames.
1128    * @return sources that are present in source list.
1129    */
1130   public Vector resolveSourceNicknames(Vector sources)
1131   {
1132     return dassourceBrowser.resolveSourceNicknames(sources);
1133   }
1134
1135   /**
1136    * get currently selected das sources. ensure you have called initDasSources
1137    * before calling this.
1138    * 
1139    * @return vector of selected das source nicknames
1140    */
1141   public Vector getSelectedSources()
1142   {
1143     return dassourceBrowser.getSelectedSources();
1144   }
1145
1146   /**
1147    * properly initialise DAS fetcher and then initiate a new thread to fetch
1148    * features from the named sources (rather than any turned on by default)
1149    * 
1150    * @param sources
1151    * @param block
1152    *          if true then runs in same thread, otherwise passes to the Swing
1153    *          executor
1154    */
1155   public void fetchDasFeatures(Vector sources, boolean block)
1156   {
1157     initDasSources();
1158     Vector resolved = resolveSourceNicknames(sources);
1159     if (resolved.size() == 0)
1160     {
1161       resolved = dassourceBrowser.getSelectedSources();
1162     }
1163     if (resolved.size() > 0)
1164     {
1165       final Vector dassources = resolved;
1166       fetchDAS.setEnabled(false);
1167       // cancelDAS.setEnabled(true); doDasFetch does this.
1168       Runnable fetcher = new Runnable()
1169       {
1170
1171         public void run()
1172         {
1173           doDasFeatureFetch(dassources, true, false);
1174
1175         }
1176       };
1177       if (block)
1178       {
1179         fetcher.run();
1180       }
1181       else
1182       {
1183         SwingUtilities.invokeLater(fetcher);
1184       }
1185     }
1186   }
1187
1188   public void saveDAS_actionPerformed(ActionEvent e)
1189   {
1190     dassourceBrowser
1191             .saveProperties(jalview.bin.Cache.applicationProperties);
1192   }
1193
1194   public void complete()
1195   {
1196     fetchDAS.setEnabled(true);
1197     cancelDAS.setEnabled(false);
1198   }
1199
1200   public void cancelDAS_actionPerformed(ActionEvent e)
1201   {
1202     if (dasFeatureFetcher != null)
1203     {
1204       dasFeatureFetcher.cancel();
1205     }
1206     fetchDAS.setEnabled(true);
1207     cancelDAS.setEnabled(false);
1208   }
1209
1210   public void noDasSourceActive()
1211   {
1212     complete();
1213     JOptionPane.showInternalConfirmDialog(Desktop.desktop,
1214             "No das sources were selected.\n"
1215                     + "Please select some sources and\n" + " try again.",
1216             "No Sources Selected", JOptionPane.DEFAULT_OPTION,
1217             JOptionPane.INFORMATION_MESSAGE);
1218   }
1219
1220   // ///////////////////////////////////////////////////////////////////////
1221   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1222   // ///////////////////////////////////////////////////////////////////////
1223   class FeatureTableModel extends AbstractTableModel
1224   {
1225     FeatureTableModel(Object[][] data)
1226     {
1227       this.data = data;
1228     }
1229
1230     private String[] columnNames =
1231     { "Feature Type", "Colour", "Display" };
1232
1233     private Object[][] data;
1234
1235     public Object[][] getData()
1236     {
1237       return data;
1238     }
1239
1240     public void setData(Object[][] data)
1241     {
1242       this.data = data;
1243     }
1244
1245     public int getColumnCount()
1246     {
1247       return columnNames.length;
1248     }
1249
1250     public Object[] getRow(int row)
1251     {
1252       return data[row];
1253     }
1254
1255     public int getRowCount()
1256     {
1257       return data.length;
1258     }
1259
1260     public String getColumnName(int col)
1261     {
1262       return columnNames[col];
1263     }
1264
1265     public Object getValueAt(int row, int col)
1266     {
1267       return data[row][col];
1268     }
1269
1270     public Class getColumnClass(int c)
1271     {
1272       return getValueAt(0, c).getClass();
1273     }
1274
1275     public boolean isCellEditable(int row, int col)
1276     {
1277       return col == 0 ? false : true;
1278     }
1279
1280     public void setValueAt(Object value, int row, int col)
1281     {
1282       data[row][col] = value;
1283       fireTableCellUpdated(row, col);
1284       updateFeatureRenderer(data);
1285     }
1286
1287   }
1288
1289   class ColorRenderer extends JLabel implements TableCellRenderer
1290   {
1291     javax.swing.border.Border unselectedBorder = null;
1292
1293     javax.swing.border.Border selectedBorder = null;
1294
1295     final String baseTT = "Click to edit, right/apple click for menu.";
1296
1297     public ColorRenderer()
1298     {
1299       setOpaque(true); // MUST do this for background to show up.
1300     }
1301
1302     public Component getTableCellRendererComponent(JTable table,
1303             Object color, boolean isSelected, boolean hasFocus, int row,
1304             int column)
1305     {
1306       // JLabel comp = new JLabel();
1307       // comp.
1308       setOpaque(true);
1309       // comp.
1310       // setBounds(getBounds());
1311       Color newColor;
1312       setToolTipText(baseTT);
1313       setBackground(table.getBackground());
1314       if (color instanceof GraduatedColor)
1315       {
1316         Rectangle cr = table.getCellRect(row, column, false);
1317         FeatureSettings.renderGraduatedColor(this, (GraduatedColor) color,
1318                 (int) cr.getWidth(), (int) cr.getHeight());
1319
1320       }
1321       else
1322       {
1323         this.setText("");
1324         this.setIcon(null);
1325         newColor = (Color) color;
1326         // comp.
1327         setBackground(newColor);
1328         // comp.setToolTipText("RGB value: " + newColor.getRed() + ", "
1329         // + newColor.getGreen() + ", " + newColor.getBlue());
1330       }
1331       if (isSelected)
1332       {
1333         if (selectedBorder == null)
1334         {
1335           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1336                   table.getSelectionBackground());
1337         }
1338         // comp.
1339         setBorder(selectedBorder);
1340       }
1341       else
1342       {
1343         if (unselectedBorder == null)
1344         {
1345           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1346                   table.getBackground());
1347         }
1348         // comp.
1349         setBorder(unselectedBorder);
1350       }
1351
1352       return this;
1353     }
1354   }
1355
1356   /**
1357    * update comp using rendering settings from gcol
1358    * 
1359    * @param comp
1360    * @param gcol
1361    */
1362   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol)
1363   {
1364     int w = comp.getWidth(), h = comp.getHeight();
1365     if (w < 20)
1366     {
1367       w = (int) comp.getPreferredSize().getWidth();
1368       h = (int) comp.getPreferredSize().getHeight();
1369       if (w < 20)
1370       {
1371         w = 80;
1372         h = 12;
1373       }
1374     }
1375     renderGraduatedColor(comp, gcol, w, h);
1376   }
1377
1378   public static void renderGraduatedColor(JLabel comp, GraduatedColor gcol,
1379           int w, int h)
1380   {
1381
1382     String tt = "";
1383     String tx = "";
1384     if (gcol.getThreshType() == AnnotationColourGradient.ABOVE_THRESHOLD)
1385     {
1386       tx += "> ";
1387       tt += "Thresholded (Above " + gcol.getThresh() + ") ";
1388     }
1389     if (gcol.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)
1390     {
1391       tx += "< ";
1392       tt += "Thresholded (Below " + gcol.getThresh() + ") ";
1393     }
1394     if (gcol.isColourByLabel())
1395     {
1396       tt = "Coloured by label text. " + tt;
1397       tx += "Label";
1398       comp.setIcon(null);
1399     }
1400     else
1401     {
1402       Color newColor = gcol.getMaxColor();
1403       comp.setBackground(newColor);
1404       System.err.println("Width is " + w / 2);
1405       Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w / 2, h);
1406       comp.setIcon(ficon);
1407       // tt+="RGB value: Max (" + newColor.getRed() + ", "
1408       // + newColor.getGreen() + ", " + newColor.getBlue()
1409       // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1410       // + ", " + minCol.getBlue() + ")");
1411     }
1412     comp.setText(tx);
1413     if (tt.length() > 0)
1414     {
1415       if (comp.getToolTipText() == null)
1416       {
1417         comp.setToolTipText(tt);
1418       }
1419       else
1420       {
1421         comp.setToolTipText(tt + " " + comp.getToolTipText());
1422       }
1423     }
1424   }
1425 }
1426
1427 class FeatureIcon implements Icon
1428 {
1429   GraduatedColor gcol;
1430
1431   Color backg;
1432
1433   int width = 50, height = 20;
1434
1435   FeatureIcon(GraduatedColor gfc, Color bg, int w, int h)
1436   {
1437     gcol = gfc;
1438     backg = bg;
1439     width = w;
1440     height = h;
1441   }
1442
1443   public int getIconWidth()
1444   {
1445     return width;
1446   }
1447
1448   public int getIconHeight()
1449   {
1450     return height;
1451   }
1452
1453   public void paintIcon(Component c, Graphics g, int x, int y)
1454   {
1455
1456     if (gcol.isColourByLabel())
1457     {
1458       g.setColor(backg);
1459       g.fillRect(0, 0, width, height);
1460       // need an icon here.
1461       g.setColor(gcol.getMaxColor());
1462
1463       g.setFont(new Font("Verdana", Font.PLAIN, 9));
1464
1465       // g.setFont(g.getFont().deriveFont(
1466       // AffineTransform.getScaleInstance(
1467       // width/g.getFontMetrics().stringWidth("Label"),
1468       // height/g.getFontMetrics().getHeight())));
1469
1470       g.drawString("Label", 0, 0);
1471
1472     }
1473     else
1474     {
1475       Color minCol = gcol.getMinColor();
1476       g.setColor(minCol);
1477       g.fillRect(0, 0, width, height);
1478     }
1479   }
1480 }
1481
1482 class ColorEditor extends AbstractCellEditor implements TableCellEditor,
1483         ActionListener
1484 {
1485   FeatureSettings me;
1486
1487   GraduatedColor currentGColor;
1488
1489   FeatureColourChooser chooser;
1490
1491   String type;
1492
1493   Color currentColor;
1494
1495   JButton button;
1496
1497   JColorChooser colorChooser;
1498
1499   JDialog dialog;
1500
1501   protected static final String EDIT = "edit";
1502
1503   int selectedRow = 0;
1504
1505   public ColorEditor(FeatureSettings me)
1506   {
1507     this.me = me;
1508     // Set up the editor (from the table's point of view),
1509     // which is a button.
1510     // This button brings up the color chooser dialog,
1511     // which is the editor from the user's point of view.
1512     button = new JButton();
1513     button.setActionCommand(EDIT);
1514     button.addActionListener(this);
1515     button.setBorderPainted(false);
1516     // Set up the dialog that the button brings up.
1517     colorChooser = new JColorChooser();
1518     dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
1519             colorChooser, this, // OK button handler
1520             null); // no CANCEL button handler
1521   }
1522
1523   /**
1524    * Handles events from the editor button and from the dialog's OK button.
1525    */
1526   public void actionPerformed(ActionEvent e)
1527   {
1528
1529     if (EDIT.equals(e.getActionCommand()))
1530     {
1531       // The user has clicked the cell, so
1532       // bring up the dialog.
1533       if (currentColor != null)
1534       {
1535         // bring up simple color chooser
1536         button.setBackground(currentColor);
1537         colorChooser.setColor(currentColor);
1538         dialog.setVisible(true);
1539       }
1540       else
1541       {
1542         // bring up graduated chooser.
1543         chooser = new FeatureColourChooser(me.fr, type);
1544         chooser.setRequestFocusEnabled(true);
1545         chooser.requestFocus();
1546         chooser.addActionListener(this);
1547       }
1548       // Make the renderer reappear.
1549       fireEditingStopped();
1550
1551     }
1552     else
1553     { // User pressed dialog's "OK" button.
1554       if (currentColor != null)
1555       {
1556         currentColor = colorChooser.getColor();
1557       }
1558       else
1559       {
1560         // class cast exceptions may be raised if the chooser created on a
1561         // non-graduated color
1562         currentGColor = (GraduatedColor) chooser.getLastColour();
1563       }
1564       me.table.setValueAt(getCellEditorValue(), selectedRow, 1);
1565       fireEditingStopped();
1566       me.table.validate();
1567     }
1568   }
1569
1570   // Implement the one CellEditor method that AbstractCellEditor doesn't.
1571   public Object getCellEditorValue()
1572   {
1573     if (currentColor == null)
1574     {
1575       return currentGColor;
1576     }
1577     return currentColor;
1578   }
1579
1580   // Implement the one method defined by TableCellEditor.
1581   public Component getTableCellEditorComponent(JTable table, Object value,
1582           boolean isSelected, int row, int column)
1583   {
1584     currentGColor = null;
1585     currentColor = null;
1586     this.selectedRow = row;
1587     type = me.table.getValueAt(row, 0).toString();
1588     button.setOpaque(true);
1589     button.setBackground(me.getBackground());
1590     if (value instanceof GraduatedColor)
1591     {
1592       currentGColor = (GraduatedColor) value;
1593       JLabel btn = new JLabel();
1594       btn.setSize(button.getSize());
1595       FeatureSettings.renderGraduatedColor(btn, currentGColor);
1596       button.setBackground(btn.getBackground());
1597       button.setIcon(btn.getIcon());
1598       button.setText(btn.getText());
1599     }
1600     else
1601     {
1602       button.setText("");
1603       button.setIcon(null);
1604       currentColor = (Color) value;
1605       button.setBackground(currentColor);
1606     }
1607     return button;
1608   }
1609 }