@@ -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; | |||