| @@ -10,6 +10,11 @@ | |||
| namespace rack { | |||
| namespace ui { | |||
| struct Menu; | |||
| } // namespace app | |||
| namespace app { | |||
| struct ModuleWidget; | |||
| } // namespace app | |||
| @@ -65,6 +70,9 @@ struct Model { | |||
| std::string getFullName(); | |||
| std::string getFactoryPresetDirectory(); | |||
| std::string getUserPresetDirectory(); | |||
| /** Returns the module or plugin manual URL, whichever exists. */ | |||
| std::string getManualUrl(); | |||
| void appendContextMenu(ui::Menu* menu); | |||
| }; | |||
| @@ -168,10 +168,12 @@ std::string getOperatingSystemInfo(); | |||
| /** Opens a URL in a browser. | |||
| Shell injection is possible, so make sure the URL is trusted or hard coded. | |||
| Does nothing if string is blank. | |||
| Does not block. | |||
| */ | |||
| void openBrowser(const std::string& url); | |||
| /** Opens Windows Explorer, Finder, etc at a directory location. | |||
| Does nothing if string is blank. | |||
| Does not block. | |||
| */ | |||
| void openDirectory(const std::string& path); | |||
| @@ -284,8 +284,11 @@ struct ModelBox : widget::OpaqueWidget { | |||
| void createContextMenu() { | |||
| ui::Menu* menu = createMenu(); | |||
| // menu->addChild(createMenuLabel(model->name)); | |||
| // menu->addChild(createMenuLabel(model->plugin->brand)); | |||
| menu->addChild(createMenuLabel(model->name)); | |||
| menu->addChild(createMenuLabel(model->plugin->brand)); | |||
| model->appendContextMenu(menu); | |||
| menu->addChild(new ui::MenuSeparator); | |||
| menu->addChild(createBoolMenuItem("Favorite", | |||
| [=]() { | |||
| @@ -16,7 +16,6 @@ | |||
| #include <settings.hpp> | |||
| #include <history.hpp> | |||
| #include <string.hpp> | |||
| #include <tag.hpp> | |||
| namespace rack { | |||
| @@ -358,9 +357,7 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||
| return; | |||
| } | |||
| if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| std::string manualUrl = (model->manualUrl != "") ? model->manualUrl : model->plugin->manualUrl; | |||
| if (!manualUrl.empty()) | |||
| system::openBrowser(manualUrl); | |||
| system::openBrowser(model->getManualUrl()); | |||
| e.consume(this); | |||
| } | |||
| } | |||
| @@ -919,92 +916,7 @@ void ModuleWidget::createContextMenu() { | |||
| // Info | |||
| menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) { | |||
| if (!weakThis) | |||
| return; | |||
| // plugin | |||
| menu->addChild(createMenuItem("Plugin: " + model->plugin->name, "", [=]() { | |||
| system::openBrowser(model->plugin->pluginUrl); | |||
| }, model->plugin->pluginUrl == "")); | |||
| // version | |||
| menu->addChild(createMenuLabel(model->plugin->version)); | |||
| // author | |||
| if (model->plugin->author != "") { | |||
| menu->addChild(createMenuItem("Author: " + model->plugin->author, "", [=]() { | |||
| system::openBrowser(model->plugin->authorUrl); | |||
| }, model->plugin->authorUrl.empty())); | |||
| } | |||
| // license | |||
| std::string license = model->plugin->license; | |||
| if (string::startsWith(license, "https://") || string::startsWith(license, "http://")) { | |||
| menu->addChild(createMenuItem("License: Open in browser", "", [=]() { | |||
| system::openBrowser(license); | |||
| })); | |||
| } | |||
| else if (license != "") { | |||
| menu->addChild(createMenuLabel("License: " + license)); | |||
| } | |||
| // tags | |||
| if (!model->tagIds.empty()) { | |||
| menu->addChild(createMenuLabel("Tags:")); | |||
| for (int tagId : model->tagIds) { | |||
| menu->addChild(createMenuLabel("• " + tag::getTag(tagId))); | |||
| } | |||
| } | |||
| menu->addChild(new ui::MenuSeparator); | |||
| // VCV Library page | |||
| menu->addChild(createMenuItem("VCV Library page", "", [=]() { | |||
| system::openBrowser("https://library.vcvrack.com/" + model->plugin->slug + "/" + model->slug); | |||
| })); | |||
| // modularGridUrl | |||
| if (model->modularGridUrl != "") { | |||
| menu->addChild(createMenuItem("ModularGrid page", "", [=]() { | |||
| system::openBrowser(model->modularGridUrl); | |||
| })); | |||
| } | |||
| // manual | |||
| std::string manualUrl = (model->manualUrl != "") ? model->manualUrl : model->plugin->manualUrl; | |||
| if (manualUrl != "") { | |||
| menu->addChild(createMenuItem("User manual", RACK_MOD_CTRL_NAME "+F1", [=]() { | |||
| system::openBrowser(manualUrl); | |||
| })); | |||
| } | |||
| // donate | |||
| if (model->plugin->donateUrl != "") { | |||
| menu->addChild(createMenuItem("Donate", "", [=]() { | |||
| system::openBrowser(model->plugin->donateUrl); | |||
| })); | |||
| } | |||
| // source code | |||
| if (model->plugin->sourceUrl != "") { | |||
| menu->addChild(createMenuItem("Source code", "", [=]() { | |||
| system::openBrowser(model->plugin->sourceUrl); | |||
| })); | |||
| } | |||
| // changelog | |||
| if (model->plugin->changelogUrl != "") { | |||
| menu->addChild(createMenuItem("Changelog", "", [=]() { | |||
| system::openBrowser(model->plugin->changelogUrl); | |||
| })); | |||
| } | |||
| // plugin folder | |||
| if (model->plugin->path != "") { | |||
| menu->addChild(createMenuItem("Open plugin folder", "", [=]() { | |||
| system::openDirectory(model->plugin->path); | |||
| })); | |||
| } | |||
| model->appendContextMenu(menu); | |||
| })); | |||
| // Preset | |||
| @@ -6,6 +6,9 @@ | |||
| #include <system.hpp> | |||
| #include <string.hpp> | |||
| #include <tag.hpp> | |||
| #include <ui/Menu.hpp> | |||
| #include <ui/MenuSeparator.hpp> | |||
| #include <helpers.hpp> | |||
| namespace rack { | |||
| @@ -84,6 +87,98 @@ std::string Model::getUserPresetDirectory() { | |||
| } | |||
| std::string Model::getManualUrl() { | |||
| if (!manualUrl.empty()) | |||
| return manualUrl; | |||
| return plugin->manualUrl; | |||
| } | |||
| void Model::appendContextMenu(ui::Menu* menu) { | |||
| // plugin | |||
| menu->addChild(createMenuItem("Plugin: " + plugin->name, "", [=]() { | |||
| system::openBrowser(plugin->pluginUrl); | |||
| }, plugin->pluginUrl == "")); | |||
| // version | |||
| menu->addChild(createMenuLabel(plugin->version)); | |||
| // author | |||
| if (plugin->author != "") { | |||
| menu->addChild(createMenuItem("Author: " + plugin->author, "", [=]() { | |||
| system::openBrowser(plugin->authorUrl); | |||
| }, plugin->authorUrl.empty())); | |||
| } | |||
| // license | |||
| std::string license = plugin->license; | |||
| if (string::startsWith(license, "https://") || string::startsWith(license, "http://")) { | |||
| menu->addChild(createMenuItem("License: Open in browser", "", [=]() { | |||
| system::openBrowser(license); | |||
| })); | |||
| } | |||
| else if (license != "") { | |||
| menu->addChild(createMenuLabel("License: " + license)); | |||
| } | |||
| // tags | |||
| if (!tagIds.empty()) { | |||
| menu->addChild(createMenuLabel("Tags:")); | |||
| for (int tagId : tagIds) { | |||
| menu->addChild(createMenuLabel("• " + tag::getTag(tagId))); | |||
| } | |||
| } | |||
| menu->addChild(new ui::MenuSeparator); | |||
| // VCV Library page | |||
| menu->addChild(createMenuItem("VCV Library page", "", [=]() { | |||
| system::openBrowser("https://library.vcvrack.com/" + plugin->slug + "/" + slug); | |||
| })); | |||
| // modularGridUrl | |||
| if (modularGridUrl != "") { | |||
| menu->addChild(createMenuItem("ModularGrid page", "", [=]() { | |||
| system::openBrowser(modularGridUrl); | |||
| })); | |||
| } | |||
| // manual | |||
| std::string manualUrl = getManualUrl(); | |||
| if (manualUrl != "") { | |||
| menu->addChild(createMenuItem("User manual", RACK_MOD_CTRL_NAME "+F1", [=]() { | |||
| system::openBrowser(manualUrl); | |||
| })); | |||
| } | |||
| // donate | |||
| if (plugin->donateUrl != "") { | |||
| menu->addChild(createMenuItem("Donate", "", [=]() { | |||
| system::openBrowser(plugin->donateUrl); | |||
| })); | |||
| } | |||
| // source code | |||
| if (plugin->sourceUrl != "") { | |||
| menu->addChild(createMenuItem("Source code", "", [=]() { | |||
| system::openBrowser(plugin->sourceUrl); | |||
| })); | |||
| } | |||
| // changelog | |||
| if (plugin->changelogUrl != "") { | |||
| menu->addChild(createMenuItem("Changelog", "", [=]() { | |||
| system::openBrowser(plugin->changelogUrl); | |||
| })); | |||
| } | |||
| // plugin folder | |||
| if (plugin->path != "") { | |||
| menu->addChild(createMenuItem("Open plugin folder", "", [=]() { | |||
| system::openDirectory(plugin->path); | |||
| })); | |||
| } | |||
| } | |||
| } // namespace plugin | |||
| @@ -712,6 +712,9 @@ std::string getOperatingSystemInfo() { | |||
| void openBrowser(const std::string& url) { | |||
| if (url.empty()) | |||
| return; | |||
| std::string urlL = url; | |||
| std::thread t([=] { | |||
| #if defined ARCH_LIN | |||
| @@ -731,6 +734,9 @@ void openBrowser(const std::string& url) { | |||
| void openDirectory(const std::string& path) { | |||
| if (path.empty()) | |||
| return; | |||
| std::string pathL = path; | |||
| std::thread t([=] { | |||
| #if defined ARCH_LIN | |||