Collection of tools useful for audio production
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

696 lines
19KB

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