/* * ShortcutsOptionPane.java - Shortcuts options panel * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1999, 2000, 2001 Slava Pestov * Copyright (C) 2001 Dirk Moebius * Copyright (C) 2011 Matthieu Casanova * * This program 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 2 * of the License, or any later version. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.options; //{{{ Imports import org.gjt.sp.jedit.*; import org.gjt.sp.jedit.gui.FilteredTableModel; import org.gjt.sp.jedit.gui.GrabKeyDialog; import org.gjt.sp.jedit.gui.GrabKeyDialog.KeyBinding; import org.jedit.keymap.Keymap; import org.jedit.keymap.KeymapManager; import org.gjt.sp.util.Log; import org.gjt.sp.util.StandardUtilities; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.AbstractTableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; import java.util.List; //}}} /** * Key binding editor. * @author Slava Pestov * @version $Id: ShortcutsOptionPane.java 23224 2013-09-30 20:51:42Z shlomy $ */ @SuppressWarnings("serial") public class ShortcutsOptionPane extends AbstractOptionPane { //{{{ ShortcutsOptionPane constructor public ShortcutsOptionPane() { super("shortcuts"); } //}}} //{{{ _init() method @Override protected void _init() { allBindings = new Vector<>(); setLayout(new BorderLayout(12, 12)); KeymapManager keymapManager = jEdit.getKeymapManager(); String keymapName = jEdit.getProperty("keymap.current"); selectedKeymap = keymapManager.getKeymap(keymapName); if (selectedKeymap == null) selectedKeymap = keymapManager.getKeymap(KeymapManager.DEFAULT_KEYMAP_NAME); initModels(); duplicateKeymap = new JButton(jEdit.getProperty("options.shortcuts.duplicatekeymap.label")); resetKeymap = new JButton(jEdit.getProperty("options.shortcuts.resetkeymap.label")); deleteKeymap = new JButton(jEdit.getProperty("options.shortcuts.deletekeymap.label")); resetButtons(); ActionListener actionHandler = new ActionHandler(); ComboBoxModel<String> model = new KeymapsModel(); keymaps = new JComboBox<>(model); keymaps.setRenderer(new KeymapCellRenderer()); keymaps.setSelectedItem(keymapName); duplicateKeymap.addActionListener(actionHandler); resetKeymap.addActionListener(actionHandler); deleteKeymap.addActionListener(actionHandler); keymaps.addActionListener(actionHandler); keymaps.setSelectedItem(selectedKeymap); JPanel keymapBox = new JPanel(new FlowLayout(FlowLayout.LEADING)); keymapBox.add(new JLabel(jEdit.getProperty( "options.shortcuts.keymap.label"))); keymapBox.add(keymaps); keymapBox.add(Box.createHorizontalStrut(6)); keymapBox.add(duplicateKeymap); keymapBox.add(resetKeymap); keymapBox.add(deleteKeymap); // combobox to choose action set selectModel = new JComboBox<>(models); selectModel.addActionListener(actionHandler); selectModel.setToolTipText(jEdit.getProperty("options.shortcuts.select.tooltip")); Box north = Box.createHorizontalBox(); north.add(new JLabel(jEdit.getProperty( "options.shortcuts.select.label"))); north.add(Box.createHorizontalStrut(6)); north.add(selectModel); filterTF = new JTextField(40); filterTF.setToolTipText(jEdit.getProperty("options.shortcuts.filter.tooltip")); filterTF.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { setFilter(); } @Override public void insertUpdate(DocumentEvent e) { setFilter(); } @Override public void removeUpdate(DocumentEvent e) { setFilter(); } }); JButton clearButton = new JButton(jEdit.getProperty( "options.shortcuts.clear.label")); clearButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { filterTF.setText(""); filterTF.requestFocus(); } }); JPanel filterPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); filterPanel.add(new JLabel(jEdit.getProperty("options.shortcuts.filter.label"))); filterPanel.add(filterTF); filterPanel.add(clearButton); keyTable = new JTable(filteredModel); filteredModel.setTable(keyTable); keyTable.getTableHeader().setReorderingAllowed(false); keyTable.getTableHeader().addMouseListener(new HeaderMouseHandler()); keyTable.addMouseListener(new TableMouseHandler()); Dimension d = keyTable.getPreferredSize(); d.height = Math.min(d.height,200); JScrollPane scroller = new JScrollPane(keyTable); scroller.setPreferredSize(d); JPanel tableFilterPanel = new JPanel(new BorderLayout()); tableFilterPanel.add(BorderLayout.NORTH,filterPanel); tableFilterPanel.add(BorderLayout.CENTER,scroller); Box northBox = Box.createVerticalBox(); northBox.add(keymapBox); northBox.add(Box.createVerticalGlue()); northBox.add(north); add(BorderLayout.NORTH,northBox); add(BorderLayout.CENTER,tableFilterPanel); try { selectModel.setSelectedIndex(jEdit.getIntegerProperty("options.shortcuts.select.index", 0)); } catch (IllegalArgumentException eae) {} } //}}} //{{{ _save() method @Override protected void _save() { jEdit.setProperty("keymap.current", selectedKeymap.toString()); if(keyTable.getCellEditor() != null) keyTable.getCellEditor().stopCellEditing(); for (ShortcutsModel model : models) model.save(); Macros.loadMacros(); selectedKeymap.save(); } //}}} //{{{ Private members /** The selected keymap. It is a copy of the current keymap in the beginning, but it may be another one */ private Keymap selectedKeymap; private JButton duplicateKeymap; private JButton resetKeymap; private JButton deleteKeymap; private JComboBox<String> keymaps; private JTable keyTable; private Vector<ShortcutsModel> models; private FilteredTableModel<ShortcutsModel> filteredModel; private JComboBox<ShortcutsModel> selectModel; private List<KeyBinding> allBindings; private JTextField filterTF; //{{{ setFilter() method private void setFilter() { filteredModel.setFilter(filterTF.getText()); } //}}} //{{{ initModels() method private void initModels() { filteredModel = new FilteredTableModel<ShortcutsModel>() { @Override public String prepareFilter(String filter) { return filter.toLowerCase(); } @Override public boolean passFilter(int row, String filter) { String name = delegated.getBindingAt(row, 0).label.toLowerCase(); return name.contains(filter); } }; models = new Vector<>(); reloadModels(); } //}}} //{{{ reloadModels() method private void reloadModels() { models.clear(); allBindings.clear(); List<KeyBinding[]> allBindings = new ArrayList<>(); Collection<String> knownBindings = new HashSet<>(); ActionSet[] actionSets = jEdit.getActionSets(); for (ActionSet actionSet : actionSets) { if (actionSet.getActionCount() != 0) { String modelLabel = actionSet.getLabel(); if (modelLabel == null) { Log.log(Log.ERROR, this, "Empty action set: " + actionSet.getPluginJAR()); } ShortcutsModel model = createModel(actionSet.getLabel(), modelLabel, actionSet.getActionNames()); models.add(model); List<KeyBinding[]> bindings = model.getBindings(); for (KeyBinding[] binding : bindings) { String name = binding[0].name; if (!knownBindings.contains(name)) { knownBindings.add(name); allBindings.add(binding); } } } } if (models.size() > 1) models.add(new ShortcutsModel(ShortcutsModel.ALL, allBindings)); ShortcutsModel delegated = filteredModel.getDelegated(); Collections.sort(models,new StandardUtilities.StringCompare<ShortcutsModel>(true)); if (delegated == null) { delegated = models.get(0); } else { for (ShortcutsModel model : models) { // Find the model with the same name if (model.toString().equals(delegated.toString())) { delegated = model; break; } } } filteredModel.setDelegated(delegated); filteredModel.fireTableDataChanged(); } //}}} //{{{ createModel() method private ShortcutsModel createModel(String actionSet, String modelLabel, String[] actions) { List<KeyBinding[]> bindings = new ArrayList<>(actions.length); for (String name : actions) { EditAction ea = jEdit.getAction(name); String label = ea.getLabel(); // Skip certain actions this way if (label == null) continue; label = GUIUtilities.prettifyMenuLabel(label); addBindings(actionSet, name, label, bindings); } return new ShortcutsModel(modelLabel,bindings); } //}}} //{{{ addBindings() method private void addBindings(String actionSet, String name, String label, Collection<KeyBinding[]> bindings) { KeyBinding[] b = new KeyBinding[2]; b[0] = createBinding(actionSet, name,label, selectedKeymap.getShortcut(name + ".shortcut")); b[1] = createBinding(actionSet, name,label, selectedKeymap.getShortcut(name + ".shortcut2")); bindings.add(b); } //}}} //{{{ createBinding() method private KeyBinding createBinding(String actionSet, String name, String label, String shortcut) { if(shortcut != null && shortcut.isEmpty()) shortcut = null; KeyBinding binding = new KeyBinding(name,label,shortcut,false); binding.actionSet = actionSet; allBindings.add(binding); return binding; } //}}} // {{{ resetButtons() methods private void resetButtons() { KeymapManager keymapManager = jEdit.getKeymapManager(); KeymapManager.State state = keymapManager.getKeymapState(selectedKeymap.toString()); resetKeymap.setEnabled(state == KeymapManager.State.SystemModified); deleteKeymap.setEnabled(state == KeymapManager.State.User); } //}}} //{{{ Inner classes //{{{ HeaderMouseHandler class private class HeaderMouseHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent evt) { ShortcutsModel shortcutsModel = filteredModel.getDelegated(); switch(keyTable.getTableHeader().columnAtPoint(evt.getPoint())) { case 0: shortcutsModel.sort(0); break; case 1: shortcutsModel.sort(1); break; case 2: shortcutsModel.sort(2); break; } setFilter(); } } //}}} //{{{ TableMouseHandler class private class TableMouseHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent evt) { int row = keyTable.getSelectedRow(); int col = keyTable.getSelectedColumn(); if(col != 0 && row != -1) { GrabKeyDialog gkd = new GrabKeyDialog( GUIUtilities.getParentDialog( ShortcutsOptionPane.this), filteredModel.getDelegated().getBindingAt(filteredModel.getTrueRow(row), col - 1), allBindings,null); if(gkd.isOK()) filteredModel.setValueAt( gkd.getShortcut(),row,col); } } } //}}} //{{{ ActionHandler class private class ActionHandler implements ActionListener { @Override public void actionPerformed(ActionEvent evt) { if (evt.getSource() == selectModel) { ShortcutsModel newModel = (ShortcutsModel)selectModel.getSelectedItem(); if(filteredModel.getDelegated() != newModel) { jEdit.setIntegerProperty("options.shortcuts.select.index", selectModel.getSelectedIndex()); filteredModel.setDelegated(newModel); setFilter(); } } else if (evt.getSource() == keymaps) { String selectedKeymapName = (String) keymaps.getSelectedItem(); KeymapManager keymapManager = jEdit.getKeymapManager(); selectedKeymap = keymapManager.getKeymap(selectedKeymapName); resetButtons(); reloadModels(); } else if (evt.getSource() == duplicateKeymap) { String newName = JOptionPane.showInputDialog(ShortcutsOptionPane.this, jEdit.getProperty( "options.shortcuts.duplicatekeymap.dialog.label"), jEdit.getProperty( "options.shortcuts.duplicatekeymap.dialog.title"), JOptionPane.QUESTION_MESSAGE); if (newName == null) { return; } newName = newName.replace(' ', '_'); KeymapManager manager = jEdit.getKeymapManager(); Collection<String> keymapNames = manager.getKeymapNames(); while (keymapNames.contains(newName)) { newName = JOptionPane.showInputDialog(ShortcutsOptionPane.this, jEdit.getProperty( "options.shortcuts.duplicatekeymap.keymapalreadyexists.label"), jEdit.getProperty( "options.shortcuts.duplicatekeymap.dialog.title"), JOptionPane.QUESTION_MESSAGE); if (newName == null) { return; } newName = newName.replace(' ', '_'); } if (manager.copyKeymap(selectedKeymap.toString(), newName)) { KeymapsModel model = (KeymapsModel) keymaps.getModel(); model.reset(); keymaps.setSelectedItem(newName); } } else if (evt.getSource() == resetKeymap) { int ret = JOptionPane.showConfirmDialog(ShortcutsOptionPane.this, jEdit.getProperty( "options.shortcuts.resetkeymap.dialog.label"), jEdit.getProperty( "options.shortcuts.resetkeymap.dialog.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ret == JOptionPane.YES_OPTION) { String name = selectedKeymap.toString(); KeymapManager manager = jEdit.getKeymapManager(); manager.resetKeymap(name); selectedKeymap = manager.getKeymap(name); resetButtons(); reloadModels(); } } else if (evt.getSource() == deleteKeymap) { int ret = JOptionPane.showConfirmDialog(ShortcutsOptionPane.this, jEdit.getProperty( "options.shortcuts.deletekeymap.dialog.label"), jEdit.getProperty( "options.shortcuts.deletekeymap.dialog.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ret == JOptionPane.YES_OPTION) { KeymapManager manager = jEdit.getKeymapManager(); KeymapManager.State keymapState = manager.getKeymapState(selectedKeymap.toString()); if (keymapState == KeymapManager.State.User) { manager.deleteUserKeymap(selectedKeymap.toString()); KeymapsModel model = (KeymapsModel) keymaps.getModel(); model.reset(); } } } } } //}}} //{{{ ShortcutsModel class private class ShortcutsModel extends AbstractTableModel { public static final String ALL = "All"; private final List<KeyBinding[]> bindings; private final String name; ShortcutsModel(String name, List<KeyBinding[]> bindings) { this.name = name; this.bindings = bindings; sort(0); } public List<KeyBinding[]> getBindings() { return bindings; } public void sort(int col) { Collections.sort(bindings,new KeyCompare(col)); } @Override public int getColumnCount() { if (ALL.equals(name)) return 4; return 3; } @Override public int getRowCount() { return bindings.size(); } @Override public Object getValueAt(int row, int col) { // The only place this gets used is in JTable's own display code, so // we translate the shortcut to platform-specific form for display here. KeyBinding bindingAt = getBindingAt(row, 0); setToolTipText(bindingAt.label); switch(col) { case 0: return bindingAt.label; case 1: return GUIUtilities.getPlatformShortcutLabel(bindingAt.shortcut); case 2: return GUIUtilities.getPlatformShortcutLabel(getBindingAt(row,1).shortcut); case 3: return bindingAt.actionSet; default: return null; } } @Override public void setValueAt(Object value, int row, int col) { if(col == 0) return; getBindingAt(row,col-1).shortcut = (String)value; // redraw the whole table because a second shortcut // might have changed, too fireTableDataChanged(); } @Override public String getColumnName(int index) { switch(index) { case 0: return jEdit.getProperty("options.shortcuts.name"); case 1: return jEdit.getProperty("options.shortcuts.shortcut1"); case 2: return jEdit.getProperty("options.shortcuts.shortcut2"); case 3: return jEdit.getProperty("options.shortcuts.actionset"); default: return null; } } public void save() { for (KeyBinding[] binding : bindings) { selectedKeymap.setShortcut( binding[0].name + ".shortcut", binding[0].shortcut); selectedKeymap.setShortcut( binding[1].name + ".shortcut2", binding[1].shortcut); } } public KeyBinding getBindingAt(int row, int nr) { KeyBinding[] binding = bindings.get(row); return binding[nr]; } @Override public String toString() { return name; } private class KeyCompare implements Comparator<KeyBinding[]> { private final int col; KeyCompare(int col) { this.col = col; } @Override public int compare(KeyBinding[] k1, KeyBinding[] k2) { String label1 = k1[0].label.toLowerCase(); String label2 = k2[0].label.toLowerCase(); if(col == 0) return StandardUtilities.compareStrings( label1,label2,true); else { String shortcut1, shortcut2; if(col == 1) { shortcut1 = k1[0].shortcut; shortcut2 = k2[0].shortcut; } else { shortcut1 = k1[1].shortcut; shortcut2 = k2[1].shortcut; } if(shortcut1 == null && shortcut2 != null) return 1; else if(shortcut2 == null && shortcut1 != null) return -1; else if(shortcut1 == null) return StandardUtilities.compareStrings(label1,label2,true); else return StandardUtilities.compareStrings(shortcut1,shortcut2,true); } } } } //}}} //{{{ KeymapsModel class private static class KeymapsModel extends AbstractListModel<String> implements ComboBoxModel<String> { private String[] keymaps; private Object selectedItem; //{{{ KeymapsModel() constructor private KeymapsModel() { reset(); } //}}} //{{{ reset() method void reset() { KeymapManager keymapManager = jEdit.getKeymapManager(); Collection<String> keymapNames = keymapManager.getKeymapNames(); keymaps = keymapNames.toArray(new String[keymapNames.size()]); if (!isValidName(selectedItem)) selectedItem = keymaps[0]; fireContentsChanged(this, 0, keymaps.length-1); } //}}} //{{{ getSize() method @Override public int getSize() { return keymaps.length; } //}}} //{{{ getElementAt() method @Override public String getElementAt(int index) { return keymaps[index]; } //}}} //{{{ setSelectedItem() method @Override public void setSelectedItem(Object anItem) { if (isValidName(anItem)) selectedItem = anItem; else selectedItem = keymaps[0]; } //}}} //{{{ getSelectedItem() method @Override public Object getSelectedItem() { return selectedItem; } //}}} //{{{ isValidName() method private boolean isValidName(Object name) { for (String keymap : keymaps) { if (keymap.equals(name)) return true; } return false; } //}}} } //}}} //{{{ KeymapsModel class private static class KeymapCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); String label = jEdit.getProperty("keymaps." + value + ".label", String.valueOf(value).replace('_', ' ')); setText(label); return this; } } //}}} //}}} //}}} }