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