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