diff --git a/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java b/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java index 39731a20a2..7b3467efac 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/services/JogService.java @@ -19,6 +19,7 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.universalgcodesender.services; import com.willwinder.universalgcodesender.listeners.ControllerState; +import com.willwinder.universalgcodesender.model.Axis; import com.willwinder.universalgcodesender.model.BackendAPI; import com.willwinder.universalgcodesender.model.PartialPosition; import com.willwinder.universalgcodesender.model.UnitUtils.Units; @@ -251,7 +252,12 @@ public boolean useStepSizeZ() { } public boolean showABCStepSize() { - return getSettings().showABCStepSize(); + boolean hasAbcAxes = backend.getController() != null && + (backend.getController().getCapabilities().hasAxis(Axis.A) || + backend.getController().getCapabilities().hasAxis(Axis.B) || + backend.getController().getCapabilities().hasAxis(Axis.C)); + + return getSettings().showABCStepSize() && hasAbcAxes; } /** diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutor.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutor.java index bac21b7858..59e744ded1 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutor.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutor.java @@ -91,7 +91,14 @@ public void release() { return; } - ((ContinuousAction) actionReference.get().getAction()).actionDeactivated(); + if (actionReference.get().getAction() instanceof ContinuousAction continuousAction) { + continuousAction.actionDeactivated(); + } actionReference.set(null); + isLongPressed = false; + } + + public boolean isLongPressed() { + return isLongPressed; } } diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionShortcutListener.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionShortcutListener.java index 5ec88324ab..d91ec9d9db 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionShortcutListener.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/ContinuousActionShortcutListener.java @@ -22,6 +22,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.lib.services.ActionReference; import com.willwinder.ugs.nbp.lib.services.ActionRegistrationService; import com.willwinder.ugs.nbp.lib.services.ShortcutService; +import static java.awt.event.KeyEvent.KEY_PRESSED; +import static java.awt.event.KeyEvent.KEY_RELEASED; import org.openide.modules.OnStart; import org.openide.util.Lookup; import org.openide.util.Utilities; @@ -38,9 +40,6 @@ This file is part of Universal Gcode Sender (UGS). import java.util.Optional; import java.util.logging.Logger; -import static java.awt.event.KeyEvent.KEY_PRESSED; -import static java.awt.event.KeyEvent.KEY_RELEASED; - /** * A shortcut listener that will attempt to intercept shortcuts to continuous actions. * If key was pressed shorter than a certain threshold the normal @@ -68,7 +67,7 @@ public class ContinuousActionShortcutListener implements Runnable, KeyEventDispa private final ContinuousActionExecutor executor; public ContinuousActionShortcutListener() { - shortcutService = Lookup.getDefault().lookup(ShortcutService.class); + shortcutService = new ShortcutService(); actionRegistrationService = Lookup.getDefault().lookup(ActionRegistrationService.class); executor = new ContinuousActionExecutor(LONG_PRESS_DELAY); } @@ -95,7 +94,7 @@ private String getKeyAsString(KeyEvent e) { } private Optional getContinuousActionByShortcut(String keyAsString) { - return shortcutService.getActionIdForShortcut(keyAsString) + return shortcutService.getActionIdForShortcut(keyAsString) .flatMap(actionRegistrationService::getActionById) .filter(action -> action.getAction() instanceof ContinuousAction); } @@ -110,6 +109,13 @@ public boolean dispatchKeyEvent(KeyEvent keyEvent) { return false; } + // On any key release we should abort a long pressed executor + if (keyEvent.getID() == KEY_RELEASED && executor.isLongPressed()) { + executor.release(); + keyEvent.consume(); + return false; + } + // Try to find an continuous action for the current keys String keyAsString = getKeyAsString(keyEvent); Optional actionById = getContinuousActionByShortcut(keyAsString); diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/JogActionService.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/JogActionService.java index b5f3a1007a..911019bdb4 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/JogActionService.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/services/JogActionService.java @@ -18,6 +18,7 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.core.services; +import static com.willwinder.ugs.nbp.core.services.JogSizeAction.Operation.*; import com.willwinder.ugs.nbp.lib.services.ActionRegistrationService; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.UnitUtils.Units; @@ -27,8 +28,6 @@ This file is part of Universal Gcode Sender (UGS). import java.io.IOException; -import static com.willwinder.ugs.nbp.core.services.JogSizeAction.Operation.*; - /** * A service for registering jog actions such as menu items and shortcuts * @@ -57,19 +56,19 @@ private void initActions() { // Jog plus/minus X ars.registerAction(JogActionService.class.getCanonicalName() + ".xPlus", Localization.getString("jogging.xPlus") , - category, "M-RIGHT" , menuPath, 0, localized, new JogAction(1, 0, 0, 0, 0, 0)); + category, "D-RIGHT" , menuPath, 0, localized, new JogAction(1, 0, 0, 0, 0, 0)); ars.registerAction(JogActionService.class.getCanonicalName() + ".xMinus", Localization.getString("jogging.xMinus"), - category, "M-LEFT" , menuPath, 0, localized, new JogAction(-1, 0, 0, 0, 0, 0)); + category, "D-LEFT" , menuPath, 0, localized, new JogAction(-1, 0, 0, 0, 0, 0)); // Jog plus/minus Y ars.registerAction(JogActionService.class.getCanonicalName() + ".yPlus", Localization.getString("jogging.yPlus") , - category, "M-UP" , menuPath, 0, localized, new JogAction(0, 1, 0, 0, 0,0)); + category, "D-UP" , menuPath, 0, localized, new JogAction(0, 1, 0, 0, 0,0)); ars.registerAction(JogActionService.class.getCanonicalName() + ".yMinus", Localization.getString("jogging.yMinus"), - category, "M-DOWN" , menuPath, 0, localized, new JogAction( 0,-1, 0,0 ,0 ,0)); + category, "D-DOWN" , menuPath, 0, localized, new JogAction( 0,-1, 0,0 ,0 ,0)); // Jog plus/minus Z ars.registerAction(JogActionService.class.getCanonicalName() + ".zPlus", Localization.getString("jogging.zPlus") , - category, "SM-UP" , menuPath, 0, localized, new JogAction(0, 0, 1, 0,0, 0)); + category, "SD-UP" , menuPath, 0, localized, new JogAction(0, 0, 1, 0,0, 0)); ars.registerAction(JogActionService.class.getCanonicalName() + ".zMinus", Localization.getString("jogging.zMinus"), - category, "SM-DOWN" , menuPath, 0, localized, new JogAction(0, 0,-1, 0,0,0)); + category, "SD-DOWN" , menuPath, 0, localized, new JogAction(0, 0,-1, 0,0,0)); // Jog plus/minus A ars.registerAction(JogActionService.class.getCanonicalName() + ".aPlus", Localization.getString("jogging.aPlus") , category, null , menuPath, 0, localized, new JogAction(0, 0, 0, 1, 0, 0)); diff --git a/ugs-platform/ugs-platform-ugscore/src/test/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutorTest.java b/ugs-platform/ugs-platform-ugscore/src/test/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutorTest.java index 418c9bb1ff..58e5e138f4 100644 --- a/ugs-platform/ugs-platform-ugscore/src/test/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutorTest.java +++ b/ugs-platform/ugs-platform-ugscore/src/test/java/com/willwinder/ugs/nbp/core/services/ContinuousActionExecutorTest.java @@ -4,17 +4,17 @@ import com.willwinder.ugs.nbp.lib.services.ActionReference; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import javax.swing.JLabel; -import java.awt.event.KeyEvent; - import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import org.mockito.MockitoAnnotations; + +import javax.swing.JLabel; +import java.awt.event.KeyEvent; public class ContinuousActionExecutorTest { @@ -73,6 +73,20 @@ public void longKeyPressAndChangeActionShouldAbortFirstAction() throws Interrupt verifyNoMoreInteractions(action); } + @Test + public void longKeyPressAndReleaseAnyKeyShouldAbortFirstAction() throws InterruptedException { + KeyEvent anyKey = mock(KeyEvent.class); + + target.setCurrentAction(actionReference); + target.keyPressed(keyEvent); + Thread.sleep(120); + target.keyReleased(anyKey); + + verify(action, times(1)).actionActivate(); + verify(action, times(1)).actionDeactivated(); + verifyNoMoreInteractions(action); + } + @Test public void keyPressAndChangeActionShouldAbortFirstAction() { target.setCurrentAction(actionReference); diff --git a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ActionRegistrationService.java b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ActionRegistrationService.java index 8c0fe33b03..3277a1dd6d 100644 --- a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ActionRegistrationService.java +++ b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ActionRegistrationService.java @@ -18,6 +18,7 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.lib.services; +import static com.willwinder.ugs.nbp.lib.services.ShortcutService.createShortcut; import org.apache.commons.lang3.StringUtils; import org.openide.cookies.InstanceCookie; import org.openide.filesystems.FileObject; @@ -26,7 +27,7 @@ This file is part of Universal Gcode Sender (UGS). import org.openide.util.Exceptions; import org.openide.util.lookup.ServiceProvider; -import javax.swing.*; +import javax.swing.Action; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -143,12 +144,7 @@ public void registerAction(String id, String name, String category, String short // Add/Update Shortcut // ///////////////////////// if (shortcut != null && shortcut.length() > 0) { - in = FileUtil.createFolder(root, "Shortcuts"); - obj = in.getFileObject(shortcut, SHADOW); - if (obj == null) { - obj = in.createData(shortcut, SHADOW); - obj.setAttribute("originalFile", originalFile); - } + createShortcut(id, category, shortcut); } invalidateCache(); @@ -241,7 +237,7 @@ public Optional lookupAction(String category, String name) { * Get actions organized by category. */ public Map> getCategoryActions() { - if(actionCache.isEmpty()) { + if (actionCache.isEmpty()) { FileObject rootFileObject = FileUtil.getConfigFile("Actions/"); actionCache = getCategoryActions(rootFileObject); } @@ -344,6 +340,7 @@ private void invalidateCache() { /** * Returns all actions by the given category + * * @param category the name of the category to fetch * @return a list of actions */ diff --git a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ShortcutService.java b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ShortcutService.java index b91c251c35..1c189c0991 100644 --- a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ShortcutService.java +++ b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/ShortcutService.java @@ -18,6 +18,7 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.nbp.lib.services; +import org.apache.commons.io.IOUtils; import org.openide.filesystems.FileAttributeEvent; import org.openide.filesystems.FileChangeListener; import org.openide.filesystems.FileEvent; @@ -30,10 +31,14 @@ This file is part of Universal Gcode Sender (UGS). import org.openide.util.lookup.ServiceProvider; import javax.swing.KeyStroke; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -51,12 +56,7 @@ public class ShortcutService implements FileChangeListener { /** * Cache the found shortcuts to speed things up using the shortcut as key and the actionId as value. */ - private final Map shortcutMap = new ConcurrentHashMap<>(); - - /** - * The current keymap used - */ - private String currentKeymap = "NetBeans"; + public static final Map shortcutMap = new ConcurrentHashMap<>(); public ShortcutService() { reloadShortcuts(); @@ -81,17 +81,19 @@ public void fileFolderCreated(FileEvent fileEvent) { @Override public void fileDataCreated(FileEvent fileEvent) { - if (!fileEvent.getFile().getPath().startsWith(getKeymapConfigRoot())) { + String currentKeymap = getCurrentKeymap(); + String keymapConfigRoot = "Keymaps/" + currentKeymap; + if (!fileEvent.getFile().getPath().startsWith(keymapConfigRoot)) { return; } - DataFolder folder = DataFolder.findFolder(FileUtil.getSystemConfigFile(getKeymapConfigRoot())); + DataFolder folder = DataFolder.findFolder(FileUtil.getSystemConfigFile(keymapConfigRoot)); String keyAsString = fileEvent.getFile().getName(); Collections.list(folder.children()).stream() .filter(DataShadow.class::isInstance).map(DataShadow.class::cast) .filter(f -> f.getName().equals(keyAsString)) .findFirst() - .ifPresent(ShortcutService.this::setShortcut); + .ifPresent(ShortcutService::setShortcut); } @Override @@ -101,6 +103,7 @@ public void fileChanged(FileEvent fileEvent) { @Override public void fileDeleted(FileEvent fileEvent) { + String currentKeymap = getCurrentKeymap(); if (!fileEvent.getFile().getPath().startsWith("Keymaps/" + currentKeymap + "/")) { return; } @@ -123,30 +126,63 @@ public void fileAttributeChanged(FileAttributeEvent fileAttributeEvent) { reloadShortcuts(); } - private String getKeymapConfigRoot() { - return "Keymaps/" + currentKeymap; - } - private void setShortcut(DataShadow file) { + private static void setShortcut(DataShadow file) { InstanceDataObject cookie = file.getCookie(InstanceDataObject.class); String actionId = cookie.getPrimaryFile().getPath(); shortcutMap.put(file.getName(), actionId); LOGGER.fine(() -> String.format("Set shortcut: %s -> %s", file.getName(), actionId)); } - private void reloadShortcuts() { - FileObject keymaps = FileUtil.getConfigFile("Keymaps"); - if (keymaps == null || keymaps.getAttribute("currentKeymap") == null) { - return; - } - - currentKeymap = keymaps.getAttribute("currentKeymap").toString(); + private static void reloadShortcuts() { + String currentKeymap = getCurrentKeymap(); LOGGER.fine(() -> String.format("Reloading shortcuts using keymap %s", currentKeymap)); shortcutMap.clear(); - DataFolder folder = DataFolder.findFolder(FileUtil.getSystemConfigFile(getKeymapConfigRoot())); + DataFolder folder = DataFolder.findFolder(FileUtil.getSystemConfigFile("Keymaps/" + currentKeymap)); Collections.list(folder.children()).stream() .filter(DataShadow.class::isInstance).map(DataShadow.class::cast) - .forEach(this::setShortcut); + .forEach(ShortcutService::setShortcut); + } + + public static void createShortcut(String id, String category, String shortcut) throws IOException { + String currentKeymap = getCurrentKeymap(); + FileObject root = FileUtil.getConfigRoot(); + FileObject keyMapsFolder = root.getFileObject("Keymaps"); + if (keyMapsFolder == null) { + keyMapsFolder = FileUtil.createFolder(root, "Keymaps"); + } + + FileObject keyMapsNetBeansFolder = keyMapsFolder.getFileObject(currentKeymap); + if (keyMapsNetBeansFolder == null) { + keyMapsNetBeansFolder = keyMapsFolder.createFolder(currentKeymap); + } + + FileObject shortcutFile = keyMapsNetBeansFolder.getFileObject(shortcut, "shadow"); + if (shortcutFile == null) { + shortcutFile = keyMapsNetBeansFolder.createData(shortcut, "shadow"); + OutputStream outputStream = shortcutFile.getOutputStream(); + IOUtils.write("nbfs://nbhost/SystemFileSystem/Actions/" + category + "/" + id + ".instance", outputStream, Charset.defaultCharset()); + outputStream.close(); + } + } + + private static String getCurrentKeymap() { + try { + FileObject keymaps = FileUtil.getConfigFile("Keymaps"); + if (keymaps == null) { + FileObject root = FileUtil.getConfigRoot(); + keymaps = FileUtil.createFolder(root, "Keymaps"); + } + + if (keymaps.getAttribute("currentKeymap") == null) { + keymaps.setAttribute("currentKeymap", "NetBeans"); + } + + return keymaps.getAttribute("currentKeymap").toString(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Could get keymaps folder", e); + return "NetBeans"; + } } }