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.

681 lines
19KB

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