package org.jabref.gui.keyboard; import java.awt.AWTError; import java.awt.HeadlessException; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.SortedMap; import java.util.TreeMap; import java.util.stream.Collectors; import javax.swing.KeyStroke; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import org.jabref.logic.util.OS; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class KeyBindingRepository { private static final Log LOGGER = LogFactory.getLog(KeyBindingRepository.class); /** * sorted by localization */ private final SortedMap<KeyBinding, String> bindings; private int shortcutMask = -1; public KeyBindingRepository() { this(Collections.emptyList(), Collections.emptyList()); } public KeyBindingRepository(List<String> bindNames, List<String> bindings) { this.bindings = new TreeMap<>(Comparator.comparing(KeyBinding::getLocalization)); if ((bindNames.isEmpty()) || (bindings.isEmpty()) || (bindNames.size() != bindings.size())) { // Use default key bindings for (KeyBinding keyBinding : KeyBinding.values()) { put(keyBinding, keyBinding.getDefaultBinding()); } } else { for (int i = 0; i < bindNames.size(); i++) { put(bindNames.get(i), bindings.get(i)); } } } public Optional<String> get(KeyBinding key) { return getKeyBinding(key).flatMap(k -> Optional.ofNullable(bindings.get(k))); } public String get(String key) { Optional<KeyBinding> keyBinding = getKeyBinding(key); Optional<String> result = keyBinding.flatMap(k -> Optional.ofNullable(bindings.get(k))); if (result.isPresent()) { return result.get(); } else if (keyBinding.isPresent()) { return keyBinding.get().getDefaultBinding(); } else { return "Not associated"; } } /** * Returns the HashMap containing all key bindings. */ public SortedMap<KeyBinding, String> getKeyBindings() { return new TreeMap<>(bindings); } public void put(KeyBinding key, String value) { getKeyBinding(key).ifPresent(binding -> bindings.put(binding, value)); } public void put(String key, String value) { getKeyBinding(key).ifPresent(binding -> bindings.put(binding, value)); } private Optional<KeyBinding> getKeyBinding(String key) { return Arrays.stream(KeyBinding.values()).filter(b -> b.getKey().equals(key)).findFirst(); } private Optional<KeyBinding> getKeyBinding(KeyBinding key) { return Arrays.stream(KeyBinding.values()).filter(b -> b.equals(key)).findFirst(); } public void resetToDefault(String key) { getKeyBinding(key).ifPresent(b -> bindings.put(b, b.getDefaultBinding())); } public void resetToDefault() { bindings.forEach((b, s) -> bindings.put(b, b.getDefaultBinding())); } public int size() { return this.bindings.size(); } /** * Returns the KeyStroke for this binding, as defined by the defaults, or in the Preferences. */ public KeyStroke getKey(KeyBinding bindName) { String s = get(bindName.getKey()); if (OS.OS_X) { return getKeyForMac(KeyStroke.getKeyStroke(s)); } else { return KeyStroke.getKeyStroke(s); } } private KeyCombination getKeyCombination(KeyBinding bindName) { String binding = get(bindName.getKey()); return KeyCombination.valueOf(binding); } /** * Check if the given keyCombination equals the given keyEvent * * @param combination as KeyCombination * @param keyEvent as KeEvent * @return true if matching, else false */ public boolean checkKeyCombinationEquality(KeyCombination combination, KeyEvent keyEvent) { KeyCode code = keyEvent.getCode(); if (code == KeyCode.UNDEFINED) { return false; } // gather the pressed modifier keys String modifiers = ""; if (keyEvent.isControlDown()) { modifiers = "ctrl"; } if (keyEvent.isShiftDown()) { modifiers += " shift"; } if (keyEvent.isAltDown()) { modifiers += " alt"; } modifiers = modifiers.trim(); String newShortcut = (modifiers.isEmpty()) ? code.toString() : modifiers + " " + code; KeyCombination pressedCombination = KeyCombination.valueOf(newShortcut); return combination.equals(pressedCombination); } /** * Check if the given KeyBinding equals the given keyEvent * * @param binding as KeyBinding * @param keyEvent as KeEvent * @return true if matching, else false */ public boolean checkKeyCombinationEquality(KeyBinding binding, KeyEvent keyEvent) { KeyCombination keyCombination = getKeyCombination(binding); return checkKeyCombinationEquality(keyCombination, keyEvent); } /** * Returns the KeyStroke for this binding, as defined by the defaults, or in the Preferences, but adapted for Mac * users, with the Command key preferred instead of Control. * TODO: Move to OS.java? Or replace with portable Java key codes, i.e. KeyEvent */ private KeyStroke getKeyForMac(KeyStroke ks) { if (ks == null) { return null; } int keyCode = ks.getKeyCode(); if ((ks.getModifiers() & InputEvent.CTRL_MASK) == 0) { return ks; } else { int modifiers = 0; if ((ks.getModifiers() & InputEvent.SHIFT_MASK) != 0) { modifiers = modifiers | InputEvent.SHIFT_MASK; } if ((ks.getModifiers() & InputEvent.ALT_MASK) != 0) { modifiers = modifiers | InputEvent.ALT_MASK; } if (shortcutMask == -1) { try { shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); } catch (AWTError | HeadlessException e) { LOGGER.warn("Problem geting shortcut mask", e); } } return KeyStroke.getKeyStroke(keyCode, shortcutMask + modifiers); } } public List<String> getBindNames() { return bindings.keySet().stream().map(KeyBinding::getKey).collect(Collectors.toList()); } public List<String> getBindings() { return new ArrayList<>(bindings.values()); } }