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