@@ -79,9 +79,10 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
json_t* toJson(); | json_t* toJson(); | ||||
void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
void pasteJsonAction(json_t* rootJ); | |||||
/** Returns whether paste was successful. */ | |||||
bool pasteJsonAction(json_t* rootJ); | |||||
void copyClipboard(); | void copyClipboard(); | ||||
void pasteClipboardAction(); | |||||
bool pasteClipboardAction(); | |||||
void load(std::string filename); | void load(std::string filename); | ||||
void loadAction(std::string filename); | void loadAction(std::string filename); | ||||
void loadTemplate(); | void loadTemplate(); | ||||
@@ -20,6 +20,7 @@ struct Cable { | |||||
json_t* toJson(); | json_t* toJson(); | ||||
void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
INTERNAL static void jsonStripIds(json_t* rootJ); | |||||
}; | }; | ||||
@@ -757,7 +757,7 @@ struct HelpButton : MenuButton { | |||||
APP->scene->addChild(tipWindowCreate()); | APP->scene->addChild(tipWindowCreate()); | ||||
})); | })); | ||||
menu->addChild(createMenuItem("Manual", "F1", [=]() { | |||||
menu->addChild(createMenuItem("User manual", "F1", [=]() { | |||||
system::openBrowser("https://vcvrack.com/manual/"); | system::openBrowser("https://vcvrack.com/manual/"); | ||||
})); | })); | ||||
@@ -312,8 +312,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
pasteClipboardAction(); | |||||
e.consume(this); | |||||
if (pasteClipboardAction()) { | |||||
e.consume(this); | |||||
} | |||||
} | } | ||||
if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
cloneAction(); | cloneAction(); | ||||
@@ -342,7 +343,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
return; | return; | ||||
} | } | ||||
if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
system::openBrowser(model->getManualUrl()); | |||||
std::string manualUrl = model->getManualUrl(); | |||||
if (!manualUrl.empty()) | |||||
system::openBrowser(manualUrl); | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
} | } | ||||
@@ -468,7 +471,7 @@ void ModuleWidget::fromJson(json_t* moduleJ) { | |||||
APP->engine->moduleFromJson(module, moduleJ); | APP->engine->moduleFromJson(module, moduleJ); | ||||
} | } | ||||
void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
bool ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
engine::Module::jsonStripIds(moduleJ); | engine::Module::jsonStripIds(moduleJ); | ||||
json_t* oldModuleJ = toJson(); | json_t* oldModuleJ = toJson(); | ||||
@@ -479,7 +482,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
catch (Exception& e) { | catch (Exception& e) { | ||||
WARN("%s", e.what()); | WARN("%s", e.what()); | ||||
json_decref(oldModuleJ); | json_decref(oldModuleJ); | ||||
return; | |||||
return false; | |||||
} | } | ||||
// history::ModuleChange | // history::ModuleChange | ||||
@@ -489,6 +492,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
h->oldModuleJ = oldModuleJ; | h->oldModuleJ = oldModuleJ; | ||||
h->newModuleJ = moduleJ; | h->newModuleJ = moduleJ; | ||||
APP->history->push(h); | APP->history->push(h); | ||||
return true; | |||||
} | } | ||||
void ModuleWidget::copyClipboard() { | void ModuleWidget::copyClipboard() { | ||||
@@ -501,22 +505,22 @@ void ModuleWidget::copyClipboard() { | |||||
glfwSetClipboardString(APP->window->win, json); | glfwSetClipboardString(APP->window->win, json); | ||||
} | } | ||||
void ModuleWidget::pasteClipboardAction() { | |||||
bool ModuleWidget::pasteClipboardAction() { | |||||
const char* json = glfwGetClipboardString(APP->window->win); | const char* json = glfwGetClipboardString(APP->window->win); | ||||
if (!json) { | if (!json) { | ||||
WARN("Could not get text from clipboard."); | WARN("Could not get text from clipboard."); | ||||
return; | |||||
return false; | |||||
} | } | ||||
json_error_t error; | json_error_t error; | ||||
json_t* moduleJ = json_loads(json, 0, &error); | json_t* moduleJ = json_loads(json, 0, &error); | ||||
if (!moduleJ) { | if (!moduleJ) { | ||||
WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | ||||
return; | |||||
return false; | |||||
} | } | ||||
DEFER({json_decref(moduleJ);}); | DEFER({json_decref(moduleJ);}); | ||||
pasteJsonAction(moduleJ); | |||||
return pasteJsonAction(moduleJ); | |||||
} | } | ||||
void ModuleWidget::load(std::string filename) { | void ModuleWidget::load(std::string filename) { | ||||
@@ -758,6 +762,9 @@ void ModuleWidget::cloneAction() { | |||||
// JSON serialization is the obvious way to do this | // JSON serialization is the obvious way to do this | ||||
json_t* moduleJ = toJson(); | json_t* moduleJ = toJson(); | ||||
DEFER({ | |||||
json_decref(moduleJ); | |||||
}); | |||||
engine::Module::jsonStripIds(moduleJ); | engine::Module::jsonStripIds(moduleJ); | ||||
// Clone Module | // Clone Module | ||||
@@ -769,7 +776,6 @@ void ModuleWidget::cloneAction() { | |||||
catch (Exception& e) { | catch (Exception& e) { | ||||
WARN("%s", e.what()); | WARN("%s", e.what()); | ||||
} | } | ||||
json_decref(moduleJ); | |||||
APP->engine->addModule(clonedModule); | APP->engine->addModule(clonedModule); | ||||
// Clone ModuleWidget | // Clone ModuleWidget | ||||
@@ -454,7 +454,7 @@ static PasteJsonReturn RackWidget_pasteJson(RackWidget* that, json_t* rootJ, his | |||||
size_t cableIndex; | size_t cableIndex; | ||||
json_t* cableJ; | json_t* cableJ; | ||||
json_array_foreach(cablesJ, cableIndex, cableJ) { | json_array_foreach(cablesJ, cableIndex, cableJ) { | ||||
json_object_del(cableJ, "id"); | |||||
engine::Cable::jsonStripIds(cableJ); | |||||
// Remap old module IDs to new IDs | // Remap old module IDs to new IDs | ||||
json_t* inputModuleIdJ = json_object_get(cableJ, "inputModuleId"); | json_t* inputModuleIdJ = json_object_get(cableJ, "inputModuleId"); | ||||
@@ -600,6 +600,9 @@ void RackWidget::removeModule(ModuleWidget* m) { | |||||
// Disconnect cables | // Disconnect cables | ||||
m->disconnect(); | m->disconnect(); | ||||
// Deselect module if selected | |||||
internal->selectedModules.erase(m); | |||||
// Remove module from ModuleContainer | // Remove module from ModuleContainer | ||||
internal->moduleContainer->removeChild(m); | internal->moduleContainer->removeChild(m); | ||||
} | } | ||||
@@ -1105,7 +1108,9 @@ void RackWidget::deleteSelectionAction() { | |||||
history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
complexAction->name = "remove modules"; | complexAction->name = "remove modules"; | ||||
for (ModuleWidget* mw : getSelected()) { | |||||
// Copy selected set since removing ModuleWidgets modifies it. | |||||
std::set<ModuleWidget*> selectedModules = getSelected(); | |||||
for (ModuleWidget* mw : selectedModules) { | |||||
mw->appendDisconnectActions(complexAction); | mw->appendDisconnectActions(complexAction); | ||||
// history::ModuleRemove | // history::ModuleRemove | ||||
@@ -136,6 +136,7 @@ void Scene::onDragHover(const DragHoverEvent& e) { | |||||
void Scene::onHoverKey(const HoverKeyEvent& e) { | void Scene::onHoverKey(const HoverKeyEvent& e) { | ||||
// Key commands that override children | |||||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | 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()); | // 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) { | if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
@@ -193,10 +194,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
settings::zoom = 0.f; | settings::zoom = 0.f; | ||||
e.consume(this); | 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); | |||||
} | |||||
if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | ||||
system::openBrowser("https://vcvrack.com/manual/"); | system::openBrowser("https://vcvrack.com/manual/"); | ||||
e.consume(this); | e.consume(this); | ||||
@@ -211,13 +208,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
// menuBar->hide(); | // menuBar->hide(); | ||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
// Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. | |||||
if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { | |||||
if (APP->window->isFullScreen()) { | |||||
APP->window->setFullScreen(false); | |||||
e.consume(this); | |||||
} | |||||
} | |||||
// Module selections | // Module selections | ||||
if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
@@ -234,10 +224,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
} | } | ||||
if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||||
rack->pasteClipboardAction(); | |||||
e.consume(this); | |||||
} | |||||
if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
if (rack->hasSelection()) { | if (rack->hasSelection()) { | ||||
rack->resetSelectionAction(); | rack->resetSelectionAction(); | ||||
@@ -307,6 +293,27 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
if (e.isConsumed()) | if (e.isConsumed()) | ||||
return; | return; | ||||
OpaqueWidget::onHoverKey(e); | OpaqueWidget::onHoverKey(e); | ||||
if (e.isConsumed()) | |||||
return; | |||||
// Key commands that can be overridden by children | |||||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||||
// Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. | |||||
if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { | |||||
if (APP->window->isFullScreen()) { | |||||
APP->window->setFullScreen(false); | |||||
e.consume(this); | |||||
} | |||||
} | |||||
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); | |||||
} | |||||
} | |||||
} | } | ||||
@@ -60,5 +60,10 @@ void Cable::fromJson(json_t* rootJ) { | |||||
} | } | ||||
void Cable::jsonStripIds(json_t* rootJ) { | |||||
json_object_del(rootJ, "id"); | |||||
} | |||||
} // namespace engine | } // namespace engine | ||||
} // namespace rack | } // namespace rack |