/* $Id: SettingsTabShortcuts.java 17842 2010-01-12 19:21:22Z linus $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* mvw
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2006-2008 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.ui.cmd;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import org.argouml.application.api.GUISettingsTabInterface;
import org.argouml.i18n.Translator;
import org.argouml.util.KeyEventUtils;
/**
* Tab for the settings dialog that makes it possible to customize Actions'
* shortcuts
*
* @author andrea.nironi@gmail
*/
class SettingsTabShortcuts extends JPanel implements
GUISettingsTabInterface, ActionListener, ListSelectionListener,
ShortcutChangedListener {
/**
* The UID.
*/
private static final long serialVersionUID = -2033414439459450620L;
private static final String NONE_NAME = Translator
.localize("label.shortcut-none");
private static final String DEFAULT_NAME = Translator
.localize("label.shortcut-default");
private static final String CUSTOM_NAME = Translator
.localize("label.shortcut-custom");
/**
* The table of modules.
*/
private JTable table;
private JPanel selectedContainer;
private ShortcutField shortcutField = new ShortcutField("", 12);
private Color shortcutFieldDefaultBg = null;
private JRadioButton customButton = new JRadioButton(CUSTOM_NAME);
private JRadioButton defaultButton = new JRadioButton(DEFAULT_NAME);
private JRadioButton noneButton = new JRadioButton(NONE_NAME);
private JLabel warningLabel = new JLabel(" ");
private ActionWrapper target;
private ActionWrapper[] actions = ShortcutMgr.getShortcuts();
private int lastRowSelected = -1;
/**
* The names of the columns in the table.
*/
private final String[] columnNames = {
Translator.localize("misc.column-name.action"),
Translator.localize("misc.column-name.shortcut"),
Translator.localize("misc.column-name.default") };
/**
* The objects representing the available actions
*/
private Object[][] elements;
/*
* @see GUISettingsTabInterface#getTabKey()
*/
public String getTabKey() {
return "tab.shortcuts";
}
/*
* @see GUISettingsTabInterface#getTabPanel()
*/
public JPanel getTabPanel() {
if (table == null) {
setLayout(new GridBagLayout());
GridBagConstraints panelConstraints = new GridBagConstraints();
panelConstraints.gridx = 0;
panelConstraints.gridy = 0;
panelConstraints.anchor = GridBagConstraints.NORTH;
panelConstraints.fill = GridBagConstraints.BOTH;
panelConstraints.weightx = 5;
panelConstraints.weighty = 15;
// let's add the table, inside a JScrollPane
table = new JTable(new ShortcutTableModel());
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
table.setShowVerticalLines(true);
table.setDefaultRenderer(KeyStroke.class,
new KeyStrokeCellRenderer());
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(this);
JPanel tableContainer = new JPanel(new BorderLayout());
tableContainer.setBorder(
BorderFactory.createTitledBorder(
Translator.localize(
"dialog.shortcut.titled-border.actions")));
tableContainer.add(new JScrollPane(table));
add(tableContainer, panelConstraints);
// now, let's set up the "selected action" container
customButton.addActionListener(this);
defaultButton.addActionListener(this);
noneButton.addActionListener(this);
selectedContainer = new JPanel(new GridBagLayout());
selectedContainer.setBorder(
BorderFactory.createTitledBorder(
Translator.localize(
"dialog.shortcut.titled-border.selected")));
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.insets = new Insets(0, 5, 10, 0);
noneButton.setActionCommand(NONE_NAME);
defaultButton.setActionCommand(DEFAULT_NAME);
customButton.setActionCommand(CUSTOM_NAME);
noneButton.addActionListener(this);
defaultButton.addActionListener(this);
customButton.addActionListener(this);
ButtonGroup radioButtonGroup = new ButtonGroup();
radioButtonGroup.add(noneButton);
radioButtonGroup.add(defaultButton);
radioButtonGroup.add(customButton);
selectedContainer.add(noneButton, constraints);
constraints.gridx = 1;
constraints.insets = new Insets(0, 5, 10, 0);
selectedContainer.add(defaultButton, constraints);
constraints.gridx = 2;
constraints.insets = new Insets(0, 5, 10, 0);
selectedContainer.add(customButton, constraints);
constraints.gridx = 3;
constraints.weightx = 10.0;
constraints.insets = new Insets(0, 10, 10, 15);
constraints.fill = GridBagConstraints.HORIZONTAL;
shortcutField.addShortcutChangedListener(this);
shortcutFieldDefaultBg = shortcutField.getBackground();
selectedContainer.add(shortcutField, constraints);
constraints.gridwidth = 4;
constraints.gridy = 1;
constraints.gridx = 0;
constraints.anchor = GridBagConstraints.WEST;
constraints.insets = new Insets(0, 10, 5, 10);
warningLabel.setForeground(Color.RED);
selectedContainer.add(warningLabel, constraints);
panelConstraints.gridy = 1;
panelConstraints.anchor = GridBagConstraints.CENTER;
panelConstraints.fill = GridBagConstraints.BOTH;
panelConstraints.weightx = 1;
panelConstraints.weighty = 1;
add(selectedContainer, panelConstraints);
JLabel restart =
new JLabel(Translator.localize("label.restart-application"));
restart.setHorizontalAlignment(SwingConstants.CENTER);
restart.setVerticalAlignment(SwingConstants.CENTER);
restart.setBorder(BorderFactory.createEmptyBorder(10, 2, 10, 2));
panelConstraints.gridy = 2;
panelConstraints.anchor = GridBagConstraints.CENTER;
panelConstraints.fill = GridBagConstraints.BOTH;
panelConstraints.weightx = 1;
panelConstraints.weighty = 0;
add(restart, panelConstraints);
this.enableFields(false);
}
return this;
}
/**
* Enable/disable the field of the lower panel
*
* @param enable
* whether to enable the fields or not
*/
private void enableFields(boolean enable) {
shortcutField.setEditable(enable);
customButton.setEnabled(enable);
defaultButton.setEnabled(enable);
noneButton.setEnabled(enable);
}
/**
* Sets the current target
*
* @param t
* the new target
*/
private void setTarget(Object t) {
target = (ActionWrapper) t;
// let's enable the radiobuttons container
enableFields(true);
// updating the radiobuttons container's title
selectedContainer.setBorder(BorderFactory.createTitledBorder(Translator
.localize("dialog.shortcut.titled-border.selected-partial")
+ " \"" + target.getActionName() + "\""));
shortcutField.setText(KeyEventUtils.formatKeyStroke(target
.getCurrentShortcut()));
resetKeyStrokeConflict();
// let's select the correct radio button
if (target.getCurrentShortcut() == null) {
// no shortcuts --> NONE
noneButton.setSelected(true);
shortcutField.setEnabled(false);
} else if (target.getDefaultShortcut() != null
&& target.getCurrentShortcut().equals(
target.getDefaultShortcut())) {
// current shortcut == default --> DEFAULT
defaultButton.setSelected(true);
shortcutField.setEnabled(false);
} else {
// customized shortcut --> CUSTOM
customButton.setSelected(true);
shortcutField.setEnabled(true);
shortcutField.requestFocus();
}
}
/*
* @see org.argouml.ui.GUISettingsTabInterface#handleResetToDefault()
*/
public void handleResetToDefault() {
// Do nothing - these buttons are not shown.
}
/*
* @see org.argouml.ui.GUISettingsTabInterface#handleSettingsTabCancel()
*/
public void handleSettingsTabCancel() {
// Do nothing!
// The next time we refresh, we will fetch the values again.
}
/*
* @see org.argouml.ui.GUISettingsTabInterface#handleSettingsTabRefresh()
*/
public void handleSettingsTabRefresh() {
// let's reload the shortcuts
actions = ShortcutMgr.getShortcuts();
table.setModel(new ShortcutTableModel());
}
/*
* @see org.argouml.ui.GUISettingsTabInterface#handleSettingsTabSave()
*/
public void handleSettingsTabSave() {
if (getActionAlreadyAssigned(ShortcutMgr
.decodeKeyStroke(shortcutField.getText())) != null) {
// conflict detected: showing a warning to the user, instead of
// saving shortcuts
JOptionPane.showMessageDialog(this,
Translator.localize(
"optionpane.shortcut-save-conflict"),
Translator.localize(
"optionpane.shortcut-save-conflict-title"),
JOptionPane.WARNING_MESSAGE);
} else {
// saving shortcuts
ShortcutMgr.saveShortcuts(actions);
}
}
/**
* This is called every time a row is selected. It just updates the fields
* and the buttons status
*
* @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
*/
public void valueChanged(ListSelectionEvent lse) {
if (lse.getValueIsAdjusting()) {
return;
}
Object src = lse.getSource();
if (src != table.getSelectionModel() || table.getSelectedRow() == -1) {
return;
}
// if a shortcut has been select then we have to check if the actual
// action is in conflict with other ones
if (!noneButton.isSelected()) {
ActionWrapper oldAction = getActionAlreadyAssigned(ShortcutMgr
.decodeKeyStroke(shortcutField.getText()));
if (oldAction != null) {
// this shortcut was already been assigned to another action;
// let's pop-up a message for the user
String t = MessageFormat.format(Translator
.localize("optionpane.conflict-shortcut"),
new Object[] {shortcutField.getText(),
oldAction.getActionName() });
int response = JOptionPane.showConfirmDialog(this, t, t,
JOptionPane.YES_NO_OPTION);
switch (response) {
case JOptionPane.YES_OPTION:
oldAction.setCurrentShortcut(null);
// blanking the old action's shortcut..
// and now refreshing the table.
table.setValueAt(oldAction, -1, -1);
break;
case JOptionPane.NO_OPTION:
// re-selecting the old row, without changing the target -
// and without throwing another ListSelectionEvent!
table.getSelectionModel().removeListSelectionListener(this);
table.getSelectionModel().setSelectionInterval(
lastRowSelected, lastRowSelected);
table.getSelectionModel().addListSelectionListener(this);
return;
}
}
}
// let's change the target
setTarget(actions[table.getSelectedRow()]);
lastRowSelected = table.getSelectedRow();
}
/**
* Verifies if the given keyStroke has already been assigned
* to another action
*
* @param keyStroke
* the KeyStroke to be checked
* @return the Action that has already been assigned
*/
public ActionWrapper getActionAlreadyAssigned(KeyStroke keyStroke) {
for (int i = 0; i < actions.length; i++) {
if (actions[i].getCurrentShortcut() != null
&& actions[i].getCurrentShortcut().equals(keyStroke)
&& !actions[i].getActionName().equals(
target.getActionName())) {
return actions[i];
}
}
// duplicate shortcut not found; let's check for duplicates
KeyStroke duplicate = ShortcutMgr.getDuplicate(keyStroke);
if (duplicate != null) {
// there's a duplicate: let's recheck if there is a conflict
for (int i = 0; i < actions.length; i++) {
if (actions[i].getCurrentShortcut() != null
&& actions[i].getCurrentShortcut().equals(duplicate)
&& !actions[i].getActionName().equals(
target.getActionName())) {
return actions[i];
}
}
}
return null;
}
/**
* This is called every time a panel button (custom / default / none) is
* pressed.
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
resetKeyStrokeConflict();
if (e.getSource() == customButton) {
setKeyStrokeValue(ShortcutMgr.decodeKeyStroke(shortcutField
.getText()));
shortcutField.setEnabled(true);
shortcutField.requestFocus();
} else if (e.getSource() == defaultButton) {
setKeyStrokeValue(target.getDefaultShortcut());
shortcutField.setEnabled(false);
checkShortcutAlreadyAssigned(target.getDefaultShortcut());
} else if (e.getSource() == noneButton) {
setKeyStrokeValue(null);
shortcutField.setEnabled(false);
}
}
/**
* This method simply reset the "conflict" gui, by blanking the warning
* label and resetting the shortcut's background color
*
*/
private void resetKeyStrokeConflict() {
this.warningLabel.setText(" ");
this.shortcutField.setBackground(shortcutFieldDefaultBg);
}
/**
* Updates the GUI with the given new KeyStroke
*
* @param newKeyStroke the KeyStroke to be set
*/
private void setKeyStrokeValue(KeyStroke newKeyStroke) {
String formattedKeyStroke = KeyEventUtils.formatKeyStroke(newKeyStroke);
// updating the shortcut field
shortcutField.setText(formattedKeyStroke);
// updating the table data
table.getModel().setValueAt(newKeyStroke, table.getSelectedRow(), 1);
// updating the actions
actions[table.getSelectedRow()].setCurrentShortcut(newKeyStroke);
table.repaint();
}
/**
* Listener method for ShortcutChangedEvents
*
* @see org.argouml.ui.cmd.ShortcutChangedListener#shortcutChange(org.argouml.ui.cmd.ShortcutChangedEvent)
*/
public void shortcutChange(ShortcutChangedEvent event) {
checkShortcutAlreadyAssigned(event.getKeyStroke());
setKeyStrokeValue(event.getKeyStroke());
this.selectedContainer.repaint();
}
/**
* Update the GUI, if there is a conflict for the given key stroke
*
* @param newKeyStroke the key stroke to be checked
*/
private void checkShortcutAlreadyAssigned(KeyStroke newKeyStroke) {
ActionWrapper oldAction = getActionAlreadyAssigned(newKeyStroke);
if (oldAction != null) {
// the shortcut has already been assigned to another action!
this.shortcutField.setBackground(Color.YELLOW);
this.warningLabel.setText(MessageFormat.format(Translator
.localize("misc.shortcuts.conflict"),
new Object[] {KeyEventUtils.formatKeyStroke(oldAction
.getCurrentShortcut()),
oldAction.getActionName()}));
} else {
resetKeyStrokeConflict();
}
}
/**
* Table model for the table with actions and shortcuts.
*/
class ShortcutTableModel extends AbstractTableModel {
/**
* Constructor.
*/
public ShortcutTableModel() {
elements = new Object[actions.length][3];
for (int i = 0; i < elements.length; i++) {
ActionWrapper currentAction = actions[i];
elements[i][0] = currentAction.getActionName();
elements[i][1] = currentAction.getCurrentShortcut();
elements[i][2] = currentAction.getDefaultShortcut();
}
}
/*
* @see javax.swing.table.TableModel#getColumnCount()
*/
public int getColumnCount() {
return columnNames.length;
}
/*
* @see javax.swing.table.TableModel#getColumnName(int)
*/
@Override
public String getColumnName(int col) {
return columnNames[col];
}
/*
* @see javax.swing.table.TableModel#getRowCount()
*/
public int getRowCount() {
return elements.length;
}
/*
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
public Object getValueAt(int row, int col) {
return elements[row][col];
}
/**
* Sets the value in the cell at <code>col</code> and
* <code>row</code> to <code>ob</code>. If <code>ob</code> is
* an Action instance, then the <code>col</code> and <code>row</code>
* parameters are ignored, and the ob Action is searched among the
* elements and the actions.
*
* @see javax.swing.table.TableModel#setValueAt( java.lang.Object, int,
* int)
*/
@Override
public void setValueAt(Object ob, int row, int col) {
// if the given object is a KeyStroke instance, then we ca
if (ob instanceof ActionWrapper) {
ActionWrapper newValueAction = (ActionWrapper) ob;
for (int i = 0; i < elements.length; i++) {
if (elements[i][0].equals(newValueAction.getActionName())) {
elements[i][1] = newValueAction.getCurrentShortcut();
repaint();
break;
}
}
// let's update also the actions array
for (int i = 0; i < actions.length; i++) {
if (actions[i].getKey().equals(newValueAction.getKey())) {
actions[i].setCurrentShortcut(newValueAction
.getCurrentShortcut());
break;
}
}
} else {
elements[row][col] = ob;
}
}
/*
* @see javax.swing.table.TableModel#getColumnClass(int)
*/
@Override
public Class<?> getColumnClass(int col) {
switch (col) {
case 0:
return String.class;
case 1:
return KeyStroke.class;
case 2:
return KeyStroke.class;
default:
return null;
}
}
/*
* @see javax.swing.table.TableModel#isCellEditable(int, int)
*/
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
}
}
/**
* TableCellRenderer for a KeyStroke object.
*
* @author andrea.nironi@gmail.com
*/
class KeyStrokeCellRenderer extends DefaultTableCellRenderer {
/**
* Construct a table cell rendered for key strokes.
*/
public KeyStrokeCellRenderer() {
super();
setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
}
/**
* Format the given KeyStroke
*
* @see javax.swing.table.DefaultTableCellRenderer#setValue(java.lang.Object)
*/
@Override
public void setValue(Object value) {
if (value != null && value instanceof KeyStroke) {
value = KeyEventUtils.formatKeyStroke((KeyStroke) value);
}
super.setValue(value);
}
}