/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2015 Alex Buloichik, Yu Tang 2017 Aaron Madlon-Kay Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.gui.shortcuts; import java.awt.Component; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.InputMap; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.KeyStroke; import org.omegat.util.Platform; import org.omegat.util.StaticUtils; import org.omegat.util.StringUtil; /** * The <code>PropertiesShortcuts</code> class represents a persistent set of shortcut. * * @author Alex Buloichik (alex73mail@gmail.com) * @author Yu Tang * @author Aaron Madlon-Kay */ public class PropertiesShortcuts { private static final Logger LOGGER = Logger.getLogger(PropertiesShortcuts.class.getName()); private static final String BUNDLED_ROOT = "/org/omegat/gui/main/"; private static final String MAIN_MENU_SHORTCUTS_FILE = "MainMenuShortcuts.properties"; private static final String EDITOR_SHORTCUTS_FILE = "EditorShortcuts.properties"; private static class LoadedShortcuts { static final PropertiesShortcuts MAIN_MENU_SHORTCUTS = loadBundled(BUNDLED_ROOT, MAIN_MENU_SHORTCUTS_FILE);; static final PropertiesShortcuts EDITOR_SHORTCUTS = loadBundled(BUNDLED_ROOT, EDITOR_SHORTCUTS_FILE); } public static PropertiesShortcuts getMainMenuShortcuts() { return LoadedShortcuts.MAIN_MENU_SHORTCUTS; } public static PropertiesShortcuts getEditorShortcuts() { return LoadedShortcuts.EDITOR_SHORTCUTS; } private final Map<String, String> data = new HashMap<>(); /** * Creates shortcut list with the specified defaults and user shortcuts. * Look for specified file in these places in this order: * <ol> * <li>Stream in classpath * <li>File of same name in the user's config dir * </ol> * For each shortcut, user shortcuts have priority, then defaults (for * Mac-specific or others). * * @param classpathRoot * the path to the file on the classpath. Should include a * trailing slash. * @param filename * name of file to load */ static PropertiesShortcuts loadBundled(String classpathRoot, String filename) { PropertiesShortcuts result = new PropertiesShortcuts(); try { result.loadFromClasspath(classpathRoot + filename); result.loadFromFile(new File(StaticUtils.getConfigDir(), filename)); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Failed to load shortcuts properties file", ex); } return result; } public void loadFromClasspath(String propertiesFile) throws IOException { boolean loaded = false; if (Platform.isMacOSX()) { String macSpecific = getMacProperties(propertiesFile); loaded = loadFromClasspathImpl(macSpecific); } if (!loaded) { loadFromClasspathImpl(propertiesFile); } } private String getMacProperties(String properties) { return properties.replaceAll("\\.properties$", ".mac.properties"); } /** * Load the properties file from the classpath * * @param path * @return whether the file was loaded (<code>false</code> if not present, * etc.) * @throws IOException */ private boolean loadFromClasspathImpl(String path) throws IOException { try (InputStream in = getClass().getResourceAsStream(path)) { if (in != null) { loadProperties(in); return true; } } return false; } public void loadFromFile(File file) throws IOException { boolean loaded = false; if (Platform.isMacOSX()) { File macSpecific = new File(getMacProperties(file.getPath())); if (macSpecific.isFile()) { loadFromFileImpl(macSpecific); loaded = true; } } if (!loaded && file.isFile()) { loadFromFileImpl(file); } } private void loadFromFileImpl(File file) throws IOException { try (FileInputStream fis = new FileInputStream(file)) { loadProperties(fis); } } private void loadProperties(InputStream in) throws IOException { Properties props = new Properties(); props.load(in); props.forEach((k, v) -> data.put(k.toString(), v.toString())); } public KeyStroke getKeyStroke(String key) { String shortcut = data.get(key); if (shortcut == null) { throw new IllegalArgumentException("Keyboard shortcut not defined. Key=" + key); } KeyStroke result = KeyStroke.getKeyStroke(shortcut); if (!shortcut.isEmpty() && result == null) { LOGGER.warning("Keyboard shortcut is invalid: " + key + "=" + shortcut); } return result; } public void bindKeyStrokes(JMenuBar menu) { applyTo(menu.getComponents()); } /** * Travel by all submenus for setup shortcuts. * * @param menu * menu or menu item */ private void applyTo(final Component[] items) { for (Component c : items) { if (c instanceof JMenuItem) { bindKeyStrokes((JMenuItem) c); } } } public void bindKeyStrokes(final JMenuItem item) { if (item instanceof JMenu) { // setAccelerator() is not defined for JMenu. applyTo(((JMenu) item).getMenuComponents()); } else { String shortcut = item.getActionCommand(); if (!StringUtil.isEmpty(shortcut)) { try { item.setAccelerator(getKeyStroke(shortcut)); } catch (Exception ex) { // Eat exception silently } } } } public void bindKeyStrokes(InputMap inputMap, String... keys) { for (String key : keys) { try { KeyStroke keyStroke = getKeyStroke(key); if (keyStroke == null) { removeEntry(inputMap, key); } else { inputMap.put(keyStroke, key); } } catch (Exception ex) { // Eat exception silently } } } private KeyStroke removeEntry(InputMap inputMap, String keyToBeRemoved) { KeyStroke removedEntry = null; for (KeyStroke ks : inputMap.keys()) { String key = (String) inputMap.get(ks); if (key.equals(keyToBeRemoved)) { inputMap.remove(ks); removedEntry = ks; break; } } return removedEntry; } public boolean isEmpty() { return data.isEmpty(); } /** * For testing purposes * * @return Unmodifiable reference to data held by this instance */ Map<String, String> getData() { return Collections.unmodifiableMap(data); } }