/** * Copyright (c) 2015 by Brainwy Software Ltda. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.debug.newconsole.prefs; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.jface.bindings.Binding; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.bindings.keys.ParseException; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.keys.IBindingService; import org.python.pydev.core.log.Log; import org.python.pydev.editor.PyEdit; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.preferences.IScopedPreferences; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_ui.bindings.KeyBindingHelper; import org.python.pydev.shared_ui.dialogs.DialogHelpers; import org.yaml.snakeyaml.Yaml; @SuppressWarnings({ "unchecked", "rawtypes" }) public class InterativeConsoleCommandsPreferencesEditor { private IScopedPreferences scopedPreferences; private Combo combo; private final Map<String, InteractiveConsoleCommand> nameToCommand = new HashMap<String, InteractiveConsoleCommand>(); private Text textCommand; private Text textKeybinding; private Label errorLabel; private Color red; private ModifyListener textKeybindingListener; private ModifyListener textCommandListener; public InterativeConsoleCommandsPreferencesEditor() { this.scopedPreferences = InteractiveConsoleCommand.getScopedPreferences(); } public Combo getCombo() { return combo; } public Control createContents(Composite parent) { parent = new Composite(parent, SWT.FLAT); parent.setLayout(new GridLayout(4, false)); Label label = new Label(parent, SWT.NONE); label.setLayoutData(createGridData()); label.setText("Command"); combo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); combo.setLayoutData(createComboGridData()); final Button button = new Button(parent, SWT.PUSH); button.setLayoutData(createGridData()); button.setText("Add"); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { String[] items = combo.getItems(); final Set<String> set = new HashSet<>(Arrays.asList(items)); IInputValidator validator = new IInputValidator() { @Override public String isValid(String newText) { if (newText.length() == 0) { return "At least 1 char must be provided."; } if (set.contains(newText)) { return "A command named: " + newText + " already exists."; } return null; } }; String name = DialogHelpers.openInputRequest("Command name", "Please enter the name of the command", button.getShell(), validator); if (name != null && name.length() > 0) { InteractiveConsoleCommand cmd = new InteractiveConsoleCommand(name); addCommand(cmd); comboSelectionChanged(); } } }); Button buttonRemove = new Button(parent, SWT.PUSH); buttonRemove.setLayoutData(createGridData()); buttonRemove.setText("Remove"); buttonRemove.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { removeSelectedCommand(); comboSelectionChanged(); } }); label = new Label(parent, SWT.NONE); label.setLayoutData(GridDataFactory.fillDefaults().span(1, 1).create()); label.setText("Keybinding"); textKeybinding = new Text(parent, SWT.SINGLE | SWT.BORDER); textKeybinding.setLayoutData(GridDataFactory.fillDefaults().span(3, 1).create()); textKeybindingListener = new ModifyListener() { @Override public void modifyText(ModifyEvent e) { String comboText = combo.getText(); InteractiveConsoleCommand interactiveConsoleCommand = nameToCommand.get(comboText); if (interactiveConsoleCommand == null) { Log.log("Expected a command to be bound to: " + comboText); return; } validateAndSetKeybinding(comboText, interactiveConsoleCommand); } }; label = new Label(parent, SWT.NONE); label.setLayoutData(GridDataFactory.fillDefaults().span(4, 1).create()); label.setText("Command text.\n\n${text} is replaced by the currently selected text\nor the full line if no text is selected."); textCommand = new Text(parent, SWT.MULTI | SWT.BORDER); textCommand.setLayoutData(createTextGridData()); textCommandListener = new ModifyListener() { @Override public void modifyText(ModifyEvent e) { String comboText = combo.getText(); InteractiveConsoleCommand interactiveConsoleCommand = nameToCommand.get(comboText); if (interactiveConsoleCommand == null) { Log.log("Expected a command to be bound to: " + comboText); return; } interactiveConsoleCommand.commandText = textCommand.getText(); } }; combo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { comboSelectionChanged(); } }); errorLabel = new Label(parent, SWT.NONE); errorLabel.setLayoutData(GridDataFactory.fillDefaults().span(4, 1).create()); errorLabel .setText("Command text.\n\n${text} is replaced by the currently selected text\nor the full line if no text is selected."); errorLabel.setVisible(false); red = new Color(Display.getCurrent(), 255, 0, 0); errorLabel.setForeground(red); this.loadCommands(); addTextListeners(); return parent; } private boolean registered = false; private void addTextListeners() { if (!registered) { textKeybinding.addModifyListener(textKeybindingListener); textCommand.addModifyListener(textCommandListener); registered = true; } } private void removeTextListeners() { if (registered) { textKeybinding.removeModifyListener(textKeybindingListener); textCommand.removeModifyListener(textCommandListener); registered = false; } } protected void showKeybindingError(String message) { errorLabel.setText(message); errorLabel.setVisible(true); errorLabel.getParent().layout(true); } protected void showKeybindingError(Exception e1) { showKeybindingError("" + e1.getMessage()); } protected void hideKeybindingError() { errorLabel.setText(""); errorLabel.setVisible(false); errorLabel.getParent().layout(true); } protected void comboSelectionChanged() { removeTextListeners(); try { String text = combo.getText(); InteractiveConsoleCommand interactiveConsoleCommand = this.nameToCommand.get(text); if (interactiveConsoleCommand == null) { textKeybinding.setText(""); textKeybinding.setEnabled(false); textCommand.setText(""); textCommand.setEnabled(false); } else { textKeybinding.setText(interactiveConsoleCommand.keybinding); textKeybinding.setEnabled(true); textCommand.setText(interactiveConsoleCommand.commandText); textCommand.setEnabled(true); } if (interactiveConsoleCommand != null) { validateAndSetKeybinding(text, interactiveConsoleCommand); } else { hideKeybindingError(); } } finally { addTextListeners(); } } public void clearCommands() { this.combo.setItems(new String[0]); this.nameToCommand.clear(); comboSelectionChanged(); } public void loadCommands() { clearCommands(); List<InteractiveConsoleCommand> loadExistingCommands = InteractiveConsoleCommand.loadExistingCommands(); for (InteractiveConsoleCommand command : loadExistingCommands) { this.addCommand(command); } comboSelectionChanged(); } private Object createTextGridData() { GridData data = new GridData(GridData.FILL_BOTH); data.grabExcessHorizontalSpace = true; data.grabExcessVerticalSpace = true; data.horizontalSpan = 4; return data; } private GridData createGridData() { GridData data = new GridData(); return data; } private GridData createComboGridData() { GridData data = new GridData(GridData.FILL_HORIZONTAL); data.grabExcessHorizontalSpace = true; return data; } public void addCommand(InteractiveConsoleCommand command) { this.combo.add(command.name); this.nameToCommand.put(command.name, command); this.combo.select(this.combo.getItemCount() - 1); } public void performSave() { Yaml yaml = new Yaml(); Map map = new HashMap<>(); ArrayList<Object> commands = new ArrayList<>(); String[] items = this.combo.getItems(); for (String string : items) { InteractiveConsoleCommand command = this.nameToCommand.get(string); if (command.isValid()) { commands.add(command.asMap()); } } map.put("commands", commands); File yamlFile = this.scopedPreferences.getWorkspaceSettingsLocation(); if (!yamlFile.getParentFile().exists()) { yamlFile.getParentFile().mkdirs(); } String dumpAsMap = yaml.dumpAsMap(map); FileUtils.writeStrToFile(dumpAsMap, yamlFile); InteractiveConsoleCommand.keepBindingsUpdated(); } public void removeSelectedCommand() { String selectedName = this.combo.getText(); InteractiveConsoleCommand command = this.nameToCommand.get(selectedName); if (command != null) { int selectionIndex = combo.getSelectionIndex(); this.nameToCommand.remove(selectedName); this.combo.remove(selectedName); if (selectionIndex >= this.combo.getItemCount()) { selectionIndex--; } if (selectionIndex >= 0 && selectionIndex < this.combo.getItemCount()) { this.combo.select(selectionIndex); } } } public void performDefaults() { if (DialogHelpers.openQuestion("Confirm", "Clear all the commands created?")) { this.clearCommands(); this.performSave(); } } public void dispose() { red.dispose(); } public void setCommandText(String text) { this.textCommand.setText(text); } public void setKeybindingText(String text) { this.textKeybinding.setText(text); } public void selectComboText(String string) { String[] items = this.combo.getItems(); for (int i = 0; i < items.length; i++) { if (string.equals(items[i])) { this.combo.select(i); comboSelectionChanged(); } } } public String getCommandText() { return this.textCommand.getText(); } public String getCommandKeybinding() { return this.textKeybinding.getText().trim(); } private Set<String> getCurrentBindings(String comboTextToIgnore) { Set<String> currentBindings = new HashSet<>(); String[] items = combo.getItems(); for (String commandName : items) { if (!commandName.equals(comboTextToIgnore)) { //Check all but the current one InteractiveConsoleCommand command = nameToCommand.get(commandName); if (command != null) { currentBindings.add(command.keybinding); } } } return currentBindings; } /** * Returns whether the keybinding is valid. * @return */ private boolean validateAndSetKeybinding(String comboText, InteractiveConsoleCommand interactiveConsoleCommand) { try { String text = textKeybinding.getText().trim(); if (text.length() == 0) { showKeybindingError("The keybinding must be specified."); return false; } Set<String> currentBindings = getCurrentBindings(comboText); if (currentBindings.contains(text)) { showKeybindingError("The keybinding: " + text + " is already being used."); return false; } KeySequence keySequence = KeyBindingHelper.getKeySequence(text); // Just check if it's valid IBindingService bindingService = null; try { bindingService = (IBindingService) PlatformUI.getWorkbench().getService( IBindingService.class); } catch (Throwable e) { } FastStringBuffer bufConflicts = new FastStringBuffer(); int numberOfConflicts = 0; if (bindingService != null) { //We can only do this check if the workbench is running... Binding[] bindings = bindingService.getBindings(); for (Binding binding : bindings) { if (binding.getContextId().equals(PyEdit.PYDEV_EDITOR_KEYBINDINGS_CONTEXT_ID)) { if (binding.getTriggerSequence().equals(keySequence)) { ParameterizedCommand parameterizedCommand = binding.getParameterizedCommand(); if (parameterizedCommand == null) { bufConflicts.append(binding.toString()).append('\n'); } else { if (!parameterizedCommand.getCommand().getId() .startsWith(InteractiveConsoleCommand.USER_COMMAND_PREFIX)) { bufConflicts.append(" - ").append(parameterizedCommand.getName()).append('\n'); numberOfConflicts += 1; } } } } } } interactiveConsoleCommand.keybinding = text; if (bufConflicts.length() > 0) { showKeybindingError("The current keybinding (" + text + ") conflicts with:\n" + bufConflicts.toString() + "(" + (numberOfConflicts == 1 ? "they" : "it") + "'ll be removed if the changes are applied and\n" + "can only be restored in the 'Keys' preferences page)."); // Although we show an error, it's valid return true; } else { hideKeybindingError(); return true; } } catch (ParseException | IllegalArgumentException e1) { showKeybindingError(e1); return false; } catch (Exception e1) { showKeybindingError(e1); Log.log(e1); return false; } } }