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