| @@ -17,7 +17,11 @@ Use this instead of GLFW_MOD_CONTROL, since Cmd should be used on Mac in place o | |||
| #define RACK_MOD_CTRL GLFW_MOD_CONTROL | |||
| #define RACK_MOD_CTRL_NAME "Ctrl" | |||
| #endif | |||
| #define RACK_MOD_SHIFT GLFW_MOD_SHIFT | |||
| #define RACK_MOD_SHIFT_NAME "Shift" | |||
| #define RACK_MOD_ALT GLFW_MOD_ALT | |||
| #define RACK_MOD_ALT_NAME "Alt" | |||
| /** Filters actual mod keys from the mod flags. | |||
| @@ -36,6 +40,10 @@ namespace rack { | |||
| namespace widget { | |||
| std::string getKeyName(int key); | |||
| std::string getKeyCommandName(int key, int mods); | |||
| struct Widget; | |||
| @@ -73,11 +73,11 @@ struct FileButton : MenuButton { | |||
| menu->cornerFlags = BND_CORNER_TOP; | |||
| menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { | |||
| menu->addChild(createMenuItem("New", widget::getKeyCommandName(GLFW_KEY_N, RACK_MOD_CTRL), []() { | |||
| APP->patch->loadTemplateDialog(); | |||
| })); | |||
| menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", []() { | |||
| menu->addChild(createMenuItem("Open", widget::getKeyCommandName(GLFW_KEY_O, RACK_MOD_CTRL), []() { | |||
| APP->patch->loadDialog(); | |||
| })); | |||
| @@ -90,11 +90,11 @@ struct FileButton : MenuButton { | |||
| } | |||
| }, settings::recentPatchPaths.empty())); | |||
| menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { | |||
| menu->addChild(createMenuItem("Save", widget::getKeyCommandName(GLFW_KEY_S, RACK_MOD_CTRL), []() { | |||
| APP->patch->saveDialog(); | |||
| })); | |||
| menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| menu->addChild(createMenuItem("Save as", widget::getKeyCommandName(GLFW_KEY_S, RACK_MOD_CTRL | GLFW_MOD_SHIFT), []() { | |||
| APP->patch->saveAsDialog(); | |||
| })); | |||
| @@ -102,7 +102,7 @@ struct FileButton : MenuButton { | |||
| APP->patch->saveAsDialog(false); | |||
| })); | |||
| menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| menu->addChild(createMenuItem("Revert", widget::getKeyCommandName(GLFW_KEY_O, RACK_MOD_CTRL | GLFW_MOD_SHIFT), []() { | |||
| APP->patch->revertDialog(); | |||
| }, APP->patch->path == "")); | |||
| @@ -119,7 +119,7 @@ struct FileButton : MenuButton { | |||
| menu->addChild(new ui::MenuSeparator); | |||
| menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { | |||
| menu->addChild(createMenuItem("Quit", widget::getKeyCommandName(GLFW_KEY_Q, RACK_MOD_CTRL), []() { | |||
| APP->window->close(); | |||
| })); | |||
| } | |||
| @@ -147,7 +147,7 @@ struct EditButton : MenuButton { | |||
| APP->history->undo(); | |||
| } | |||
| }; | |||
| menu->addChild(createMenuItem<UndoItem>("", RACK_MOD_CTRL_NAME "+Z")); | |||
| menu->addChild(createMenuItem<UndoItem>("", widget::getKeyCommandName(GLFW_KEY_Z, RACK_MOD_CTRL))); | |||
| struct RedoItem : ui::MenuItem { | |||
| void step() override { | |||
| @@ -159,7 +159,7 @@ struct EditButton : MenuButton { | |||
| APP->history->redo(); | |||
| } | |||
| }; | |||
| menu->addChild(createMenuItem<RedoItem>("", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+Z")); | |||
| menu->addChild(createMenuItem<RedoItem>("", widget::getKeyCommandName(GLFW_KEY_Z, RACK_MOD_CTRL | GLFW_MOD_SHIFT))); | |||
| menu->addChild(createMenuItem("Clear cables", "", [=]() { | |||
| APP->patch->disconnectDialog(); | |||
| @@ -403,7 +403,7 @@ struct ViewButton : MenuButton { | |||
| menu->addChild(createMenuLabel("Window")); | |||
| bool fullscreen = APP->window->isFullScreen(); | |||
| std::string fullscreenText = "F11"; | |||
| std::string fullscreenText = widget::getKeyCommandName(GLFW_KEY_F11, 0); | |||
| if (fullscreen) | |||
| fullscreenText += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() { | |||
| @@ -424,7 +424,7 @@ struct ViewButton : MenuButton { | |||
| zoomSlider->box.size.x = 250.0; | |||
| menu->addChild(zoomSlider); | |||
| menu->addChild(createMenuItem("Zoom to fit modules", "F4", [=]() { | |||
| menu->addChild(createMenuItem("Zoom to fit modules", widget::getKeyCommandName(GLFW_KEY_F4, 0), [=]() { | |||
| APP->scene->rackScroll->zoomToModules(); | |||
| })); | |||
| @@ -668,7 +668,7 @@ struct EngineButton : MenuButton { | |||
| menu->cornerFlags = BND_CORNER_TOP; | |||
| menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| std::string cpuMeterText = "F3"; | |||
| std::string cpuMeterText = widget::getKeyCommandName(GLFW_KEY_F3, 0); | |||
| if (settings::cpuMeter) | |||
| cpuMeterText += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() { | |||
| @@ -986,7 +986,7 @@ struct HelpButton : MenuButton { | |||
| APP->scene->addChild(tipWindowCreate()); | |||
| })); | |||
| menu->addChild(createMenuItem("User manual", "F1", [=]() { | |||
| menu->addChild(createMenuItem("User manual", widget::getKeyCommandName(GLFW_KEY_F1, 0), [=]() { | |||
| system::openBrowser("https://vcvrack.com/manual"); | |||
| })); | |||
| @@ -1015,13 +1015,13 @@ void ModuleWidget::createContextMenu() { | |||
| // Preset | |||
| menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) { | |||
| menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { | |||
| menu->addChild(createMenuItem("Copy", widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->copyClipboard(); | |||
| })); | |||
| menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { | |||
| menu->addChild(createMenuItem("Paste", widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->pasteClipboardAction(); | |||
| @@ -1063,28 +1063,28 @@ void ModuleWidget::createContextMenu() { | |||
| })); | |||
| // Initialize | |||
| menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() { | |||
| menu->addChild(createMenuItem("Initialize", widget::getKeyCommandName(GLFW_KEY_I, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->resetAction(); | |||
| })); | |||
| // Randomize | |||
| menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() { | |||
| menu->addChild(createMenuItem("Randomize", widget::getKeyCommandName(GLFW_KEY_R, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->randomizeAction(); | |||
| })); | |||
| // Disconnect cables | |||
| menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() { | |||
| menu->addChild(createMenuItem("Disconnect cables", widget::getKeyCommandName(GLFW_KEY_U, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->disconnectAction(); | |||
| })); | |||
| // Bypass | |||
| std::string bypassText = RACK_MOD_CTRL_NAME "+E"; | |||
| std::string bypassText = widget::getKeyCommandName(GLFW_KEY_E, RACK_MOD_CTRL); | |||
| bool bypassed = module && module->isBypassed(); | |||
| if (bypassed) | |||
| bypassText += " " CHECKMARK_STRING; | |||
| @@ -1095,28 +1095,28 @@ void ModuleWidget::createContextMenu() { | |||
| })); | |||
| // Duplicate | |||
| menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
| menu->addChild(createMenuItem("Duplicate", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->cloneAction(false); | |||
| })); | |||
| // Duplicate with cables | |||
| menu->addChild(createMenuItem("â”” with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { | |||
| menu->addChild(createMenuItem("â”” with cables", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL | GLFW_MOD_SHIFT), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->cloneAction(true); | |||
| })); | |||
| // Delete | |||
| menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | |||
| menu->addChild(createMenuItem("Delete", widget::getKeyCommandName(GLFW_KEY_BACKSPACE, 0) + "/" + widget::getKeyCommandName(GLFW_KEY_DELETE, 0), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| weakThis->removeAction(); | |||
| }, false, true)); | |||
| // Zoom to fit | |||
| menu->addChild(createMenuItem("Zoom to fit", RACK_MOD_CTRL_NAME "+F4", [=]() { | |||
| menu->addChild(createMenuItem("Zoom to fit", widget::getKeyCommandName(GLFW_KEY_F4, RACK_MOD_CTRL), [=]() { | |||
| if (!weakThis) | |||
| return; | |||
| APP->scene->rackScroll->zoomToBound(weakThis->getBox()); | |||
| @@ -1306,22 +1306,22 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
| // Enable alwaysConsume of menu items if the number of selected modules changes | |||
| // Select all | |||
| menu->addChild(createMenuItem("Select all", RACK_MOD_CTRL_NAME "+A", [=]() { | |||
| menu->addChild(createMenuItem("Select all", widget::getKeyCommandName(GLFW_KEY_A, RACK_MOD_CTRL), [=]() { | |||
| selectAll(); | |||
| }, false, true)); | |||
| // Deselect | |||
| menu->addChild(createMenuItem("Deselect", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+A", [=]() { | |||
| menu->addChild(createMenuItem("Deselect", widget::getKeyCommandName(GLFW_KEY_A, RACK_MOD_CTRL | GLFW_MOD_SHIFT), [=]() { | |||
| deselectAll(); | |||
| }, n == 0, true)); | |||
| // Copy | |||
| menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { | |||
| menu->addChild(createMenuItem("Copy", widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL), [=]() { | |||
| copyClipboardSelection(); | |||
| }, n == 0)); | |||
| // Paste | |||
| menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { | |||
| menu->addChild(createMenuItem("Paste", widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL), [=]() { | |||
| pasteClipboardAction(); | |||
| }, false, true)); | |||
| @@ -1331,22 +1331,22 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
| }, n == 0)); | |||
| // Initialize | |||
| menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() { | |||
| menu->addChild(createMenuItem("Initialize", widget::getKeyCommandName(GLFW_KEY_I, RACK_MOD_CTRL), [=]() { | |||
| resetSelectionAction(); | |||
| }, n == 0)); | |||
| // Randomize | |||
| menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() { | |||
| menu->addChild(createMenuItem("Randomize", widget::getKeyCommandName(GLFW_KEY_R, RACK_MOD_CTRL), [=]() { | |||
| randomizeSelectionAction(); | |||
| }, n == 0)); | |||
| // Disconnect cables | |||
| menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() { | |||
| menu->addChild(createMenuItem("Disconnect cables", widget::getKeyCommandName(GLFW_KEY_U, RACK_MOD_CTRL), [=]() { | |||
| disconnectSelectionAction(); | |||
| }, n == 0)); | |||
| // Bypass | |||
| std::string bypassText = RACK_MOD_CTRL_NAME "+E"; | |||
| std::string bypassText = widget::getKeyCommandName(GLFW_KEY_E, RACK_MOD_CTRL); | |||
| bool bypassed = (n > 0) && isSelectionBypassed(); | |||
| if (bypassed) | |||
| bypassText += " " CHECKMARK_STRING; | |||
| @@ -1355,17 +1355,17 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
| }, n == 0, true)); | |||
| // Duplicate | |||
| menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
| menu->addChild(createMenuItem("Duplicate", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL), [=]() { | |||
| cloneSelectionAction(false); | |||
| }, n == 0)); | |||
| // Duplicate with cables | |||
| menu->addChild(createMenuItem("â”” with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { | |||
| menu->addChild(createMenuItem("â”” with cables", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL | GLFW_MOD_SHIFT), [=]() { | |||
| cloneSelectionAction(true); | |||
| }, n == 0)); | |||
| // Delete | |||
| menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | |||
| menu->addChild(createMenuItem("Delete", widget::getKeyCommandName(GLFW_KEY_BACKSPACE, 0) + "/" + widget::getKeyCommandName(GLFW_KEY_DELETE, 0), [=]() { | |||
| deleteSelectionAction(); | |||
| }, n == 0, true)); | |||
| } | |||
| @@ -156,7 +156,7 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { | |||
| // manual | |||
| std::string manualUrl = getManualUrl(); | |||
| if (manualUrl != "") { | |||
| menu->addChild(createMenuItem("User manual", RACK_MOD_CTRL_NAME "+F1", [=]() { | |||
| menu->addChild(createMenuItem("User manual", widget::getKeyCommandName(GLFW_KEY_F1, RACK_MOD_CTRL), [=]() { | |||
| system::openBrowser(manualUrl); | |||
| })); | |||
| } | |||
| @@ -349,25 +349,25 @@ void TextField::createContextMenu() { | |||
| TextFieldCutItem* cutItem = new TextFieldCutItem; | |||
| cutItem->text = "Cut"; | |||
| cutItem->rightText = RACK_MOD_CTRL_NAME "+X"; | |||
| cutItem->rightText = widget::getKeyCommandName(GLFW_KEY_X, RACK_MOD_CTRL); | |||
| cutItem->textField = this; | |||
| menu->addChild(cutItem); | |||
| TextFieldCopyItem* copyItem = new TextFieldCopyItem; | |||
| copyItem->text = "Copy"; | |||
| copyItem->rightText = RACK_MOD_CTRL_NAME "+C"; | |||
| copyItem->rightText = widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL); | |||
| copyItem->textField = this; | |||
| menu->addChild(copyItem); | |||
| TextFieldPasteItem* pasteItem = new TextFieldPasteItem; | |||
| pasteItem->text = "Paste"; | |||
| pasteItem->rightText = RACK_MOD_CTRL_NAME "+V"; | |||
| pasteItem->rightText = widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL); | |||
| pasteItem->textField = this; | |||
| menu->addChild(pasteItem); | |||
| TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem; | |||
| selectAllItem->text = "Select all"; | |||
| selectAllItem->rightText = RACK_MOD_CTRL_NAME "+A"; | |||
| selectAllItem->rightText = widget::getKeyCommandName(GLFW_KEY_A, RACK_MOD_CTRL); | |||
| selectAllItem->textField = this; | |||
| menu->addChild(selectAllItem); | |||
| } | |||
| @@ -9,6 +9,61 @@ namespace rack { | |||
| namespace widget { | |||
| std::string getKeyName(int key) { | |||
| // glfwGetKeyName overrides | |||
| switch (key) { | |||
| case GLFW_KEY_SPACE: return "Space"; | |||
| } | |||
| const char* keyNameC = glfwGetKeyName(key, GLFW_KEY_UNKNOWN); | |||
| if (keyNameC) { | |||
| std::string keyName = keyNameC; | |||
| if (keyName.size() == 1) | |||
| keyName[0] = std::toupper((unsigned char) keyName[0]); | |||
| return keyName; | |||
| } | |||
| // Unprintable keys with names | |||
| switch (key) { | |||
| case GLFW_KEY_ESCAPE: return "Escape"; | |||
| case GLFW_KEY_ENTER: return "Enter"; | |||
| case GLFW_KEY_TAB: return "Tab"; | |||
| case GLFW_KEY_BACKSPACE: return "Backspace"; | |||
| case GLFW_KEY_INSERT: return "Insert"; | |||
| case GLFW_KEY_DELETE: return "Delete"; | |||
| case GLFW_KEY_RIGHT: return "Right"; | |||
| case GLFW_KEY_LEFT: return "Left"; | |||
| case GLFW_KEY_DOWN: return "Down"; | |||
| case GLFW_KEY_UP: return "Up"; | |||
| case GLFW_KEY_PAGE_UP: return "Page Up"; | |||
| case GLFW_KEY_PAGE_DOWN: return "Page Down"; | |||
| case GLFW_KEY_HOME: return "Home"; | |||
| case GLFW_KEY_END: return "End"; | |||
| case GLFW_KEY_KP_ENTER: return "Enter"; | |||
| } | |||
| if (GLFW_KEY_F1 <= key && key <= GLFW_KEY_F25) | |||
| return string::f("F%d", key - GLFW_KEY_F1 + 1); | |||
| return ""; | |||
| } | |||
| std::string getKeyCommandName(int key, int mods) { | |||
| std::string modsName; | |||
| if (mods & RACK_MOD_CTRL) { | |||
| modsName += RACK_MOD_CTRL_NAME "+"; | |||
| } | |||
| if (mods & GLFW_MOD_SHIFT) { | |||
| modsName += RACK_MOD_SHIFT_NAME "+"; | |||
| } | |||
| if (mods & GLFW_MOD_ALT) { | |||
| modsName += RACK_MOD_ALT_NAME "+"; | |||
| } | |||
| return modsName + getKeyName(key); | |||
| } | |||
| void EventState::setHoveredWidget(widget::Widget* w) { | |||
| if (w == hoveredWidget) | |||
| return; | |||