2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.bin.Cache;
24 import jalview.io.JalviewFileChooser;
25 import jalview.io.JalviewFileView;
26 import jalview.jbgui.GUserDefinedColours;
27 import jalview.schemabinding.version2.Colour;
28 import jalview.schemabinding.version2.JalviewUserColours;
29 import jalview.schemes.ColourSchemeI;
30 import jalview.schemes.ColourSchemeLoader;
31 import jalview.schemes.ColourSchemes;
32 import jalview.schemes.ResidueProperties;
33 import jalview.schemes.UserColourScheme;
34 import jalview.util.ColorUtils;
35 import jalview.util.Format;
36 import jalview.util.MessageManager;
38 import java.awt.Color;
40 import java.awt.Insets;
41 import java.awt.event.MouseAdapter;
42 import java.awt.event.MouseEvent;
44 import java.io.FileOutputStream;
45 import java.io.OutputStreamWriter;
46 import java.io.PrintWriter;
47 import java.util.ArrayList;
48 import java.util.List;
50 import javax.swing.JButton;
51 import javax.swing.JInternalFrame;
52 import javax.swing.event.ChangeEvent;
53 import javax.swing.event.ChangeListener;
56 * This panel allows the user to assign colours to Amino Acid residue codes, and
57 * save the colour scheme.
59 * @author Andrew Waterhouse
60 * @author Mungo Carstairs
62 public class UserDefinedColours extends GUserDefinedColours implements
65 private static final Font VERDANA_BOLD_10 = new Font("Verdana",
68 public static final String USER_DEFINED_COLOURS = "USER_DEFINED_COLOURS";
70 private static final String LAST_DIRECTORY = "LAST_DIRECTORY";
72 private static final int MY_FRAME_HEIGHT = 440;
74 private static final int MY_FRAME_WIDTH = 810;
76 private static final int MY_FRAME_WIDTH_CASE_SENSITIVE = 970;
80 ColourSchemeI oldColourScheme;
84 List<JButton> upperCaseButtons;
86 List<JButton> lowerCaseButtons;
89 * Creates and displays a new UserDefinedColours panel
93 public UserDefinedColours(AlignmentPanel alignPanel)
97 lcaseColour.setEnabled(false);
101 oldColourScheme = alignPanel.av.getGlobalColourScheme();
103 if (oldColourScheme instanceof UserColourScheme)
105 schemeName.setText(oldColourScheme.getSchemeName());
106 if (((UserColourScheme) oldColourScheme).getLowerCaseColours() != null)
108 caseSensitive.setSelected(true);
109 lcaseColour.setEnabled(true);
110 resetButtonPanel(true);
114 resetButtonPanel(false);
119 resetButtonPanel(false);
128 selectedButtons = new ArrayList<JButton>();
133 colorChooser.getSelectionModel().addChangeListener(this);
134 frame = new JInternalFrame();
135 frame.setContentPane(this);
136 Desktop.addInternalFrame(frame,
137 MessageManager.getString("label.user_defined_colours"),
138 MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
142 * Rebuilds the panel with coloured buttons for residues. If not case
143 * sensitive colours, show 3-letter amino acid code as button text. If case
144 * sensitive, just show the single letter code, in order to make space for the
145 * additional buttons.
147 * @param isCaseSensitive
149 void resetButtonPanel(boolean isCaseSensitive)
151 buttonPanel.removeAll();
153 if (upperCaseButtons == null)
155 upperCaseButtons = new ArrayList<JButton>();
158 for (int i = 0; i < 20; i++)
160 String label = isCaseSensitive ? ResidueProperties.aa[i]
161 : ResidueProperties.aa2Triplet.get(ResidueProperties.aa[i])
163 JButton button = makeButton(label, ResidueProperties.aa[i],
164 upperCaseButtons, i);
165 buttonPanel.add(button);
168 buttonPanel.add(makeButton("B", "B", upperCaseButtons, 20));
169 buttonPanel.add(makeButton("Z", "Z", upperCaseButtons, 21));
170 buttonPanel.add(makeButton("X", "X", upperCaseButtons, 22));
171 buttonPanel.add(makeButton("Gap", "-", upperCaseButtons, 23));
173 if (!isCaseSensitive)
175 gridLayout.setRows(6);
176 gridLayout.setColumns(4);
180 gridLayout.setRows(7);
182 gridLayout.setColumns(cols + 1);
184 if (lowerCaseButtons == null)
186 lowerCaseButtons = new ArrayList<JButton>();
189 for (int i = 0; i < 20; i++)
191 int row = i / cols + 1;
192 int index = (row * cols) + i;
193 JButton button = makeButton(ResidueProperties.aa[i].toLowerCase(),
194 ResidueProperties.aa[i].toLowerCase(), lowerCaseButtons, i);
196 buttonPanel.add(button, index);
202 buttonPanel.add(makeButton("b", "b", lowerCaseButtons, 20));
203 buttonPanel.add(makeButton("z", "z", lowerCaseButtons, 21));
204 buttonPanel.add(makeButton("x", "x", lowerCaseButtons, 22));
207 // JAL-1360 widen the frame dynamically to accommodate case-sensitive AA
209 if (this.frame != null)
211 int newWidth = isCaseSensitive ? MY_FRAME_WIDTH_CASE_SENSITIVE
213 this.frame.setSize(newWidth, this.frame.getHeight());
216 buttonPanel.validate();
221 * ChangeListener handler for when a colour is picked in the colour chooser.
222 * The action is to apply the colour to all selected buttons as their
223 * background colour. Foreground colour (text) is set to a lighter shade in
224 * order to highlight which buttons are selected. If 'Lower Case Colour' is
225 * active, then the colour is applied to all lower case buttons (as well as
226 * the Lower Case Colour button itself).
231 public void stateChanged(ChangeEvent evt)
233 JButton button = null;
234 final Color newColour = colorChooser.getColor();
235 if (lcaseColour.isSelected())
237 selectedButtons.clear();
238 for (int i = 0; i < lowerCaseButtons.size(); i++)
240 button = lowerCaseButtons.get(i);
241 button.setBackground(newColour);
242 button.setForeground(ColorUtils.brighterThan(button.getBackground()));
245 for (int i = 0; i < selectedButtons.size(); i++)
247 button = selectedButtons.get(i);
248 button.setBackground(newColour);
249 button.setForeground(ColorUtils.brighterThan(newColour));
254 * Performs actions when a residue button is clicked. This manages the button
255 * selection set (highlighted by brighter foreground text).
257 * On select button(s) with Ctrl/click or Shift/click: set button foreground
258 * text to brighter than background.
260 * On unselect button(s) with Ctrl/click on selected, or click to release
261 * current selection: reset foreground text to darker than background.
263 * Simple click: clear selection (resetting foreground to darker); set clicked
264 * button foreground to brighter
266 * Finally, synchronize the colour chooser to the colour of the first button
267 * in the selected set.
271 public void colourButtonPressed(MouseEvent e)
273 JButton pressed = (JButton) e.getSource();
277 JButton start, end = (JButton) e.getSource();
278 if (selectedButtons.size() > 0)
280 start = selectedButtons.get(selectedButtons.size() - 1);
284 start = (JButton) e.getSource();
287 int startIndex = 0, endIndex = 0;
288 for (int b = 0; b < buttonPanel.getComponentCount(); b++)
290 if (buttonPanel.getComponent(b) == start)
294 if (buttonPanel.getComponent(b) == end)
300 if (startIndex > endIndex)
302 int temp = startIndex;
303 startIndex = endIndex;
307 for (int b = startIndex; b <= endIndex; b++)
309 JButton button = (JButton) buttonPanel.getComponent(b);
310 if (!selectedButtons.contains(button))
312 button.setForeground(ColorUtils.brighterThan(button
314 selectedButtons.add(button);
318 else if (!e.isControlDown())
320 for (int b = 0; b < selectedButtons.size(); b++)
322 JButton button = selectedButtons.get(b);
323 button.setForeground(ColorUtils.darkerThan(button.getBackground()));
325 selectedButtons.clear();
326 pressed.setForeground(ColorUtils.brighterThan(pressed.getBackground()));
327 selectedButtons.add(pressed);
330 else if (e.isControlDown())
332 if (selectedButtons.contains(pressed))
334 pressed.setForeground(ColorUtils.darkerThan(pressed.getBackground()));
335 selectedButtons.remove(pressed);
339 pressed.setForeground(ColorUtils.brighterThan(pressed
341 selectedButtons.add(pressed);
345 if (selectedButtons.size() > 0)
347 colorChooser.setColor((selectedButtons.get(0)).getBackground());
352 * A helper method to update or make a colour button, whose background colour
353 * is the associated colour, and text colour a darker shade of the same. If
354 * the button is already in the list, then its text and margins are updated,
355 * if not then it is created and added. This method supports toggling between
356 * case-sensitive and case-insensitive button panels. The case-sensitive
357 * version has abbreviated button text in order to fit in more buttons.
364 * the button's position in the list
366 JButton makeButton(String label, String residue, List<JButton> buttons,
369 final JButton button;
372 if (buttonIndex < buttons.size())
374 button = buttons.get(buttonIndex);
375 col = button.getBackground();
379 button = new JButton();
380 button.addMouseListener(new MouseAdapter()
383 public void mouseClicked(MouseEvent e)
385 colourButtonPressed(e);
392 * make initial button colour that of the current colour scheme,
393 * if it is a simple per-residue colouring, else white
396 if (oldColourScheme != null && oldColourScheme.isSimple())
398 col = oldColourScheme.findColour(residue.charAt(0), 0, null, null,
403 if (caseSensitive.isSelected())
405 button.setMargin(new Insets(2, 2, 2, 2));
409 button.setMargin(new Insets(2, 14, 2, 14));
412 button.setOpaque(true); // required for the next line to have effect
413 button.setBackground(col);
414 button.setText(label);
415 button.setForeground(ColorUtils.darkerThan(col));
416 button.setFont(VERDANA_BOLD_10);
422 * On 'OK', check that at least one colour has been assigned to a residue (and
423 * if not issue a warning), and apply the chosen colour scheme and close the
427 protected void okButton_actionPerformed()
429 if (isNoSelectionMade())
431 JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
432 .getString("label.no_colour_selection_in_scheme"),
433 MessageManager.getString("label.no_colour_selection_warn"),
434 JvOptionPane.WARNING_MESSAGE);
438 applyButton_actionPerformed();
442 frame.setClosed(true);
443 } catch (Exception ex)
450 * Returns true if the user has not made any colour selection (including if
451 * 'case-sensitive' selected and no lower-case colour chosen).
455 protected boolean isNoSelectionMade()
457 final boolean noUpperCaseSelected = upperCaseButtons == null
458 || upperCaseButtons.isEmpty();
459 final boolean noLowerCaseSelected = caseSensitive.isSelected()
460 && (lowerCaseButtons == null || lowerCaseButtons.isEmpty());
461 final boolean noSelectionMade = noUpperCaseSelected
462 || noLowerCaseSelected;
463 return noSelectionMade;
467 * Applies the current colour scheme to the alignment or sequence group
470 protected void applyButton_actionPerformed()
472 if (isNoSelectionMade())
474 JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
475 .getString("label.no_colour_selection_in_scheme"),
476 MessageManager.getString("label.no_colour_selection_warn"),
477 JvOptionPane.WARNING_MESSAGE);
480 UserColourScheme ucs = getSchemeFromButtons();
482 ap.alignFrame.changeColour(ucs);
486 * Constructs an instance of UserColourScheme with the residue colours
487 * currently set on the buttons on the panel
491 UserColourScheme getSchemeFromButtons()
494 Color[] newColours = new Color[24];
496 int length = upperCaseButtons.size();
500 for (JButton btn : upperCaseButtons)
502 newColours[i] = btn.getBackground();
508 for (int i = 0; i < 24; i++)
510 JButton button = upperCaseButtons.get(i);
511 newColours[i] = button.getBackground();
515 UserColourScheme ucs = new UserColourScheme(newColours);
516 ucs.setName(schemeName.getText());
518 if (caseSensitive.isSelected())
520 newColours = new Color[23];
521 length = lowerCaseButtons.size();
525 for (JButton btn : lowerCaseButtons)
527 newColours[i] = btn.getBackground();
533 for (int i = 0; i < 23; i++)
535 JButton button = lowerCaseButtons.get(i);
536 newColours[i] = button.getBackground();
539 ucs.setLowerCaseColours(newColours);
546 * Action on clicking Load scheme button.
548 * <li>Open a file chooser to browse for files with extension .jc</li>
549 * <li>Load in the colour scheme and transfer it to this panel's buttons</li>
550 * <li>Register the loaded colour scheme</li>
554 protected void loadbutton_actionPerformed()
556 upperCaseButtons = new ArrayList<JButton>();
557 lowerCaseButtons = new ArrayList<JButton>();
559 JalviewFileChooser chooser = new JalviewFileChooser("jc",
560 "Jalview User Colours");
561 chooser.setFileView(new JalviewFileView());
562 chooser.setDialogTitle(MessageManager
563 .getString("label.load_colour_scheme"));
564 chooser.setToolTipText(MessageManager.getString("action.load"));
566 int value = chooser.showOpenDialog(this);
568 if (value != JalviewFileChooser.APPROVE_OPTION)
572 File choice = chooser.getSelectedFile();
573 Cache.setProperty(LAST_DIRECTORY, choice.getParent());
575 UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(choice
577 Color[] colors = ucs.getColours();
578 schemeName.setText(ucs.getSchemeName());
580 if (ucs.getLowerCaseColours() != null)
582 caseSensitive.setSelected(true);
583 lcaseColour.setEnabled(true);
584 resetButtonPanel(true);
585 for (int i = 0; i < lowerCaseButtons.size(); i++)
587 JButton button = lowerCaseButtons.get(i);
588 button.setBackground(ucs.getLowerCaseColours()[i]);
593 caseSensitive.setSelected(false);
594 lcaseColour.setEnabled(false);
595 resetButtonPanel(false);
598 for (int i = 0; i < upperCaseButtons.size(); i++)
600 JButton button = upperCaseButtons.get(i);
601 button.setBackground(colors[i]);
604 addNewColourScheme(choice.getPath());
608 * Loads the user-defined colour scheme from the first file listed in property
609 * "USER_DEFINED_COLOURS". If this fails, returns an all-white colour scheme.
613 public static UserColourScheme loadDefaultColours()
615 UserColourScheme ret = null;
617 String colours = Cache.getProperty(USER_DEFINED_COLOURS);
620 if (colours.indexOf("|") > -1)
622 colours = colours.substring(0, colours.indexOf("|"));
624 ret = ColourSchemeLoader.loadColourScheme(colours);
629 ret = new UserColourScheme("white");
636 * Action on pressing the Save button.
638 * <li>Check a name has been entered</li>
639 * <li>Warn if the name already exists, remove any existing scheme of the same
640 * name if overwriting</li>
641 * <li>Do the standard file chooser thing to write with extension .jc</li>
642 * <li>If saving changes (possibly not yet applied) to the currently selected
643 * colour scheme, then apply the changes, as it is too late to back out now</li>
644 * <li>Don't apply the changes if the currently selected scheme is different,
645 * to allow a new scheme to be configured and saved but not applied</li>
649 protected void savebutton_actionPerformed()
651 String name = schemeName.getText().trim();
652 if (name.length() < 1)
654 JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
655 .getString("label.user_colour_scheme_must_have_name"),
656 MessageManager.getString("label.no_name_colour_scheme"),
657 JvOptionPane.WARNING_MESSAGE);
661 if (ColourSchemes.getInstance().nameExists(name))
663 int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
664 MessageManager.formatMessage(
665 "label.colour_scheme_exists_overwrite", new Object[] {
667 MessageManager.getString("label.duplicate_scheme_name"),
668 JvOptionPane.YES_NO_OPTION);
669 if (reply != JvOptionPane.YES_OPTION)
674 JalviewFileChooser chooser = new JalviewFileChooser("jc",
675 "Jalview User Colours");
677 JalviewFileView fileView = new JalviewFileView();
678 chooser.setFileView(fileView);
679 chooser.setDialogTitle(MessageManager
680 .getString("label.save_colour_scheme"));
681 chooser.setToolTipText(MessageManager.getString("action.save"));
683 int value = chooser.showSaveDialog(this);
685 if (value == JalviewFileChooser.APPROVE_OPTION)
687 File file = chooser.getSelectedFile();
688 addNewColourScheme(file.getPath());
692 * changes saved - apply to alignment if we are changing
693 * the currently selected colour scheme
695 if (oldColourScheme != null
696 && name.equals(oldColourScheme.getSchemeName()))
698 applyButton_actionPerformed();
704 * Adds the current colour scheme to the Jalview properties file so it is
705 * loaded on next startup, and updates the Colour menu in the parent
706 * AlignFrame (if there is one). Note this action does not including applying
711 protected void addNewColourScheme(String filePath)
714 * update the delimited list of user defined colour files in
715 * Jalview property USER_DEFINED_COLOURS
717 String defaultColours = Cache
718 .getDefault(USER_DEFINED_COLOURS, filePath);
719 if (defaultColours.indexOf(filePath) == -1)
721 if (defaultColours.length() > 0)
723 defaultColours = defaultColours.concat("|");
725 defaultColours = defaultColours.concat(filePath);
727 Cache.setProperty(USER_DEFINED_COLOURS, defaultColours);
730 * construct and register the colour scheme
732 UserColourScheme ucs = getSchemeFromButtons();
733 ColourSchemes.getInstance().registerColourScheme(ucs);
736 * update the Colour menu items
740 ap.alignFrame.buildColourMenu();
745 * Saves the colour scheme to file in XML format
749 protected void saveToFile(File toFile)
752 * build a Java model of colour scheme as XML, and
755 JalviewUserColours ucs = new JalviewUserColours();
756 String name = schemeName.getText();
757 ucs.setSchemeName(name);
760 PrintWriter out = new PrintWriter(new OutputStreamWriter(
761 new FileOutputStream(toFile), "UTF-8"));
763 for (int i = 0; i < buttonPanel.getComponentCount(); i++)
765 JButton button = (JButton) buttonPanel.getComponent(i);
766 Colour col = new Colour();
767 col.setName(button.getText());
768 col.setRGB(Format.getHexString(button.getBackground()));
773 } catch (Exception ex)
775 ex.printStackTrace();
780 * On cancel, restores the colour scheme that was selected before the dialogue
784 protected void cancelButton_actionPerformed()
786 ap.alignFrame.changeColour(oldColourScheme);
787 ap.paintAlignment(true);
791 frame.setClosed(true);
792 } catch (Exception ex)
798 * Action on selecting or deselecting the Case Sensitive option. When
799 * selected, separate buttons are shown for lower case residues, and the panel
800 * is resized to accommodate them. Also, the checkbox for 'apply colour to all
801 * lower case' is enabled.
804 public void caseSensitive_actionPerformed()
806 boolean selected = caseSensitive.isSelected();
807 resetButtonPanel(selected);
808 lcaseColour.setEnabled(selected);