Skip to content

Commit

Permalink
Issue: miho#11. several fixes related to context menu actions.
Browse files Browse the repository at this point in the history
  • Loading branch information
kia committed Feb 9, 2023
1 parent 223cee4 commit 62b9725
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 73 deletions.
17 changes: 17 additions & 0 deletions src/main/java/eu/mihosoft/monacofx/AbstractEditorAction.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package eu.mihosoft.monacofx;

import java.util.Objects;

/**
* This is a Call Back class where the action method can be implemented for a custom usage.
* Using @see {@link MonacoFX#addContextMenuAction} the implementation of this class can be added to
Expand Down Expand Up @@ -96,4 +98,19 @@ public String[] getKeyBindings() {
public void setKeyBindings(String... keyBindings) {
this.keyBindings = keyBindings;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

AbstractEditorAction that = (AbstractEditorAction) o;

return Objects.equals(actionId, that.actionId);
}

@Override
public int hashCode() {
return actionId != null ? actionId.hashCode() : 0;
}
}
6 changes: 4 additions & 2 deletions src/main/java/eu/mihosoft/monacofx/CutAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ public CutAction() {
setContextMenuOrder("3");
setContextMenuGroupId("9_cutcopypaste");
setVisibleOnReadonly(false);
setKeyBindings("monaco.KeyMod.Shift | monaco.KeyCode.Delete",
setKeyBindings(
"monaco.KeyMod.Shift | monaco.KeyCode.Delete",
"monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyX");
setRunScript("clipboardBridge.copy(editorView.getSelection());\n"
setRunScript(
"clipboardBridge.copy(editorView.getSelection());\n"
+ "document.execCommand('cut');\n"
);
}
Expand Down
135 changes: 72 additions & 63 deletions src/main/java/eu/mihosoft/monacofx/MonacoFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.input.KeyCode;
Expand All @@ -44,6 +44,9 @@
import netscape.javascript.JSObject;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
Expand All @@ -64,11 +67,12 @@ public class MonacoFX extends Region {
private boolean readOnly;

private Worker.State workerState;
private Timeline oneSecondWonder;

private Set<AbstractEditorAction> addedActions;

public MonacoFX() {
view = new WebView();

addedActions = Collections.synchronizedSet(new HashSet<>());
getChildren().add(view);
engine = view.getEngine();
String url = getClass().getResource(EDITOR_HTML_RESOURCE_LOCATION).toExternalForm();
Expand Down Expand Up @@ -113,19 +117,21 @@ public MonacoFX() {
}
});
engine.load(url);
waitForLoad();
addClipboardFunctions();
waitForSucceededWorkerState();
}

/**
* wait a bit
* wait for succeeded state of the load worker
*/
private void waitForLoad() {
oneSecondWonder = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
if ( Worker.State.SUCCEEDED == workerState) {
private void waitForSucceededWorkerState() {
Timeline oneSecondWonder = new Timeline();
EventHandler<ActionEvent> actionEventEventHandler = (ActionEvent event) -> {
if (Worker.State.SUCCEEDED == workerState) {
oneSecondWonder.stop();
}
}));
};
KeyFrame keyFrame = new KeyFrame(Duration.seconds(1), actionEventEventHandler);
oneSecondWonder.getKeyFrames().setAll(keyFrame);
oneSecondWonder.setCycleCount(15);
oneSecondWonder.play();
}
Expand All @@ -135,10 +141,6 @@ public void reload() {
setReadonly(isReadOnly());
}

private void addClipboardFunctions() {
addEventFilter(KeyEvent.KEY_PRESSED, event -> systemClipboardWrapper.handleCopyCutKeyEvent(event, (a) -> getSelectionObject(), readOnly));
}

private Object getSelectionObject() {
return engine.executeScript("editorView.getModel().getValueInRange(editorView.getSelection())");
}
Expand All @@ -159,20 +161,6 @@ public void requestFocus() {
});
}

protected void postConstruct() {
addPasteAction();
addCutAction();
}

private void addPasteAction() {
final PasteAction pasteAction = new PasteAction();
addContextMenuAction(pasteAction);
}

private void addCutAction() {
final CutAction cutAction = new CutAction();
addContextMenuAction(cutAction);
}
@Override protected double computePrefWidth(double height) {
return view.prefWidth(height);
}
Expand Down Expand Up @@ -208,10 +196,32 @@ public WebEngine getWebEngine() {
* @param action {@link eu.mihosoft.monacofx.AbstractEditorAction} call back object as abstract action.
*/
public void addContextMenuAction(AbstractEditorAction action) {
executeJavaScriptLambda(action, param -> {
doAddContextMenuAction(action);
return null;
});
if (!readOnly || action.isVisibleOnReadonly() ) {
executeJavaScriptLambda(action, param -> {
if (!addedActions.contains(action)) {
doAddContextMenuAction(action);
}
return null;
});
}
}

public void removeContextMenuAction(AbstractEditorAction action) {
if (addedActions.contains(action)) {
removeAction(action);
addedActions.remove(action);
}
}

public void removeContextMenuActions() {
addedActions.forEach(this::removeAction);
addedActions.clear();
}
private void removeAction(AbstractEditorAction action) {
String script = String.format("removeAction('%s');", action.getActionId());
getWebEngine().executeScript(script);
JSObject window = (JSObject) getWebEngine().executeScript("window");
window.removeMember(action.getName());
}

public boolean isReadOnly() {
Expand Down Expand Up @@ -247,7 +257,7 @@ private void doAddContextMenuAction(AbstractEditorAction action) {
try {
getWebEngine().executeScript(
"editorView.addAction({\n" +
"id: \"" + action.getActionId() + actionName + "\",\n" +
"id: \"" + action.getActionId() + "\",\n" +
"label: \"" + action.getLabel() + "\",\n" +
"contextMenuGroupId: \"" + action.getContextMenuGroupId() + "\",\n" +
"precondition: " + precondition + ",\n" +
Expand All @@ -259,49 +269,48 @@ private void doAddContextMenuAction(AbstractEditorAction action) {
"}\n" +
"});"
);
addedActions.add(action);
} catch (JSException exception) {
LOGGER.log(Level.SEVERE, exception.getMessage());
}

}

private void executeJavaScriptLambda(Object parameter , Callback<Object, Void> callback) {
ReadOnlyObjectProperty<Worker.State> stateProperty = getWebEngine().getLoadWorker().stateProperty();
if (Worker.State.SUCCEEDED == stateProperty.getValue()) {
callback.call(parameter);
} else {
ChangeListener<Worker.State> stateChangeListener = (o, old, state) -> {
if (Worker.State.SUCCEEDED == state) {
AtomicBoolean jsDone = new AtomicBoolean(false);
AtomicInteger attempts = new AtomicInteger();
Thread thread = new Thread(() -> {
while (!jsDone.get()) {
// check if JS execution is done.
Platform.runLater(() -> {
JSObject window = (JSObject) engine.executeScript("window");
Object jsEditorObj = window.call("getEditorView");
if (jsEditorObj instanceof JSObject) {
callback.call(parameter);
jsDone.set(true);
}
});
if (attempts.getAndIncrement() > 10) {
throw new RuntimeException(
"Cannot initialize editor (JS execution not complete). Max number of attempts reached."
);
}
if (!jsDone.get()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
waitForSucceededWorkerState();
AtomicBoolean jsDone = new AtomicBoolean(false);
AtomicInteger attempts = new AtomicInteger();
Thread thread = new Thread(() -> {
while (!jsDone.get()) {
// check if JS execution is done.
Platform.runLater(() -> {
JSObject window = (JSObject) engine.executeScript("window");
Object jsEditorObj = window.call("getEditorView");
if (jsEditorObj instanceof JSObject) {
callback.call(parameter);
jsDone.set(true);
}
});
thread.start();
if (attempts.getAndIncrement() > 10) {
throw new RuntimeException(
"Cannot initialize editor (JS execution not complete). Max number of attempts reached."
);
}
if (!jsDone.get()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
stateProperty.addListener(stateChangeListener);
});
thread.start();

}
}
}
3 changes: 2 additions & 1 deletion src/main/java/eu/mihosoft/monacofx/PasteAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public PasteAction() {
setContextMenuOrder("3");
setContextMenuGroupId("9_cutcopypaste");
setVisibleOnReadonly(false);
setKeyBindings("monaco.KeyMod.Shift | monaco.KeyCode.Insert",
setKeyBindings(
"monaco.KeyMod.Shift | monaco.KeyCode.Insert",
"monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyV");
setRunScript("let position = editor.getPosition();\n"
+ "let newPosition = clipboardBridge.paste(editor.getSelection(), position);\n"
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/eu/mihosoft/monacofx/SystemClipboardWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public class SystemClipboardWrapper {

private final KeyCodeCombination KEY_CODE_CTRL_C = new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN);
private final KeyCodeCombination KEY_CODE_CTRL_X = new KeyCodeCombination(KeyCode.X, KeyCombination.SHORTCUT_DOWN);
private final KeyCodeCombination KEY_CODE_CTRL_INSERT = new KeyCodeCombination(KeyCode.INSERT, KeyCombination.SHORTCUT_DOWN);
private final KeyCodeCombination KEY_CODE_CTRL_DELETE = new KeyCodeCombination(KeyCode.DELETE, KeyCombination.SHORTCUT_DOWN);
private final KeyCodeCombination KEY_CODE_SHIFT_INSERT = new KeyCodeCombination(KeyCode.INSERT, KeyCombination.SHIFT_DOWN);
private final KeyCodeCombination KEY_CODE_SHIFT_DELETE = new KeyCodeCombination(KeyCode.DELETE, KeyCombination.SHIFT_DOWN);

/**
* Puts the text into clipboard.
Expand All @@ -66,13 +66,13 @@ public void handleCopyCutKeyEvent(KeyEvent event, Callback<Void, Object> getSele
if (event.getEventType().getName().equals("KEY_PRESSED")
&& KEY_CODE_CTRL_X.match(event)
|| KEY_CODE_CTRL_C.match(event)
|| KEY_CODE_CTRL_DELETE.match(event)
|| KEY_CODE_CTRL_INSERT.match(event)) {
|| KEY_CODE_SHIFT_DELETE.match(event)
|| KEY_CODE_SHIFT_INSERT.match(event)) {
Object obj = getSelectionObjectCallBack.call(null);
String selectedText = String.valueOf(obj);
if ((readOnly && KEY_CODE_CTRL_X.match(event))
|| (readOnly && KEY_CODE_CTRL_INSERT.match(event))
|| (readOnly && KEY_CODE_CTRL_DELETE.match(event))
|| (readOnly && KEY_CODE_SHIFT_INSERT.match(event))
|| (readOnly && KEY_CODE_SHIFT_DELETE.match(event))
|| selectedText.isEmpty()
) {
event.consume();
Expand Down
15 changes: 14 additions & 1 deletion src/main/resources/eu/mihosoft/monacofx/monaco-editor/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
id: 'editor.action.clipboardCopyAction',
label: 'Copy',
contextMenuGroupId: '9_cutcopypaste',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyC],
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Insert, monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyC ],
run: () => {
clipboardBridge.copy(editorView.getSelection());
},
Expand Down Expand Up @@ -204,6 +204,19 @@
return editorView;
}

function removeAction(actionId) {
actionId = "vs.editor.ICodeEditor:1:" + actionId;
let menus = require('vs/platform/actions/common/actions').MenuRegistry._menuItems;
let contextMenuEntry = [...menus].find(entry => entry[0].id === "EditorContext");
let contextMenuLinks = contextMenuEntry[1];
let node = contextMenuLinks._first;
do {
if (actionId === node.element?.command?.id) {
contextMenuLinks._remove(node);
}
} while ((node = node.next));
}

</script>

</body>
Expand Down

0 comments on commit 62b9725

Please sign in to comment.