1 /*
2  * Patchbay Canvas engine using QGraphicsView/Scene
3  * Copyright (C) 2010-2012 Filipe Coelho <falktx@falktx.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * For a full copy of the GNU General Public License see the COPYING file
16  */
17 
18 #include "canvasbox.h"
19 
20 #include <QtCore/QTimer>
21 #include <QtGui/QCursor>
22 #include <QtGui/QInputDialog>
23 #include <QtGui/QMenu>
24 #include <QtGui/QGraphicsSceneContextMenuEvent>
25 #include <QtGui/QGraphicsSceneMouseEvent>
26 #include <QtGui/QPainter>
27 
28 #include "canvasline.h"
29 #include "canvasbezierline.h"
30 #include "canvasport.h"
31 #include "canvasboxshadow.h"
32 #include "canvasicon.h"
33 
34 START_NAMESPACE_PATCHCANVAS
35 
CanvasBox(int group_id,QString group_name,Icon icon,QGraphicsItem * parent)36 CanvasBox::CanvasBox(int group_id, QString group_name, Icon icon, QGraphicsItem* parent) :
37     QGraphicsItem(parent, canvas.scene)
38 {
39     // Save Variables, useful for later
40     m_group_id   = group_id;
41     m_group_name = group_name;
42 
43     // Base Variables
44     p_width  = 50;
45     p_height = 25;
46 
47     m_last_pos = QPointF();
48     m_splitted = false;
49     m_splitted_mode = PORT_MODE_NULL;
50 
51     m_cursor_moving = false;
52     m_forced_split  = false;
53     m_mouse_down    = false;
54 
55     m_port_list_ids.clear();
56     m_connection_lines.clear();
57 
58     // Set Font
59     m_font_name = QFont(canvas.theme->box_font_name, canvas.theme->box_font_size, canvas.theme->box_font_state);
60     m_font_port = QFont(canvas.theme->port_font_name, canvas.theme->port_font_size, canvas.theme->port_font_state);
61 
62     // Icon
63     icon_svg = new CanvasIcon(icon, group_name, this);
64 
65     // Shadow
66     if (options.eyecandy)
67     {
68         shadow = new CanvasBoxShadow(toGraphicsObject());
69         shadow->setFakeParent(this);
70         setGraphicsEffect(shadow);
71     }
72     else
73         shadow = 0;
74 
75     // Final touches
76     setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable);
77 
78     // Wait for at least 1 port
79     if (options.auto_hide_groups)
80         setVisible(false);
81 
82     updatePositions();
83 }
84 
~CanvasBox()85 CanvasBox::~CanvasBox()
86 {
87     if (shadow)
88         delete shadow;
89     delete icon_svg;
90 }
91 
getGroupId()92 int CanvasBox::getGroupId()
93 {
94     return m_group_id;
95 }
96 
getGroupName()97 QString CanvasBox::getGroupName()
98 {
99     return m_group_name;
100 }
101 
isSplitted()102 bool CanvasBox::isSplitted()
103 {
104     return m_splitted;
105 }
106 
getSplittedMode()107 PortMode CanvasBox::getSplittedMode()
108 {
109     return m_splitted_mode;
110 }
111 
getPortCount()112 int CanvasBox::getPortCount()
113 {
114     return m_port_list_ids.count();
115 }
116 
getPortList()117 QList<int> CanvasBox::getPortList()
118 {
119     return m_port_list_ids;
120 }
121 
setIcon(Icon icon)122 void CanvasBox::setIcon(Icon icon)
123 {
124     icon_svg->setIcon(icon, m_group_name);
125 }
126 
setSplit(bool split,PortMode mode)127 void CanvasBox::setSplit(bool split, PortMode mode)
128 {
129     m_splitted = split;
130     m_splitted_mode = mode;
131 }
132 
setGroupName(QString group_name)133 void CanvasBox::setGroupName(QString group_name)
134 {
135     m_group_name = group_name;
136     updatePositions();
137 }
138 
setShadowOpacity(float opacity)139 void CanvasBox::setShadowOpacity(float opacity)
140 {
141     if (shadow)
142         shadow->setOpacity(opacity);
143 }
144 
addPortFromGroup(int port_id,QString port_name,PortMode port_mode,PortType port_type)145 CanvasPort* CanvasBox::addPortFromGroup(int port_id, QString port_name, PortMode port_mode, PortType port_type)
146 {
147     if (m_port_list_ids.count() == 0)
148     {
149         if (options.auto_hide_groups)
150         {
151             if (options.eyecandy == EYECANDY_FULL)
152                 CanvasItemFX(this, true);
153             setVisible(true);
154         }
155     }
156 
157     CanvasPort* new_widget = new CanvasPort(port_id, port_name, port_mode, port_type, this);
158 
159     port_dict_t port_dict;
160     port_dict.group_id  = m_group_id;
161     port_dict.port_id   = port_id;
162     port_dict.port_name = port_name;
163     port_dict.port_mode = port_mode;
164     port_dict.port_type = port_type;
165     port_dict.widget    = new_widget;
166 
167     m_port_list_ids.append(port_id);
168 
169     return new_widget;
170 }
171 
removePortFromGroup(int port_id)172 void CanvasBox::removePortFromGroup(int port_id)
173 {
174     if (m_port_list_ids.contains(port_id))
175     {
176         m_port_list_ids.removeOne(port_id);
177     }
178     else
179     {
180         qCritical("PatchCanvas::CanvasBox->removePort(%i) - unable to find port to remove", port_id);
181         return;
182     }
183 
184     if (m_port_list_ids.count() > 0)
185     {
186         updatePositions();
187     }
188     else if (isVisible())
189     {
190         if (options.auto_hide_groups)
191         {
192             if (options.eyecandy == EYECANDY_FULL)
193                 CanvasItemFX(this, false);
194             else
195                 setVisible(false);
196         }
197     }
198 }
199 
addLineFromGroup(AbstractCanvasLine * line,int connection_id)200 void CanvasBox::addLineFromGroup(AbstractCanvasLine* line, int connection_id)
201 {
202     cb_line_t new_cbline;
203     new_cbline.line = line;
204     new_cbline.connection_id = connection_id;
205     m_connection_lines.append(new_cbline);
206 }
207 
removeLineFromGroup(int connection_id)208 void CanvasBox::removeLineFromGroup(int connection_id)
209 {
210     foreach2 (const cb_line_t& connection, m_connection_lines)
211         if (connection.connection_id == connection_id)
212         {
213             m_connection_lines.takeAt(i);
214             return;
215         }
216     }
217 
218     qCritical("PatchCanvas::CanvasBox->removeLineFromGroup(%i) - unable to find line to remove", connection_id);
219 }
220 
221 void CanvasBox::checkItemPos()
222 {
223     if (canvas.size_rect.isNull() == false)
224     {
225         QPointF pos = scenePos();
226         if (canvas.size_rect.contains(pos) == false || canvas.size_rect.contains(pos+QPointF(p_width, p_height)) == false)
227         {
228             if (pos.x() < canvas.size_rect.x())
229                 setPos(canvas.size_rect.x(), pos.y());
230             else if (pos.x()+p_width > canvas.size_rect.width())
231                 setPos(canvas.size_rect.width()-p_width, pos.y());
232 
233             pos = scenePos();
234             if (pos.y() < canvas.size_rect.y())
235                 setPos(pos.x(), canvas.size_rect.y());
236             else if (pos.y()+p_height > canvas.size_rect.height())
237                 setPos(pos.x(), canvas.size_rect.height()-p_height);
238         }
239     }
240 }
241 
242 void CanvasBox::removeIconFromScene()
243 {
244     canvas.scene->removeItem(icon_svg);
245 }
246 
247 void CanvasBox::updatePositions()
248 {
249     prepareGeometryChange();
250 
251     int max_in_width   = 0;
252     int max_in_height  = 24;
253     int max_out_width  = 0;
254     int max_out_height = 24;
255     bool have_audio_jack_in, have_audio_jack_out, have_midi_jack_in, have_midi_jack_out;
256     bool have_midi_a2j_in,  have_midi_a2j_out, have_midi_alsa_in,  have_midi_alsa_out;
257     have_audio_jack_in  = have_midi_jack_in  = have_midi_a2j_in  = have_midi_alsa_in  = false;
258     have_audio_jack_out = have_midi_jack_out = have_midi_a2j_out = have_midi_alsa_out = false;
259 
260     // reset box size
261     p_width  = 50;
262     p_height = 25;
263 
264     // Check Text Name size
265     int app_name_size = QFontMetrics(m_font_name).width(m_group_name)+30;
266     if (app_name_size > p_width)
267         p_width = app_name_size;
268 
269     // Get Port List
270     QList<port_dict_t> port_list;
271     foreach (const port_dict_t& port, canvas.port_list)
272     {
273         if (m_port_list_ids.contains(port.port_id))
274             port_list.append(port);
275     }
276 
277     // Get Max Box Width/Height
278     foreach (const port_dict_t& port, port_list)
279     {
280         if (port.port_mode == PORT_MODE_INPUT)
281         {
282             max_in_height += 18;
283 
284             int size = QFontMetrics(m_font_port).width(port.port_name);
285             if (size > max_in_width)
286                 max_in_width = size;
287 
288             if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_in == false)
289             {
290                 have_audio_jack_in = true;
291                 max_in_height += 2;
292             }
293             else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_in == false)
294             {
295                 have_midi_jack_in = true;
296                 max_in_height += 2;
297             }
298             else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_in == false)
299             {
300                 have_midi_a2j_in = true;
301                 max_in_height += 2;
302             }
303             else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_in == false)
304             {
305                 have_midi_alsa_in = true;
306                 max_in_height += 2;
307             }
308         }
309         else if (port.port_mode == PORT_MODE_OUTPUT)
310         {
311             max_out_height += 18;
312 
313             int size = QFontMetrics(m_font_port).width(port.port_name);
314             if (size > max_out_width)
315                 max_out_width = size;
316 
317             if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_out == false)
318             {
319                 have_audio_jack_out = true;
320                 max_out_height += 2;
321             }
322             else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_out == false)
323             {
324                 have_midi_jack_out = true;
325                 max_out_height += 2;
326             }
327             else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_out == false)
328             {
329                 have_midi_a2j_out = true;
330                 max_out_height += 2;
331             }
332             else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_out == false)
333             {
334                 have_midi_alsa_out = true;
335                 max_out_height += 2;
336             }
337         }
338     }
339 
340     int final_width = 30 + max_in_width + max_out_width;
341     if (final_width > p_width)
342         p_width = final_width;
343 
344     if (max_in_height > p_height)
345         p_height = max_in_height;
346 
347     if (max_out_height > p_height)
348         p_height = max_out_height;
349 
350     // Remove bottom space
351     p_height -= 2;
352 
353     int last_in_pos  = 24;
354     int last_out_pos = 24;
355     PortType last_in_type  = PORT_TYPE_NULL;
356     PortType last_out_type = PORT_TYPE_NULL;
357 
358     // Re-position ports, AUDIO_JACK
359     foreach (const port_dict_t& port, port_list)
360     {
361         if (port.port_type == PORT_TYPE_AUDIO_JACK)
362         {
363             if (port.port_mode == PORT_MODE_INPUT)
364             {
365                 port.widget->setPos(QPointF(1, last_in_pos));
366                 port.widget->setPortWidth(max_in_width);
367 
368                 last_in_pos += 18;
369                 last_in_type = port.port_type;
370             }
371             else if (port.port_mode == PORT_MODE_OUTPUT)
372             {
373                 port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
374                 port.widget->setPortWidth(max_out_width);
375 
376                 last_out_pos += 18;
377                 last_out_type = port.port_type;
378             }
379         }
380     }
381 
382     // Re-position ports, MIDI_JACK
383     foreach (const port_dict_t& port, port_list)
384     {
385         if (port.port_type == PORT_TYPE_MIDI_JACK)
386         {
387             if (port.port_mode == PORT_MODE_INPUT)
388             {
389                 if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
390                     last_in_pos += 2;
391 
392                 port.widget->setPos(QPointF(1, last_in_pos));
393                 port.widget->setPortWidth(max_in_width);
394 
395                 last_in_pos += 18;
396                 last_in_type = port.port_type;
397             }
398             else if (port.port_mode == PORT_MODE_OUTPUT)
399             {
400                 if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
401                     last_out_pos += 2;
402 
403                 port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
404                 port.widget->setPortWidth(max_out_width);
405 
406                 last_out_pos += 18;
407                 last_out_type = port.port_type;
408             }
409         }
410     }
411 
412     // Re-position ports, MIDI_A2J
413     foreach (const port_dict_t& port, port_list)
414     {
415         if (port.port_type == PORT_TYPE_MIDI_A2J)
416         {
417             if (port.port_mode == PORT_MODE_INPUT)
418             {
419                 if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
420                     last_in_pos += 2;
421 
422                 port.widget->setPos(QPointF(1, last_in_pos));
423                 port.widget->setPortWidth(max_in_width);
424 
425                 last_in_pos += 18;
426                 last_in_type = port.port_type;
427             }
428             else if (port.port_mode == PORT_MODE_OUTPUT)
429             {
430                 if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
431                     last_out_pos += 2;
432 
433                 port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
434                 port.widget->setPortWidth(max_out_width);
435 
436                 last_out_pos += 18;
437                 last_out_type = port.port_type;
438             }
439         }
440     }
441 
442     // Re-position ports, MIDI_ALSA
443     foreach (const port_dict_t& port, port_list)
444     {
445         if (port.port_type == PORT_TYPE_MIDI_ALSA)
446         {
447             if (port.port_mode == PORT_MODE_INPUT)
448             {
449                 if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
450                     last_in_pos += 2;
451 
452                 port.widget->setPos(QPointF(1, last_in_pos));
453                 port.widget->setPortWidth(max_in_width);
454 
455                 last_in_pos += 18;
456                 last_in_type = port.port_type;
457             }
458             else if (port.port_mode == PORT_MODE_OUTPUT)
459             {
460                 if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
461                     last_out_pos += 2;
462 
463                 port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
464                 port.widget->setPortWidth(max_out_width);
465 
466                 last_out_pos += 18;
467                 last_out_type = port.port_type;
468             }
469         }
470     }
471 
472     repaintLines(true);
473     update();
474 }
475 
476 void CanvasBox::repaintLines(bool forced)
477 {
478     if (pos() != m_last_pos || forced)
479     {
480         foreach (const cb_line_t& connection, m_connection_lines)
481             connection.line->updateLinePos();
482     }
483 
484     m_last_pos = pos();
485 }
486 
487 void CanvasBox::resetLinesZValue()
488 {
489     foreach (const connection_dict_t& connection, canvas.connection_list)
490     {
491         int z_value;
492         if (m_port_list_ids.contains(connection.port_out_id) && m_port_list_ids.contains(connection.port_in_id))
493             z_value = canvas.last_z_value;
494         else
495             z_value = canvas.last_z_value-1;
496 
497         connection.widget->setZValue(z_value);
498     }
499 }
500 
501 int CanvasBox::type() const
502 {
503     return CanvasBoxType;
504 }
505 
506 void CanvasBox::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
507 {
508     QMenu menu;
509     QMenu discMenu("Disconnect", &menu);
510 
511     QList<int> port_con_list;
512     QList<int> port_con_list_ids;
513 
514     foreach (const int& port_id, m_port_list_ids)
515     {
516         QList<int> tmp_port_con_list = CanvasGetPortConnectionList(port_id);
517         foreach (const int& port_con_id, tmp_port_con_list)
518         {
519             if (port_con_list.contains(port_con_id) == false)
520             {
521                 port_con_list.append(port_con_id);
522                 port_con_list_ids.append(port_id);
523             }
524         }
525     }
526 
527     if (port_con_list.count() > 0)
528     {
529         for (int i=0; i < port_con_list.count(); i++)
530         {
531             int port_con_id = CanvasGetConnectedPort(port_con_list[i], port_con_list_ids[i]);
532             QAction* act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id));
533             act_x_disc->setData(port_con_list[i]);
534             QObject::connect(act_x_disc, SIGNAL(triggered()), canvas.qobject, SLOT(PortContextMenuDisconnect()));
535         }
536     }
537     else
538     {
539         QAction* act_x_disc = discMenu.addAction("No connections");
540         act_x_disc->setEnabled(false);
541     }
542 
543     menu.addMenu(&discMenu);
544     QAction* act_x_disc_all   = menu.addAction("Disconnect &All");
545     QAction* act_x_sep1       = menu.addSeparator();
546     QAction* act_x_info       = menu.addAction("&Info");
547     QAction* act_x_rename     = menu.addAction("&Rename");
548     QAction* act_x_sep2       = menu.addSeparator();
549     QAction* act_x_split_join = menu.addAction(m_splitted ? "Join" : "Split");
550 
551     if (features.group_info == false)
552         act_x_info->setVisible(false);
553 
554     if (features.group_rename == false)
555         act_x_rename->setVisible(false);
556 
557     if (features.group_info == false && features.group_rename == false)
558         act_x_sep1->setVisible(false);
559 
560     bool haveIns, haveOuts;
561     haveIns = haveOuts = false;
562     foreach (const port_dict_t& port, canvas.port_list)
563     {
564         if (m_port_list_ids.contains(port.port_id))
565         {
566             if (port.port_mode == PORT_MODE_INPUT)
567                 haveIns = true;
568             else if (port.port_mode == PORT_MODE_OUTPUT)
569                 haveOuts = true;
570         }
571     }
572 
573     if (m_splitted == false && (haveIns && haveOuts) == false)
574     {
575         act_x_sep2->setVisible(false);
576         act_x_split_join->setVisible(false);
577     }
578 
579     QAction* act_selected = menu.exec(event->screenPos());
580 
581     if (act_selected == act_x_disc_all)
582     {
583         foreach (const int& port_id, port_con_list)
584             canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, "");
585     }
586     else if (act_selected == act_x_info)
587     {
588         canvas.callback(ACTION_GROUP_INFO, m_group_id, 0, "");
589     }
590     else if (act_selected == act_x_rename)
591     {
592         bool ok_check;
593         QString new_name = QInputDialog::getText(0, "Rename Group", "New name:", QLineEdit::Normal, m_group_name, &ok_check);
594         if (ok_check and !new_name.isEmpty())
595         {
596             canvas.callback(ACTION_GROUP_RENAME, m_group_id, 0, new_name);
597         }
598     }
599     else if (act_selected == act_x_split_join)
600     {
601         if (m_splitted)
602             canvas.callback(ACTION_GROUP_JOIN, m_group_id, 0, "");
603         else
604             canvas.callback(ACTION_GROUP_SPLIT, m_group_id, 0, "");
605 
606     }
607 
608     event->accept();
609 }
610 
611 void CanvasBox::mousePressEvent(QGraphicsSceneMouseEvent* event)
612 {
613     canvas.last_z_value += 1;
614     setZValue(canvas.last_z_value);
615     resetLinesZValue();
616     m_cursor_moving = false;
617 
618     if (event->button() == Qt::RightButton)
619     {
620         canvas.scene->clearSelection();
621         setSelected(true);
622         m_mouse_down = false;
623         return event->accept();
624     }
625     else if (event->button() == Qt::LeftButton)
626     {
627         if (sceneBoundingRect().contains(event->scenePos()))
628             m_mouse_down = true;
629         else
630         {
631             // Fixes a weird Qt behaviour with right-click mouseMove
632             m_mouse_down = false;
633             return event->ignore();
634         }
635     }
636     else
637         m_mouse_down = false;
638 
639     QGraphicsItem::mousePressEvent(event);
640 }
641 
642 void CanvasBox::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
643 {
644     if (m_mouse_down)
645     {
646         if (m_cursor_moving == false)
647         {
648             setCursor(QCursor(Qt::SizeAllCursor));
649             m_cursor_moving = true;
650         }
651         repaintLines();
652     }
653     QGraphicsItem::mouseMoveEvent(event);
654 }
655 
656 void CanvasBox::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
657 {
658     if (m_cursor_moving)
659         setCursor(QCursor(Qt::ArrowCursor));
660     m_mouse_down = false;
661     m_cursor_moving = false;
662     QGraphicsItem::mouseReleaseEvent(event);
663 }
664 
665 QRectF CanvasBox::boundingRect() const
666 {
667     return QRectF(0, 0, p_width, p_height);
668 }
669 
670 void CanvasBox::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
671 {
672     painter->setRenderHint(QPainter::Antialiasing, false);
673 
674     if (isSelected())
675         painter->setPen(canvas.theme->box_pen_sel);
676     else
677         painter->setPen(canvas.theme->box_pen);
678 
679     QLinearGradient box_gradient(0, 0, 0, p_height);
680     box_gradient.setColorAt(0, canvas.theme->box_bg_1);
681     box_gradient.setColorAt(1, canvas.theme->box_bg_2);
682 
683     painter->setBrush(box_gradient);
684     painter->drawRect(0, 0, p_width, p_height);
685 
686     QPointF text_pos(25, 16);
687 
688     painter->setFont(m_font_name);
689     painter->setPen(canvas.theme->box_text);
690     painter->drawText(text_pos, m_group_name);
691 
692     repaintLines();
693 }
694 
695 END_NAMESPACE_PATCHCANVAS
696