Crossfire JXClient, Trunk
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-2017,2019-2023 Andreas Kirschbaum
20  * Copyright (C) 2010-2012,2014-2018,2020-2023 Nicolas Weeger
21  */
22 
23 package com.realtime.crossfire.jxclient.gui.keybindings;
24 
28 import java.awt.event.KeyEvent;
29 import java.io.BufferedReader;
30 import java.io.BufferedWriter;
31 import java.io.IOException;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Files;
34 import java.nio.file.NoSuchFileException;
35 import java.nio.file.Path;
36 import java.util.Collection;
37 import java.util.TreeSet;
38 import java.util.function.Predicate;
39 import java.util.stream.Collectors;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 
47 public class KeyBindings {
48 
52  @NotNull
54 
58  @NotNull
59  private final Collection<KeyBinding> keybindings = new TreeSet<>();
60 
65  private boolean modified;
66 
70  @Nullable
71  private final Path fileVersion2;
72 
77  @Nullable
78  private final Path fileVersion1;
79 
83  @Nullable
85 
94  public KeyBindings(@Nullable final Path fileVersion2, @Nullable final Path fileVersion1, @NotNull final GUICommandFactory guiCommandFactory) {
95  this.fileVersion2 = fileVersion2;
96  this.fileVersion1 = fileVersion1;
97  this.guiCommandFactory = guiCommandFactory;
98  }
99 
107  public void addKeyBinding(@NotNull final KeyEvent2 keyEvent, @NotNull final CommandList cmdList, final boolean isDefault) {
108  addKeyBinding(new KeyBinding(keyEvent, cmdList, isDefault));
109  saveKeyBindings();
110  }
111 
116  private void addKeyBinding(@NotNull final KeyBinding keyBinding) {
117  keybindings.remove(keyBinding);
118  keybindings.add(keyBinding);
119  modified = true;
120  }
121 
126  public void deleteKeyBinding(@NotNull final KeyEvent2 keyEvent) {
127  deleteKeyBinding(getKeyBinding(keyEvent));
128  }
129 
134  private void deleteKeyBinding(@Nullable final KeyBinding keyBinding) {
135  if (keyBinding != null) {
136  keybindings.remove(keyBinding);
137  modified = true;
138  saveKeyBindings();
139  }
140  }
141 
146  public void loadKeyBindings() {
147  modified = false;
148 
149  final Path file;
150  if (fileVersion2 != null && Files.exists(fileVersion2)) {
151  file = fileVersion2;
152  } else if (fileVersion1 != null && Files.exists(fileVersion1)) {
153  file = fileVersion1;
154  } else {
155  return;
156  }
157 
158  try {
159  try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
160  while (true) {
161  final String line = reader.readLine();
162  if (line == null) {
163  break;
164  }
165 
166  try {
167  parseKeyBinding(line, false);
168  } catch (final InvalidKeyBindingException ex) {
169  System.err.println("ignoring invalid key binding ("+ex.getMessage()+"): "+line);
170  }
171  }
172  }
173  } catch (final NoSuchFileException ignored) {
174  // no error message
175  keybindings.clear();
176  } catch (final IOException ex) {
177  keybindings.clear();
178  modified = false;
179  System.err.println("Cannot read keybindings file "+file+": "+ex.getMessage());
180  }
181 
182  modified = false;
183  }
184 
189  public void saveKeyBindings() {
190  if (fileVersion2 == null || !modified) {
191  return;
192  }
193 
194  if (keybindings.size() <= 0) {
195  try {
196  Files.deleteIfExists(fileVersion2);
197  } catch (final IOException ex) {
198  System.err.println("Cannot write keybindings file "+fileVersion2+": "+ex.getMessage());
199  }
200  return;
201  }
202 
203  try {
204  try (BufferedWriter bw = Files.newBufferedWriter(fileVersion2, StandardCharsets.UTF_8)) {
205  for (KeyBinding keyBinding : keybindings) {
206  //noinspection StatementWithEmptyBody
207  if (keyBinding.isDefault()) {
208  // ignore
209  } else {
210  if (keyCodeMap == null) {
211  keyCodeMap = new KeyCodeMap();
212  }
213 
214  final KeyEvent2 keyEvent = keyBinding.getKeyEvent2();
215  final int code = keyEvent.getKeyCode();
216  if (code == KeyEvent.VK_UNDEFINED) {
217  bw.write("char ");
218  bw.write(Integer.toString(keyEvent.getKeyChar()));
219  } else {
220  bw.write("code ");
221  bw.write(keyCodeMap.getKeyName(code));
222  bw.write(' ');
223  bw.write(KeyEvent2.modifiersToString(keyEvent.getModifiers()));
224  }
225  bw.write(' ');
226  bw.write(guiCommandFactory.encode(keyBinding.getCommandString()));
227  bw.newLine();
228  }
229  }
230  }
231  } catch (final IOException ex) {
232  System.err.println("Cannot write keybindings file "+fileVersion2+": "+ex.getMessage());
233  }
234 
235  modified = false;
236  }
237 
243  @Nullable
244  private KeyBinding getKeyBinding(@NotNull final KeyEvent2 keyEvent) {
245  for (KeyBinding keyBinding : keybindings) {
246  if (keyBinding.getKeyEvent2().equals(keyEvent)) {
247  return keyBinding;
248  }
249  }
250 
251  return null;
252  }
253 
261  public void parseKeyBinding(@NotNull final String line, final boolean isDefault) throws InvalidKeyBindingException {
262  if (line.startsWith("char ")) {
263  final String[] tmp = line.substring(5).split(" +", 2);
264  if (tmp.length != 2) {
265  throw new InvalidKeyBindingException("syntax error");
266  }
267 
268  try {
269  final char keyChar = (char)Integer.parseInt(tmp[0]);
270  final CommandList commandList = new CommandList(CommandListType.AND);
271  commandList.add(guiCommandFactory.createCommandDecode(tmp[1]));
272  addKeyBinding(new KeyBinding(new KeyEvent2(KeyEvent.VK_UNDEFINED, keyChar, 0), commandList, isDefault));
273  } catch (final NumberFormatException ex) {
274  final InvalidKeyBindingException keyBindingException = new InvalidKeyBindingException("syntax error");
275  keyBindingException.initCause(ex);
276  throw keyBindingException;
277  }
278  } else if (line.startsWith("code ")) {
279  final String[] tmp = line.substring(5).split(" +", 3);
280  if (tmp.length != 3) {
281  throw new InvalidKeyBindingException("syntax error");
282  }
283 
284  if (keyCodeMap == null) {
285  keyCodeMap = new KeyCodeMap();
286  }
287 
288  final int keyCode;
289  try {
290  keyCode = keyCodeMap.getKeyCode(tmp[0]);
291  } catch (final NoSuchKeyCodeException ex) {
292  final InvalidKeyBindingException keyBindingException = new InvalidKeyBindingException("invalid key code: "+tmp[0]);
293  keyBindingException.initCause(ex);
294  throw keyBindingException;
295  }
296  if (keyCode == KeyEvent.VK_UNDEFINED) {
297  throw new InvalidKeyBindingException("invalid key code: "+tmp[0]);
298  }
299 
300  int modifiers;
301  try {
302  modifiers = KeyEvent2.convertModifiers(Integer.parseInt(tmp[1]));
303  } catch (final NumberFormatException ignored) {
304  modifiers = KeyEvent2.stringToModifiers(tmp[1]);
305  }
306 
307  final CommandList commandList = new CommandList(CommandListType.AND);
308  commandList.add(guiCommandFactory.createCommandDecode(tmp[2]));
309  addKeyBinding(new KeyBinding(new KeyEvent2(keyCode, KeyEvent.CHAR_UNDEFINED, modifiers), commandList, isDefault));
310  } else {
311  throw new InvalidKeyBindingException("syntax error");
312  }
313  }
314 
320  public boolean handleKeyPress(@NotNull final KeyEvent2 e) {
321  for (KeyBinding keyBinding : keybindings) {
322  if (keyBinding.matchesKeyEvent(e)) {
323  executeKeyBinding(keyBinding);
324  return true;
325  }
326  }
327 
328  return false;
329  }
330 
335  private static void executeKeyBinding(@NotNull final KeyBinding keyBinding) {
336  keyBinding.getCommands().execute();
337  }
338 
344  @NotNull
345  public Collection<KeyBinding> getBindings(@NotNull final Predicate<KeyBinding> predicate) {
346  return keybindings.stream().filter(predicate).collect(Collectors.toSet());
347  }
348 
349 }
com.realtime.crossfire.jxclient
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.getKeyChar
char getKeyChar()
Definition: KeyEvent2.java:111
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.KeyBindings
KeyBindings(@Nullable final Path fileVersion2, @Nullable final Path fileVersion1, @NotNull final GUICommandFactory guiCommandFactory)
Definition: KeyBindings.java:94
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2
Definition: KeyEvent2.java:34
com.realtime.crossfire.jxclient.gui.keybindings.KeyCodeMap.getKeyCode
int getKeyCode(@NotNull final String keyName)
Definition: KeyCodeMap.java:76
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.handleKeyPress
boolean handleKeyPress(@NotNull final KeyEvent2 e)
Definition: KeyBindings.java:320
com.realtime.crossfire.jxclient.gui.keybindings.KeyCodeMap.getKeyName
String getKeyName(final int keyCode)
Definition: KeyCodeMap.java:96
com.realtime.crossfire.jxclient.gui.commandlist.CommandList
Definition: CommandList.java:34
com.realtime.crossfire.jxclient.gui.commandlist.GUICommandFactory.encode
String encode(@NotNull final String command)
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.getModifiers
int getModifiers()
Definition: KeyEvent2.java:120
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.saveKeyBindings
void saveKeyBindings()
Definition: KeyBindings.java:189
com.realtime.crossfire.jxclient.gui.commandlist.CommandListType.AND
AND
Definition: CommandListType.java:34
com.realtime.crossfire.jxclient.gui.commandlist
Definition: CommandList.java:23
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.fileVersion2
final Path fileVersion2
Definition: KeyBindings.java:71
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.getKeyBinding
KeyBinding getKeyBinding(@NotNull final KeyEvent2 keyEvent)
Definition: KeyBindings.java:244
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.stringToModifiers
static int stringToModifiers(@NotNull final String modifiers)
Definition: KeyEvent2.java:236
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.getKeyCode
int getKeyCode()
Definition: KeyEvent2.java:103
com.realtime.crossfire.jxclient.gui.keybindings.NoSuchKeyCodeException
Definition: NoSuchKeyCodeException.java:29
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.modifiersToString
static String modifiersToString(final int modifiers)
Definition: KeyEvent2.java:209
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.guiCommandFactory
final GUICommandFactory guiCommandFactory
Definition: KeyBindings.java:53
com.realtime.crossfire.jxclient.gui.commandlist.GUICommandFactory
Definition: GUICommandFactory.java:32
com.realtime.crossfire.jxclient.gui.commandlist.GUICommandFactory.createCommandDecode
GUICommand createCommandDecode(@NotNull String encodedCommandString)
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.fileVersion1
final Path fileVersion1
Definition: KeyBindings.java:78
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.parseKeyBinding
void parseKeyBinding(@NotNull final String line, final boolean isDefault)
Definition: KeyBindings.java:261
com.realtime.crossfire.jxclient.gui
com.realtime.crossfire.jxclient.gui.keybindings.InvalidKeyBindingException
Definition: InvalidKeyBindingException.java:31
com.realtime.crossfire.jxclient.gui.keybindings.KeyBinding
Definition: KeyBinding.java:37
com.realtime.crossfire.jxclient.gui.keybindings.KeyCodeMap
Definition: KeyCodeMap.java:35
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.addKeyBinding
void addKeyBinding(@NotNull final KeyEvent2 keyEvent, @NotNull final CommandList cmdList, final boolean isDefault)
Definition: KeyBindings.java:107
com.realtime.crossfire.jxclient.gui.commandlist.CommandList.add
void add(@NotNull final GUICommand guiCommand)
Definition: CommandList.java:60
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.getBindings
Collection< KeyBinding > getBindings(@NotNull final Predicate< KeyBinding > predicate)
Definition: KeyBindings.java:345
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.keyCodeMap
KeyCodeMap keyCodeMap
Definition: KeyBindings.java:84
com.realtime.crossfire
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.keybindings
final Collection< KeyBinding > keybindings
Definition: KeyBindings.java:59
com.realtime
com
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.loadKeyBindings
void loadKeyBindings()
Definition: KeyBindings.java:146
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.deleteKeyBinding
void deleteKeyBinding(@NotNull final KeyEvent2 keyEvent)
Definition: KeyBindings.java:126
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings
Definition: KeyBindings.java:47
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.modified
boolean modified
Definition: KeyBindings.java:65
com.realtime.crossfire.jxclient.gui.commandlist.CommandListType
Definition: CommandListType.java:29
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.deleteKeyBinding
void deleteKeyBinding(@Nullable final KeyBinding keyBinding)
Definition: KeyBindings.java:134
com.realtime.crossfire.jxclient.gui.keybindings.KeyEvent2.convertModifiers
static int convertModifiers(final int modifiers)
Definition: KeyEvent2.java:183
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.executeKeyBinding
static void executeKeyBinding(@NotNull final KeyBinding keyBinding)
Definition: KeyBindings.java:335
com.realtime.crossfire.jxclient.gui.keybindings.KeyBindings.addKeyBinding
void addKeyBinding(@NotNull final KeyBinding keyBinding)
Definition: KeyBindings.java:116