Crossfire JXClient, Trunk  R20561
KeyBindings.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff.
19  * Copyright (C) 2006-2011 Andreas Kirschbaum.
20  */
21 
22 package com.realtime.crossfire.jxclient.gui.keybindings;
23 
27 import java.io.BufferedWriter;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStreamReader;
34 import java.io.LineNumberReader;
35 import java.io.OutputStreamWriter;
36 import java.util.Collection;
37 import java.util.HashSet;
38 import java.util.stream.Collectors;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41 
46 public class KeyBindings {
47 
51  @NotNull
53 
57  @NotNull
58  private final Collection<KeyBinding> keybindings = new HashSet<>();
59 
64  private boolean modified;
65 
69  @Nullable
70  private final File file;
71 
75  @Nullable
77 
83  public KeyBindings(@Nullable final File file, @NotNull final GUICommandFactory guiCommandFactory) {
84  this.file = file;
85  this.guiCommandFactory = guiCommandFactory;
86  }
87 
92  @Nullable
93  public File getFile() {
94  return file;
95  }
96 
104  public void addKeyBindingAsKeyCode(@NotNull final KeyEvent2 keyEvent, @NotNull final CommandList cmdList, final boolean isDefault) {
105  addKeyBinding(new KeyCodeKeyBinding(keyEvent, cmdList, isDefault));
106  }
107 
115  public void addKeyBindingAsKeyChar(final char keyChar, @NotNull final CommandList cmdList, final boolean isDefault) {
116  addKeyBinding(new KeyCharKeyBinding(keyChar, cmdList, isDefault));
117  }
118 
123  private void addKeyBinding(@NotNull final KeyBinding keyBinding) {
124  keybindings.remove(keyBinding);
125  keybindings.add(keyBinding);
126  modified = true;
127  try {
128  saveKeyBindings();
129  } catch (final IOException ex) {
130  System.err.println("Cannot write keybindings file "+file+": "+ex.getMessage());
131  }
132  }
133 
138  public void deleteKeyBindingAsKeyCode(@NotNull final KeyEvent2 keyEvent) {
140  }
141 
146  private void deleteKeyBinding(@Nullable final KeyBinding keyBinding) {
147  if (keyBinding != null) {
148  keybindings.remove(keyBinding);
149  modified = true;
150  try {
151  saveKeyBindings();
152  } catch (final IOException ex) {
153  System.err.println("Cannot write keybindings file "+file+": "+ex.getMessage());
154  }
155  }
156  }
157 
162  public void loadKeyBindings() throws IOException {
163  modified = false;
164 
165  if (file == null) {
166  return;
167  }
168 
169  try {
170  try (final FileInputStream fis = new FileInputStream(file)) {
171  try (final InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) {
172  try (final LineNumberReader lnr = new LineNumberReader(isr)) {
173  while (true) {
174  final String line = lnr.readLine();
175  if (line == null) {
176  break;
177  }
178 
179  try {
180  parseKeyBinding(line, false);
181  } catch (final InvalidKeyBindingException ex) {
182  System.err.println("ignoring invalid key binding ("+ex.getMessage()+"): "+line);
183  }
184  }
185  }
186  }
187  }
188  } catch (final FileNotFoundException ignored) {
189  // no error message
190  keybindings.clear();
191  } catch (final IOException ex) {
192  keybindings.clear();
193  modified = false;
194  throw ex;
195  }
196 
197  modified = false;
198  }
199 
204  public void saveKeyBindings() throws IOException {
205  if (file == null || !modified) {
206  return;
207  }
208 
209  if (keybindings.size() <= 0) {
210  if (!file.delete()) {
211  throw new IOException("cannot delete file");
212  }
213  return;
214  }
215 
216  try (final FileOutputStream fos = new FileOutputStream(file)) {
217  try (final OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {
218  try (final BufferedWriter bw = new BufferedWriter(osw)) {
219  for (final KeyBinding keyBinding : keybindings) {
220  if (keyBinding.isDefault()) {
221  // ignore
222  } else if (keyBinding instanceof KeyCodeKeyBinding) {
223  if (keyCodeMap == null) {
224  keyCodeMap = new KeyCodeMap();
225  }
226 
227  final KeyCodeKeyBinding keyCodeKeyBinding = (KeyCodeKeyBinding)keyBinding;
228  bw.write("code ");
229  final KeyEvent2 keyEvent = keyCodeKeyBinding.getKeyEvent2();
230  bw.write(keyCodeMap.getKeyName(keyEvent.getKeyCode()));
231  bw.write(' ');
232  bw.write(Integer.toString(keyEvent.getModifiers()));
233  bw.write(' ');
234  bw.write(guiCommandFactory.encode(keyCodeKeyBinding.getCommandString()));
235  bw.newLine();
236  } else {
237  throw new AssertionError("Cannot encode "+keyBinding.getClass().getName());
238  }
239  }
240  }
241  }
242  }
243  }
244 
250  @Nullable
251  private KeyBinding getKeyBindingAsKeyCode(final KeyEvent2 keyEvent) {
252  for (final KeyBinding keyBinding : keybindings) {
253  if (keyBinding.matchesKeyCode(keyEvent)) {
254  return keyBinding;
255  }
256  }
257 
258  return null;
259  }
260 
266  @Nullable
267  private KeyBinding getKeyBindingAsKeyChar(final char keyChar) {
268  for (final KeyBinding keyBinding : keybindings) {
269  if (keyBinding.matchesKeyChar(keyChar)) {
270  return keyBinding;
271  }
272  }
273 
274  return null;
275  }
276 
284  public void parseKeyBinding(@NotNull final String line, final boolean isDefault) throws InvalidKeyBindingException {
285  if (line.startsWith("char ")) {
286  if (!isDefault) {
287  // ignore "key char" definitions in user keybindings files --
288  // these are not supported anymore.
289  return;
290  }
291 
292  final String[] tmp = line.substring(5).split(" +", 2);
293  if (tmp.length != 2) {
294  throw new InvalidKeyBindingException("syntax error");
295  }
296 
297  try {
298  final char keyChar = (char)Integer.parseInt(tmp[0]);
299  final CommandList commandList = new CommandList(CommandListType.AND);
300  commandList.add(guiCommandFactory.createCommandDecode(tmp[1]));
301  addKeyBindingAsKeyChar(keyChar, commandList, isDefault);
302  } catch (final NumberFormatException ex) {
303  final InvalidKeyBindingException keyBindingException = new InvalidKeyBindingException("syntax error");
304  keyBindingException.initCause(ex);
305  throw keyBindingException;
306  }
307  } else if (line.startsWith("code ")) {
308  final String[] tmp = line.substring(5).split(" +", 3);
309  if (tmp.length != 3) {
310  throw new InvalidKeyBindingException("syntax error");
311  }
312 
313  if (keyCodeMap == null) {
314  keyCodeMap = new KeyCodeMap();
315  }
316 
317  final int keyCode;
318  try {
319  keyCode = keyCodeMap.getKeyCode(tmp[0]);
320  } catch (final NoSuchKeyCodeException ex) {
321  final InvalidKeyBindingException keyBindingException = new InvalidKeyBindingException("invalid key code: "+tmp[0]);
322  keyBindingException.initCause(ex);
323  throw keyBindingException;
324  }
325 
326  final int modifiers;
327  try {
328  modifiers = Integer.parseInt(tmp[1]);
329  } catch (final NumberFormatException ex) {
330  final InvalidKeyBindingException keyBindingException = new InvalidKeyBindingException("invalid modifier: "+tmp[1]);
331  keyBindingException.initCause(ex);
332  throw keyBindingException;
333  }
334 
335  final CommandList commandList = new CommandList(CommandListType.AND);
336  commandList.add(guiCommandFactory.createCommandDecode(tmp[2]));
337  addKeyBindingAsKeyCode(new KeyEvent2(keyCode, (char)0, modifiers), commandList, isDefault);
338  } else {
339  throw new InvalidKeyBindingException("syntax error");
340  }
341  }
342 
348  public boolean handleKeyPress(@NotNull final KeyEvent2 e) {
349  final KeyBinding keyBindingCode = getKeyBindingAsKeyCode(e);
350  if (keyBindingCode != null) {
351  executeKeyBinding(keyBindingCode);
352  return true;
353  }
354 
355  final KeyBinding keyBindingChar = getKeyBindingAsKeyChar(e.getKeyChar());
356  if (keyBindingChar != null) {
357  executeKeyBinding(keyBindingChar);
358  return true;
359  }
360 
361  return false;
362  }
363 
368  private static void executeKeyBinding(@NotNull final KeyBinding keyBinding) {
369  keyBinding.getCommands().execute();
370  }
371 
379  @NotNull
380  public Collection<KeyBinding> getBindingsForPartialCommand(@NotNull final String command, final boolean startOnly) {
381  return keybindings.stream().filter(binding -> (startOnly && binding.getCommandString().startsWith(command)) || (!startOnly && binding.getCommandString().contains(command))).collect(Collectors.toSet());
382  }
383 
384 }
GUICommand createCommandDecode(@NotNull String encodedCommandString)
Creates a new GUICommand instance from string representation.
KeyBindings(@Nullable final File file, @NotNull final GUICommandFactory guiCommandFactory)
Creates a new instance.
void parseKeyBinding(@NotNull final String line, final boolean isDefault)
Parses and add a key binding.
final File file
The file for saving the bindings;.
final Collection< KeyBinding > keybindings
The active key bindings.
void addKeyBindingAsKeyCode(@NotNull final KeyEvent2 keyEvent, @NotNull final CommandList cmdList, final boolean isDefault)
Adds a key binding for a key code/modifiers pair.
KeyBinding getKeyBindingAsKeyChar(final char keyChar)
Finds a key binding associated to a key character.
boolean modified
Whether the contents of keybindings have been modified from the last saved state. ...
Represents a pressed or released key.
Definition: KeyEvent2.java:33
String getKeyName(final int keyCode)
Returns the key name for a key code.
void add(@NotNull final GUICommand guiCommand)
Adds a command to the end of this command list.
Maps between key codes integer constants and string representations.
Definition: KeyCodeMap.java:34
void saveKeyBindings()
Saves the key bindings to the given file.
void deleteKeyBinding(@Nullable final KeyBinding keyBinding)
Removes a key binding.
final GUICommandFactory guiCommandFactory
The GUICommandFactory for creating commands.
KeyBinding getKeyBindingAsKeyCode(final KeyEvent2 keyEvent)
Finds a key binding associated to a key code/modifiers pair.
static void executeKeyBinding(@NotNull final KeyBinding keyBinding)
Executes a KeyBinding instance.
Factory for creating GUICommand instances from string representation.
Collection< KeyBinding > getBindingsForPartialCommand(@NotNull final String command, final boolean startOnly)
Search bindings having a command text starting with the specified value.
void deleteKeyBindingAsKeyCode(@NotNull final KeyEvent2 keyEvent)
Removes a key binding for a key code/modifiers pair.
void addKeyBindingAsKeyChar(final char keyChar, @NotNull final CommandList cmdList, final boolean isDefault)
Adds a key binding for a key character.
String getCommandString()
Returns the commands as a string.
void loadKeyBindings()
Loads the key bindings from the given file.
AND
List is executed if all entries can execute.
String encode(@NotNull final String command)
Encodes a key binding if necessary.
int getKeyCode(@NotNull final String keyName)
Returns the key code for a key name.
Definition: KeyCodeMap.java:83
File getFile()
Returns the file for saving the bindings;.
A KeyBinding that matches by key code/modifiers pair.
boolean handleKeyPress(@NotNull final KeyEvent2 e)
Executes a "key press" event.
void addKeyBinding(@NotNull final KeyBinding keyBinding)
Adds (or replace) a key binding.