summaryrefslogtreecommitdiffstats
path: root/src/progs/ingenuity/PatchCanvas.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/progs/ingenuity/PatchCanvas.cpp')
-rw-r--r--src/progs/ingenuity/PatchCanvas.cpp368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/progs/ingenuity/PatchCanvas.cpp b/src/progs/ingenuity/PatchCanvas.cpp
new file mode 100644
index 00000000..fd84443c
--- /dev/null
+++ b/src/progs/ingenuity/PatchCanvas.cpp
@@ -0,0 +1,368 @@
+/* This file is part of Ingen. Copyright (C) 2006 Dave Robillard.
+ *
+ * Ingen 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 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Ingen 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 details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "PatchCanvas.h"
+#include <cassert>
+#include <flowcanvas/FlowCanvas.h>
+#include "App.h"
+#include "ModelEngineInterface.h"
+#include "PatchModel.h"
+#include "PatchWindow.h"
+#include "PatchPortModule.h"
+#include "LoadPluginWindow.h"
+#include "LoadSubpatchWindow.h"
+#include "NewSubpatchWindow.h"
+#include "Port.h"
+#include "NodeModel.h"
+#include "NodeModule.h"
+#include "PatchPortModule.h"
+#include "SubpatchModule.h"
+#include "GladeFactory.h"
+#include "WindowFactory.h"
+
+namespace Ingenuity {
+
+
+PatchCanvas::PatchCanvas(CountedPtr<PatchModel> patch, int width, int height)
+: FlowCanvas(width, height),
+ m_patch(patch),
+ m_last_click_x(0),
+ m_last_click_y(0)
+{
+ Glib::RefPtr<Gnome::Glade::Xml> xml = GladeFactory::new_glade_reference();
+ xml->get_widget("canvas_menu", m_menu);
+
+ xml->get_widget("canvas_menu_add_audio_input", m_menu_add_audio_input);
+ xml->get_widget("canvas_menu_add_audio_output", m_menu_add_audio_output);
+ xml->get_widget("canvas_menu_add_control_input", m_menu_add_control_input);
+ xml->get_widget("canvas_menu_add_control_output", m_menu_add_control_output);
+ xml->get_widget("canvas_menu_add_midi_input", m_menu_add_midi_input);
+ xml->get_widget("canvas_menu_add_midi_output", m_menu_add_midi_output);
+ xml->get_widget("canvas_menu_load_plugin", m_menu_load_plugin);
+ xml->get_widget("canvas_menu_load_patch", m_menu_load_patch);
+ xml->get_widget("canvas_menu_new_patch", m_menu_new_patch);
+
+ build_canvas();
+
+ // Add port menu items
+ m_menu_add_audio_input->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "audio_input", "AUDIO", false));
+ m_menu_add_audio_output->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "audio_output", "AUDIO", true));
+ m_menu_add_control_input->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "control_input", "CONTROL", false));
+ m_menu_add_control_output->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "control_output", "CONTROL", true));
+ m_menu_add_midi_input->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "midi_input", "MIDI", false));
+ m_menu_add_midi_output->signal_activate().connect(
+ sigc::bind(sigc::mem_fun(this, &PatchCanvas::menu_add_port),
+ "midi_output", "MIDI", true));
+
+ // Connect to model signals to track state
+ m_patch->new_node_sig.connect(sigc::mem_fun(this, &PatchCanvas::add_node));
+ m_patch->removed_node_sig.connect(sigc::mem_fun(this, &PatchCanvas::remove_node));
+ m_patch->new_port_sig.connect(sigc::mem_fun(this, &PatchCanvas::add_port));
+ m_patch->removed_port_sig.connect(sigc::mem_fun(this, &PatchCanvas::remove_port));
+ m_patch->new_connection_sig.connect(sigc::mem_fun(this, &PatchCanvas::connection));
+ m_patch->removed_connection_sig.connect(sigc::mem_fun(this, &PatchCanvas::disconnection));
+
+ // Connect widget signals to do things
+ m_menu_load_plugin->signal_activate().connect(sigc::mem_fun(this, &PatchCanvas::menu_load_plugin));
+ m_menu_load_patch->signal_activate().connect(sigc::mem_fun(this, &PatchCanvas::menu_load_patch));
+ m_menu_new_patch->signal_activate().connect(sigc::mem_fun(this, &PatchCanvas::menu_new_patch));
+}
+
+
+void
+PatchCanvas::build_canvas() {
+
+ // Create modules for nodes
+ for (NodeModelMap::const_iterator i = m_patch->nodes().begin();
+ i != m_patch->nodes().end(); ++i) {
+ add_node((*i).second);
+ }
+
+ // Create pseudo modules for ports (ports on this canvas, not on our module)
+ for (PortModelList::const_iterator i = m_patch->ports().begin();
+ i != m_patch->ports().end(); ++i) {
+ cerr << "FIXME: PORT MODULE LEAK!" << endl;
+ new PatchPortModule(this, *i);
+ }
+
+ // Create connections
+ for (list<CountedPtr<ConnectionModel> >::const_iterator i = m_patch->connections().begin();
+ i != m_patch->connections().end(); ++i) {
+ connection(*i);
+ }
+}
+
+
+void
+PatchCanvas::add_node(CountedPtr<NodeModel> nm)
+{
+ cerr << "FIXME: MODULE LEAK!" << endl;
+
+ CountedPtr<PatchModel> pm = PtrCast<PatchModel>(nm);
+ if (pm)
+ new SubpatchModule(this, pm);
+ else
+ new NodeModule(this, nm);
+}
+
+
+void
+PatchCanvas::remove_node(CountedPtr<NodeModel> nm)
+{
+ LibFlowCanvas::Module* module = get_module(nm->path().name());
+ delete module;
+}
+
+
+void
+PatchCanvas::add_port(CountedPtr<PortModel> pm)
+{
+ cerr << "FIXME: PORT MODULE LEAK!" << endl;
+
+ new PatchPortModule(this, pm);
+}
+
+
+void
+PatchCanvas::remove_port(CountedPtr<PortModel> pm)
+{
+ cerr << "FIXME: PORT REMOVE" << endl;
+ //LibFlowCanvas::Module* module = get_module(pm->path().name());
+ //delete module;
+}
+
+
+void
+PatchCanvas::connection(CountedPtr<ConnectionModel> cm)
+{
+ // Deal with port "anonymous nodes" for this patch's own ports...
+ const Path& src_parent_path = cm->src_port_path().parent();
+ const Path& dst_parent_path = cm->dst_port_path().parent();
+
+ const string& src_parent_name =
+ (src_parent_path == m_patch->path()) ? "" : src_parent_path.name();
+ const string& dst_parent_name =
+ (dst_parent_path == m_patch->path()) ? "" : dst_parent_path.name();
+
+ LibFlowCanvas::Port* src_port = get_port(src_parent_name, cm->src_port_path().name());
+ LibFlowCanvas::Port* dst_port = get_port(dst_parent_name, cm->dst_port_path().name());
+ assert(src_port && dst_port);
+
+ add_connection(src_port, dst_port);
+}
+
+
+void
+PatchCanvas::disconnection(const Path& src_port_path, const Path& dst_port_path)
+{
+ const string& src_node_name = src_port_path.parent().name();
+ const string& src_port_name = src_port_path.name();
+ const string& dst_node_name = dst_port_path.parent().name();
+ const string& dst_port_name = dst_port_path.name();
+
+ LibFlowCanvas::Port* src_port = get_port(src_node_name, src_port_name);
+ LibFlowCanvas::Port* dst_port = get_port(dst_node_name, dst_port_name);
+
+ if (src_port && dst_port) {
+ remove_connection(src_port, dst_port);
+ }
+
+ //patch_model()->remove_connection(src_port_path, dst_port_path);
+
+ cerr << "FIXME: disconnection\n";
+ /*
+ // Enable control slider in destination node control window
+ PortController* p = (PortController)Store::instance().port(dst_port_path)->controller();
+ assert(p);
+
+ if (p->control_panel())
+ p->control_panel()->enable_port(p->path());
+ */
+}
+
+
+void
+PatchCanvas::connect(const LibFlowCanvas::Port* src_port, const LibFlowCanvas::Port* dst_port)
+{
+ assert(src_port != NULL);
+ assert(dst_port != NULL);
+
+ const Ingenuity::Port* const src = dynamic_cast<const Ingenuity::Port* const>(src_port);
+ const Ingenuity::Port* const dst = dynamic_cast<const Ingenuity::Port* const>(dst_port);
+ assert(src && dst);
+
+ // Midi binding/learn shortcut
+ if (src->model()->type() == PortModel::MIDI &&
+ dst->model()->type() == PortModel::CONTROL)
+ {
+ cerr << "FIXME: MIDI binding" << endl;
+#if 0
+ CountedPtr<PluginModel> pm(new PluginModel(PluginModel::Internal, "", "midi_control_in", ""));
+ CountedPtr<NodeModel> nm(new NodeModel(pm, m_patch->path().base()
+ + src->name() + "-" + dst->name(), false));
+ nm->set_metadata("module-x", Atom((float)
+ (dst->module()->property_x() - dst->module()->width() - 20)));
+ nm->set_metadata("module-y", Atom((float)
+ (dst->module()->property_y())));
+ App::instance().engine()->create_node_from_model(nm.get());
+ App::instance().engine()->connect(src->model()->path(), nm->path() + "/MIDI_In");
+ App::instance().engine()->connect(nm->path() + "/Out_(CR)", dst->model()->path());
+ App::instance().engine()->midi_learn(nm->path());
+
+ // Set control node range to port's user range
+
+ App::instance().engine()->set_port_value_queued(nm->path().base() + "Min",
+ dst->model()->get_metadata("user-min").get_float());
+ App::instance().engine()->set_port_value_queued(nm->path().base() + "Max",
+ dst->model()->get_metadata("user-max").get_float());
+#endif
+ } else {
+ App::instance().engine()->connect(src->model()->path(), dst->model()->path());
+ }
+}
+
+
+void
+PatchCanvas::disconnect(const LibFlowCanvas::Port* src_port, const LibFlowCanvas::Port* dst_port)
+{
+ assert(src_port != NULL);
+ assert(dst_port != NULL);
+
+ App::instance().engine()->disconnect(((Ingenuity::Port*)src_port)->model()->path(),
+ ((Ingenuity::Port*)dst_port)->model()->path());
+}
+
+
+bool
+PatchCanvas::canvas_event(GdkEvent* event)
+{
+ assert(event != NULL);
+
+ switch (event->type) {
+
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 3) {
+ m_last_click_x = (int)event->button.x;
+ m_last_click_y = (int)event->button.y;
+ show_menu(event);
+ }
+ break;
+
+ /*case GDK_KEY_PRESS:
+ if (event->key.keyval == GDK_Delete)
+ destroy_selected();
+ break;
+ */
+
+ default:
+ break;
+ }
+
+ return FlowCanvas::canvas_event(event);
+}
+
+
+void
+PatchCanvas::destroy_selected()
+{
+ for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
+ App::instance().engine()->destroy(((NodeModule*)(*m))->node()->path());
+}
+
+
+string
+PatchCanvas::generate_port_name(const string& base) {
+ string name = base;
+
+ char num_buf[5];
+ for (uint i=1; i < 9999; ++i) {
+ snprintf(num_buf, 5, "%u", i);
+ name = base + "_";
+ name += num_buf;
+ if (!m_patch->get_port(name))
+ break;
+ }
+
+ assert(Path::is_valid(string("/") + name));
+
+ return name;
+}
+
+
+void
+PatchCanvas::menu_add_port(const string& name, const string& type, bool is_output)
+{
+ const Path& path = m_patch->path().base() + generate_port_name(name);
+ App::instance().engine()->create_port_with_data(path, type, is_output, get_initial_data());
+}
+
+
+/** Try to guess a suitable location for a new module.
+ */
+void
+PatchCanvas::get_new_module_location(double& x, double& y)
+{
+ int scroll_x;
+ int scroll_y;
+ get_scroll_offsets(scroll_x, scroll_y);
+ x = scroll_x + 20;
+ y = scroll_y + 20;
+}
+
+
+MetadataMap
+PatchCanvas::get_initial_data()
+{
+ MetadataMap result;
+
+ result["module-x"] = Atom((float)m_last_click_x);
+ result["module-y"] = Atom((float)m_last_click_y);
+
+ return result;
+}
+
+void
+PatchCanvas::menu_load_plugin()
+{
+ App::instance().window_factory()->present_load_plugin(m_patch, get_initial_data());
+}
+
+
+void
+PatchCanvas::menu_load_patch()
+{
+ App::instance().window_factory()->present_load_subpatch(m_patch, get_initial_data());
+}
+
+
+void
+PatchCanvas::menu_new_patch()
+{
+ App::instance().window_factory()->present_new_subpatch(m_patch, get_initial_data());
+}
+
+
+} // namespace Ingenuity