Gridarta Editor
ShortcutsDialog.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.gui.dialog.shortcuts;
21 
22 import java.awt.Color;
23 import java.awt.Component;
24 import java.awt.Container;
25 import java.awt.Dimension;
26 import java.awt.GridBagConstraints;
27 import java.awt.GridBagLayout;
28 import java.awt.Insets;
29 import java.util.regex.Pattern;
30 import javax.swing.Action;
31 import javax.swing.Icon;
32 import javax.swing.JButton;
33 import javax.swing.JComponent;
34 import javax.swing.JDialog;
35 import javax.swing.JOptionPane;
36 import javax.swing.JPanel;
37 import javax.swing.JScrollPane;
38 import javax.swing.JTextArea;
39 import javax.swing.JTree;
40 import javax.swing.JViewport;
41 import javax.swing.ScrollPaneConstants;
42 import javax.swing.ToolTipManager;
43 import javax.swing.WindowConstants;
44 import javax.swing.border.LineBorder;
45 import javax.swing.border.TitledBorder;
46 import javax.swing.event.TreeSelectionEvent;
47 import javax.swing.event.TreeSelectionListener;
48 import javax.swing.tree.DefaultMutableTreeNode;
49 import javax.swing.tree.DefaultTreeCellRenderer;
50 import javax.swing.tree.DefaultTreeModel;
51 import javax.swing.tree.MutableTreeNode;
52 import javax.swing.tree.TreeCellRenderer;
53 import javax.swing.tree.TreeNode;
54 import javax.swing.tree.TreeSelectionModel;
59 import net.sf.japi.swing.action.ActionBuilder;
60 import net.sf.japi.swing.action.ActionBuilderFactory;
61 import net.sf.japi.swing.action.ActionMethod;
62 import net.sf.japi.swing.action.IconManager;
63 import org.jetbrains.annotations.NotNull;
64 import org.jetbrains.annotations.Nullable;
65 
66 public class ShortcutsDialog extends JOptionPane {
67 
71  private static final long serialVersionUID = 1L;
72 
76  @NotNull
77  private static final String CATEGORY_PREFIX = "1";
78 
82  @NotNull
83  private static final String ACTION_PREFIX = "2";
84 
88  @NotNull
90 
94  @NotNull
95  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
96 
100  @NotNull
101  private static final Pattern PATTERN_CATEGORIES = StringUtils.PATTERN_COMMA;
102 
106  @NotNull
107  private static final Pattern PATTERN_SUB_CATEGORIES = StringUtils.PATTERN_SLASH;
108 
113  @NotNull
114  private final JButton okButton = new JButton(ACTION_BUILDER.createAction(false, "shortcutsOkay", this));
115 
120  @NotNull
121  private final Action aSetShortcut = ACTION_BUILDER.createAction(false, "shortcutsSetShortcut", this);
122 
127  @NotNull
128  private final Action aUnsetShortcut = ACTION_BUILDER.createAction(false, "shortcutsUnsetShortcut", this);
129 
134  @NotNull
135  private final JDialog dialog;
136 
141  @NotNull
142  private final JTree actionsTree = new JTree() {
143 
147  private static final long serialVersionUID = 1L;
148 
149  @Override
150  public String convertValueToText(final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
151  final Action action = getAction(value);
152  if (action != null) {
153  return ActionUtils.getActionName(action);
154  }
155 
156  return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
157  }
158 
159  };
160 
165  @NotNull
166  private final JTextArea actionDescription = new JTextArea();
167 
172  @NotNull
173  private final JTextArea actionShortcut = new JTextArea();
174 
178  @Nullable
179  private Action selectedAction;
180 
186  public ShortcutsDialog(@NotNull final Component parentComponent, @NotNull final ShortcutsManager shortcutsManager) {
187  this.shortcutsManager = shortcutsManager;
188  okButton.setDefaultCapable(true);
189  final JButton defaultsButton = new JButton(ACTION_BUILDER.createAction(false, "shortcutsDefaults", this));
190  setOptions(new Object[] { okButton, defaultsButton });
191 
192  setMessage(createPanel());
193 
194  dialog = createDialog(parentComponent, ActionBuilderUtils.getString(ACTION_BUILDER, "shortcuts.title"));
195  dialog.setResizable(true);
196  dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
197  dialog.getRootPane().setDefaultButton(okButton);
198  dialog.setModal(true);
199 
200  dialog.setMinimumSize(new Dimension(400, 300));
201  dialog.setPreferredSize(new Dimension(800, 600));
202  dialog.pack();
203  }
204 
209  public void showDialog(@NotNull final Component parentComponent) {
210  dialog.setLocationRelativeTo(parentComponent);
211  dialog.setVisible(true);
212  setInitialValue(actionsTree);
213  actionsTree.setSelectionRow(0);
215  }
216 
221  @NotNull
222  private JPanel createPanel() {
223  final DefaultMutableTreeNode top = new DefaultMutableTreeNode(ActionBuilderUtils.getString(ACTION_BUILDER, "shortcuts.allActions"));
224  createNodes(top);
225 
226  actionsTree.setModel(new DefaultTreeModel(top, false));
227  actionsTree.setRootVisible(false);
228  actionsTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
229  final TreeSelectionListener treeSelectionListener = new TreeSelectionListener() {
230 
231  @Override
232  public void valueChanged(@NotNull final TreeSelectionEvent e) {
233  setSelectedAction(getAction(actionsTree.getLastSelectedPathComponent()));
234  }
235 
236  };
237  actionsTree.addTreeSelectionListener(treeSelectionListener);
238 
239  final Icon emptyIcon = IconManager.getDefaultIconManager().getIcon(ActionBuilderUtils.getString(ACTION_BUILDER, "shortcuts.defaultIcon"));
240  //noinspection RefusedBequest
241  final TreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer() {
242 
246  private static final long serialVersionUID = 1L;
247 
251  @Nullable
252  private Icon icon;
253 
254  @Override
255  public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean sel, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
256  if (leaf) {
257  final Action action = getAction(value);
258  if (action != null) {
259  final Icon tmpIcon = ActionUtils.getActionIcon(action);
260  icon = tmpIcon == null ? emptyIcon : tmpIcon;
261  setToolTipText(ActionUtils.getActionDescription(action));
262  } else {
263  icon = emptyIcon;
264  }
265  } else {
266  icon = emptyIcon;
267  }
268  return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
269  }
270 
271  @Override
272  public Icon getLeafIcon() {
273  return icon;
274  }
275 
276  };
277  actionsTree.setCellRenderer(treeCellRenderer);
278 
279  ToolTipManager.sharedInstance().registerComponent(actionsTree);
280 
281  final GridBagConstraints gbc = new GridBagConstraints();
282  gbc.insets = new Insets(2, 2, 2, 2);
283 
284  final JScrollPane actionsScrollPane = new JScrollPane();
285  actionsScrollPane.setViewportView(actionsTree);
286  actionsScrollPane.setBackground(actionsTree.getBackground());
287  actionsScrollPane.getViewport().add(actionsTree);
288  actionsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
289  actionsScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
290  actionsScrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
291  final JComponent actionsPanel = new JPanel(new GridBagLayout());
292  actionsPanel.setBorder(new TitledBorder(ActionBuilderUtils.getString(ACTION_BUILDER, "shortcuts.borderAllActions")));
293  gbc.gridx = 0;
294  gbc.gridy = 0;
295  gbc.fill = GridBagConstraints.BOTH;
296  gbc.weightx = 1.0;
297  gbc.weighty = 1.0;
298  actionsPanel.add(actionsScrollPane, gbc);
299 
300  actionDescription.setEditable(false);
301  actionDescription.setColumns(20);
302  actionDescription.setLineWrap(true);
303  actionDescription.setWrapStyleWord(true);
304  actionDescription.setBorder(new LineBorder(Color.black));
305  actionDescription.setFocusable(false);
306 
307  actionShortcut.setEditable(false);
308  actionShortcut.setColumns(20);
309  actionShortcut.setRows(3);
310  actionShortcut.setLineWrap(true);
311  actionShortcut.setWrapStyleWord(true);
312  actionShortcut.setBorder(new LineBorder(Color.black));
313  actionShortcut.setFocusable(false);
314 
315  final Container actionDescriptionPanel = new JPanel(new GridBagLayout());
316  gbc.gridx = 0;
317  gbc.gridy = 0;
318  gbc.fill = GridBagConstraints.HORIZONTAL;
319  gbc.weightx = 1.0;
320  gbc.weighty = 0.0;
321  actionDescriptionPanel.add(ActionBuilderUtils.newLabel(ACTION_BUILDER, "shortcuts.actionDescription"), gbc);
322  gbc.gridy++;
323  gbc.fill = GridBagConstraints.BOTH;
324  gbc.weighty = 1.0;
325  actionDescriptionPanel.add(actionDescription, gbc);
326 
327  final Container actionShortcutPanel = new JPanel(new GridBagLayout());
328  gbc.gridx = 0;
329  gbc.gridy = 0;
330  gbc.fill = GridBagConstraints.HORIZONTAL;
331  gbc.weightx = 1.0;
332  gbc.weighty = 0.0;
333  actionShortcutPanel.add(ActionBuilderUtils.newLabel(ACTION_BUILDER, "shortcuts.shortcut"), gbc);
334  gbc.gridy++;
335  actionShortcutPanel.add(actionShortcut, gbc);
336 
337  final Component addShortcutButton = new JButton(aSetShortcut);
338  final Component removeShortcutButton = new JButton(aUnsetShortcut);
339  final Container actionButtonsPanel = new JPanel();
340  actionButtonsPanel.setLayout(new GridBagLayout());
341  gbc.gridx = 0;
342  gbc.gridy = 0;
343  gbc.fill = GridBagConstraints.HORIZONTAL;
344  gbc.weightx = 1.0;
345  gbc.weighty = 0.0;
346  actionButtonsPanel.add(addShortcutButton, gbc);
347  gbc.gridy++;
348  actionButtonsPanel.add(removeShortcutButton, gbc);
349 
350  final JComponent selectedActionPanel = new JPanel();
351  selectedActionPanel.setLayout(new GridBagLayout());
352  selectedActionPanel.setBorder(new TitledBorder(ActionBuilderUtils.getString(ACTION_BUILDER, "shortcuts.selectedAction")));
353  gbc.gridx = 0;
354  gbc.gridy = 0;
355  gbc.fill = GridBagConstraints.BOTH;
356  gbc.weightx = 1.0;
357  gbc.weighty = 1.0;
358  selectedActionPanel.add(actionDescriptionPanel, gbc);
359  gbc.fill = GridBagConstraints.HORIZONTAL;
360  gbc.weighty = 0.0;
361  gbc.gridy++;
362  selectedActionPanel.add(actionShortcutPanel, gbc);
363  gbc.gridy++;
364  selectedActionPanel.add(actionButtonsPanel, gbc);
365 
366  final JPanel panel = new JPanel(new GridBagLayout());
367  gbc.gridx = 0;
368  gbc.gridy = 0;
369  gbc.fill = GridBagConstraints.BOTH;
370  gbc.weightx = 1.0;
371  gbc.weighty = 1.0;
372  panel.add(actionsPanel, gbc);
373  gbc.gridx++;
374  gbc.weightx = 0.0;
375  panel.add(selectedActionPanel, gbc);
376 
377  panel.setBorder(GUIConstants.DIALOG_BORDER);
378  return panel;
379  }
380 
384  @ActionMethod
385  public void shortcutsOkay() {
386  setValue(okButton);
387  }
388 
392  @ActionMethod
393  public void shortcutsDefaults() {
394  if (ACTION_BUILDER.showQuestionDialog(dialog, "shortcutsRestoreDefaults")) {
395  shortcutsManager.revertAll();
397  shortcutsManager.saveShortcuts();
398  }
399  }
400 
401  @Override
402  public void setValue(@Nullable final Object newValue) {
403  super.setValue(newValue);
404  if (newValue != UNINITIALIZED_VALUE) {
405  dialog.dispose();
406  }
407  }
408 
413  private void createNodes(@NotNull final DefaultMutableTreeNode root) {
414  for (final Action action : shortcutsManager) {
415  final String categories = ActionUtils.getActionCategory(action);
416  for (final String category : PATTERN_CATEGORIES.split(categories, -1)) {
417  addNode(root, category, action);
418  }
419  }
420  }
421 
428  private static void addNode(@NotNull final DefaultMutableTreeNode root, @NotNull final CharSequence category, @NotNull final Action action) {
429  final DefaultMutableTreeNode node = getOrCreateNodeForCategory(root, category);
430  final MutableTreeNode treeNode = new DefaultMutableTreeNode(action, false);
431  insertChildNode(node, treeNode);
432  }
433 
440  @NotNull
441  private static DefaultMutableTreeNode getOrCreateNodeForCategory(@NotNull final DefaultMutableTreeNode root, @NotNull final CharSequence category) {
442  DefaultMutableTreeNode node = root;
443  for (final String subCategory : PATTERN_SUB_CATEGORIES.split(category, -1)) {
444  node = getOrCreateChildNode(node, subCategory);
445  }
446  return node;
447  }
448 
455  @NotNull
456  private static DefaultMutableTreeNode getOrCreateChildNode(@NotNull final MutableTreeNode root, @NotNull final String subCategory) {
457  final int childCount = root.getChildCount();
458  for (int i = 0; i < childCount; i++) {
459  final TreeNode treeNode = root.getChildAt(i);
460  final String nodeTitle = getTitle(treeNode);
461  if (nodeTitle != null && nodeTitle.equals(CATEGORY_PREFIX + subCategory)) {
462  assert treeNode instanceof DefaultMutableTreeNode;
463  return (DefaultMutableTreeNode) treeNode;
464  }
465  }
466 
467  final DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(subCategory);
468  insertChildNode(root, treeNode);
469  return treeNode;
470  }
471 
477  private static void insertChildNode(@NotNull final MutableTreeNode branchNode, @NotNull final MutableTreeNode childNode) {
478  branchNode.insert(childNode, getInsertionIndex(branchNode, childNode));
479  }
480 
487  private static int getInsertionIndex(@NotNull final TreeNode parentNode, @NotNull final TreeNode childNode) {
488  final String childTitle = getTitle(childNode);
489  if (childTitle == null) {
490  throw new IllegalArgumentException();
491  }
492 
493  final int childCount = parentNode.getChildCount();
494  for (int i = 0; i < childCount; i++) {
495  final TreeNode treeNode = parentNode.getChildAt(i);
496  final String nodeTitle = getTitle(treeNode);
497  if (nodeTitle != null && nodeTitle.compareToIgnoreCase(childTitle) > 0) {
498  return i;
499  }
500  }
501 
502  return childCount;
503  }
504 
510  @Nullable
511  private static Action getAction(@Nullable final Object node) {
512  if (node == null || !(node instanceof DefaultMutableTreeNode)) {
513  return null;
514  }
515 
516  final DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode) node;
517  final Object userObject = defaultMutableTreeNode.getUserObject();
518  if (userObject == null || !(userObject instanceof Action)) {
519  return null;
520  }
521 
522  return (Action) userObject;
523  }
524 
530  @Nullable
531  private static String getTitle(@NotNull final TreeNode node) {
532  if (!(node instanceof DefaultMutableTreeNode)) {
533  return null;
534  }
535 
536  final DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode) node;
537  final Object userObject = defaultMutableTreeNode.getUserObject();
538  if (userObject == null) {
539  return null;
540  }
541 
542  if (userObject instanceof String) {
543  return CATEGORY_PREFIX + userObject;
544  }
545 
546  if (userObject instanceof Action) {
547  return ACTION_PREFIX + ActionUtils.getActionName((Action) userObject);
548  }
549 
550  return null;
551  }
552 
557  private void setSelectedAction(@Nullable final Action selectedAction) {
558  if (this.selectedAction == selectedAction) {
559  return;
560  }
561 
562  this.selectedAction = selectedAction;
564  }
565 
569  private void updateSelectedAction() {
570  actionDescription.setText(selectedAction == null ? "" : ActionUtils.getActionDescription(selectedAction));
571  if (selectedAction == null) {
572  actionShortcut.setText("");
573  } else {
574  actionShortcut.setText(ActionUtils.getShortcutDescription(selectedAction, Action.ACCELERATOR_KEY));
575  }
576  aSetShortcut.setEnabled(selectedAction != null);
577  aUnsetShortcut.setEnabled(getUnsetShortcutEnabled() != null);
578  }
579 
584  @Nullable
585  private Action getUnsetShortcutEnabled() {
586  final Action action = selectedAction;
587  return action != null && ActionUtils.getShortcut(action) != null ? action : null;
588  }
589 
593  @ActionMethod
594  public void shortcutsSetShortcut() {
595  final Action action = selectedAction;
596  if (action == null) {
597  return;
598  }
599 
600  final KeyStrokeDialog keyStrokeDialog = new KeyStrokeDialog(dialog, shortcutsManager, action);
601  if (keyStrokeDialog.showDialog(dialog)) {
602  ActionUtils.setActionShortcut(action, keyStrokeDialog.getKeyStroke());
604  shortcutsManager.saveShortcuts();
605  }
606  }
607 
611  @ActionMethod
612  public void shortcutsUnsetShortcut() {
613  final Action action = getUnsetShortcutEnabled();
614  if (action != null) {
615  ActionUtils.setActionShortcut(action, null);
617  shortcutsManager.saveShortcuts();
618  }
619  }
620 
621 }
KeyStroke getKeyStroke()
Returns the currently shown key stroke.
Utility class for string manipulation.
Graphical User Interface of Gridarta.
static String getShortcutDescription(@NotNull final Action action, @NotNull final String key)
Returns a description of the shortcut of an Action.
void revertAll()
Reverts all shortcuts to their default values.
static final ActionBuilder ACTION_BUILDER
The ActionBuilder.
boolean showDialog(@NotNull final Component parentComponent)
Opens the dialog.
void shortcutsDefaults()
Action method for restore to defaults.
static String getActionCategory(@NotNull final Action action)
Returns an Action&#39;s category.
static String getTitle(@NotNull final TreeNode node)
Returns the category for a node in the action tree.
final Action aUnsetShortcut
The Action for the "unset shortcut" button.
Utility class implementing Action related functions.
ShortcutsDialog(@NotNull final Component parentComponent, @NotNull final ShortcutsManager shortcutsManager)
Creates a new instance.
void saveShortcuts()
Saves all shortcuts to the preferences.
final JTextArea actionShortcut
The shortcut of the selected action.
static void addNode(@NotNull final DefaultMutableTreeNode root, @NotNull final CharSequence category, @NotNull final Action action)
Adds an Action to a branch node.
static final String ACTION_PREFIX
Prefix for internal action names.
static int getInsertionIndex(@NotNull final TreeNode parentNode, @NotNull final TreeNode childNode)
Returns the index to insert a new child node into a parent node.
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
void shortcutsUnsetShortcut()
The action method for the "set shortcut" button.
Base package of all Gridarta classes.
final JTextArea actionDescription
The description of the selected action.
void createNodes(@NotNull final DefaultMutableTreeNode root)
Creates nodes for all actions.
static DefaultMutableTreeNode getOrCreateChildNode(@NotNull final MutableTreeNode root, @NotNull final String subCategory)
Returns a child node by category name.
static Icon getActionIcon(@NotNull final Action action)
Returns an Icon associated with the action.
static final Pattern PATTERN_CATEGORIES
The Pattern to split a list of action categories.
Manager for shortcuts of all Actions in an ActionBuilder instance.
Action getUnsetShortcutEnabled()
Returns whether "unset shortcut" is enabled.
static DefaultMutableTreeNode getOrCreateNodeForCategory(@NotNull final DefaultMutableTreeNode root, @NotNull final CharSequence category)
Returns the branch DefaultMutableTreeNode for a given category.
static final Pattern PATTERN_COMMA
The pattern that matches a single comma (",").
static KeyStroke getShortcut(@NotNull final Action action)
Returns the shortcut of an Action.
static final String CATEGORY_PREFIX
Prefix for internal category names.
Utility class for ActionBuilder related functions.
static void setActionShortcut(@NotNull final Action action, @Nullable final KeyStroke shortcut)
Sets the shortcut of an Action.
void setValue(@Nullable final Object newValue)
final Action aSetShortcut
The Action for the "set shortcut" button.
static String getActionDescription(@NotNull final Action action)
Returns the description for an Action.
Border DIALOG_BORDER
The Border object to be used when creating dialogs.
final ShortcutsManager shortcutsManager
The ShortcutsManager to affect.
final JTree actionsTree
The JTree showing all actions.
static final long serialVersionUID
The serial Version UID.
static String getActionName(@NotNull final Action action)
Returns the name of an Action.
void updateSelectedAction()
Updates the information shown for the selected action.
static JLabel newLabel(@NotNull final ActionBuilder actionBuilder, @NotNull final String key)
Creates a new JLabel from a resource key.
static final Pattern PATTERN_SUB_CATEGORIES
The Pattern to split a category into sub-categories.
static Action getAction(@Nullable final Object node)
Returns the Action for a node in the action tree.
void setSelectedAction(@Nullable final Action selectedAction)
Updates the selected action.
Defines common UI constants used in different dialogs.
void shortcutsSetShortcut()
The action method for the "set shortcut" button.
void showDialog(@NotNull final Component parentComponent)
Opens the dialog.
static void insertChildNode(@NotNull final MutableTreeNode branchNode, @NotNull final MutableTreeNode childNode)
Inserts a new child node into a branch node.
static final Pattern PATTERN_SLASH
The pattern that matches a single slash ("/").