bugfix + create/amend whilst feature panel open (still fixing)
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 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.datamodel.*;
34 import jalview.io.*;
35
36 public class FeatureSettings
37     extends JPanel
38 {
39   DasSourceBrowser dassourceBrowser;
40   jalview.io.DasSequenceFeatureFetcher dasFeatureFetcher;
41   JPanel settingsPane = new JPanel();
42   JPanel dasSettingsPane = new JPanel();
43
44   final FeatureRenderer fr;
45   public final AlignFrame af;
46   Object[][] originalData;
47   final JInternalFrame frame;
48   JScrollPane scrollPane = new JScrollPane();
49   JTable table;
50   JPanel groupPanel;
51   JSlider transparency = new JSlider();
52
53   JPanel transPanel = new JPanel(new FlowLayout());
54
55   public FeatureSettings(AlignFrame af)
56   {
57     this.af = af;
58     fr = af.getFeatureRenderer();
59
60     transparency.setMaximum(100 - (int) (fr.transparency * 100));
61
62     try
63     {
64       jbInit();
65     }
66     catch (Exception ex)
67     {
68       ex.printStackTrace();
69     }
70
71     table = new JTable();
72     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
73     table.setFont(new Font("Verdana", Font.PLAIN, 12));
74     table.setDefaultRenderer(Color.class,
75                              new ColorRenderer());
76
77     table.setDefaultEditor(Color.class,
78                            new ColorEditor());
79
80     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
81
82     table.addMouseListener(new MouseAdapter()
83     {
84       public void mousePressed(MouseEvent evt)
85       {
86         selectedRow = table.rowAtPoint(evt.getPoint());
87       }
88     });
89
90     table.addMouseMotionListener(new MouseMotionAdapter()
91     {
92       public void mouseDragged(MouseEvent evt)
93       {
94         int newRow = table.rowAtPoint(evt.getPoint());
95         if (newRow != selectedRow
96             && selectedRow != -1
97             && newRow != -1)
98         {
99           Object[] temp = new Object[3];
100           temp[0] = table.getValueAt(selectedRow, 0);
101           temp[1] = table.getValueAt(selectedRow, 1);
102           temp[2] = table.getValueAt(selectedRow, 2);
103
104           table.setValueAt(table.getValueAt(newRow, 0), selectedRow, 0);
105           table.setValueAt(table.getValueAt(newRow, 1), selectedRow, 1);
106           table.setValueAt(table.getValueAt(newRow, 2), selectedRow, 2);
107
108           table.setValueAt(temp[0], newRow, 0);
109           table.setValueAt(temp[1], newRow, 1);
110           table.setValueAt(temp[2], newRow, 2);
111
112           selectedRow = newRow;
113         }
114       }
115     });
116
117     scrollPane.setViewportView(table);
118
119     dassourceBrowser = new DasSourceBrowser();
120     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
121
122     if (af.getViewport().featuresDisplayed == null || fr.renderOrder == null)
123     {
124       fr.findAllFeatures(true); // display everything!
125     }
126
127     setTableData();
128     final PropertyChangeListener change;
129     final FeatureSettings fs=this;
130     fr.addPropertyChangeListener(change=new PropertyChangeListener() {
131       public void propertyChange(PropertyChangeEvent evt)
132       {
133         if (!fs.resettingTable && !fs.handlingUpdate) {
134           fs.handlingUpdate=true;
135           fs.resetTable(new String[] {}); // groups may be added
136           fs.handlingUpdate=false;
137         }
138       }
139       
140     });
141     
142     frame = new JInternalFrame();
143     frame.setContentPane(this);
144     Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 450);
145     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
146     {
147       public void internalFrameClosed(
148           javax.swing.event.InternalFrameEvent evt)
149       {
150         fr.removePropertyChangeListener(change);
151       }
152       ;
153     });
154     frame.setLayer(JLayeredPane.PALETTE_LAYER);
155   }
156   /**
157    * true when Feature Settings are updating from feature renderer
158    */
159   private boolean handlingUpdate=false;
160   
161   /**
162    * contains a float[3] for each feature type string. created by setTableData
163    */
164   Hashtable typeWidth=null;
165   synchronized public void setTableData()
166   {
167     if (fr.featureGroups == null)
168     {
169       fr.featureGroups = new Hashtable();
170     }
171     Vector allFeatures = new Vector();
172     Vector allGroups = new Vector();
173     SequenceFeature[] tmpfeatures;
174     String group;
175     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
176     {
177       if (af.getViewport().alignment.getSequenceAt(i).getDatasetSequence().
178           getSequenceFeatures() == null)
179       {
180         continue;
181       }
182
183       tmpfeatures = af.getViewport().alignment.getSequenceAt(i).
184           getDatasetSequence().getSequenceFeatures();
185
186       int index = 0;
187       while (index < tmpfeatures.length)
188       {
189         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
190         {
191           index++;
192           continue;
193         }
194
195         if (tmpfeatures[index].getFeatureGroup() != null)
196         {
197           group = tmpfeatures[index].featureGroup;
198           if (!allGroups.contains(group))
199           {
200             allGroups.addElement(group);
201
202             boolean visible;
203             if (fr.featureGroups.containsKey(group))
204             {
205               visible = ( (Boolean) fr.featureGroups.get(group)).booleanValue();
206             } else {
207               visible=true; //initGroups || fr.av.featuresDisplayed.containsKey(tmpfeatures[index].getType());
208             }
209
210             if (groupPanel == null)
211             {
212               groupPanel = new JPanel();
213             }
214
215             boolean alreadyAdded = false;
216             for (int g = 0; g < groupPanel.getComponentCount(); g++)
217             {
218               if ( ( (JCheckBox) groupPanel.getComponent(g))
219                   .getText().equals(group))
220               {
221                 alreadyAdded = true;
222                 break;
223               }
224             }
225
226             if (alreadyAdded)
227             {
228               
229               continue;
230             }
231
232             fr.featureGroups.put(group, new Boolean(visible));
233             final String grp = group;
234             final JCheckBox check = new JCheckBox(group, visible);
235             check.setFont(new Font("Serif", Font.BOLD, 12));
236             check.addItemListener(new ItemListener()
237             {
238               public void itemStateChanged(ItemEvent evt)
239               {
240                 fr.featureGroups.put(check.getText(),
241                                      new Boolean(check.isSelected()));
242                 af.alignPanel.seqPanel.seqCanvas.repaint();
243                 if (af.alignPanel.overviewPanel != null)
244                 {
245                   af.alignPanel.overviewPanel.updateOverviewImage();
246                 }
247
248                 resetTable(new String[] { grp } );
249               }
250             });
251             groupPanel.add(check);
252           }
253         }
254
255         if (!allFeatures.contains(tmpfeatures[index].getType()))
256         {
257           allFeatures.addElement(tmpfeatures[index].getType());
258         }
259         index++;
260       }
261     }
262     
263     resetTable(null);
264
265     validate();
266   }
267   boolean resettingTable=false;
268   void resetTable(String[] groupChanged)
269   {
270     resettingTable=true;
271     typeWidth=new Hashtable();
272     Vector groupchanged = new Vector();
273     // TODO: change avWidth calculation to 'per-sequence' average and use long rather than float
274     float[] avWidth=null;
275     SequenceFeature[] tmpfeatures;
276     String group = null, type;
277     Vector visibleChecks = new Vector();
278
279     //Find out which features should be visible depending on which groups
280     //are selected / deselected
281     //and recompute average width ordering
282     for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
283     {
284
285       tmpfeatures = af.getViewport().alignment.getSequenceAt(i).
286           getDatasetSequence().getSequenceFeatures();
287       if (tmpfeatures == null)
288       {
289         continue;
290       }
291
292       int index = 0;
293       while (index < tmpfeatures.length)
294       {
295         group = tmpfeatures[index].featureGroup;
296
297         if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
298         {
299           index++;
300           continue;
301         }
302
303         if (group == null || fr.featureGroups.get(group) == null ||
304             ( (Boolean) fr.featureGroups.get(group)).booleanValue())
305         {
306           type = tmpfeatures[index].getType();
307           if (!visibleChecks.contains(type))
308           {
309             visibleChecks.addElement(type);
310           }
311         }
312         if (!typeWidth.containsKey(tmpfeatures[index].getType())) {
313           typeWidth.put(tmpfeatures[index].getType(), avWidth=new float[3]);
314         } else {
315           avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
316         }
317         avWidth[0]++;
318         if (tmpfeatures[index].getBegin()>tmpfeatures[index].getEnd())
319         {
320           avWidth[1]+=1+tmpfeatures[index].getBegin()-tmpfeatures[index].getEnd();
321         } else {
322           avWidth[1]+=1+tmpfeatures[index].getEnd()-tmpfeatures[index].getBegin();
323         }
324         index++;
325       }
326     }
327
328     int fSize = visibleChecks.size();
329     Object[][] data = new Object[fSize][3];
330     int dataIndex = 0;
331
332     if (fr.renderOrder != null)
333     {
334       if (!handlingUpdate)
335         fr.findAllFeatures(groupChanged!=null); // prod to update colourschemes. but don't affect display
336       //First add the checks in the previous render order,
337       //in case the window has been closed and reopened
338       for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
339       {
340         type = fr.renderOrder[ro];
341
342         if (!visibleChecks.contains(type))
343         {
344           continue;
345         }
346
347         data[dataIndex][0] = type;
348         data[dataIndex][1] = fr.getColour(type);
349         data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed.containsKey(type));
350         dataIndex++;
351         visibleChecks.removeElement(type);
352       }
353     }
354
355     fSize = visibleChecks.size();
356     for (int i = 0; i < fSize; i++)
357     {
358       //These must be extra features belonging to the group
359       //which was just selected
360       type = visibleChecks.elementAt(i).toString();
361       data[dataIndex][0] = type;
362
363       data[dataIndex][1] = fr.getColour(type);
364       if (data[dataIndex][1] == null)
365       {
366         //"Colour has been updated in another view!!"
367         fr.renderOrder = null;
368         return;
369       }
370
371       data[dataIndex][2] = new Boolean(true);
372       dataIndex++;
373     }
374
375     if (originalData == null)
376     {
377       originalData = new Object[data.length][3];
378       System.arraycopy(data, 0, originalData, 0, data.length);
379     }
380
381     table.setModel(new FeatureTableModel(data));
382     table.getColumnModel().getColumn(0).setPreferredWidth(200);
383
384     if (groupPanel != null)
385     {
386       groupPanel.setLayout(
387           new GridLayout(fr.featureGroups.size() / 4 + 1, 4));
388
389       groupPanel.validate();
390       bigPanel.add(groupPanel, BorderLayout.NORTH);
391     }
392
393     updateFeatureRenderer(data, groupChanged!=null);
394     resettingTable=false;
395   }
396   /**
397    * reorder data based on the featureRenderers global priority list.
398    * @param data
399    */
400   private void ensureOrder(Object[][] data)
401   {
402     boolean sort=false;
403     float[] order = new float[data.length];
404     for (int i=0;i<order.length; i++)
405     {
406       order[i] = fr.getOrder(data[i][0].toString());
407       if (order[i]<0)
408         order[i] = fr.setOrder(data[i][0].toString(), i/order.length);
409       if (i>1)
410         sort = sort || order[i-1]>order[i];
411     }
412     if (sort)
413       jalview.util.QuickSort.sort(order, data);
414   }
415
416   void load()
417   {
418     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.
419         getProperty(
420             "LAST_DIRECTORY"), new String[]
421         {"fc"},
422         new String[]
423         {"Sequence Feature Colours"}, "Sequence Feature Colours");
424     chooser.setFileView(new jalview.io.JalviewFileView());
425     chooser.setDialogTitle("Load Feature Colours");
426     chooser.setToolTipText("Load");
427
428     int value = chooser.showOpenDialog(this);
429
430     if (value == JalviewFileChooser.APPROVE_OPTION)
431     {
432       File file = chooser.getSelectedFile();
433
434       try
435       {
436         InputStreamReader in = new InputStreamReader(new FileInputStream(
437             file), "UTF-8");
438
439         jalview.binding.JalviewUserColours jucs = new jalview.binding.
440             JalviewUserColours();
441         jucs = (jalview.binding.JalviewUserColours) jucs.unmarshal(in);
442
443         for (int i = jucs.getColourCount()-1; i >=0; i--)
444         {
445           String name;
446           fr.setColour(name=jucs.getColour(i).getName(),
447                        new Color(Integer.parseInt(jucs.getColour(i).getRGB(),
448                                                   16)));
449           fr.setOrder(name,(i==0) ? 0 : i/jucs.getColourCount());
450         }
451         if (table!=null) {
452           resetTable(null);
453           Object[][] data=((FeatureTableModel) table.getModel()).getData();
454           ensureOrder(data);
455           updateFeatureRenderer(data,false);
456           table.repaint();
457         }
458       }
459       catch (Exception ex)
460       {
461         System.out.println("Error loading User Colour File\n" + ex);
462       }
463     }
464   }
465
466   void save()
467   {
468     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.
469         getProperty(
470             "LAST_DIRECTORY"), new String[]
471         {"fc"},
472         new String[]
473         {"Sequence Feature Colours"}, "Sequence Feature Colours");
474     chooser.setFileView(new jalview.io.JalviewFileView());
475     chooser.setDialogTitle("Save Feature Colour Scheme");
476     chooser.setToolTipText("Save");
477
478     int value = chooser.showSaveDialog(this);
479
480     if (value == JalviewFileChooser.APPROVE_OPTION)
481     {
482       String choice = chooser.getSelectedFile().getPath();
483       jalview.binding.JalviewUserColours ucs = new jalview.binding.
484           JalviewUserColours();
485       ucs.setSchemeName("Sequence Features");
486       try
487       {
488         PrintWriter out = new PrintWriter(new OutputStreamWriter(
489             new FileOutputStream(choice), "UTF-8"));
490
491         Enumeration e = fr.featureColours.keys();
492         float[] sortOrder = new float[fr.featureColours.size()];
493         String[] sortTypes = new String[fr.featureColours.size()];
494         int i=0;
495         while (e.hasMoreElements())
496         {
497           sortTypes[i] = e.nextElement().toString();
498           sortOrder[i]  = fr.getOrder(sortTypes[i]);
499           i++;
500         }
501         jalview.util.QuickSort.sort(sortOrder, sortTypes);
502         sortOrder=null;
503         for (i=0; i<sortTypes.length; i++) {
504           jalview.binding.Colour col = new jalview.binding.Colour();
505           col.setName(sortTypes[i]);
506           col.setRGB(jalview.util.Format.getHexString(
507               fr.getColour(col.getName())));
508           ucs.addColour(col);
509         }
510         ucs.marshal(out);
511         out.close();
512       }
513       catch (Exception ex)
514       {
515         ex.printStackTrace();
516       }
517     }
518   }
519
520   public void invertSelection()
521   {
522     for (int i = 0; i < table.getRowCount(); i++)
523     {
524       Boolean value = (Boolean) table.getValueAt(i, 2);
525
526       table.setValueAt(
527           new Boolean(!value.booleanValue()),
528           i, 2);
529     }
530   }
531   public void orderByAvWidth() {
532     if (table==null || table.getModel()==null)
533       return;
534     Object[][] data = ((FeatureTableModel) table.getModel()).getData();
535     float[] width = new float[data.length];
536     float[] awidth;
537     float max=0;
538     int num=0;
539     for (int i=0;i<data.length;i++) {
540        awidth = (float[]) typeWidth.get(data[i][0]);
541        if (awidth[0]>0) {
542          width[i] = awidth[1]/awidth[0];// *awidth[0]*awidth[2]; - better weight - but have to make per sequence, too (awidth[2])
543          //if (width[i]==1) // hack to distinguish single width sequences.
544          num++;
545        } else {
546          width[i]=0;
547        }
548        if (max<width[i])
549          max=width[i];
550     }
551     boolean sort=false;
552     for (int i=0;i<width.length; i++) {
553       //awidth = (float[]) typeWidth.get(data[i][0]);
554       if (width[i]==0) 
555       {
556         width[i] = fr.getOrder(data[i][0].toString());
557         if (width[i]<0)
558         {
559           width[i] = fr.setOrder(data[i][0].toString(), i/data.length);
560         }
561       } else { 
562         width[i] /=max; // normalize
563         fr.setOrder(data[i][0].toString(), width[i]); // store for later 
564       }
565       if (i>0)
566         sort = sort || width[i-1]>width[i];
567     }
568     if (sort)
569       jalview.util.QuickSort.sort(width, data);
570     // update global priority order
571     
572     updateFeatureRenderer(data,false);
573     table.repaint();
574   }
575   public void close()
576   {
577     try
578     {
579       frame.setClosed(true);
580     }
581     catch (Exception exe)
582     {}
583
584   }
585
586   public void updateFeatureRenderer(Object[][] data)
587   {
588     updateFeatureRenderer(data, true);
589   }
590   private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
591   {
592     fr.setFeaturePriority(data, visibleNew);
593     af.alignPanel.paintAlignment(true);
594
595     if (af.alignPanel.overviewPanel != null)
596     {
597       af.alignPanel.overviewPanel.updateOverviewImage();
598     }
599   }
600
601   int selectedRow = -1;
602   JTabbedPane tabbedPane = new JTabbedPane();
603   BorderLayout borderLayout1 = new BorderLayout();
604   BorderLayout borderLayout2 = new BorderLayout();
605   BorderLayout borderLayout3 = new BorderLayout();
606   JPanel bigPanel = new JPanel();
607   BorderLayout borderLayout4 = new BorderLayout();
608   JButton invert = new JButton();
609   JPanel buttonPanel = new JPanel();
610   JButton cancel = new JButton();
611   JButton ok = new JButton();
612   JButton loadColours = new JButton();
613   JButton saveColours = new JButton();
614   JPanel dasButtonPanel = new JPanel();
615   JButton fetchDAS = new JButton();
616   JButton saveDAS = new JButton();
617   JButton cancelDAS = new JButton();
618   JButton optimizeOrder = new JButton();
619   JPanel transbuttons = new JPanel(new BorderLayout());
620   private void jbInit()
621       throws Exception
622   {
623     this.setLayout(borderLayout1);
624     settingsPane.setLayout(borderLayout2);
625     dasSettingsPane.setLayout(borderLayout3);
626     bigPanel.setLayout(borderLayout4);
627     invert.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
628     invert.setText("Invert Selection");
629     invert.addActionListener(new ActionListener()
630     {
631       public void actionPerformed(ActionEvent e)
632       {
633         invertSelection();
634       }
635     });
636     optimizeOrder.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
637     optimizeOrder.setText("Optimise Order");
638     optimizeOrder.addActionListener(new ActionListener() {
639       public void actionPerformed(ActionEvent e) {
640         orderByAvWidth();
641       }
642     });
643     cancel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
644     cancel.setText("Cancel");
645     cancel.addActionListener(new ActionListener()
646     {
647       public void actionPerformed(ActionEvent e)
648       {
649         updateFeatureRenderer(originalData);
650         close();
651       }
652     });
653     ok.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
654     ok.setText("OK");
655     ok.addActionListener(new ActionListener()
656     {
657       public void actionPerformed(ActionEvent e)
658       {
659         close();
660       }
661     });
662     loadColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
663     loadColours.setText("Load Colours");
664     loadColours.addActionListener(new ActionListener()
665     {
666       public void actionPerformed(ActionEvent e)
667       {
668         load();
669       }
670     });
671     saveColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
672     saveColours.setText("Save Colours");
673     saveColours.addActionListener(new ActionListener()
674     {
675       public void actionPerformed(ActionEvent e)
676       {
677         save();
678       }
679     });
680     transparency.addChangeListener(new ChangeListener()
681     {
682       public void stateChanged(ChangeEvent evt)
683       {
684         fr.setTransparency( (float) (100 - transparency.getValue()) / 100f);
685         af.alignPanel.paintAlignment(true);
686       }
687     });
688
689     transparency.setMaximum(70);
690     fetchDAS.setText("Fetch DAS Features");
691     fetchDAS.addActionListener(new ActionListener()
692     {
693       public void actionPerformed(ActionEvent e)
694       {
695         fetchDAS_actionPerformed(e);
696       }
697     });
698     saveDAS.setText("Save as default");
699     saveDAS.addActionListener(new ActionListener()
700     {
701       public void actionPerformed(ActionEvent e)
702       {
703         saveDAS_actionPerformed(e);
704       }
705     });
706     dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
707     dasSettingsPane.setBorder(null);
708     cancelDAS.setEnabled(false);
709     cancelDAS.setText("Cancel Fetch");
710     cancelDAS.addActionListener(new ActionListener()
711     {
712       public void actionPerformed(ActionEvent e)
713       {
714         cancelDAS_actionPerformed(e);
715       }
716     });
717     this.add(tabbedPane, java.awt.BorderLayout.CENTER);
718     tabbedPane.addTab("Feature Settings", settingsPane);
719     tabbedPane.addTab("DAS Settings", dasSettingsPane);
720     bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
721     transPanel.add(transparency);
722     transbuttons.add(invert, java.awt.BorderLayout.NORTH);
723     transbuttons.add(optimizeOrder,java.awt.BorderLayout.SOUTH);
724     transPanel.add(transbuttons);
725     buttonPanel.add(ok);
726     buttonPanel.add(cancel);
727     buttonPanel.add(loadColours);
728     buttonPanel.add(saveColours);
729     bigPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
730     dasSettingsPane.add(dasButtonPanel, java.awt.BorderLayout.SOUTH);
731     dasButtonPanel.add(fetchDAS);
732     dasButtonPanel.add(cancelDAS);
733     dasButtonPanel.add(saveDAS);
734     settingsPane.add(bigPanel, java.awt.BorderLayout.CENTER);
735     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
736   }
737
738   public void fetchDAS_actionPerformed(ActionEvent e)
739   {
740     fetchDAS.setEnabled(false);
741     cancelDAS.setEnabled(true);
742     Vector selectedSources = dassourceBrowser.getSelectedSources();
743
744     SequenceI[] dataset, seqs;
745     int iSize;
746
747     if (af.getViewport().getSelectionGroup() != null
748         && af.getViewport().getSelectionGroup().getSize() > 0)
749     {
750       iSize = af.getViewport().getSelectionGroup().getSize();
751       dataset = new SequenceI[iSize];
752       seqs = af.getViewport().getSelectionGroup().
753           getSequencesInOrder(
754               af.getViewport().getAlignment());
755     }
756     else
757     {
758       iSize = af.getViewport().getAlignment().getHeight();
759       seqs = af.getViewport().getAlignment().getSequencesArray();
760     }
761
762     dataset = new SequenceI[iSize];
763     for (int i = 0; i < iSize; i++)
764     {
765       dataset[i] = seqs[i].getDatasetSequence();
766     }
767
768     dasFeatureFetcher =
769         new jalview.io.DasSequenceFeatureFetcher(
770             dataset,
771             this,
772             selectedSources);
773     cancelDAS.setEnabled(true);
774     af.getViewport().setShowSequenceFeatures(true);
775     af.showSeqFeatures.setSelected(true);
776   }
777
778   public void saveDAS_actionPerformed(ActionEvent e)
779   {
780     dassourceBrowser.saveProperties(jalview.bin.Cache.applicationProperties);
781   }
782
783   public void complete()
784   {
785     fetchDAS.setEnabled(true);
786     cancelDAS.setEnabled(false);
787   }
788
789   public void cancelDAS_actionPerformed(ActionEvent e)
790   {
791     dasFeatureFetcher.cancel();
792     fetchDAS.setEnabled(true);
793     cancelDAS.setEnabled(false);
794   }
795
796   /////////////////////////////////////////////////////////////////////////
797   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
798   /////////////////////////////////////////////////////////////////////////
799   class FeatureTableModel
800       extends AbstractTableModel
801   {
802     FeatureTableModel(Object[][] data)
803     {
804       this.data = data;
805     }
806
807     private String[] columnNames =
808         {
809         "Feature Type", "Colour", "Display"};
810     private Object[][] data;
811
812     public Object[][] getData()
813     {
814       return data;
815     }
816
817     public void setData(Object[][] data)
818     {
819       this.data = data;
820     }
821
822     public int getColumnCount()
823     {
824       return columnNames.length;
825     }
826
827     public Object[] getRow(int row)
828     {
829       return data[row];
830     }
831
832     public int getRowCount()
833     {
834       return data.length;
835     }
836
837     public String getColumnName(int col)
838     {
839       return columnNames[col];
840     }
841
842     public Object getValueAt(int row, int col)
843     {
844       return data[row][col];
845     }
846
847     public Class getColumnClass(int c)
848     {
849       return getValueAt(0, c).getClass();
850     }
851
852     public boolean isCellEditable(int row, int col)
853     {
854       return col == 0 ? false : true;
855     }
856
857     public void setValueAt(Object value, int row, int col)
858     {
859       data[row][col] = value;
860       fireTableCellUpdated(row, col);
861       updateFeatureRenderer(data);
862     }
863
864   }
865
866   class ColorRenderer
867       extends JLabel implements TableCellRenderer
868   {
869     javax.swing.border.Border unselectedBorder = null;
870     javax.swing.border.Border selectedBorder = null;
871
872     public ColorRenderer()
873     {
874       setOpaque(true); //MUST do this for background to show up.
875     }
876
877     public Component getTableCellRendererComponent(
878         JTable table, Object color,
879         boolean isSelected, boolean hasFocus,
880         int row, int column)
881     {
882       Color newColor = (Color) color;
883       setBackground(newColor);
884       if (isSelected)
885       {
886         if (selectedBorder == null)
887         {
888           selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
889               table.getSelectionBackground());
890         }
891         setBorder(selectedBorder);
892       }
893       else
894       {
895         if (unselectedBorder == null)
896         {
897           unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
898               table.getBackground());
899         }
900         setBorder(unselectedBorder);
901       }
902
903       setToolTipText("RGB value: " + newColor.getRed() + ", "
904                      + newColor.getGreen() + ", "
905                      + newColor.getBlue());
906       return this;
907     }
908   }
909 }
910
911 class ColorEditor
912     extends AbstractCellEditor implements TableCellEditor,
913     ActionListener
914 {
915   Color currentColor;
916   JButton button;
917   JColorChooser colorChooser;
918   JDialog dialog;
919   protected static final String EDIT = "edit";
920
921   public ColorEditor()
922   {
923     //Set up the editor (from the table's point of view),
924     //which is a button.
925     //This button brings up the color chooser dialog,
926     //which is the editor from the user's point of view.
927     button = new JButton();
928     button.setActionCommand(EDIT);
929     button.addActionListener(this);
930     button.setBorderPainted(false);
931     //Set up the dialog that the button brings up.
932     colorChooser = new JColorChooser();
933     dialog = JColorChooser.createDialog(button,
934                                         "Select new Colour",
935                                         true, //modal
936                                         colorChooser,
937                                         this, //OK button handler
938                                         null); //no CANCEL button handler
939   }
940
941   /**
942    * Handles events from the editor button and from
943    * the dialog's OK button.
944    */
945   public void actionPerformed(ActionEvent e)
946   {
947
948     if (EDIT.equals(e.getActionCommand()))
949     {
950       //The user has clicked the cell, so
951       //bring up the dialog.
952       button.setBackground(currentColor);
953       colorChooser.setColor(currentColor);
954       dialog.setVisible(true);
955
956       //Make the renderer reappear.
957       fireEditingStopped();
958
959     }
960     else
961     { //User pressed dialog's "OK" button.
962       currentColor = colorChooser.getColor();
963     }
964   }
965
966   //Implement the one CellEditor method that AbstractCellEditor doesn't.
967   public Object getCellEditorValue()
968   {
969     return currentColor;
970   }
971
972   //Implement the one method defined by TableCellEditor.
973   public Component getTableCellEditorComponent(JTable table,
974                                                Object value,
975                                                boolean isSelected,
976                                                int row,
977                                                int column)
978   {
979     currentColor = (Color) value;
980     return button;
981   }
982 }