Closes #50 Signed-off-by: falkTX <falktx@falktx.com>tags/22.02
| @@ -0,0 +1 @@ | |||
| ../CardinalCommon.cpp | |||
| @@ -1 +0,0 @@ | |||
| ../override/MenuBar.cpp | |||
| @@ -0,0 +1,157 @@ | |||
| /* | |||
| * DISTRHO Cardinal Plugin | |||
| * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 3 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the LICENSE file. | |||
| */ | |||
| #include "CardinalCommon.hpp" | |||
| #include "AsyncDialog.hpp" | |||
| #include "PluginContext.hpp" | |||
| #include <context.hpp> | |||
| #include <history.hpp> | |||
| #include <patch.hpp> | |||
| #include <string.hpp> | |||
| #include <system.hpp> | |||
| #include <app/Scene.hpp> | |||
| #ifdef NDEBUG | |||
| # undef DEBUG | |||
| #endif | |||
| // for finding home dir | |||
| #ifndef ARCH_WIN | |||
| # include <pwd.h> | |||
| # include <unistd.h> | |||
| #endif | |||
| namespace patchUtils | |||
| { | |||
| static void promptClear(const char* const message, const std::function<void()> action) | |||
| { | |||
| if (APP->history->isSaved() || APP->scene->rack->hasModules()) | |||
| return action(); | |||
| asyncDialog::create(message, action); | |||
| } | |||
| static std::string homeDir() | |||
| { | |||
| #ifdef ARCH_WIN | |||
| if (const char* const userprofile = getenv("USERPROFILE")) | |||
| { | |||
| return userprofile; | |||
| } | |||
| else if (const char* const homedrive = getenv("HOMEDRIVE")) | |||
| { | |||
| if (const char* const homepath = getenv("HOMEPATH")) | |||
| return system::join(homedrive, homepath); | |||
| } | |||
| #else | |||
| if (const char* const home = getenv("HOME")) | |||
| return home; | |||
| else if (struct passwd* const pwd = getpwuid(getuid())) | |||
| return pwd->pw_dir; | |||
| #endif | |||
| return {}; | |||
| } | |||
| using namespace rack; | |||
| void loadDialog() | |||
| { | |||
| promptClear("The current patch is unsaved. Clear it and open a new patch?", []() { | |||
| std::string dir; | |||
| if (! APP->patch->path.empty()) | |||
| dir = system::getDirectory(APP->patch->path); | |||
| else | |||
| dir = homeDir(); | |||
| CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP); | |||
| DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,); | |||
| CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui); | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| FileBrowserOptions opts; | |||
| opts.startDir = dir.c_str(); | |||
| opts.saving = ui->saving = false; | |||
| ui->openFileBrowser(opts); | |||
| }); | |||
| } | |||
| void loadPathDialog(const std::string& path) | |||
| { | |||
| promptClear("The current patch is unsaved. Clear it and open the new patch?", [path]() { | |||
| APP->patch->loadAction(path); | |||
| }); | |||
| } | |||
| void loadTemplateDialog() | |||
| { | |||
| promptClear("The current patch is unsaved. Clear it and start a new patch?", []() { | |||
| APP->patch->loadTemplate(); | |||
| }); | |||
| } | |||
| void revertDialog() | |||
| { | |||
| if (APP->patch->path.empty()) | |||
| return; | |||
| promptClear("Revert patch to the last saved state?", []{ | |||
| APP->patch->loadAction(APP->patch->path); | |||
| }); | |||
| } | |||
| void saveDialog(const std::string& path) | |||
| { | |||
| if (path.empty()) { | |||
| return; | |||
| } | |||
| // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property. | |||
| APP->history->setSaved(); | |||
| try { | |||
| APP->patch->save(path); | |||
| } | |||
| catch (Exception& e) { | |||
| asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str()); | |||
| return; | |||
| } | |||
| } | |||
| void saveAsDialog() | |||
| { | |||
| std::string dir; | |||
| if (! APP->patch->path.empty()) | |||
| dir = system::getDirectory(APP->patch->path); | |||
| else | |||
| dir = homeDir(); | |||
| CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP); | |||
| DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,); | |||
| CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui); | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| FileBrowserOptions opts; | |||
| opts.startDir = dir.c_str(); | |||
| opts.saving = ui->saving = true; | |||
| ui->openFileBrowser(opts); | |||
| } | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| /* | |||
| * DISTRHO Cardinal Plugin | |||
| * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 3 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the LICENSE file. | |||
| */ | |||
| #include <string> | |||
| #pragma once | |||
| namespace patchUtils | |||
| { | |||
| void loadDialog(); | |||
| void loadPathDialog(const std::string& path); | |||
| void loadTemplateDialog(); | |||
| void revertDialog(); | |||
| void saveDialog(const std::string& path); | |||
| void saveAsDialog(); | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| ../CardinalCommon.cpp | |||
| @@ -1 +0,0 @@ | |||
| ../override/MenuBar.cpp | |||
| @@ -0,0 +1 @@ | |||
| ../CardinalCommon.cpp | |||
| @@ -1 +0,0 @@ | |||
| ../override/MenuBar.cpp | |||
| @@ -179,7 +179,7 @@ GLFWAPI const char* glfwGetKeyName(const int key, int) | |||
| namespace rack { | |||
| namespace app { | |||
| widget::Widget* createMenuBar(CardinalBaseUI* const ui, bool isStandalone); | |||
| widget::Widget* createMenuBar(bool isStandalone); | |||
| } | |||
| namespace window { | |||
| void WindowSetPluginUI(Window* window, DISTRHO_NAMESPACE::UI* ui); | |||
| @@ -283,7 +283,7 @@ public: | |||
| if (context->scene->menuBar != nullptr) | |||
| context->scene->removeChild(context->scene->menuBar); | |||
| context->scene->menuBar = rack::app::createMenuBar(this, getApp().isStandalone()); | |||
| context->scene->menuBar = rack::app::createMenuBar(getApp().isStandalone()); | |||
| context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll); | |||
| // hide "Browse VCV Library" button | |||
| @@ -88,6 +88,8 @@ BUILD_CXX_FLAGS += -DnsvgParseFromFile=nsvgParseFromFileCardinal | |||
| RACK_FILES += AsyncDialog.cpp | |||
| RACK_FILES += override/Engine.cpp | |||
| RACK_FILES += override/MenuBar.cpp | |||
| RACK_FILES += override/Scene.cpp | |||
| RACK_FILES += override/asset.cpp | |||
| RACK_FILES += override/context.cpp | |||
| RACK_FILES += override/dep.cpp | |||
| @@ -112,6 +114,7 @@ IGNORED_FILES += Rack/src/network.cpp | |||
| IGNORED_FILES += Rack/src/rtaudio.cpp | |||
| IGNORED_FILES += Rack/src/rtmidi.cpp | |||
| IGNORED_FILES += Rack/src/app/MenuBar.cpp | |||
| IGNORED_FILES += Rack/src/app/Scene.cpp | |||
| IGNORED_FILES += Rack/src/engine/Engine.cpp | |||
| IGNORED_FILES += Rack/src/window/Window.cpp | |||
| @@ -80,6 +80,7 @@ include ../../dpf/Makefile.base.mk | |||
| # Files to build (DPF stuff) | |||
| FILES_DSP = CardinalPlugin.cpp | |||
| FILES_DSP += CardinalCommon.cpp | |||
| FILES_DSP += common.cpp | |||
| ifeq ($(HEADLESS),true) | |||
| @@ -87,7 +88,6 @@ FILES_DSP += RemoteNanoVG.cpp | |||
| FILES_DSP += RemoteWindow.cpp | |||
| else | |||
| FILES_UI = CardinalUI.cpp | |||
| FILES_UI += MenuBar.cpp | |||
| FILES_UI += Window.cpp | |||
| endif | |||
| @@ -50,23 +50,11 @@ | |||
| #include <patch.hpp> | |||
| #include <library.hpp> | |||
| #ifdef NDEBUG | |||
| # undef DEBUG | |||
| #endif | |||
| // for finding home dir | |||
| #ifndef ARCH_WIN | |||
| # include <pwd.h> | |||
| # include <unistd.h> | |||
| #endif | |||
| #ifdef HAVE_LIBLO | |||
| # include <lo/lo.h> | |||
| #endif | |||
| #include <Window.hpp> | |||
| #include "../AsyncDialog.hpp" | |||
| #include "../PluginContext.hpp" | |||
| #include "../CardinalCommon.hpp" | |||
| // #define REMOTE_HOST "localhost" | |||
| #define REMOTE_HOST "192.168.51.1" | |||
| @@ -99,37 +87,7 @@ struct MenuButton : ui::Button { | |||
| //////////////////// | |||
| static void promptClear(const char* const message, const std::function<void()> action) | |||
| { | |||
| if (APP->history->isSaved() || APP->scene->rack->hasModules()) | |||
| return action(); | |||
| asyncDialog::create(message, action); | |||
| } | |||
| static std::string homeDir() | |||
| { | |||
| #ifdef ARCH_WIN | |||
| if (const char* const userprofile = getenv("USERPROFILE")) | |||
| { | |||
| return userprofile; | |||
| } | |||
| else if (const char* const homedrive = getenv("HOMEDRIVE")) | |||
| { | |||
| if (const char* const homepath = getenv("HOMEPATH")) | |||
| return system::join(homedrive, homepath); | |||
| } | |||
| #else | |||
| if (const char* const home = getenv("HOME")) | |||
| return home; | |||
| else if (struct passwd* const pwd = getpwuid(getuid())) | |||
| return pwd->pw_dir; | |||
| #endif | |||
| return {}; | |||
| } | |||
| struct FileButton : MenuButton { | |||
| CardinalBaseUI* const ui; | |||
| const bool isStandalone; | |||
| #ifdef HAVE_LIBLO | |||
| @@ -153,53 +111,29 @@ struct FileButton : MenuButton { | |||
| } | |||
| #endif | |||
| FileButton(CardinalBaseUI* const ui2, const bool standalone) | |||
| : MenuButton(), ui(ui2), isStandalone(standalone) {} | |||
| FileButton(const bool standalone) | |||
| : MenuButton(), isStandalone(standalone) {} | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| menu->cornerFlags = BND_CORNER_TOP; | |||
| menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
| menu->addChild(createMenuItem("New", ""/*RACK_MOD_CTRL_NAME "+N"*/, []() { | |||
| // see APP->patch->loadTemplateDialog(); | |||
| promptClear("The current patch is unsaved. Clear it and start a new patch?", []() { | |||
| APP->patch->loadTemplate(); | |||
| }); | |||
| menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { | |||
| patchUtils::loadTemplateDialog(); | |||
| })); | |||
| menu->addChild(createMenuItem("Open / Import...", ""/*RACK_MOD_CTRL_NAME "+O"*/, [this]() { | |||
| // see APP->patch->loadDialog(); | |||
| promptClear("The current patch is unsaved. Clear it and open a new patch?", [this]() { | |||
| std::string dir; | |||
| if (! APP->patch->path.empty()) | |||
| dir = system::getDirectory(APP->patch->path); | |||
| else | |||
| dir = homeDir(); | |||
| Window::FileBrowserOptions opts; | |||
| opts.startDir = dir.c_str(); | |||
| opts.saving = ui->saving = false; | |||
| ui->openFileBrowser(opts); | |||
| }); | |||
| menu->addChild(createMenuItem("Open / Import...", RACK_MOD_CTRL_NAME "+O", []() { | |||
| patchUtils::loadDialog(); | |||
| })); | |||
| menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { | |||
| APP->patch->saveDialog(); | |||
| // NOTE: will do nothing if path is empty, intentionally | |||
| patchUtils::saveDialog(APP->patch->path); | |||
| }, APP->patch->path.empty())); | |||
| menu->addChild(createMenuItem("Save as / Export...", ""/*RACK_MOD_CTRL_NAME "+Shift+S"*/, [this]() { | |||
| // see APP->patch->saveAsDialog(); | |||
| std::string dir; | |||
| if (! APP->patch->path.empty()) | |||
| dir = system::getDirectory(APP->patch->path); | |||
| else | |||
| dir = homeDir(); | |||
| Window::FileBrowserOptions opts; | |||
| opts.startDir = dir.c_str(); | |||
| opts.saving = ui->saving = true; | |||
| ui->openFileBrowser(opts); | |||
| menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| patchUtils::saveAsDialog(); | |||
| })); | |||
| #ifdef HAVE_LIBLO | |||
| @@ -236,13 +170,8 @@ struct FileButton : MenuButton { | |||
| } | |||
| #endif | |||
| menu->addChild(createMenuItem("Revert", ""/*RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O"*/, []() { | |||
| // APP->patch->revertDialog(); | |||
| if (APP->patch->path.empty()) | |||
| return; | |||
| promptClear("Revert patch to the last saved state?", []{ | |||
| APP->patch->loadAction(APP->patch->path); | |||
| }); | |||
| menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| patchUtils::revertDialog(); | |||
| }, APP->patch->path.empty())); | |||
| if (isStandalone) { | |||
| @@ -678,10 +607,9 @@ struct MeterLabel : ui::Label { | |||
| struct MenuBar : widget::OpaqueWidget { | |||
| // CardinalPluginContext* const context; | |||
| MeterLabel* meterLabel; | |||
| MenuBar(CardinalBaseUI* const ui, const bool isStandalone) | |||
| MenuBar(const bool isStandalone) | |||
| : widget::OpaqueWidget() | |||
| // : context(ctx) | |||
| { | |||
| @@ -693,7 +621,7 @@ struct MenuBar : widget::OpaqueWidget { | |||
| layout->spacing = math::Vec(0, 0); | |||
| addChild(layout); | |||
| FileButton* fileButton = new FileButton(ui, isStandalone); | |||
| FileButton* fileButton = new FileButton(isStandalone); | |||
| fileButton->text = "File"; | |||
| layout->addChild(fileButton); | |||
| @@ -746,8 +674,8 @@ widget::Widget* createMenuBar() { | |||
| return new widget::Widget; | |||
| } | |||
| widget::Widget* createMenuBar(CardinalBaseUI* const ui, const bool isStandalone) { | |||
| menuBar::MenuBar* menuBar = new menuBar::MenuBar(ui, isStandalone); | |||
| widget::Widget* createMenuBar(const bool isStandalone) { | |||
| menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone); | |||
| return menuBar; | |||
| } | |||
| @@ -0,0 +1,300 @@ | |||
| #include <thread> | |||
| #include <app/Scene.hpp> | |||
| #include <app/Browser.hpp> | |||
| #include <app/TipWindow.hpp> | |||
| #include <app/MenuBar.hpp> | |||
| #include <context.hpp> | |||
| #include <system.hpp> | |||
| #include <network.hpp> | |||
| #include <history.hpp> | |||
| #include <settings.hpp> | |||
| #include <patch.hpp> | |||
| #include <asset.hpp> | |||
| #include "../CardinalCommon.hpp" | |||
| namespace rack { | |||
| namespace app { | |||
| struct Scene::Internal { | |||
| bool heldArrowKeys[4] = {}; | |||
| }; | |||
| Scene::Scene() { | |||
| internal = new Internal; | |||
| rackScroll = new RackScrollWidget; | |||
| addChild(rackScroll); | |||
| rack = rackScroll->rackWidget; | |||
| menuBar = createMenuBar(); | |||
| addChild(menuBar); | |||
| browser = browserCreate(); | |||
| browser->hide(); | |||
| addChild(browser); | |||
| } | |||
| Scene::~Scene() { | |||
| delete internal; | |||
| } | |||
| math::Vec Scene::getMousePos() { | |||
| return mousePos; | |||
| } | |||
| void Scene::step() { | |||
| // Resize owned descendants | |||
| menuBar->box.size.x = box.size.x; | |||
| rackScroll->box.pos.y = menuBar->box.size.y; | |||
| rackScroll->box.size = box.size.minus(rackScroll->box.pos); | |||
| // Scroll RackScrollWidget with arrow keys | |||
| math::Vec arrowDelta; | |||
| if (internal->heldArrowKeys[0]) { | |||
| arrowDelta.x -= 1; | |||
| } | |||
| if (internal->heldArrowKeys[1]) { | |||
| arrowDelta.x += 1; | |||
| } | |||
| if (internal->heldArrowKeys[2]) { | |||
| arrowDelta.y -= 1; | |||
| } | |||
| if (internal->heldArrowKeys[3]) { | |||
| arrowDelta.y += 1; | |||
| } | |||
| if (!arrowDelta.isZero()) { | |||
| int mods = APP->window->getMods(); | |||
| float arrowSpeed = 32.f; | |||
| if ((mods & RACK_MOD_MASK) == RACK_MOD_CTRL) | |||
| arrowSpeed /= 4.f; | |||
| if ((mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) | |||
| arrowSpeed *= 4.f; | |||
| if ((mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) | |||
| arrowSpeed /= 16.f; | |||
| rackScroll->offset += arrowDelta * arrowSpeed; | |||
| } | |||
| Widget::step(); | |||
| } | |||
| void Scene::draw(const DrawArgs& args) { | |||
| Widget::draw(args); | |||
| } | |||
| void Scene::onHover(const HoverEvent& e) { | |||
| mousePos = e.pos; | |||
| if (mousePos.y < menuBar->box.size.y) { | |||
| menuBar->show(); | |||
| } | |||
| OpaqueWidget::onHover(e); | |||
| } | |||
| void Scene::onDragHover(const DragHoverEvent& e) { | |||
| mousePos = e.pos; | |||
| OpaqueWidget::onDragHover(e); | |||
| } | |||
| void Scene::onHoverKey(const HoverKeyEvent& e) { | |||
| // Key commands that override children | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| // DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str()); | |||
| if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| patchUtils::loadTemplateDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "q" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| APP->window->close(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| patchUtils::loadDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| patchUtils::revertDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| // NOTE: will do nothing if path is empty, intentionally | |||
| patchUtils::saveDialog(APP->patch->path); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| patchUtils::saveAsDialog(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| APP->history->undo(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| APP->history->redo(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "-" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| float zoom = std::log2(APP->scene->rackScroll->getZoom()); | |||
| zoom *= 2; | |||
| zoom = std::ceil(zoom - 0.01f) - 1; | |||
| zoom /= 2; | |||
| APP->scene->rackScroll->setZoom(std::pow(2.f, zoom)); | |||
| e.consume(this); | |||
| } | |||
| // Numpad has a "+" key, but the main keyboard section hides it under "=" | |||
| if ((e.keyName == "=" || e.keyName == "+") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| float zoom = std::log2(APP->scene->rackScroll->getZoom()); | |||
| zoom *= 2; | |||
| zoom = std::floor(zoom + 0.01f) + 1; | |||
| zoom /= 2; | |||
| APP->scene->rackScroll->setZoom(std::pow(2.f, zoom)); | |||
| e.consume(this); | |||
| } | |||
| if ((e.keyName == "0") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| APP->scene->rackScroll->setZoom(1.f); | |||
| e.consume(this); | |||
| } | |||
| if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | |||
| system::openBrowser("https://vcvrack.com/manual/"); | |||
| e.consume(this); | |||
| } | |||
| if (e.key == GLFW_KEY_F3 && (e.mods & RACK_MOD_MASK) == 0) { | |||
| settings::cpuMeter ^= true; | |||
| e.consume(this); | |||
| } | |||
| // Module selections | |||
| if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| rack->selectAll(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| rack->deselectAll(); | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->copyClipboardSelection(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->resetSelectionAction(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->randomizeSelectionAction(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->disconnectSelectionAction(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->bypassSelectionAction(!rack->isSelectionBypassed()); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (rack->hasSelection()) { | |||
| rack->cloneSelectionAction(false); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | |||
| if (rack->hasSelection()) { | |||
| rack->cloneSelectionAction(true); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { | |||
| if (rack->hasSelection()) { | |||
| rack->deleteSelectionAction(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| } | |||
| // Scroll RackScrollWidget with arrow keys | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_RELEASE) { | |||
| if (e.key == GLFW_KEY_LEFT) { | |||
| internal->heldArrowKeys[0] = (e.action == GLFW_PRESS); | |||
| e.consume(this); | |||
| } | |||
| if (e.key == GLFW_KEY_RIGHT) { | |||
| internal->heldArrowKeys[1] = (e.action == GLFW_PRESS); | |||
| e.consume(this); | |||
| } | |||
| if (e.key == GLFW_KEY_UP) { | |||
| internal->heldArrowKeys[2] = (e.action == GLFW_PRESS); | |||
| e.consume(this); | |||
| } | |||
| if (e.key == GLFW_KEY_DOWN) { | |||
| internal->heldArrowKeys[3] = (e.action == GLFW_PRESS); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| if (e.isConsumed()) | |||
| return; | |||
| OpaqueWidget::onHoverKey(e); | |||
| if (e.isConsumed()) | |||
| return; | |||
| // Key commands that can be overridden by children | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| rack->pasteClipboardAction(); | |||
| e.consume(this); | |||
| } | |||
| if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { | |||
| browser->show(); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| } | |||
| void Scene::onPathDrop(const PathDropEvent& e) { | |||
| if (e.paths.size() >= 1) { | |||
| const std::string& path = e.paths[0]; | |||
| std::string extension = system::getExtension(path); | |||
| if (extension == ".vcv") { | |||
| patchUtils::loadPathDialog(path); | |||
| e.consume(this); | |||
| return; | |||
| } | |||
| if (extension == ".vcvs") { | |||
| APP->scene->rack->loadSelection(path); | |||
| e.consume(this); | |||
| return; | |||
| } | |||
| } | |||
| OpaqueWidget::onPathDrop(e); | |||
| } | |||
| } // namespace app | |||
| } // namespace rack | |||