1 /* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard.
3 * FlowCanvas is free software; you can redistribute it and/or modify it under the
4 * terms of the GNU General Public License as published by the Free Software
5 * Foundation; either version 2 of the License, or (at your option) any later
8 * FlowCanvas is distributed in the hope that it will be useful, but WITHOUT ANY
9 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
12 * You should have received a copy of the GNU General Public License along
13 * with this program; if not, write to the Free Software Foundation, Inc.,
14 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #include "flowcanvas/FlowCanvas.h"
23 #include "flowcanvas/Port.h"
24 #include "flowcanvas/Module.h"
26 using std::cerr; using std::cout; using std::endl;
28 namespace LibFlowCanvas {
31 FlowCanvas::FlowCanvas(double width, double height)
32 : m_selected_port(NULL),
37 m_drag_state(NOT_DRAGGING),
38 m_base_rect(*root(), 0, 0, width, height),
42 set_scroll_region(0.0, 0.0, width, height);
43 set_center_scroll_region(true);
45 m_base_rect.property_fill_color_rgba() = 0x000000FF;
47 //m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::scroll_drag_handler));
48 m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::select_drag_handler));
49 m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::connection_drag_handler));
50 m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::canvas_event));
52 set_dither(Gdk::RGB_DITHER_NORMAL); // NONE or NORMAL or MAX
54 // Dash style for selected modules and selection box
55 m_select_dash = new ArtVpathDash();
56 m_select_dash->n_dash = 2;
57 m_select_dash->dash = art_new(double, 2);
58 m_select_dash->dash[0] = 5;
59 m_select_dash->dash[1] = 5;
61 Glib::signal_timeout().connect(
62 sigc::mem_fun(this, &FlowCanvas::animate_selected), 150);
66 FlowCanvas::~FlowCanvas()
73 FlowCanvas::zoom(float pix_per_unit)
76 m_zoom = static_cast<int>(pix_per_unit*4) / 4.0;
80 set_pixels_per_unit(m_zoom);
82 for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
83 (*m).second->zoom(m_zoom);
88 FlowCanvas::clear_selection()
90 for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
91 (*m)->selected(false);
93 for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
94 (*c)->selected(false);
96 m_selected_modules.clear();
97 m_selected_connections.clear();
101 /** Add a module to the current selection, and automagically select any connections
102 * between selected modules */
104 FlowCanvas::select_module(Module* m)
106 assert(! m->selected());
108 m_selected_modules.push_back(m);
111 for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
113 if ( !c->selected()) {
114 if (c->source_port()->module() == m && c->dest_port()->module()->selected()) {
116 m_selected_connections.push_back(c);
117 } else if (c->dest_port()->module() == m && c->source_port()->module()->selected()) {
119 m_selected_connections.push_back(c);
129 FlowCanvas::unselect_module(Module* m)
131 assert(m->selected());
133 // Remove any connections that aren't selected anymore because this module isn't
135 for (ConnectionList::iterator i = m_selected_connections.begin(); i != m_selected_connections.end();) {
138 && ((c->source_port()->module() == m && c->dest_port()->module()->selected())
139 || c->dest_port()->module() == m && c->source_port()->module()->selected()))
142 i = m_selected_connections.erase(i);
149 for (list<Module*>::iterator i = m_selected_modules.begin(); i != m_selected_modules.end(); ++i) {
151 m_selected_modules.erase(i);
160 /** Removes all ports and connections and modules.
163 FlowCanvas::destroy()
165 for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
167 for (ConnectionList::iterator c = m_connections.begin(); c != m_connections.end(); ++c)
171 m_connections.clear();
173 m_selected_port = NULL;
174 m_connect_port = NULL;
179 FlowCanvas::selected_port(Port* p)
181 if (m_selected_port != NULL)
182 m_selected_port->rect()->property_fill_color_rgba() = m_selected_port->colour(); // "turn off" the old one
187 m_selected_port->rect()->property_fill_color() = "red";
192 FlowCanvas::find_module(const string& name)
194 ModuleMap::iterator m = m_modules.find(name);
196 if (m != m_modules.end())
203 /** Sets the passed module's location to a reasonable default.
206 FlowCanvas::set_default_placement(Module* m)
210 // Simple cascade. This will get more clever in the future.
211 double x = ((m_width / 2.0) + (m_modules.size() * 25));
212 double y = ((m_height / 2.0) + (m_modules.size() * 25));
219 FlowCanvas::add_module(Module* m)
222 std::pair<string, Module*> p(m->name(), m);
228 FlowCanvas::remove_module(const string& name)
230 ModuleMap::iterator m = m_modules.find(name);
232 if (m != m_modules.end()) {
236 cerr << "[FlowCanvas::remove_module] Unable to find module!" << endl;
242 FlowCanvas::find_port(const string& node_name, const string& port_name)
244 Module* module = NULL;
247 for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
248 module = (*i).second;
249 port = module->port(port_name);
250 if (module->name() == node_name && port != NULL)
254 cerr << "[FlowCanvas::find_port] Failed to find port " <<
255 node_name << ":" << port_name << endl;
262 FlowCanvas::rename_module(const string& old_name, const string& new_name)
264 Module* module = NULL;
266 for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
267 module = (*i).second;
268 assert(module != NULL);
269 if (module->name() == old_name) {
271 module->name(new_name);
277 cerr << "[FlowCanvas::rename_module] Failed to find module " <<
282 /** Add a connection.
285 FlowCanvas::add_connection(const string& node1_name, const string& port1_name,
286 const string& node2_name, const string& port2_name)
288 Port* port1 = find_port(node1_name, port1_name);
289 Port* port2 = find_port(node2_name, port2_name);
292 cerr << "Unable to find port " << node1_name << ":" << port1_name
293 << " to make connection." << endl;
294 } else if (port2 == NULL) {
295 cerr << "Unable to find port " << node2_name << ":" << port2_name
296 << " to make connection." << endl;
298 add_connection(port1, port2);
304 FlowCanvas::remove_connection(Port* port1, Port* port2)
306 assert(port1 != NULL);
307 assert(port2 != NULL);
309 Connection* c = get_connection(port1, port2);
311 cerr << "Couldn't find connection.\n";
314 remove_connection(c);
320 /** Remove a connection.
322 * Returns whether or not the connection was found (and removed).
325 FlowCanvas::remove_connection(const string& mod1_name, const string& port1_name, const string& mod2_name, const string& port2_name)
327 Connection* c = get_connection(find_port(mod1_name, port1_name),
328 find_port(mod2_name, port2_name));
330 cerr << "Couldn't find connection.\n";
333 remove_connection(c);
340 FlowCanvas::are_connected(const Port* port1, const Port* port2)
342 assert(port1 != NULL);
343 assert(port2 != NULL);
345 ConnectionList::const_iterator c;
346 const Connection* connection;
349 for (c = m_connections.begin(); c != m_connections.end(); ++c) {
351 if (connection->source_port() == port1 && connection->dest_port() == port2)
353 if (connection->source_port() == port2 && connection->dest_port() == port1)
362 FlowCanvas::get_connection(const Port* port1, const Port* port2)
364 assert(port1 != NULL);
365 assert(port2 != NULL);
367 for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
368 if ( (*i)->source_port() == port1 && (*i)->dest_port() == port2 )
370 else if ( (*i)->dest_port() == port1 && (*i)->source_port() == port2 )
379 FlowCanvas::add_connection(Port* port1, Port* port2)
381 assert(port1->is_input() != port2->is_input());
382 assert(port1->is_output() != port2->is_output());
383 Port* src_port = NULL;
384 Port* dst_port = NULL;
385 if (port1->is_output() && port2->is_input()) {
393 // Create (graphical) connection object
394 if (get_connection(port1, port2) == NULL) {
395 Connection* c = new Connection(this, src_port, dst_port);
396 port1->add_connection(c);
397 port2->add_connection(c);
398 m_connections.push_back(c);
404 FlowCanvas::remove_connection(Connection* connection)
406 assert(connection != NULL);
408 ConnectionList::iterator i = find(m_connections.begin(), m_connections.end(), connection);
412 m_connections.erase(i);
417 /** Called when two ports are 'toggled' (connected or disconnected)
420 FlowCanvas::ports_joined(Port* port1, Port* port2)
422 assert(port1 != NULL);
423 assert(port2 != NULL);
425 port1->hilite(false);
426 port2->hilite(false);
428 string src_mod_name, dst_mod_name, src_port_name, dst_port_name;
430 Port* src_port = NULL;
431 Port* dst_port = NULL;
433 if (port2->is_input() && ! port1->is_input()) {
436 } else if ( ! port2->is_input() && port1->is_input()) {
443 if (are_connected(src_port, dst_port))
444 disconnect(src_port, dst_port);
446 connect(src_port, dst_port);
450 /** Event handler for ports.
452 * These events can't be handled in the Port class because they have to do with
453 * connections etc. which deal with multiple ports (ie m_selected_port). Ports
454 * pass their events on to this function to get around this.
457 FlowCanvas::port_event(GdkEvent* event, Port* port)
459 static bool port_dragging = false;
462 switch (event->type) {
464 case GDK_BUTTON_PRESS:
465 if (event->button.button == 1) {
466 port_dragging = true;
467 } else if (event->button.button == 3) {
468 m_selected_port = port;
469 port->popup_menu(event->button.button, event->button.time);
475 case GDK_BUTTON_RELEASE:
477 if (m_connect_port == NULL) {
479 m_connect_port = port;
481 ports_joined(port, m_connect_port);
482 m_connect_port = NULL;
485 port_dragging = false;
491 case GDK_ENTER_NOTIFY:
492 if (port != m_selected_port)
496 case GDK_LEAVE_NOTIFY:
498 m_drag_state = CONNECTION;
499 m_connect_port = port;
501 m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
502 Gdk::Cursor(Gdk::CROSSHAIR), event->button.time);
504 port_dragging = false;
506 if (port != m_selected_port)
521 FlowCanvas::canvas_event(GdkEvent* event)
523 if (m_connection_dragging) {
524 return connection_drag_handler(event);
525 } else if (m_scroll_dragging) {
526 return scroll_drag_handler(event);
527 } else if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
528 get_scroll_offsets(m_scroll_offset_x, m_scroll_offset_y);
530 //window_to_world(event->button.x, event->button.y, x, y);
531 //w2c(x, y, m_scroll_origin_x, m_scroll_origin_y);
532 m_scroll_origin_x = event->button.x;
533 m_scroll_origin_y = event->button.y;
534 //root()->w2i(m_scroll_origin_x, m_scroll_origin_y);
535 //window_to_world(event->button.x, event->button.y, x, y);
536 //w2c(x, y, m_scroll_origin_x, m_scroll_origin_y);
537 m_scroll_dragging = true;
544 /* I can not get this to work for the life of me.
545 * Man I hate gnomecanvas.
547 FlowCanvas::scroll_drag_handler(GdkEvent* event)
552 static int original_scroll_x = 0;
553 static int original_scroll_y = 0;
554 static double origin_x = 0;
555 static double origin_y = 0;
556 static double x_offset = 0;
557 static double y_offset = 0;
558 static double scroll_offset_x = 0;
559 static double scroll_offset_y = 0;
560 static double last_x = 0;
561 static double last_y = 0;
563 bool first_motion = true;
565 if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
566 m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK,
567 Gdk::Cursor(Gdk::FLEUR), event->button.time);
568 get_scroll_offsets(original_scroll_x, original_scroll_y);
569 scroll_offset_x = original_scroll_x;
570 scroll_offset_y = original_scroll_y;
571 origin_x = event->button.x;
572 origin_y = event->button.y;
576 m_scroll_dragging = true;
578 } else if (event->type == GDK_MOTION_NOTIFY && m_scroll_dragging) {
579 // These are world-relative coordinates
580 double x = event->motion.x_root;
581 double y = event->motion.y_root;
584 //world_to_window(x, y, x, y);
585 //window_to_world(event->button.x, event->button.y, x, y);
588 x_offset += last_x - x;//x + original_scroll_x;
589 y_offset += last_y - y;// + original_scroll_y;
591 //cerr << "Coord: (" << x << "," << y << ")\n";
592 //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n";
596 w2c(lrint(x_offset), lrint(y_offset),
598 scroll_offset_x += temp_x;
599 scroll_offset_y += temp_y;
600 scroll_to(scroll_offset_x,
604 } else if (event->type == GDK_BUTTON_RELEASE && m_scroll_dragging) {
605 m_base_rect.ungrab(event->button.time);
606 m_scroll_dragging = false;
618 FlowCanvas::select_drag_handler(GdkEvent* event)
620 Module* module = NULL;
622 if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) {
623 assert(m_select_rect == NULL);
624 m_drag_state = SELECT;
625 if ( !(event->button.state & GDK_CONTROL_MASK))
627 m_select_rect = new Gnome::Canvas::Rect(*root(),
628 event->button.x, event->button.y, event->button.x, event->button.y);
629 m_select_rect->property_fill_color_rgba() = 0x273344FF;
630 m_select_rect->property_outline_color_rgba() = 0xEEEEFFFF;
631 m_select_rect->lower_to_bottom();
632 m_base_rect.lower_to_bottom();
634 } else if (event->type == GDK_MOTION_NOTIFY && m_drag_state == SELECT) {
635 assert(m_select_rect != NULL);
636 m_select_rect->property_x2() = event->button.x;
637 m_select_rect->property_y2() = event->button.y;
639 } else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == SELECT) {
640 // Select all modules within rect
641 for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
642 module = (*i).second;
643 if (module->is_within(m_select_rect)) {
644 if (module->selected())
645 unselect_module(module);
647 select_module(module);
651 delete m_select_rect;
652 m_select_rect = NULL;
653 m_drag_state = NOT_DRAGGING;
660 /** Updates m_select_dash for rotation effect, and updates any
661 * selected item's borders (and the selection rectangle).
664 FlowCanvas::animate_selected()
671 m_select_dash->offset = i;
673 if (m_select_rect != NULL)
674 m_select_rect->property_dash() = m_select_dash;
676 for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
677 (*m)->rect()->property_dash() = m_select_dash;
679 for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
680 (*c)->property_dash() = m_select_dash;
687 FlowCanvas::connection_drag_handler(GdkEvent* event)
691 // These are invisible, just used for making connections (while dragging)
692 static Module* drag_module = NULL;
693 static Port* drag_port = NULL;
695 static Connection* drag_connection = NULL;
696 static Port* snapped_port = NULL;
698 static bool snapped = false;
700 if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
701 m_drag_state = SCROLL;
702 } else if (event->type == GDK_MOTION_NOTIFY && m_drag_state == CONNECTION) {
703 double x = event->button.x, y = event->button.y;
706 if (drag_connection == NULL) { // Havn't created the connection yet
707 assert(drag_port == NULL);
708 assert(m_connect_port != NULL);
710 drag_module = new Module(this, "");
711 bool drag_port_is_input = true;
712 if (m_connect_port->is_input())
713 drag_port_is_input = false;
715 drag_port = new Port(drag_module, "", drag_port_is_input, m_connect_port->colour());
716 drag_module->add_port(drag_port);
721 drag_module->move_to(x, y);
723 drag_port->property_x() = 0;
724 drag_port->property_y() = 0;
725 drag_port->rect()->property_x2() = 1;
726 drag_port->rect()->property_y2() = 1;
727 if (drag_port_is_input)
728 drag_connection = new Connection(this, m_connect_port, drag_port);
730 drag_connection = new Connection(this, drag_port, m_connect_port);
732 drag_connection->update_location();
733 //drag_connection->property_line_style() = Gdk::LINE_DOUBLE_DASH;
734 //drag_connection->property_last_arrowhead() = true;
738 if (drag_connection != NULL) drag_connection->hide();
739 Port* p = get_port_at(x, y);
740 if (drag_connection != NULL) drag_connection->show();
742 if (p != m_selected_port) {
743 if (snapped_port != NULL)
744 snapped_port->hilite(false);
748 drag_module->property_x() = p->module()->property_x().get_value();
749 drag_module->rect()->property_x2() = p->module()->rect()->property_x2().get_value();
750 drag_module->property_y() = p->module()->property_y().get_value();
751 drag_module->rect()->property_y2() = p->module()->rect()->property_y2().get_value();
752 drag_port->property_x() = p->property_x().get_value();
753 drag_port->property_y() = p->property_y().get_value();
754 } else { // off the port now, unsnap
755 if (snapped_port != NULL)
756 snapped_port->hilite(false);
759 drag_module->property_x() = x;
760 drag_module->property_y() = y;
761 drag_port->property_x() = 0;
762 drag_port->property_y() = 0;
763 drag_port->rect()->property_x2() = 1;
764 drag_port->rect()->property_y2() = 1;
766 drag_connection->update_location();
767 } else { // not snapped to a port
768 assert(drag_module != NULL);
769 assert(drag_port != NULL);
770 assert(m_connect_port != NULL);
772 // "Snap" to port, if we're on a port and it's the right direction
773 if (drag_connection != NULL) drag_connection->hide();
774 Port* p = get_port_at(x, y);
775 if (drag_connection != NULL) drag_connection->show();
776 if (p != NULL && p->is_input() != m_connect_port->is_input()) {
780 // Make drag module and port exactly the same size/loc as the snapped
781 drag_module->move_to(p->module()->property_x().get_value(), p->module()->property_y().get_value());
782 drag_module->width(p->module()->width());
783 drag_module->height(p->module()->height());
784 drag_port->property_x() = p->property_x().get_value();
785 drag_port->property_y() = p->property_y().get_value();
786 // Make the drag port as wide as the snapped port so the connection coords are the same
787 drag_port->rect()->property_x2() = p->rect()->property_x2().get_value();
788 drag_port->rect()->property_y2() = p->rect()->property_y2().get_value();
790 drag_module->property_x() = x;
791 drag_module->property_y() = y;
793 drag_connection->update_location();
795 } else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == CONNECTION) {
796 m_base_rect.ungrab(event->button.time);
798 double x = event->button.x;
799 double y = event->button.y;
800 m_base_rect.i2w(x, y);
802 if (drag_connection != NULL) drag_connection->hide();
803 Port* p = get_port_at(x, y);
804 if (drag_connection != NULL) drag_connection->show();
807 if (p == m_connect_port) { // drag ended on same port it started on
808 if (m_selected_port == NULL) { // no active port, just activate (hilite) it
809 selected_port(m_connect_port);
810 } else { // there is already an active port, connect it with this one
811 if (m_selected_port != m_connect_port)
812 ports_joined(m_selected_port, m_connect_port);
814 m_connect_port = NULL;
817 } else { // drag ended on different port
819 ports_joined(m_connect_port, p);
821 m_connect_port = NULL;
826 // Clean up dragging stuff
827 if (m_connect_port != NULL)
828 m_connect_port->hilite(false);
830 m_drag_state = NOT_DRAGGING;
831 delete drag_connection;
832 drag_connection = NULL;
835 delete drag_module; // deletes drag_port
847 FlowCanvas::get_port_at(double x, double y)
849 Gnome::Canvas::Item* item = get_item_at(x, y);
850 if (item == NULL) return NULL;
853 // Loop through every port and see if the item at these coordinates is that port
854 // yes, this is disgusting ;)
855 for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
856 for (PortList::iterator j = (*i).second->ports().begin(); j != (*i).second->ports().end(); ++j) {
859 if ((Gnome::Canvas::Item*)p == item
860 || (Gnome::Canvas::Item*)(p->rect()) == item
861 || (Gnome::Canvas::Item*)(p->label()) == item) {
871 FlowCanvas::port_menu_disconnect_all()
873 Connection* c = NULL;
874 list<Connection*> temp_list = m_selected_port->connections();
875 for (list<Connection*>::iterator i = temp_list.begin(); i != temp_list.end(); ++i) {
877 disconnect(c->source_port(), c->dest_port());
884 } // namespace LibFlowCanvas