LV2 support.
[ardour.git] / libs / flowcanvas / src / FlowCanvas.cpp
1 /* This file is part of FlowCanvas.  Copyright (C) 2005 Dave Robillard.
2  * 
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
6  * version.
7  * 
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.
11  * 
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
15  */
16
17
18 #include "flowcanvas/FlowCanvas.h"
19 #include <cassert>
20 #include <map>
21 #include <iostream>
22 #include <cmath>
23 #include "flowcanvas/Port.h"
24 #include "flowcanvas/Module.h"
25
26 using std::cerr; using std::cout; using std::endl;
27
28 namespace LibFlowCanvas {
29         
30
31 FlowCanvas::FlowCanvas(double width, double height)
32 : m_selected_port(NULL),
33   m_connect_port(NULL),
34   m_zoom(1.0),
35   m_width(width),
36   m_height(height),
37   m_drag_state(NOT_DRAGGING),
38   m_base_rect(*root(), 0, 0, width, height),
39   m_select_rect(NULL),
40   m_select_dash(NULL)
41 {
42         set_scroll_region(0.0, 0.0, width, height);
43         set_center_scroll_region(true);
44         
45         m_base_rect.property_fill_color_rgba() = 0x000000FF;
46         m_base_rect.show();
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));
51         
52         set_dither(Gdk::RGB_DITHER_NORMAL); // NONE or NORMAL or MAX
53         
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;
60                 
61         Glib::signal_timeout().connect(
62                 sigc::mem_fun(this, &FlowCanvas::animate_selected), 150);
63 }
64
65
66 FlowCanvas::~FlowCanvas()
67 {
68         destroy();
69 }
70
71
72 void
73 FlowCanvas::zoom(float pix_per_unit)
74 {
75         // Round to .25
76         m_zoom = static_cast<int>(pix_per_unit*4) / 4.0;
77         if (m_zoom < 0.25)
78                 m_zoom = 0.25;
79         
80         set_pixels_per_unit(m_zoom);
81
82         for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
83                 (*m).second->zoom(m_zoom);
84 }
85
86
87 void
88 FlowCanvas::clear_selection()
89 {
90         for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
91                 (*m)->selected(false);
92         
93         for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
94                 (*c)->selected(false);
95
96         m_selected_modules.clear();
97         m_selected_connections.clear();
98 }
99
100         
101 /** Add a module to the current selection, and automagically select any connections
102  * between selected modules */
103 void
104 FlowCanvas::select_module(Module* m)
105 {
106         assert(! m->selected());
107         
108         m_selected_modules.push_back(m);
109
110         Connection* c;
111         for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
112                 c = (*i);
113                 if ( !c->selected()) {
114                         if (c->source_port()->module() == m && c->dest_port()->module()->selected()) {
115                                 c->selected(true);
116                                 m_selected_connections.push_back(c);
117                         } else if (c->dest_port()->module() == m && c->source_port()->module()->selected()) {
118                                 c->selected(true);
119                                 m_selected_connections.push_back(c);
120                         } 
121                 }
122         }
123                                 
124         m->selected(true);
125 }
126
127
128 void
129 FlowCanvas::unselect_module(Module* m)
130 {
131         assert(m->selected());
132         
133         // Remove any connections that aren't selected anymore because this module isn't
134         Connection* c;
135         for (ConnectionList::iterator i = m_selected_connections.begin(); i != m_selected_connections.end();) {
136                 c = (*i);
137                 if (c->selected()
138                         && ((c->source_port()->module() == m && c->dest_port()->module()->selected())
139                                 || c->dest_port()->module() == m && c->source_port()->module()->selected()))
140                         {
141                                 c->selected(false);
142                                 i = m_selected_connections.erase(i);
143                 } else {
144                         ++i;
145                 }
146         }
147
148         // Remove the module
149         for (list<Module*>::iterator i = m_selected_modules.begin(); i != m_selected_modules.end(); ++i) {
150                 if ((*i) == m) {
151                         m_selected_modules.erase(i);
152                         break;
153                 }
154         }
155         
156         m->selected(false);
157 }
158
159
160 /** Removes all ports and connections and modules.
161  */
162 void
163 FlowCanvas::destroy()
164 {
165         for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
166                 delete (*m).second;
167         for (ConnectionList::iterator c = m_connections.begin(); c != m_connections.end(); ++c)
168                 delete (*c);
169
170         m_modules.clear();
171         m_connections.clear();
172
173         m_selected_port = NULL;
174         m_connect_port = NULL;
175 }
176
177
178 void
179 FlowCanvas::selected_port(Port* p)
180 {
181         if (m_selected_port != NULL)
182                 m_selected_port->rect()->property_fill_color_rgba() = m_selected_port->colour(); // "turn off" the old one
183         
184         m_selected_port = p;
185         
186         if (p != NULL)
187                 m_selected_port->rect()->property_fill_color() = "red";
188 }
189
190
191 Module*
192 FlowCanvas::find_module(const string& name)
193 {
194         ModuleMap::iterator m = m_modules.find(name);
195
196         if (m != m_modules.end())
197                 return (*m).second;
198         else
199                 return NULL;
200 }
201
202
203 /** Sets the passed module's location to a reasonable default.
204  */
205 void
206 FlowCanvas::set_default_placement(Module* m)
207 {
208         assert(m != NULL);
209         
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));
213
214         m->move_to(x, y);
215 }
216
217
218 void
219 FlowCanvas::add_module(Module* m)
220 {
221         assert(m != NULL);
222         std::pair<string, Module*> p(m->name(), m);
223         m_modules.insert(p);
224 }
225
226
227 void
228 FlowCanvas::remove_module(const string& name)
229 {
230         ModuleMap::iterator m = m_modules.find(name);
231
232         if (m != m_modules.end()) {
233                 delete (*m).second;
234                 m_modules.erase(m);
235         } else {
236                 cerr << "[FlowCanvas::remove_module] Unable to find module!" << endl;
237         }
238 }
239
240
241 Port*
242 FlowCanvas::find_port(const string& node_name, const string& port_name)
243 {
244         Module* module = NULL;
245         Port*   port   = NULL;
246         
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)
251                         return port;
252         }
253         
254         cerr << "[FlowCanvas::find_port] Failed to find port " <<
255                 node_name << ":" << port_name << endl;
256
257         return NULL;
258 }
259
260
261 void
262 FlowCanvas::rename_module(const string& old_name, const string& new_name)
263 {
264         Module* module = NULL;
265         
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) {
270                         m_modules.erase(i);
271                         module->name(new_name);
272                         add_module(module);
273                         return;
274                 }
275         }
276         
277         cerr << "[FlowCanvas::rename_module] Failed to find module " <<
278                 old_name << endl;
279 }
280
281
282 /** Add a connection.
283  */
284 void
285 FlowCanvas::add_connection(const string& node1_name, const string& port1_name,
286                              const string& node2_name, const string& port2_name)
287 {
288         Port* port1 = find_port(node1_name, port1_name);
289         Port* port2 = find_port(node2_name, port2_name);
290
291         if (port1 == NULL) {
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;
297         } else {
298                 add_connection(port1, port2);
299         }
300 }
301
302
303 bool
304 FlowCanvas::remove_connection(Port* port1, Port* port2)
305 {
306         assert(port1 != NULL);
307         assert(port2 != NULL);
308         
309         Connection* c = get_connection(port1, port2);
310         if (c == NULL) {
311                 cerr << "Couldn't find connection.\n";
312                 return false;
313         } else {
314                 remove_connection(c);
315                 return true;
316         }
317 }
318
319
320 /** Remove a connection.
321  * 
322  * Returns whether or not the connection was found (and removed).
323  */
324 bool
325 FlowCanvas::remove_connection(const string& mod1_name, const string& port1_name, const string& mod2_name, const string& port2_name)
326 {
327         Connection* c = get_connection(find_port(mod1_name, port1_name),
328                                        find_port(mod2_name, port2_name));
329         if (c == NULL) {
330                 cerr << "Couldn't find connection.\n";
331                 return false;
332         } else {
333                 remove_connection(c);
334                 return true;
335         }
336 }
337
338
339 bool
340 FlowCanvas::are_connected(const Port* port1, const Port* port2)
341 {
342         assert(port1 != NULL);
343         assert(port2 != NULL);
344         
345         ConnectionList::const_iterator c;
346         const Connection* connection;
347
348
349         for (c = m_connections.begin(); c != m_connections.end(); ++c) {
350                 connection = *c;
351                 if (connection->source_port() == port1 && connection->dest_port() == port2)
352                         return true;
353                 if (connection->source_port() == port2 && connection->dest_port() == port1)
354                         return true;
355         }
356
357         return false;
358 }
359
360
361 Connection*
362 FlowCanvas::get_connection(const Port* port1, const Port* port2)
363 {
364         assert(port1 != NULL);
365         assert(port2 != NULL);
366         
367         for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
368                 if ( (*i)->source_port() == port1 && (*i)->dest_port() == port2 )
369                         return *i;
370                 else if ( (*i)->dest_port() == port1 && (*i)->source_port() == port2 )
371                         return *i;
372         }
373         
374         return NULL;
375 }
376
377
378 void
379 FlowCanvas::add_connection(Port* port1, Port* port2)
380 {
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()) {
386                 src_port = port1;
387                 dst_port = port2;
388         } else {
389                 src_port = port2;
390                 dst_port = port1;
391         }
392
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);
399         }
400 }
401
402
403 void
404 FlowCanvas::remove_connection(Connection* connection)
405 {
406         assert(connection != NULL);
407         
408         ConnectionList::iterator i = find(m_connections.begin(), m_connections.end(), connection);
409         Connection* c = *i;
410         
411         c->disconnect();
412         m_connections.erase(i);
413         delete c;
414 }
415
416
417 /** Called when two ports are 'toggled' (connected or disconnected)
418  */
419 void
420 FlowCanvas::ports_joined(Port* port1, Port* port2)
421 {
422         assert(port1 != NULL);
423         assert(port2 != NULL);
424
425         port1->hilite(false);
426         port2->hilite(false);
427
428         string src_mod_name, dst_mod_name, src_port_name, dst_port_name;
429
430         Port* src_port = NULL;
431         Port* dst_port = NULL;
432         
433         if (port2->is_input() && ! port1->is_input()) {
434                 src_port = port1;
435                 dst_port = port2;
436         } else if ( ! port2->is_input() && port1->is_input()) {
437                 src_port = port2;
438                 dst_port = port1;
439         } else {
440                 return;
441         }
442         
443         if (are_connected(src_port, dst_port))
444                 disconnect(src_port, dst_port);
445         else
446                 connect(src_port, dst_port);
447 }
448
449
450 /** Event handler for ports.
451  *
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.
455  */
456 bool
457 FlowCanvas::port_event(GdkEvent* event, Port* port)
458 {
459         static bool port_dragging = false;
460         bool handled = true;
461         
462         switch (event->type) {
463         
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);
470                 } else {
471                         handled = false;
472                 }
473                 break;
474
475         case GDK_BUTTON_RELEASE:
476                 if (port_dragging) {
477                         if (m_connect_port == NULL) {
478                                 selected_port(port);
479                                 m_connect_port = port;
480                         } else {
481                                 ports_joined(port, m_connect_port);
482                                 m_connect_port = NULL;
483                                 selected_port(NULL);
484                         }
485                         port_dragging = false;
486                 } else {
487                         handled = false;
488                 }
489                 break;
490
491         case GDK_ENTER_NOTIFY:
492                 if (port != m_selected_port)
493                         port->hilite(true);
494                 break;
495
496         case GDK_LEAVE_NOTIFY:
497                 if (port_dragging) {
498                         m_drag_state = CONNECTION;
499                         m_connect_port = port;
500                         
501                         m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
502                                 Gdk::Cursor(Gdk::CROSSHAIR), event->button.time);
503
504                         port_dragging = false;
505                 } else {
506                         if (port != m_selected_port)
507                                 port->hilite(false);
508                 }
509                 break;
510
511         default:
512                 handled = false;
513         }
514         
515         return handled;
516 }
517
518
519 /*
520 bool
521 FlowCanvas::canvas_event(GdkEvent* event)
522 {
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);
529                 //double x, 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;
538         }
539         return false;
540 }
541 */
542
543
544 /* I can not get this to work for the life of me.
545  * Man I hate gnomecanvas.
546 bool
547 FlowCanvas::scroll_drag_handler(GdkEvent* event)
548 {
549         
550         bool handled = true;
551         
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;
562
563         bool first_motion = true;
564         
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;
573                 last_x = origin_x;
574                 last_y = origin_y;
575                 first_motion = true;
576                 m_scroll_dragging = true;
577                 
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;
582                 
583                 //c2w(x, y, x, y);
584                 //world_to_window(x, y, x, y);
585                 //window_to_world(event->button.x, event->button.y, x, y);
586                 //w2c(x, y, x, y);
587
588                 x_offset += last_x - x;//x + original_scroll_x;
589                 y_offset += last_y - y;// + original_scroll_y;
590                 
591                 //cerr << "Coord: (" << x << "," << y << ")\n";
592                 //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n";
593
594                 int temp_x;
595                 int temp_y;
596                 w2c(lrint(x_offset), lrint(y_offset),
597                         temp_x, temp_y);
598                 scroll_offset_x += temp_x;
599                 scroll_offset_y += temp_y;
600                 scroll_to(scroll_offset_x,
601                           scroll_offset_y);
602                 last_x = x;
603                 last_y = y;
604         } else if (event->type == GDK_BUTTON_RELEASE && m_scroll_dragging) {
605                 m_base_rect.ungrab(event->button.time);
606                 m_scroll_dragging = false;
607         } else {
608                 handled = false;
609         }
610
611         return handled;
612         return false;
613 }
614 */
615
616
617 bool
618 FlowCanvas::select_drag_handler(GdkEvent* event)
619 {
620         Module* module = NULL;
621         
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))
626                         clear_selection();
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();
633                 return true;
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;
638                 return true;
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);
646                                 else
647                                         select_module(module);
648                         }
649                 }
650                 
651                 delete m_select_rect;
652                 m_select_rect = NULL;
653                 m_drag_state = NOT_DRAGGING;
654                 return true;
655         }
656         return false;
657 }
658
659
660 /** Updates m_select_dash for rotation effect, and updates any
661   * selected item's borders (and the selection rectangle).
662   */
663 bool
664 FlowCanvas::animate_selected()
665 {
666         static int i = 10;
667
668         if (--i == 0)
669                 i = 10;
670
671         m_select_dash->offset = i;
672         
673         if (m_select_rect != NULL)
674                 m_select_rect->property_dash() = m_select_dash;
675         
676         for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
677                 (*m)->rect()->property_dash() = m_select_dash;
678         
679         for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
680                 (*c)->property_dash() = m_select_dash;
681         
682         return true;
683 }
684
685
686 bool
687 FlowCanvas::connection_drag_handler(GdkEvent* event)
688 {
689         bool handled = true;
690         
691         // These are invisible, just used for making connections (while dragging)
692         static Module*      drag_module = NULL;
693         static Port*        drag_port = NULL;
694         
695         static Connection*  drag_connection = NULL;
696         static Port*        snapped_port = NULL;
697
698         static bool snapped = false;
699         
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;
704                 root()->w2i(x, y);
705
706                 if (drag_connection == NULL) { // Havn't created the connection yet
707                         assert(drag_port == NULL);
708                         assert(m_connect_port != NULL);
709                         
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;
714                                 
715                         drag_port = new Port(drag_module, "", drag_port_is_input, m_connect_port->colour());
716                         drag_module->add_port(drag_port);
717
718                         //drag_port->hide();
719                         drag_module->hide();
720
721                         drag_module->move_to(x, y);
722                         
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);
729                         else
730                                 drag_connection = new Connection(this, drag_port, m_connect_port);
731                                 
732                         drag_connection->update_location();
733                         //drag_connection->property_line_style() = Gdk::LINE_DOUBLE_DASH;
734                         //drag_connection->property_last_arrowhead() = true;
735                 }
736
737                 if (snapped) {
738                         if (drag_connection != NULL) drag_connection->hide();
739                         Port* p = get_port_at(x, y);
740                         if (drag_connection != NULL) drag_connection->show();
741                         if (p != NULL) {
742                                 if (p != m_selected_port) {
743                                         if (snapped_port != NULL)
744                                                 snapped_port->hilite(false);
745                                         p->hilite(true);
746                                         snapped_port = p;
747                                 }
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);
757                                 snapped_port = NULL;
758                                 snapped = 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;
765                         }
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);
771
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()) {
777                                 p->hilite(true);
778                                 snapped_port = p;
779                                 snapped = true;
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();
789                         } else {
790                                 drag_module->property_x() = x;
791                                 drag_module->property_y() = y;
792                         }
793                         drag_connection->update_location();
794                 }
795         } else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == CONNECTION) {
796                 m_base_rect.ungrab(event->button.time);
797                 
798                 double x = event->button.x;
799                 double y = event->button.y;
800                 m_base_rect.i2w(x, y);
801
802                 if (drag_connection != NULL) drag_connection->hide();
803                 Port* p = get_port_at(x, y);
804                 if (drag_connection != NULL) drag_connection->show();
805         
806                 if (p != NULL) {
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);
813                                         selected_port(NULL);
814                                         m_connect_port = NULL;
815                                         snapped_port = NULL;
816                                 }
817                         } else {  // drag ended on different port
818                                 //p->hilite(false);
819                                 ports_joined(m_connect_port, p);
820                                 selected_port(NULL);
821                                 m_connect_port = NULL;
822                                 snapped_port = NULL;
823                         }
824                 }
825                 
826                 // Clean up dragging stuff
827                 if (m_connect_port != NULL)
828                         m_connect_port->hilite(false);
829
830                 m_drag_state = NOT_DRAGGING;
831                 delete drag_connection;
832                 drag_connection = NULL;
833                 //delete drag_port;
834                 drag_port = NULL;
835                 delete drag_module; // deletes drag_port
836                 drag_module = NULL;
837                 snapped_port = NULL;
838         } else {
839                 handled = false;
840         }
841
842         return handled;
843 }
844
845
846 Port*
847 FlowCanvas::get_port_at(double x, double y)
848 {
849         Gnome::Canvas::Item* item = get_item_at(x, y);
850         if (item == NULL) return NULL;
851         
852         Port* p = 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) {
857                         p = (*j);
858                         
859                         if ((Gnome::Canvas::Item*)p == item
860                                         || (Gnome::Canvas::Item*)(p->rect()) == item
861                                         || (Gnome::Canvas::Item*)(p->label()) == item) {
862                                 return p;
863                         }
864                 }
865         }
866         return NULL;
867 }
868
869 /*
870 void
871 FlowCanvas::port_menu_disconnect_all()
872 {
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) {
876                 c = *i;
877                 disconnect(c->source_port(), c->dest_port());
878         }
879         
880         selected_port(NULL);
881 }
882 */
883
884 } // namespace LibFlowCanvas