/* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard. * * FlowCanvas 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. * * FlowCanvas 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "FlowCanvas.h" #include #include #include #include #include "Port.h" #include "Module.h" using std::cerr; using std::cout; using std::endl; namespace LibFlowCanvas { FlowCanvas::FlowCanvas(double width, double height) : m_selected_port(NULL), m_connect_port(NULL), m_zoom(1.0), m_width(width), m_height(height), m_drag_state(NOT_DRAGGING), m_base_rect(*root(), 0, 0, width, height), m_select_rect(NULL), m_select_dash(NULL) { set_scroll_region(0.0, 0.0, width, height); set_center_scroll_region(true); m_base_rect.property_fill_color_rgba() = 0x000000FF; //m_base_rect.show(); //m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::scroll_drag_handler)); m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::canvas_event)); m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::select_drag_handler)); m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::connection_drag_handler)); set_dither(Gdk::RGB_DITHER_NORMAL); // NONE or NORMAL or MAX // Dash style for selected modules and selection box m_select_dash = new ArtVpathDash(); m_select_dash->n_dash = 2; m_select_dash->dash = art_new(double, 2); m_select_dash->dash[0] = 5; m_select_dash->dash[1] = 5; Glib::signal_timeout().connect( sigc::mem_fun(this, &FlowCanvas::animate_selected), 300); } FlowCanvas::~FlowCanvas() { destroy(); } void FlowCanvas::on_show() { Gnome::Canvas::Canvas::on_show(); get_window()->set_cursor(Gdk::Cursor(Gdk::HAND2)); } void FlowCanvas::zoom(float pix_per_unit) { // Round to .25 m_zoom = static_cast(pix_per_unit*4) / 4.0; if (m_zoom < 0.25) m_zoom = 0.25; set_pixels_per_unit(m_zoom); for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m) (*m).second->zoom(m_zoom); } void FlowCanvas::clear_selection() { for (list::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m) (*m)->selected(false); for (list::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c) (*c)->selected(false); m_selected_modules.clear(); m_selected_connections.clear(); } /** Add a module to the current selection, and automagically select any connections * between selected modules */ void FlowCanvas::select_module(Module* m) { assert(! m->selected()); m_selected_modules.push_back(m); Connection* c; for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { c = (*i); if ( !c->selected()) { if (c->source_port()->module() == m && c->dest_port()->module()->selected()) { c->selected(true); m_selected_connections.push_back(c); } else if (c->dest_port()->module() == m && c->source_port()->module()->selected()) { c->selected(true); m_selected_connections.push_back(c); } } } m->selected(true); } void FlowCanvas::unselect_module(Module* m) { assert(m->selected()); // Remove any connections that aren't selected anymore because this module isn't Connection* c; for (ConnectionList::iterator i = m_selected_connections.begin(); i != m_selected_connections.end();) { c = (*i); if (c->selected() && ((c->source_port()->module() == m && c->dest_port()->module()->selected()) || c->dest_port()->module() == m && c->source_port()->module()->selected())) { c->selected(false); i = m_selected_connections.erase(i); } else { ++i; } } // Remove the module for (list::iterator i = m_selected_modules.begin(); i != m_selected_modules.end(); ++i) { if ((*i) == m) { m_selected_modules.erase(i); break; } } m->selected(false); } /** Removes all ports and connections and modules. */ void FlowCanvas::destroy() { clear_selection(); for (ConnectionList::iterator c = m_connections.begin(); c != m_connections.end(); ++c) delete (*c); for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m) delete (*m).second; m_modules.clear(); m_connections.clear(); m_selected_port = NULL; m_connect_port = NULL; } void FlowCanvas::selected_port(Port* p) { if (m_selected_port != NULL) m_selected_port->rect()->property_fill_color_rgba() = m_selected_port->colour(); // "turn off" the old one m_selected_port = p; if (p != NULL) m_selected_port->rect()->property_fill_color() = "red"; } Module* FlowCanvas::find_module(const string& name) { ModuleMap::iterator m = m_modules.find(name); if (m != m_modules.end()) return (*m).second; else return NULL; } /** Sets the passed module's location to a reasonable default. */ void FlowCanvas::set_default_placement(Module* m) { assert(m != NULL); // Simple cascade. This will get more clever in the future. double x = ((m_width / 2.0) + (m_modules.size() * 25)); double y = ((m_height / 2.0) + (m_modules.size() * 25)); m->move_to(x, y); } void FlowCanvas::add_module(Module* m) { assert(m != NULL); std::pair p(m->name(), m); m_modules.insert(p); } void FlowCanvas::remove_module(const string& name) { ModuleMap::iterator m = m_modules.find(name); if (m != m_modules.end()) { delete (*m).second; m_modules.erase(m); } else { cerr << "[FlowCanvas::remove_module] Unable to find module " << name << endl; } } Port* FlowCanvas::find_port(const string& node_name, const string& port_name) { Module* module = NULL; Port* port = NULL; for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) { module = (*i).second; port = module->port(port_name); if (module->name() == node_name && port != NULL) return port; } cerr << "[FlowCanvas::find_port] Failed to find port " << node_name << ":" << port_name << endl; return NULL; } void FlowCanvas::rename_module(const string& old_name, const string& new_name) { Module* module = NULL; for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) { module = (*i).second; assert(module != NULL); if (module->name() == old_name) { m_modules.erase(i); module->name(new_name); add_module(module); return; } } cerr << "[FlowCanvas::rename_module] Failed to find module " << old_name << endl; } /** Add a connection. */ void FlowCanvas::add_connection(const string& node1_name, const string& port1_name, const string& node2_name, const string& port2_name) { Port* port1 = find_port(node1_name, port1_name); Port* port2 = find_port(node2_name, port2_name); if (port1 == NULL) { cerr << "Unable to find port " << node1_name << "/" << port1_name << " to make connection." << endl; } else if (port2 == NULL) { cerr << "Unable to find port " << node2_name << "/" << port2_name << " to make connection." << endl; } else { add_connection(port1, port2); } } bool FlowCanvas::remove_connection(Port* port1, Port* port2) { assert(port1 != NULL); assert(port2 != NULL); Connection* c = get_connection(port1, port2); if (c == NULL) { cerr << "Couldn't find connection.\n"; return false; } else { remove_connection(c); return true; } } /** Remove a connection. * * Returns whether or not the connection was found (and removed). */ bool FlowCanvas::remove_connection(const string& mod1_name, const string& port1_name, const string& mod2_name, const string& port2_name) { Connection* c = get_connection(find_port(mod1_name, port1_name), find_port(mod2_name, port2_name)); if (c == NULL) { cerr << "Couldn't find connection.\n"; return false; } else { remove_connection(c); return true; } } bool FlowCanvas::are_connected(const Port* port1, const Port* port2) { assert(port1 != NULL); assert(port2 != NULL); ConnectionList::const_iterator c; const Connection* connection; for (c = m_connections.begin(); c != m_connections.end(); ++c) { connection = *c; if (connection->source_port() == port1 && connection->dest_port() == port2) return true; if (connection->source_port() == port2 && connection->dest_port() == port1) return true; } return false; } Connection* FlowCanvas::get_connection(const Port* port1, const Port* port2) { assert(port1 != NULL); assert(port2 != NULL); for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { if ( (*i)->source_port() == port1 && (*i)->dest_port() == port2 ) return *i; else if ( (*i)->dest_port() == port1 && (*i)->source_port() == port2 ) return *i; } return NULL; } void FlowCanvas::add_connection(Port* port1, Port* port2) { assert(port1->is_input() != port2->is_input()); assert(port1->is_output() != port2->is_output()); Port* src_port = NULL; Port* dst_port = NULL; if (port1->is_output() && port2->is_input()) { src_port = port1; dst_port = port2; } else { src_port = port2; dst_port = port1; } // Create (graphical) connection object if (get_connection(port1, port2) == NULL) { Connection* c = new Connection(this, src_port, dst_port); port1->add_connection(c); port2->add_connection(c); m_connections.push_back(c); } } void FlowCanvas::remove_connection(Connection* connection) { assert(connection != NULL); ConnectionList::iterator i = find(m_connections.begin(), m_connections.end(), connection); Connection* c = *i; c->disconnect(); m_connections.erase(i); delete c; } /** Called when two ports are 'toggled' (connected or disconnected) */ void FlowCanvas::ports_joined(Port* port1, Port* port2) { assert(port1 != NULL); assert(port2 != NULL); port1->hilite(false); port2->hilite(false); string src_mod_name, dst_mod_name, src_port_name, dst_port_name; Port* src_port = NULL; Port* dst_port = NULL; if (port2->is_input() && ! port1->is_input()) { src_port = port1; dst_port = port2; } else if ( ! port2->is_input() && port1->is_input()) { src_port = port2; dst_port = port1; } else { return; } if (are_connected(src_port, dst_port)) disconnect(src_port, dst_port); else connect(src_port, dst_port); } /** Event handler for ports. * * These events can't be handled in the Port class because they have to do with * connections etc. which deal with multiple ports (ie m_selected_port). Ports * pass their events on to this function to get around this. */ bool FlowCanvas::port_event(GdkEvent* event, Port* port) { static bool port_dragging = false; bool handled = true; switch (event->type) { case GDK_BUTTON_PRESS: if (event->button.button == 1) { port_dragging = true; } else if (event->button.button == 3) { m_selected_port = port; port->popup_menu(event->button.button, event->button.time); } else { handled = false; } break; case GDK_BUTTON_RELEASE: if (port_dragging) { if (m_connect_port == NULL) { selected_port(port); m_connect_port = port; } else { ports_joined(port, m_connect_port); m_connect_port = NULL; selected_port(NULL); } port_dragging = false; } else { handled = false; } m_base_rect.ungrab(event->button.time); break; case GDK_ENTER_NOTIFY: if (port != m_selected_port) port->hilite(true); break; case GDK_LEAVE_NOTIFY: if (port_dragging) { m_drag_state = CONNECTION; m_connect_port = port; m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, Gdk::Cursor(Gdk::CROSSHAIR), event->button.time); port_dragging = false; } else { if (port != m_selected_port) port->hilite(false); } break; default: handled = false; } return handled; } bool FlowCanvas::canvas_event(GdkEvent* event) { // Hack around bug I can't nail down :) if (event->type == GDK_BUTTON_RELEASE) { m_base_rect.ungrab(event->button.time); } return false; } /* I can not get this to work for the life of me. * Man I hate gnomecanvas. bool FlowCanvas::scroll_drag_handler(GdkEvent* event) { bool handled = true; static int original_scroll_x = 0; static int original_scroll_y = 0; static double origin_x = 0; static double origin_y = 0; static double x_offset = 0; static double y_offset = 0; static double scroll_offset_x = 0; static double scroll_offset_y = 0; static double last_x = 0; static double last_y = 0; bool first_motion = true; if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) { m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK, Gdk::Cursor(Gdk::FLEUR), event->button.time); get_scroll_offsets(original_scroll_x, original_scroll_y); scroll_offset_x = original_scroll_x; scroll_offset_y = original_scroll_y; origin_x = event->button.x; origin_y = event->button.y; last_x = origin_x; last_y = origin_y; first_motion = true; m_scroll_dragging = true; } else if (event->type == GDK_MOTION_NOTIFY && m_scroll_dragging) { // These are world-relative coordinates double x = event->motion.x_root; double y = event->motion.y_root; //c2w(x, y, x, y); //world_to_window(x, y, x, y); //window_to_world(event->button.x, event->button.y, x, y); //w2c(x, y, x, y); x_offset += last_x - x;//x + original_scroll_x; y_offset += last_y - y;// + original_scroll_y; //cerr << "Coord: (" << x << "," << y << ")\n"; //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n"; int temp_x; int temp_y; w2c(lrint(x_offset), lrint(y_offset), temp_x, temp_y); scroll_offset_x += temp_x; scroll_offset_y += temp_y; scroll_to(scroll_offset_x, scroll_offset_y); last_x = x; last_y = y; } else if (event->type == GDK_BUTTON_RELEASE && m_scroll_dragging) { m_base_rect.ungrab(event->button.time); m_scroll_dragging = false; } else { handled = false; } return handled; return false; } */ bool FlowCanvas::select_drag_handler(GdkEvent* event) { Module* module = NULL; if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) { assert(m_select_rect == NULL); m_drag_state = SELECT; if ( !(event->button.state & GDK_CONTROL_MASK)) clear_selection(); m_select_rect = new Gnome::Canvas::Rect(*root(), event->button.x, event->button.y, event->button.x, event->button.y); m_select_rect->property_fill_color_rgba() = 0x273344FF; m_select_rect->property_outline_color_rgba() = 0xEEEEFFFF; m_select_rect->lower_to_bottom(); m_base_rect.lower_to_bottom(); return true; } else if (event->type == GDK_MOTION_NOTIFY && m_drag_state == SELECT) { assert(m_select_rect != NULL); double x = event->button.x, y = event->button.y; if (event->motion.is_hint) { gint t_x; gint t_y; GdkModifierType state; gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state); x = t_x; y = t_y; } m_select_rect->property_x2() = x; m_select_rect->property_y2() = y; return true; } else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == SELECT) { // Select all modules within rect for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) { module = (*i).second; if (module->is_within(m_select_rect)) { if (module->selected()) unselect_module(module); else select_module(module); } } m_base_rect.ungrab(event->button.time); delete m_select_rect; m_select_rect = NULL; m_drag_state = NOT_DRAGGING; return true; } return false; } /** Updates m_select_dash for rotation effect, and updates any * selected item's borders (and the selection rectangle). */ bool FlowCanvas::animate_selected() { static int i = 0; i = (i+1) % 10; m_select_dash->offset = i; for (list::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m) (*m)->rect()->property_dash() = m_select_dash; for (list::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c) (*c)->property_dash() = m_select_dash; return true; } bool FlowCanvas::connection_drag_handler(GdkEvent* event) { bool handled = true; // These are invisible, just used for making connections (while dragging) static Module* drag_module = NULL; static Port* drag_port = NULL; static Connection* drag_connection = NULL; static Port* snapped_port = NULL; static bool snapped = false; /*if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) { m_drag_state = SCROLL; } else */if (event->type == GDK_MOTION_NOTIFY && m_drag_state == CONNECTION) { double x = event->button.x, y = event->button.y; if (event->motion.is_hint) { gint t_x; gint t_y; GdkModifierType state; gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state); x = t_x; y = t_y; } root()->w2i(x, y); if (drag_connection == NULL) { // Havn't created the connection yet assert(drag_port == NULL); assert(m_connect_port != NULL); drag_module = new Module(this, ""); bool drag_port_is_input = true; if (m_connect_port->is_input()) drag_port_is_input = false; drag_port = new Port(drag_module, "", drag_port_is_input, m_connect_port->colour()); drag_module->add_port(drag_port); //drag_port->hide(); drag_module->hide(); drag_module->move_to(x, y); drag_port->property_x() = 0; drag_port->property_y() = 0; drag_port->rect()->property_x2() = 1; drag_port->rect()->property_y2() = 1; if (drag_port_is_input) drag_connection = new Connection(this, m_connect_port, drag_port); else drag_connection = new Connection(this, drag_port, m_connect_port); drag_connection->update_location(); } if (snapped) { if (drag_connection != NULL) drag_connection->hide(); Port* p = get_port_at(x, y); if (drag_connection != NULL) drag_connection->show(); if (p != NULL) { if (p != m_selected_port) { if (snapped_port != NULL) snapped_port->hilite(false); p->hilite(true); snapped_port = p; } drag_module->property_x() = p->module()->property_x().get_value(); drag_module->rect()->property_x2() = p->module()->rect()->property_x2().get_value(); drag_module->property_y() = p->module()->property_y().get_value(); drag_module->rect()->property_y2() = p->module()->rect()->property_y2().get_value(); drag_port->property_x() = p->property_x().get_value(); drag_port->property_y() = p->property_y().get_value(); } else { // off the port now, unsnap if (snapped_port != NULL) snapped_port->hilite(false); snapped_port = NULL; snapped = false; drag_module->property_x() = x; drag_module->property_y() = y; drag_port->property_x() = 0; drag_port->property_y() = 0; drag_port->rect()->property_x2() = 1; drag_port->rect()->property_y2() = 1; } drag_connection->update_location(); } else { // not snapped to a port assert(drag_module != NULL); assert(drag_port != NULL); assert(m_connect_port != NULL); // "Snap" to port, if we're on a port and it's the right direction if (drag_connection != NULL) drag_connection->hide(); Port* p = get_port_at(x, y); if (drag_connection != NULL) drag_connection->show(); if (p != NULL && p->is_input() != m_connect_port->is_input()) { p->hilite(true); snapped_port = p; snapped = true; // Make drag module and port exactly the same size/loc as the snapped drag_module->move_to(p->module()->property_x().get_value(), p->module()->property_y().get_value()); drag_module->width(p->module()->width()); drag_module->height(p->module()->height()); drag_port->property_x() = p->property_x().get_value(); drag_port->property_y() = p->property_y().get_value(); // Make the drag port as wide as the snapped port so the connection coords are the same drag_port->rect()->property_x2() = p->rect()->property_x2().get_value(); drag_port->rect()->property_y2() = p->rect()->property_y2().get_value(); } else { drag_module->property_x() = x; drag_module->property_y() = y; } drag_connection->update_location(); } } else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == CONNECTION) { m_base_rect.ungrab(event->button.time); double x = event->button.x; double y = event->button.y; m_base_rect.i2w(x, y); if (drag_connection != NULL) drag_connection->hide(); Port* p = get_port_at(x, y); if (drag_connection != NULL) drag_connection->show(); if (p != NULL) { if (p == m_connect_port) { // drag ended on same port it started on if (m_selected_port == NULL) { // no active port, just activate (hilite) it selected_port(m_connect_port); } else { // there is already an active port, connect it with this one if (m_selected_port != m_connect_port) ports_joined(m_selected_port, m_connect_port); selected_port(NULL); m_connect_port = NULL; snapped_port = NULL; } } else { // drag ended on different port //p->hilite(false); ports_joined(m_connect_port, p); selected_port(NULL); m_connect_port = NULL; snapped_port = NULL; } } // Clean up dragging stuff m_base_rect.ungrab(event->button.time); if (m_connect_port != NULL) m_connect_port->hilite(false); m_drag_state = NOT_DRAGGING; delete drag_connection; drag_connection = NULL; //delete drag_port; drag_port = NULL; delete drag_module; // deletes drag_port drag_module = NULL; snapped_port = NULL; selected_port(NULL); m_connect_port = NULL; } else { handled = false; } return handled; } Port* FlowCanvas::get_port_at(double x, double y) { Gnome::Canvas::Item* item = get_item_at(x, y); if (item == NULL) return NULL; Port* p = NULL; // Loop through every port and see if the item at these coordinates is that port // yes, this is disgusting ;) for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) { for (PortList::iterator j = (*i).second->ports().begin(); j != (*i).second->ports().end(); ++j) { p = (*j); if ((Gnome::Canvas::Item*)p == item || (Gnome::Canvas::Item*)(p->rect()) == item || (Gnome::Canvas::Item*)(p->label()) == item) { return p; } } } return NULL; } } // namespace LibFlowCanvas