Groups compatibility
[jalview.git] / src / jalview / gui / FeatureSettings.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.gui;\r
20 \r
21 import jalview.datamodel.*;\r
22 import javax.swing.*;\r
23 import javax.swing.event.*;\r
24 import java.awt.*;\r
25 import java.util.*;\r
26 import javax.swing.BorderFactory;\r
27 import java.awt.event.*;\r
28 import javax.swing.table.*;\r
29 import java.io.*;\r
30 import jalview.io.JalviewFileChooser;\r
31 \r
32 public class FeatureSettings extends JPanel\r
33 {\r
34 \r
35   final FeatureRenderer fr;\r
36   final AlignmentPanel ap;\r
37   final AlignViewport av;\r
38   Object [][] originalData;\r
39   final JInternalFrame frame;\r
40   JScrollPane scrollPane = new JScrollPane();\r
41   JTable table;\r
42   JPanel groupPanel;\r
43 \r
44   boolean alignmentHasFeatures = false;\r
45 \r
46   public FeatureSettings(AlignViewport av, final AlignmentPanel ap)\r
47   {\r
48     this.ap = ap;\r
49     this.av = av;\r
50     fr = ap.seqPanel.seqCanvas.getFeatureRenderer();\r
51     av.alignment.getSequences();\r
52     frame = new JInternalFrame();\r
53     frame.setContentPane(this);\r
54     Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 300);\r
55 \r
56     setTableData();\r
57 \r
58     final JSlider transparency = new JSlider(0, 70, 0);\r
59     transparency.addChangeListener(new ChangeListener()\r
60     {\r
61       public void stateChanged(ChangeEvent evt)\r
62       {\r
63         fr.setTransparency( (float) (100 - transparency.getValue()) / 100f);\r
64         ap.repaint();\r
65       }\r
66     });\r
67 \r
68     JPanel transPanel = new JPanel(new FlowLayout());\r
69     transPanel.add(new JLabel("Transparency"));\r
70     transPanel.add(transparency);\r
71 \r
72     //////////////////////////////////////////////\r
73     //We're going to need those OK cancel buttons\r
74     JPanel buttonPanel = new JPanel(new FlowLayout());\r
75     JButton button = new JButton("OK");\r
76     button.addActionListener(new ActionListener()\r
77     {\r
78       public void actionPerformed(ActionEvent evt)\r
79       {\r
80         try\r
81         {\r
82           frame.setClosed(true);\r
83         }\r
84         catch (Exception exe)\r
85         {}\r
86       }\r
87     });\r
88     buttonPanel.add(button);\r
89     button = new JButton("Cancel");\r
90     button.addActionListener(new ActionListener()\r
91     {\r
92       public void actionPerformed(ActionEvent evt)\r
93       {\r
94         try\r
95         {\r
96           updateFeatureRenderer(originalData);\r
97           frame.setClosed(true);\r
98         }\r
99         catch (Exception exe)\r
100         {}\r
101       }\r
102     });\r
103     buttonPanel.add(button);\r
104 \r
105     button = new JButton("Load Colours");\r
106     button.addActionListener(new ActionListener()\r
107     {\r
108       public void actionPerformed(ActionEvent evt)\r
109       {\r
110         load();\r
111       }\r
112     });\r
113     buttonPanel.add(button);\r
114     button = new JButton("Save Colours");\r
115     button.addActionListener(new ActionListener()\r
116     {\r
117       public void actionPerformed(ActionEvent evt)\r
118       {\r
119         save();\r
120       }\r
121     });\r
122     buttonPanel.add(button);\r
123 \r
124     this.setLayout(new BorderLayout());\r
125     JPanel bigPanel = new JPanel(new BorderLayout());\r
126     bigPanel.add(transPanel, BorderLayout.SOUTH);\r
127     bigPanel.add(scrollPane, BorderLayout.CENTER);\r
128     if(groupPanel!=null)\r
129     {\r
130       groupPanel.setLayout(\r
131           new GridLayout(fr.featureGroups.size() / 4 + 1, 4));\r
132 \r
133       groupPanel.validate();\r
134       bigPanel.add(groupPanel, BorderLayout.NORTH);\r
135     }\r
136     add(bigPanel, BorderLayout.CENTER);\r
137     add(buttonPanel, BorderLayout.SOUTH);\r
138 \r
139 \r
140   }\r
141 \r
142   void setTableData()\r
143   {\r
144     alignmentHasFeatures = false;\r
145 \r
146     if (fr.featureGroups == null)\r
147       fr.featureGroups = new Hashtable();\r
148 \r
149     Vector allFeatures = new Vector();\r
150     Vector allGroups = new Vector();\r
151     SequenceFeature[] tmpfeatures;\r
152     String group;\r
153 \r
154     for (int i = 0; i < av.alignment.getHeight(); i++)\r
155     {\r
156       if (av.alignment.getSequenceAt(i).getDatasetSequence().getSequenceFeatures() == null)\r
157         continue;\r
158 \r
159       alignmentHasFeatures = true;\r
160 \r
161       tmpfeatures = av.alignment.getSequenceAt(i).getDatasetSequence().getSequenceFeatures();\r
162       int index = 0;\r
163       while (index < tmpfeatures.length)\r
164       {\r
165         if(tmpfeatures[index].getFeatureGroup()!=null)\r
166         {\r
167           group = tmpfeatures[index].featureGroup;\r
168           if(!allGroups.contains(group))\r
169            {\r
170              allGroups.addElement(group);\r
171 \r
172              boolean visible = true;\r
173              if (fr.featureGroups.containsKey(group))\r
174              {\r
175                visible = ( (Boolean) fr.featureGroups.get(group)).booleanValue();\r
176              }\r
177 \r
178              fr.featureGroups.put(group, new Boolean(visible));\r
179 \r
180              if (groupPanel == null)\r
181              {\r
182                groupPanel = new JPanel();\r
183              }\r
184 \r
185              final JCheckBox check = new JCheckBox(group, visible);\r
186              check.setFont(new Font("Serif", Font.BOLD, 12));\r
187              check.addItemListener(new ItemListener()\r
188              {\r
189                public void itemStateChanged(ItemEvent evt)\r
190                {\r
191                  fr.featureGroups.put(check.getText(),\r
192                                       new Boolean(check.isSelected()));\r
193                  ap.seqPanel.seqCanvas.repaint();\r
194                  if (ap.overviewPanel != null)\r
195                    ap.overviewPanel.updateOverviewImage();\r
196 \r
197                  resetTable(true);\r
198                }\r
199              });\r
200              groupPanel.add(check);\r
201 \r
202            }\r
203 \r
204        }\r
205 \r
206        if (!allFeatures.contains(tmpfeatures[index].getType()))\r
207        {\r
208            allFeatures.addElement(tmpfeatures[index].getType());\r
209        }\r
210        index ++;\r
211     }\r
212   }\r
213 \r
214 \r
215     if(!alignmentHasFeatures)\r
216      {\r
217        try\r
218        { frame.setClosed(true);  }\r
219        catch (Exception ex){}\r
220 \r
221        JOptionPane.showInternalMessageDialog(\r
222            Desktop.desktop, "No features have been added to this alignment!",\r
223            "No Sequence Features", JOptionPane.WARNING_MESSAGE);\r
224 \r
225        return;\r
226      }\r
227 \r
228      resetTable(false);\r
229   }\r
230 \r
231   void resetTable(boolean groupsChanged)\r
232   {\r
233    SequenceFeature [] tmpfeatures;\r
234    String group=null, type;\r
235    Vector visibleChecks = new Vector();\r
236 \r
237    //Find out which features should be visible depending on which groups\r
238    //are selected / deselected\r
239     for (int i = 0; i < av.alignment.getHeight(); i++)\r
240     {\r
241         if (av.alignment.getSequenceAt(i).getDatasetSequence().getSequenceFeatures() == null)\r
242           continue;\r
243 \r
244         tmpfeatures = av.alignment.getSequenceAt(i).getDatasetSequence().getSequenceFeatures();\r
245         int index = 0;\r
246         while (index < tmpfeatures.length)\r
247         {\r
248           group = tmpfeatures[index].featureGroup;\r
249 \r
250           if (group==null || fr.featureGroups.get(group)==null ||\r
251               ((Boolean) fr.featureGroups.get(group)).booleanValue())\r
252           {\r
253             type = tmpfeatures[index].getType();\r
254             if(!visibleChecks.contains(type) )\r
255             {\r
256               visibleChecks.addElement(type);\r
257             }\r
258           }\r
259           index++;\r
260         }\r
261     }\r
262 \r
263     int fSize = visibleChecks.size();\r
264     Object [][] data = new Object[fSize][3];\r
265     int dataIndex = 0;\r
266 \r
267     if(fr.renderOrder!=null)\r
268     {\r
269       //First add the checks in the previous render order,\r
270       //in case the window has been closed and reopened\r
271       for(int ro=fr.renderOrder.length-1; ro>-1; ro--)\r
272       {\r
273            type = fr.renderOrder[ro];\r
274 \r
275            if(!visibleChecks.contains(type))\r
276              continue;\r
277 \r
278            data[dataIndex][0] = type;\r
279            data[dataIndex][1] = fr.getColour(type);\r
280            data[dataIndex][2] = new Boolean(av.featuresDisplayed.containsKey(type));\r
281            dataIndex++;\r
282            visibleChecks.removeElement(type);\r
283       }\r
284     }\r
285 \r
286     fSize = visibleChecks.size();\r
287     for(int i=0; i<fSize; i++)\r
288     {\r
289       //These must be extra features belonging to the group\r
290       //which was just selected\r
291       type = visibleChecks.elementAt(i).toString();\r
292       data[dataIndex][0] = type;\r
293       data[dataIndex][1] = fr.getColour(type);\r
294       data[dataIndex][2] = new Boolean(true);\r
295       dataIndex++;\r
296     }\r
297 \r
298     if(originalData==null)\r
299     {\r
300       originalData = new Object[data.length][3];\r
301       System.arraycopy(data,0,originalData,0,data.length);\r
302     }\r
303 \r
304     table = new JTable(new FeatureTableModel(data));\r
305     scrollPane.setViewportView(table);\r
306     table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));\r
307     table.setFont(new Font("Verdana", Font.PLAIN, 12));\r
308     table.setDefaultRenderer(Color.class,\r
309                          new ColorRenderer());\r
310 \r
311     table.setDefaultEditor(Color.class,\r
312                       new ColorEditor());\r
313 \r
314     table.getColumnModel().getColumn(0).setPreferredWidth(200);\r
315 \r
316     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
317 \r
318     table.addMouseListener(new MouseAdapter()\r
319         {\r
320           public void mousePressed(MouseEvent evt)\r
321           {\r
322             selectedRow = table.rowAtPoint(evt.getPoint());\r
323           }\r
324         });\r
325 \r
326     table.addMouseMotionListener(new MouseMotionAdapter()\r
327         {\r
328           public void mouseDragged(MouseEvent evt)\r
329           {\r
330             int newRow = table.rowAtPoint(evt.getPoint());\r
331             if(newRow!=selectedRow\r
332                && selectedRow!=-1\r
333                && newRow!=-1)\r
334             {\r
335               Object[] temp = new Object[3];\r
336               temp[0] = table.getValueAt(selectedRow, 0);\r
337               temp[1] = table.getValueAt(selectedRow, 1);\r
338               temp[2] = table.getValueAt(selectedRow, 2);\r
339 \r
340               table.setValueAt(table.getValueAt(newRow, 0), selectedRow, 0);\r
341               table.setValueAt(table.getValueAt(newRow, 1), selectedRow, 1);\r
342               table.setValueAt(table.getValueAt(newRow, 2), selectedRow, 2);\r
343 \r
344               table.setValueAt(temp[0], newRow, 0);\r
345               table.setValueAt(temp[1], newRow, 1);\r
346               table.setValueAt(temp[2], newRow, 2);\r
347 \r
348               selectedRow = newRow;\r
349             }\r
350           }\r
351     });\r
352 \r
353     updateFeatureRenderer(data);\r
354 \r
355   }\r
356 \r
357   void load()\r
358   {\r
359     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.getProperty(\r
360                  "LAST_DIRECTORY"), new String[] { "fc" },\r
361              new String[] { "Sequence Feature Colours" }, "Sequence Feature Colours");\r
362      chooser.setFileView(new jalview.io.JalviewFileView());\r
363      chooser.setDialogTitle("Load Feature Colours");\r
364      chooser.setToolTipText("Load");\r
365 \r
366      int value = chooser.showOpenDialog(this);\r
367 \r
368      if (value == JalviewFileChooser.APPROVE_OPTION)\r
369      {\r
370        File file = chooser.getSelectedFile();\r
371 \r
372        try\r
373        {\r
374          InputStreamReader in = new InputStreamReader(new FileInputStream(\r
375              file), "UTF-8");\r
376 \r
377          jalview.binding.JalviewUserColours jucs = new jalview.binding.\r
378              JalviewUserColours();\r
379          jucs = (jalview.binding.JalviewUserColours) jucs.unmarshal(in);\r
380 \r
381 \r
382          for (int i = 0; i < jucs.getColourCount(); i++)\r
383          {\r
384            fr.setColour( jucs.getColour(i).getName(),\r
385                           new Color(Integer.parseInt( jucs.getColour(i).getRGB(), 16)));\r
386          }\r
387 \r
388          setTableData();\r
389          ap.repaint();\r
390        }\r
391        catch (Exception ex)\r
392        {\r
393          System.out.println("Error loading User ColourFile\n" + ex);\r
394        }\r
395      }\r
396   }\r
397 \r
398   void save()\r
399   {\r
400     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.getProperty(\r
401                 "LAST_DIRECTORY"), new String[] { "fc" },\r
402             new String[] { "Sequence Feature Colours" }, "Sequence Feature Colours");\r
403     chooser.setFileView(new jalview.io.JalviewFileView());\r
404     chooser.setDialogTitle("Save Feature Colour Scheme");\r
405     chooser.setToolTipText("Save");\r
406 \r
407     int value = chooser.showSaveDialog(this);\r
408 \r
409     if (value == JalviewFileChooser.APPROVE_OPTION)\r
410     {\r
411         String choice = chooser.getSelectedFile().getPath();\r
412         jalview.binding.JalviewUserColours ucs = new jalview.binding.JalviewUserColours();\r
413         ucs.setSchemeName("Sequence Features");\r
414         try\r
415         {\r
416             PrintWriter out = new PrintWriter(new OutputStreamWriter(\r
417                         new FileOutputStream(choice), "UTF-8"));\r
418 \r
419             Enumeration e = fr.featureColours.keys();\r
420            while(e.hasMoreElements())\r
421            {\r
422 \r
423 \r
424                 jalview.binding.Colour col = new jalview.binding.Colour();\r
425                 col.setName(e.nextElement().toString());\r
426                 col.setRGB(jalview.util.Format.getHexString(\r
427                         fr.getColour(col.getName())));\r
428                 ucs.addColour(col);\r
429             }\r
430 \r
431             ucs.marshal(out);\r
432             out.close();\r
433         }\r
434         catch (Exception ex)\r
435         {\r
436             ex.printStackTrace();\r
437         }\r
438     }\r
439 \r
440   }\r
441 \r
442   public void updateFeatureRenderer(Object [][] data)\r
443   {\r
444     fr.setFeaturePriority( data );\r
445     ap.repaint();\r
446 \r
447     if(ap.overviewPanel!=null)\r
448       ap.overviewPanel.updateOverviewImage();\r
449   }\r
450 \r
451   int selectedRow =-1;\r
452 \r
453 \r
454   /////////////////////////////////////////////////////////////////////////\r
455   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html\r
456   /////////////////////////////////////////////////////////////////////////\r
457   class FeatureTableModel\r
458       extends AbstractTableModel\r
459   {\r
460     FeatureTableModel(Object[][] data)\r
461     {\r
462       this.data = data;\r
463     }\r
464 \r
465     private String[] columnNames = {"Feature Type", "Colour","Display"};\r
466           private Object[][] data;\r
467 \r
468           public Object[][] getData()\r
469           {\r
470             return data;\r
471           }\r
472 \r
473           public void setData(Object[][] data)\r
474           {\r
475             this.data = data;\r
476           }\r
477 \r
478           public int getColumnCount() {\r
479               return columnNames.length;\r
480           }\r
481 \r
482           public Object[] getRow(int row)\r
483           {\r
484             return data[row];\r
485           }\r
486 \r
487           public int getRowCount() {\r
488               return data.length;\r
489           }\r
490 \r
491           public String getColumnName(int col) {\r
492               return columnNames[col];\r
493           }\r
494 \r
495           public Object getValueAt(int row, int col) {\r
496               return data[row][col];\r
497           }\r
498 \r
499           public Class getColumnClass(int c) {\r
500               return getValueAt(0, c).getClass();\r
501           }\r
502 \r
503           public boolean isCellEditable(int row, int col) {\r
504               return col==0 ? false:true;\r
505           }\r
506 \r
507           public void setValueAt(Object value, int row, int col) {\r
508               data[row][col] = value;\r
509               fireTableCellUpdated(row, col);\r
510               updateFeatureRenderer(data);\r
511           }\r
512 \r
513     }\r
514     class ColorRenderer extends JLabel\r
515                               implements TableCellRenderer {\r
516        javax.swing.border.Border unselectedBorder = null;\r
517        javax.swing.border.Border selectedBorder = null;\r
518 \r
519        public ColorRenderer() {\r
520            setOpaque(true); //MUST do this for background to show up.\r
521        }\r
522 \r
523        public Component getTableCellRendererComponent(\r
524                                JTable table, Object color,\r
525                                boolean isSelected, boolean hasFocus,\r
526                                int row, int column) {\r
527            Color newColor = (Color)color;\r
528            setBackground(newColor);\r
529                if (isSelected) {\r
530                    if (selectedBorder == null) {\r
531                        selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,\r
532                                                  table.getSelectionBackground());\r
533                    }\r
534                    setBorder(selectedBorder);\r
535                } else {\r
536                    if (unselectedBorder == null) {\r
537                        unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,\r
538                                                  table.getBackground());\r
539                    }\r
540                    setBorder(unselectedBorder);\r
541                }\r
542 \r
543            setToolTipText("RGB value: " + newColor.getRed() + ", "\r
544                                         + newColor.getGreen() + ", "\r
545                                         + newColor.getBlue());\r
546            return this;\r
547        }\r
548    }\r
549 }\r
550 \r
551  class ColorEditor extends AbstractCellEditor\r
552                           implements TableCellEditor,\r
553                                      ActionListener {\r
554      Color currentColor;\r
555      JButton button;\r
556      JColorChooser colorChooser;\r
557      JDialog dialog;\r
558      protected static final String EDIT = "edit";\r
559 \r
560      public ColorEditor() {\r
561          //Set up the editor (from the table's point of view),\r
562          //which is a button.\r
563          //This button brings up the color chooser dialog,\r
564          //which is the editor from the user's point of view.\r
565          button = new JButton();\r
566          button.setActionCommand(EDIT);\r
567          button.addActionListener(this);\r
568          button.setBorderPainted(false);\r
569          //Set up the dialog that the button brings up.\r
570          colorChooser = new JColorChooser();\r
571          dialog = JColorChooser.createDialog(button,\r
572                                          "Select new Colour",\r
573                                          true,  //modal\r
574                                          colorChooser,\r
575                                          this,  //OK button handler\r
576                                          null); //no CANCEL button handler\r
577      }\r
578 \r
579      /**\r
580       * Handles events from the editor button and from\r
581       * the dialog's OK button.\r
582       */\r
583      public void actionPerformed(ActionEvent e) {\r
584 \r
585           if (EDIT.equals(e.getActionCommand())) {\r
586              //The user has clicked the cell, so\r
587              //bring up the dialog.\r
588              button.setBackground(currentColor);\r
589              colorChooser.setColor(currentColor);\r
590              dialog.setVisible(true);\r
591 \r
592              //Make the renderer reappear.\r
593              fireEditingStopped();\r
594 \r
595          } else { //User pressed dialog's "OK" button.\r
596              currentColor = colorChooser.getColor();\r
597          }\r
598      }\r
599 \r
600      //Implement the one CellEditor method that AbstractCellEditor doesn't.\r
601      public Object getCellEditorValue() {\r
602          return currentColor;\r
603      }\r
604 \r
605      //Implement the one method defined by TableCellEditor.\r
606      public Component getTableCellEditorComponent(JTable table,\r
607                                                   Object value,\r
608                                                   boolean isSelected,\r
609                                                   int row,\r
610                                                   int column) {\r
611          currentColor = (Color)value;\r
612          return button;\r
613      }\r
614 }\r