From 970e227c20aef624aad081b7c3d4ea0043f9c42c Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 21 Sep 2017 03:08:59 -0400 Subject: [PATCH] Changed onSelect/onDeselect to onFocus/onDefocus, added key commands for Open/Save/Save As, open in last used directory --- include/app.hpp | 5 +++++ include/gui.hpp | 1 + include/util.hpp | 3 +++ include/widgets.hpp | 16 ++++++++-------- src/app/ModuleWidget.cpp | 14 ++++++++------ src/app/RackScene.cpp | 20 ++++++++++++++++++-- src/app/RackWidget.cpp | 31 +++++++++++++++++++++++++++++++ src/app/Toolbar.cpp | 29 +++++++++++++---------------- src/core/MidiInterface.cpp | 10 ++++++++++ src/core/core.hpp | 1 + src/gui.cpp | 37 ++++++++++++++++++++++++++----------- src/util.cpp | 16 ++++++++++++++++ src/widgets.cpp | 2 +- src/widgets/TextField.cpp | 8 ++++---- src/widgets/Widget.cpp | 8 ++++---- 15 files changed, 149 insertions(+), 52 deletions(-) diff --git a/include/app.hpp b/include/app.hpp index b1d97b65..38c04fc0 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -90,10 +90,15 @@ struct RackWidget : OpaqueWidget { // Only put WireWidgets in here Widget *wireContainer; WireWidget *activeWire = NULL; + std::string lastPath; RackWidget(); ~RackWidget(); + void clear(); + void openDialog(); + void saveDialog(); + void saveAsDialog(); void savePatch(std::string filename); void loadPatch(std::string filename); json_t *toJson(); diff --git a/include/gui.hpp b/include/gui.hpp index 9262a08c..5400aa69 100644 --- a/include/gui.hpp +++ b/include/gui.hpp @@ -29,5 +29,6 @@ void guiClose(); void guiCursorLock(); void guiCursorUnlock(); bool guiIsModPressed(); +bool guiIsShiftPressed(); } // namespace rack diff --git a/include/util.hpp b/include/util.hpp index 25c0b279..3db0d400 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -52,6 +52,9 @@ std::string stringf(const char *format, ...); /** Truncates and adds "..." to a string, not exceeding `len` characters */ std::string ellipsize(std::string s, size_t len); +std::string extractDirectory(std::string path); +std::string extractFilename(std::string path); + /** Opens a URL, also happens to work with PDFs and folders. Shell injection is possible, so make sure the URL is trusted or hard coded. May block, so open in a new thread. diff --git a/include/widgets.hpp b/include/widgets.hpp index d7411f9f..b3272b7c 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -100,10 +100,10 @@ struct Widget { virtual void onMouseEnter() {} /** Called when another widget begins responding to `onMouseMove` events */ virtual void onMouseLeave() {} - virtual void onSelect() {} - virtual void onDeselect() {} - virtual bool onText(int codepoint) {return false;} - virtual bool onKey(int key) {return false;} + virtual void onFocus() {} + virtual void onDefocus() {} + virtual bool onFocusText(int codepoint) {return false;} + virtual bool onFocusKey(int key) {return false;} virtual Widget *onScroll(Vec pos, Vec scrollRel); /** Called when a widget responds to `onMouseDown` for a left button press */ @@ -370,9 +370,9 @@ struct TextField : OpaqueWidget { } void draw(NVGcontext *vg); Widget *onMouseDown(Vec pos, int button); - bool onText(int codepoint); - bool onKey(int scancode); - void onSelect(); + bool onFocusText(int codepoint); + bool onFocusKey(int scancode); + void onFocus(); void insertText(std::string newText); }; @@ -408,7 +408,7 @@ extern Vec gMousePos; extern Widget *gHoveredWidget; extern Widget *gDraggedWidget; extern Widget *gDragHoveredWidget; -extern Widget *gSelectedWidget; +extern Widget *gFocusedWidget; extern int gGuiFrame; extern Scene *gScene; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index c638063c..e48cc524 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -155,20 +155,22 @@ Widget *ModuleWidget::onHoverKey(Vec pos, int key) { switch (key) { case GLFW_KEY_DELETE: case GLFW_KEY_BACKSPACE: - gRackWidget->deleteModule(this); - this->finalizeEvents(); - delete this; + if (!guiIsModPressed() && !guiIsShiftPressed()) { + gRackWidget->deleteModule(this); + this->finalizeEvents(); + delete this; + } break; case GLFW_KEY_I: - if (guiIsModPressed()) + if (guiIsModPressed() && !guiIsShiftPressed()) initialize(); break; case GLFW_KEY_R: - if (guiIsModPressed()) + if (guiIsModPressed() && !guiIsShiftPressed()) randomize(); break; case GLFW_KEY_D: - if (guiIsModPressed()) + if (guiIsModPressed() && !guiIsShiftPressed()) gRackWidget->cloneModule(this); break; } diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index e6fc8141..fc4d779b 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -70,17 +70,33 @@ void RackScene::draw(NVGcontext *vg) { Widget *RackScene::onHoverKey(Vec pos, int key) { switch (key) { case GLFW_KEY_N: - if (guiIsModPressed()) { + if (guiIsModPressed() && !guiIsShiftPressed()) { gRackWidget->clear(); return this; } break; case GLFW_KEY_Q: - if (guiIsModPressed()) { + if (guiIsModPressed() && !guiIsShiftPressed()) { guiClose(); return this; } break; + case GLFW_KEY_O: + if (guiIsModPressed() && !guiIsShiftPressed()) { + gRackWidget->openDialog(); + return this; + } + break; + case GLFW_KEY_S: + if (guiIsModPressed() && !guiIsShiftPressed()) { + gRackWidget->saveDialog(); + return this; + } + if (guiIsModPressed() && guiIsShiftPressed()) { + gRackWidget->saveAsDialog(); + return this; + } + break; } return Widget::onHoverKey(pos, key); diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 995e9aee..9873ceff 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -4,6 +4,7 @@ #include "engine.hpp" #include "plugin.hpp" #include "gui.hpp" +#include "../ext/osdialog/osdialog.h" namespace rack { @@ -46,6 +47,36 @@ void RackWidget::clear() { moduleContainer->clearChildren(); } +void RackWidget::openDialog() { + std::string dir = lastPath.empty() ? "." : extractDirectory(lastPath); + char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); + if (path) { + loadPatch(path); + lastPath = path; + free(path); + } +} + +void RackWidget::saveDialog() { + if (!lastPath.empty()) { + savePatch(lastPath); + } + else { + saveAsDialog(); + } +} + +void RackWidget::saveAsDialog() { + std::string dir = lastPath.empty() ? "." : extractDirectory(lastPath); + char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcv", NULL); + if (path) { + savePatch(path); + lastPath = path; + free(path); + } +} + + void RackWidget::savePatch(std::string filename) { printf("Saving patch %s\n", filename.c_str()); FILE *file = fopen(filename.c_str(), "w"); diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index c86e2007..905a8f3b 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -1,7 +1,6 @@ #include "app.hpp" #include "gui.hpp" #include "engine.hpp" -#include "../ext/osdialog/osdialog.h" namespace rack { @@ -13,23 +12,21 @@ struct NewItem : MenuItem { } }; +struct OpenItem : MenuItem { + void onAction() { + gRackWidget->openDialog(); + } +}; + struct SaveItem : MenuItem { void onAction() { - char *path = osdialog_file(OSDIALOG_SAVE, "./patches", "Untitled.vcv", NULL); - if (path) { - gRackWidget->savePatch(path); - free(path); - } + gRackWidget->saveDialog(); } }; -struct OpenItem : MenuItem { +struct SaveAsItem : MenuItem { void onAction() { - char *path = osdialog_file(OSDIALOG_OPEN, "./patches", NULL, NULL); - if (path) { - gRackWidget->loadPatch(path); - free(path); - } + gRackWidget->saveAsDialog(); } }; @@ -48,11 +45,11 @@ struct FileChoice : ChoiceButton { openItem->text = "Open"; menu->pushChild(openItem); - // MenuItem *saveItem = new SaveItem(); - // saveItem->text = "Save"; - // menu->pushChild(saveItem); + MenuItem *saveItem = new SaveItem(); + saveItem->text = "Save"; + menu->pushChild(saveItem); - MenuItem *saveAsItem = new SaveItem(); + MenuItem *saveAsItem = new SaveAsItem(); saveAsItem->text = "Save As"; menu->pushChild(saveAsItem); } diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index df48028c..b19da9e7 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -3,6 +3,7 @@ #include #include #include "core.hpp" +#include "gui.hpp" using namespace rack; @@ -408,3 +409,12 @@ MidiInterfaceWidget::MidiInterfaceWidget() { yPos += 37 + margin; } } + +void MidiInterfaceWidget::step() { + // Assume QWERTY +#define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); + + // MIDI_KEY(GLFW_KEY_Z, 48); + + ModuleWidget::step(); +} diff --git a/src/core/core.hpp b/src/core/core.hpp index a38a592e..a93ba7f1 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -13,4 +13,5 @@ struct AudioInterfaceWidget : ModuleWidget { struct MidiInterfaceWidget : ModuleWidget { MidiInterfaceWidget(); + void step(); }; diff --git a/src/gui.cpp b/src/gui.cpp index de7d1d69..20ffed29 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -56,14 +56,14 @@ void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { } gDraggedWidget = w; - if (w != gSelectedWidget) { - if (gSelectedWidget) { - w->onDeselect(); + if (w != gFocusedWidget) { + if (gFocusedWidget) { + w->onDefocus(); } if (w) { - w->onSelect(); + w->onFocus(); } - gSelectedWidget = w; + gFocusedWidget = w; } } } @@ -155,15 +155,15 @@ void scrollCallback(GLFWwindow *window, double x, double y) { } void charCallback(GLFWwindow *window, unsigned int codepoint) { - if (gSelectedWidget) { - gSelectedWidget->onText(codepoint); + if (gFocusedWidget) { + gFocusedWidget->onFocusText(codepoint); } } void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { - // onKey - if (gSelectedWidget && gSelectedWidget->onKey(key)) + // onFocusKey + if (gFocusedWidget && gFocusedWidget->onFocusKey(key)) return; // onHoverKey gScene->onHoverKey(gMousePos, key); @@ -218,8 +218,7 @@ void guiInit() { // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); - std::string title = gApplicationName + " " + gApplicationVersion; - gWindow = glfwCreateWindow(640, 480, title.c_str(), NULL, NULL); + gWindow = glfwCreateWindow(640, 480, "", NULL, NULL); if (!gWindow) { osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Cannot open window with OpenGL 2.0 renderer. Does your graphics card support OpenGL 2.0? If so, are the latest drivers installed?"); exit(1); @@ -280,12 +279,24 @@ void guiRun() { double lastTime = 0.0; while(!glfwWindowShouldClose(gWindow)) { gGuiFrame++; + + // Poll events glfwPollEvents(); { double xpos, ypos; glfwGetCursorPos(gWindow, &xpos, &ypos); cursorPosCallback(gWindow, xpos, ypos); } + + // Set window title + std::string title = gApplicationName + " " + gApplicationVersion; + if (!gRackWidget->lastPath.empty()) { + title += " - "; + title += extractFilename(gRackWidget->lastPath); + } + glfwSetWindowTitle(gWindow, title.c_str()); + + // Step scene gScene->step(); // Render @@ -332,6 +343,10 @@ bool guiIsModPressed() { #endif } +bool guiIsShiftPressed() { + return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; +} + //////////////////// // resources diff --git a/src/util.cpp b/src/util.cpp index 411a57bf..0f75e792 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,7 +1,9 @@ #include "util.hpp" #include #include +#include #include +#include // for dirname and basename #if ARCH_WIN #include @@ -53,6 +55,20 @@ std::string ellipsize(std::string s, size_t len) { return s.substr(0, len - 3) + "..."; } +std::string extractDirectory(std::string path) { + char *pathDup = strdup(path.c_str()); + std::string directory = dirname(pathDup); + free(pathDup); + return directory; +} + +std::string extractFilename(std::string path) { + char *pathDup = strdup(path.c_str()); + std::string filename = basename(pathDup); + free(pathDup); + return filename; +} + void openBrowser(std::string url) { #if ARCH_LIN std::string command = "xdg-open " + url; diff --git a/src/widgets.cpp b/src/widgets.cpp index 912700cb..fa807bc0 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -6,7 +6,7 @@ Vec gMousePos; Widget *gHoveredWidget = NULL; Widget *gDraggedWidget = NULL; Widget *gDragHoveredWidget = NULL; -Widget *gSelectedWidget = NULL; +Widget *gFocusedWidget = NULL; int gGuiFrame; Scene *gScene = NULL; diff --git a/src/widgets/TextField.cpp b/src/widgets/TextField.cpp index 31846f0e..c223b657 100644 --- a/src/widgets/TextField.cpp +++ b/src/widgets/TextField.cpp @@ -10,7 +10,7 @@ namespace rack { void TextField::draw(NVGcontext *vg) { BNDwidgetState state; - if (this == gSelectedWidget) + if (this == gFocusedWidget) state = BND_ACTIVE; else if (this == gHoveredWidget) state = BND_HOVER; @@ -29,14 +29,14 @@ Widget *TextField::onMouseDown(Vec pos, int button) { } -bool TextField::onText(int codepoint) { +bool TextField::onFocusText(int codepoint) { char c = codepoint; std::string newText(1, c); insertText(newText); return true; } -bool TextField::onKey(int key) { +bool TextField::onFocusKey(int key) { switch (key) { case GLFW_KEY_BACKSPACE: if (begin < end) { @@ -103,7 +103,7 @@ bool TextField::onKey(int key) { return true; } -void TextField::onSelect() { +void TextField::onFocus() { begin = 0; end = text.size(); } diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index fb4bfea5..62af465d 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -12,7 +12,7 @@ Widget::~Widget() { if (gHoveredWidget == this) gHoveredWidget = NULL; if (gDraggedWidget == this) gDraggedWidget = NULL; if (gDragHoveredWidget == this) gDragHoveredWidget = NULL; - if (gSelectedWidget == this) gSelectedWidget = NULL; + if (gFocusedWidget == this) gFocusedWidget = NULL; clearChildren(); } @@ -76,9 +76,9 @@ void Widget::finalizeEvents() { if (gDragHoveredWidget == this) { gDragHoveredWidget = NULL; } - if (gSelectedWidget == this) { - gSelectedWidget->onDeselect(); - gSelectedWidget = NULL; + if (gFocusedWidget == this) { + gFocusedWidget->onDefocus(); + gFocusedWidget = NULL; } for (Widget *child : children) { child->finalizeEvents();