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