minor visual change for panner
[ardour.git] / libs / flowcanvas / src / Module.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/Module.h"
19 #include "flowcanvas/FlowCanvas.h"
20 #include <functional>
21 #include <list>
22 #include <cstdlib>
23 #include <cassert>
24 #include <cmath>
25
26 using std::string;
27
28 namespace LibFlowCanvas {
29
30 static const int MODULE_FILL_COLOUR    = 0x122A3CFF;
31 static const int MODULE_OUTLINE_COLOUR = 0x8899AAFF;
32 static const int MODULE_TITLE_COLOUR   = 0xDDEEFFFF;
33
34 Module::Module(FlowCanvas* patch_bay, const string& name, double x, double y)
35 : Gnome::Canvas::Group(*patch_bay->root(), x, y),
36   m_name(name),
37   m_selected(false),
38   m_patch_bay(patch_bay),
39   m_module_box(*this, 0, 0, 0, 0), // w, h set later
40   m_canvas_title(*this, 0, 6, name) // x set later
41 {
42         assert(m_patch_bay != NULL);
43
44         m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
45
46         m_module_box.property_outline_color_rgba() = MODULE_OUTLINE_COLOUR;
47         m_module_box.property_join_style() = Gdk::JOIN_ROUND;
48         border_width(1.0);
49
50         m_canvas_title.property_size_set() = true;
51         m_canvas_title.property_size() = 10000;
52         m_canvas_title.property_weight_set() = true;
53         m_canvas_title.property_weight() = 400;
54         m_canvas_title.property_fill_color_rgba() = MODULE_TITLE_COLOUR;
55
56         width(m_canvas_title.property_text_width() + 6.0);
57         height(m_canvas_title.property_text_height() + 2.0);
58         m_canvas_title.property_x() = m_width/2.0;
59
60         signal_event().connect(sigc::mem_fun(this, &Module::module_event));
61 }
62
63
64 Module::~Module()
65 {
66         if (m_selected) {
67                 for (list<Module*>::iterator i = m_patch_bay->selected_modules().begin();
68                                 i != m_patch_bay->selected_modules().end(); ++i)
69                 {
70                         if ((*i) == this) {
71                                 m_patch_bay->selected_modules().erase(i);
72                                 break;
73                         }
74                 }
75         }
76         for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
77                 delete (*p);
78 }
79
80 /** Set the border width of the module.
81  *
82  * Do NOT directly set the width_units property on the rect, use this function.
83  */
84 void
85 Module::border_width(double w)
86 {
87         m_border_width = w;
88         m_module_box.property_width_units() = w;
89 }
90
91
92 bool
93 Module::module_event(GdkEvent* event)
94 {
95         assert(event != NULL);
96
97         static double x, y;
98         static double drag_start_x, drag_start_y;
99         double module_x, module_y; // FIXME: bad name, actually mouse click loc
100         static bool dragging = false;
101         bool handled = true;
102         
103         module_x = event->button.x;
104         module_y = event->button.y;
105
106         property_parent().get_value()->w2i(module_x, module_y);
107         
108         switch (event->type) {
109
110         case GDK_2BUTTON_PRESS:
111                 on_double_click();
112                 handled = true;
113                 break;
114
115         case GDK_BUTTON_PRESS:
116                 if (event->button.button == 1) {
117                         x = module_x;
118                         y = module_y;
119                         // Set these so we can tell on a button release if a drag actually
120                         // happened (if not, it's just a click)
121                         drag_start_x = x;
122                         drag_start_y = y;
123                         grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK,
124                                    Gdk::Cursor(Gdk::FLEUR),
125                                    event->button.time);
126                         dragging = true;
127                 } else if (event->button.button == 2) {
128                         on_double_click();
129                         handled = true;
130                 } else if (event->button.button == 3) {
131                         show_menu(&event->button);
132                 } else {
133                         handled = false;
134                 }
135                 break;
136         
137         case GDK_MOTION_NOTIFY:
138                 if ((dragging && (event->motion.state & GDK_BUTTON1_MASK))) {
139                         double new_x = module_x;
140                         double new_y = module_y;
141
142                         // Move any other selected modules if we're selected
143                         if (m_selected) {
144                                 for (list<Module*>::iterator i = m_patch_bay->selected_modules().begin();
145                                                 i != m_patch_bay->selected_modules().end(); ++i)
146                                 {
147                                         (*i)->move(new_x - x, new_y - y);
148                                 }
149                         } else {
150                                 move(new_x - x, new_y - y);
151                         }
152
153                         x = new_x;
154                         y = new_y;
155                 } else {
156                         handled = false;
157                 }
158                 break;
159
160         case GDK_BUTTON_RELEASE:
161                 if (dragging) {
162                         ungrab(event->button.time);
163                         dragging = false;
164                         if (module_x != drag_start_x || module_y != drag_start_y) {// dragged
165                                 store_location();
166                         } else { // just a click
167                                 if (m_selected) {
168                                         m_patch_bay->unselect_module(this);
169                                         assert(!m_selected);
170                                 } else {
171                                         if ( !(event->button.state & GDK_CONTROL_MASK))
172                                                 m_patch_bay->clear_selection();
173                                         m_patch_bay->select_module(this);
174                                         assert(m_selected);
175                                 }
176                         }
177                 } else {
178                         handled = false;
179                 }
180                 break;
181
182         case GDK_ENTER_NOTIFY:
183                 hilite(true);
184                 raise_to_top();
185                 for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
186                         (*p)->raise_connections();
187                 break;
188
189         case GDK_LEAVE_NOTIFY:
190                 hilite(false);
191                 break;
192
193         default:
194                 handled = false;
195         }
196
197         return handled;
198 }
199
200
201 void
202 Module::zoom(float z)
203 {
204         m_canvas_title.property_size() = static_cast<int>(roundf(10000.0f * z));
205         for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
206                 (*p)->zoom(z);
207 }
208
209
210 void
211 Module::hilite(bool b)
212 {
213         if (b) {
214                 m_module_box.property_fill_color_rgba() = 0x223553FF;
215         } else {
216                 m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
217         }
218 }
219
220
221 void
222 Module::selected(bool selected)
223 {
224         m_selected = selected;
225         if (selected) {
226                 m_module_box.property_fill_color_rgba() = 0x223553FF;
227                 m_module_box.property_outline_color_rgba() = 0xEEEEFFFF;
228                 m_module_box.property_dash() = m_patch_bay->select_dash();
229                 m_canvas_title.property_fill_color_rgba() = 0xEEEEFFFF;
230                 //for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
231                 //      (*p)->rect()->property_outline_color_rgba() = 0xEEEEFFFF;
232         } else {
233                 m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
234                 m_module_box.property_outline_color_rgba() = MODULE_OUTLINE_COLOUR;
235                 m_module_box.property_dash() = NULL;
236                 m_canvas_title.property_fill_color_rgba() = MODULE_TITLE_COLOUR;
237                 //for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
238                 //      (*p)->rect()->property_outline_color_rgba() = 0x8899AAFF;
239         }
240 }
241
242
243 bool
244 Module::is_within(const Gnome::Canvas::Rect* const rect)
245 {
246         const double x1 = rect->property_x1();
247         const double y1 = rect->property_y1();
248         const double x2 = rect->property_x2();
249         const double y2 = rect->property_y2();
250
251         if (x1 < x2 && y1 < y2) {
252                 return (property_x() > x1
253                         && property_y() > y1
254                         && property_x() + width() < x2
255                         && property_y() + height() < y2);
256         } else if (x2 < x1 && y2 < y1) {
257                 return (property_x() > x2
258                         && property_y() > y2
259                         && property_x() + width() < x1
260                         && property_y() + height() < y1);
261         } else if (x1 < x2 && y2 < y1) {
262                 return (property_x() > x1
263                         && property_y() > y2
264                         && property_x() + width() < x2
265                         && property_y() + height() < y1);
266         } else if (x2 < x1 && y1 < y2) {
267                 return (property_x() > x2
268                         && property_y() > y1
269                         && property_x() + width() < x1
270                         && property_y() + height() < y2);
271         } else {
272                 return false;
273         }
274 }
275
276
277 void
278 Module::remove_port(const string& port_name, bool resize_to_fit)
279 {
280         for (PortList::iterator i = m_ports.begin(); i != m_ports.end(); ++i) {
281                 if ((*i)->name() == port_name) {
282                         delete (*i);
283                         i = m_ports.erase(i);
284                 }
285         }
286
287         if (resize_to_fit)
288                 resize();
289 }
290
291
292 void
293 Module::width(double w)
294 {
295         m_width = w;
296         m_module_box.property_x2() = m_module_box.property_x1() + w;
297 }
298
299
300 void
301 Module::height(double h)
302 {
303         m_height = h;
304         m_module_box.property_y2() = m_module_box.property_y1() + h;
305 }
306
307
308 /** Overloaded Group::move to update connection paths and keep module on the canvas */
309 void
310 Module::move(double dx, double dy)
311 {
312         double new_x = property_x() + dx;
313         double new_y = property_y() + dy;
314         
315         if (new_x < 0) dx = property_x() * -1;
316         else if (new_x + m_width > m_patch_bay->width()) dx = m_patch_bay->width() - property_x() - m_width;
317         
318         if (new_y < 0) dy = property_y() * -1;
319         else if (new_y + m_height > m_patch_bay->height()) dy = m_patch_bay->height() - property_y() - m_height;
320
321         Gnome::Canvas::Group::move(dx, dy);
322
323         // Deal with moving the connection lines
324         for (PortList::iterator p = ports().begin(); p != ports().end(); ++p)
325                 (*p)->move_connections();
326 }
327
328
329 /** Move to the specified absolute coordinate on the canvas */
330 void
331 Module::move_to(double x, double y)
332 {
333         if (x < 0) x = 0;
334         if (y < 0) y = 0;
335         if (x + m_width > m_patch_bay->width()) x = m_patch_bay->width() - m_width;
336         if (y + m_height > m_patch_bay->height()) y = m_patch_bay->height() - m_height;
337                 
338         // Man, not many things left to try to get the damn things to move! :)
339         property_x() = x;
340         property_y() = y;
341         move(0, 0);
342         // Deal with moving the connection lines
343         for (PortList::iterator p = ports().begin(); p != ports().end(); ++p)
344                 (*p)->move_connections();
345 }
346
347
348 void
349 Module::name(const string& n)
350 {
351         m_name = n;
352         m_canvas_title.property_text() = m_name;
353         resize();
354 }
355
356 /*
357 void
358 Module::add_port(const string& port_name, bool is_input, int colour, bool resize_to_fit)
359 {
360         Port* port = new Port(this, port_name, is_input, colour);
361
362         port->signal_event().connect(
363                 sigc::bind<Port*>(sigc::mem_fun(m_patch_bay, &FlowCanvas::port_event), port));
364
365         add_port(port, resize_to_fit);
366 }
367 */
368
369 void
370 Module::add_port(Port* port, bool resize_to_fit)
371 {
372         m_ports.push_back(port);
373         
374         if (resize_to_fit)
375                 resize();
376 }
377
378
379 /** Resize the module to fit its contents best.
380  */
381 void
382 Module::resize()
383 {
384         double widest_in = 0.0;
385         double widest_out = 0.0;
386         
387         Port* p = NULL;
388         
389         // Find widest in/out ports
390         for (PortList::iterator i = m_ports.begin(); i != m_ports.end(); ++i) {
391                 p = (*i);
392                 if (p->is_input() && p->width() > widest_in)
393                         widest_in = p->width();
394                 else if (p->is_output() && p->width() > widest_out)
395                         widest_out = p->width();
396         }
397         
398         // Make sure module is wide enough for ports
399         if (widest_in > widest_out)
400                 width(widest_in + 5.0 + border_width()*2.0);
401         else
402                 width(widest_out + 5.0 + border_width()*2.0);
403         
404         // Make sure module is wide enough for title
405         if (m_canvas_title.property_text_width() + 6.0 > m_width)
406                 width(m_canvas_title.property_text_width() + 6.0);
407
408         // Set height to contain ports and title
409         double height_base = m_canvas_title.property_text_height() + 2;
410         double h = height_base;
411         if (m_ports.size() > 0)
412                 h += m_ports.size() * ((*m_ports.begin())->height()+2.0);
413         height(h);
414         
415         // Move ports to appropriate locations
416         
417         double y;
418         int i = 0;
419         for (PortList::iterator pi = m_ports.begin(); pi != m_ports.end(); ++pi, ++i) {
420                 Port* p = (*pi);
421
422                 y = height_base + (i * (p->height() + 2.0));
423                 if (p->is_input()) {
424                         p->width(widest_in);
425                         p->property_x() = 1.0;//border_width();
426                         p->property_y() = y;
427                 } else {
428                         p->width(widest_out);
429                         p->property_x() = m_width - p->width() - 1.0;//p->border_width();
430                         p->property_y() = y;
431                 }
432         }
433
434         // Center title
435         m_canvas_title.property_x() = m_width/2.0;
436
437         // Update connection locations if we've moved/resized
438         for (PortList::iterator pi = m_ports.begin(); pi != m_ports.end(); ++pi, ++i) {
439                 (*pi)->move_connections();
440         }
441         
442         // Make things actually move to their new locations (?!)
443         move(0, 0);
444 }
445
446
447 /** Port offset, for connection drawing.  See doc/port_offsets.dia */
448 double
449 Module::port_connection_point_offset(Port* port)
450 {
451         assert(port->module() == this);
452         assert(ports().size() > 0);
453
454         return (port->connection_coords().get_y()
455                         - m_ports.front()->connection_coords().get_y());
456 }
457
458
459 /** Range of port offsets, for connection drawing.  See doc/port_offsets.dia */
460 double
461 Module::port_connection_points_range()
462 {
463         assert(m_ports.size() > 0);
464
465         double ret = fabs(m_ports.back()->connection_coords().get_y()
466                         - m_ports.front()->connection_coords().get_y());
467
468         return (ret < 1.0) ? 1.0 : ret;
469 }
470
471
472 } // namespace LibFlowCanvas