Audio plugin host https://kx.studio/carla
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.

675 lines
25KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # PatchBay Canvas engine using QGraphicsView/Scene
  4. # Copyright (C) 2010-2019 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from PyQt5.QtCore import qCritical, Qt, QPointF, QRectF, QTimer
  20. from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen
  21. from PyQt5.QtWidgets import QGraphicsItem, QMenu
  22. # ------------------------------------------------------------------------------------------------------------
  23. # Imports (Custom)
  24. from . import (
  25. canvas,
  26. features,
  27. options,
  28. port_dict_t,
  29. CanvasBoxType,
  30. ANTIALIASING_FULL,
  31. ACTION_PLUGIN_EDIT,
  32. ACTION_PLUGIN_SHOW_UI,
  33. ACTION_PLUGIN_CLONE,
  34. ACTION_PLUGIN_REMOVE,
  35. ACTION_PLUGIN_RENAME,
  36. ACTION_PLUGIN_REPLACE,
  37. ACTION_GROUP_INFO,
  38. ACTION_GROUP_JOIN,
  39. ACTION_GROUP_SPLIT,
  40. ACTION_GROUP_RENAME,
  41. ACTION_PORTS_DISCONNECT,
  42. ACTION_INLINE_DISPLAY,
  43. EYECANDY_FULL,
  44. PORT_MODE_NULL,
  45. PORT_MODE_INPUT,
  46. PORT_MODE_OUTPUT,
  47. PORT_TYPE_NULL,
  48. PORT_TYPE_AUDIO_JACK,
  49. PORT_TYPE_MIDI_ALSA,
  50. PORT_TYPE_MIDI_JACK,
  51. PORT_TYPE_PARAMETER,
  52. MAX_PLUGIN_ID_ALLOWED,
  53. )
  54. from .canvasboxshadow import CanvasBoxShadow
  55. from .canvasicon import CanvasIcon
  56. from .canvasport import CanvasPort
  57. from .theme import Theme
  58. from .utils import CanvasItemFX, CanvasGetFullPortName, CanvasGetPortConnectionList
  59. # ------------------------------------------------------------------------------------------------------------
  60. class cb_line_t(object):
  61. def __init__(self, line, connection_id):
  62. self.line = line
  63. self.connection_id = connection_id
  64. # ------------------------------------------------------------------------------------------------------------
  65. class CanvasBox(QGraphicsItem):
  66. def __init__(self, group_id, group_name, icon, parent=None):
  67. QGraphicsItem.__init__(self)
  68. self.setParentItem(parent)
  69. # Save Variables, useful for later
  70. self.m_group_id = group_id
  71. self.m_group_name = group_name
  72. # plugin Id, < 0 if invalid
  73. self.m_plugin_id = -1
  74. self.m_plugin_ui = False
  75. self.m_plugin_inline = False
  76. # Base Variables
  77. self.p_width = 50
  78. self.p_width_in = 0
  79. self.p_width_out = 0
  80. self.p_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing + 1
  81. self.m_last_pos = QPointF()
  82. self.m_splitted = False
  83. self.m_splitted_mode = PORT_MODE_NULL
  84. self.m_cursor_moving = False
  85. self.m_forced_split = False
  86. self.m_mouse_down = False
  87. self.m_port_list_ids = []
  88. self.m_connection_lines = []
  89. # Set Font
  90. self.m_font_name = QFont()
  91. self.m_font_name.setFamily(canvas.theme.box_font_name)
  92. self.m_font_name.setPixelSize(canvas.theme.box_font_size)
  93. self.m_font_name.setWeight(canvas.theme.box_font_state)
  94. self.m_font_port = QFont()
  95. self.m_font_port.setFamily(canvas.theme.port_font_name)
  96. self.m_font_port.setPixelSize(canvas.theme.port_font_size)
  97. self.m_font_port.setWeight(canvas.theme.port_font_state)
  98. # Icon
  99. if canvas.theme.box_use_icon:
  100. self.icon_svg = CanvasIcon(icon, self.m_group_name, self)
  101. else:
  102. self.icon_svg = None
  103. # Shadow
  104. # FIXME FX on top of graphic items make them lose high-dpi
  105. # See https://bugreports.qt.io/browse/QTBUG-65035
  106. if options.eyecandy and canvas.scene.getDevicePixelRatioF() == 1.0:
  107. self.shadow = CanvasBoxShadow(self.toGraphicsObject())
  108. self.shadow.setFakeParent(self)
  109. self.setGraphicsEffect(self.shadow)
  110. else:
  111. self.shadow = None
  112. # Final touches
  113. self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
  114. # Wait for at least 1 port
  115. if options.auto_hide_groups:
  116. self.setVisible(False)
  117. if options.auto_select_items:
  118. self.setAcceptHoverEvents(True)
  119. self.updatePositions()
  120. canvas.scene.addItem(self)
  121. QTimer.singleShot(0, self.fixPos)
  122. def getGroupId(self):
  123. return self.m_group_id
  124. def getGroupName(self):
  125. return self.m_group_name
  126. def isSplitted(self):
  127. return self.m_splitted
  128. def getSplittedMode(self):
  129. return self.m_splitted_mode
  130. def getPortCount(self):
  131. return len(self.m_port_list_ids)
  132. def getPortList(self):
  133. return self.m_port_list_ids
  134. def setAsPlugin(self, plugin_id, hasUI, hasInlineDisplay):
  135. self.m_plugin_id = plugin_id
  136. self.m_plugin_ui = hasUI
  137. self.m_plugin_inline = hasInlineDisplay
  138. self.update()
  139. def setIcon(self, icon):
  140. if self.icon_svg is not None:
  141. self.icon_svg.setIcon(icon, self.m_group_name)
  142. def setSplit(self, split, mode=PORT_MODE_NULL):
  143. self.m_splitted = split
  144. self.m_splitted_mode = mode
  145. def setGroupName(self, group_name):
  146. self.m_group_name = group_name
  147. self.updatePositions()
  148. def setShadowOpacity(self, opacity):
  149. if self.shadow:
  150. self.shadow.setOpacity(opacity)
  151. def addPortFromGroup(self, port_id, port_mode, port_type, port_name, is_alternate):
  152. if len(self.m_port_list_ids) == 0:
  153. if options.auto_hide_groups:
  154. if options.eyecandy == EYECANDY_FULL:
  155. CanvasItemFX(self, True, False)
  156. self.setVisible(True)
  157. new_widget = CanvasPort(self.m_group_id, port_id, port_name, port_mode, port_type, is_alternate, self)
  158. port_dict = port_dict_t()
  159. port_dict.group_id = self.m_group_id
  160. port_dict.port_id = port_id
  161. port_dict.port_name = port_name
  162. port_dict.port_mode = port_mode
  163. port_dict.port_type = port_type
  164. port_dict.is_alternate = is_alternate
  165. port_dict.widget = new_widget
  166. self.m_port_list_ids.append(port_id)
  167. return new_widget
  168. def removePortFromGroup(self, port_id):
  169. if port_id in self.m_port_list_ids:
  170. self.m_port_list_ids.remove(port_id)
  171. else:
  172. qCritical("PatchCanvas::CanvasBox.removePort(%i) - unable to find port to remove" % port_id)
  173. return
  174. if len(self.m_port_list_ids) > 0:
  175. self.updatePositions()
  176. elif self.isVisible():
  177. if options.auto_hide_groups:
  178. if options.eyecandy == EYECANDY_FULL:
  179. CanvasItemFX(self, False, False)
  180. else:
  181. self.setVisible(False)
  182. def addLineFromGroup(self, line, connection_id):
  183. new_cbline = cb_line_t(line, connection_id)
  184. self.m_connection_lines.append(new_cbline)
  185. def removeLineFromGroup(self, connection_id):
  186. for connection in self.m_connection_lines:
  187. if connection.connection_id == connection_id:
  188. self.m_connection_lines.remove(connection)
  189. return
  190. qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id)
  191. def checkItemPos(self):
  192. if not canvas.size_rect.isNull():
  193. pos = self.scenePos()
  194. if not (canvas.size_rect.contains(pos) and
  195. canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
  196. if pos.x() < canvas.size_rect.x():
  197. self.setPos(canvas.size_rect.x(), pos.y())
  198. elif pos.x() + self.p_width > canvas.size_rect.width():
  199. self.setPos(canvas.size_rect.width() - self.p_width, pos.y())
  200. pos = self.scenePos()
  201. if pos.y() < canvas.size_rect.y():
  202. self.setPos(pos.x(), canvas.size_rect.y())
  203. elif pos.y() + self.p_height > canvas.size_rect.height():
  204. self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)
  205. def removeIconFromScene(self):
  206. if self.icon_svg is None:
  207. return
  208. item = self.icon_svg
  209. self.icon_svg = None
  210. canvas.scene.removeItem(item)
  211. del item
  212. def updatePositions(self):
  213. self.prepareGeometryChange()
  214. # Check Text Name size
  215. app_name_size = QFontMetrics(self.m_font_name).width(self.m_group_name) + 30
  216. self.p_width = max(200 if self.m_plugin_inline else 50, app_name_size)
  217. # Get Port List
  218. port_list = []
  219. for port in canvas.port_list:
  220. if port.group_id == self.m_group_id and port.port_id in self.m_port_list_ids:
  221. port_list.append(port)
  222. if len(port_list) == 0:
  223. self.p_height = canvas.theme.box_header_height
  224. self.p_width_in = 0
  225. self.p_width_out = 0
  226. else:
  227. max_in_width = max_out_width = 0
  228. port_spacing = canvas.theme.port_height + canvas.theme.port_spacing
  229. # Get Max Box Width, vertical ports re-positioning
  230. port_types = [PORT_TYPE_AUDIO_JACK, PORT_TYPE_MIDI_JACK, PORT_TYPE_MIDI_ALSA, PORT_TYPE_PARAMETER]
  231. last_in_type = last_out_type = PORT_TYPE_NULL
  232. last_in_pos = last_out_pos = canvas.theme.box_header_height + canvas.theme.box_header_spacing
  233. for port_type in port_types:
  234. for port in port_list:
  235. if port.port_type != port_type:
  236. continue
  237. size = QFontMetrics(self.m_font_port).width(port.port_name)
  238. if port.port_mode == PORT_MODE_INPUT:
  239. max_in_width = max(max_in_width, size)
  240. if port.port_type != last_in_type:
  241. if last_in_type != PORT_TYPE_NULL:
  242. last_in_pos += canvas.theme.port_spacingT
  243. last_in_type = port.port_type
  244. port.widget.setY(last_in_pos)
  245. last_in_pos += port_spacing
  246. elif port.port_mode == PORT_MODE_OUTPUT:
  247. max_out_width = max(max_out_width, size)
  248. if port.port_type != last_out_type:
  249. if last_out_type != PORT_TYPE_NULL:
  250. last_out_pos += canvas.theme.port_spacingT
  251. last_out_type = port.port_type
  252. port.widget.setY(last_out_pos)
  253. last_out_pos += port_spacing
  254. self.p_width = max(self.p_width, (100 if self.m_plugin_inline else 30) + max_in_width + max_out_width)
  255. self.p_width_in = max_in_width
  256. self.p_width_out = max_out_width
  257. #if self.m_plugin_inline:
  258. #self.p_width += 10
  259. # Horizontal ports re-positioning
  260. inX = canvas.theme.port_offset
  261. outX = self.p_width - max_out_width - canvas.theme.port_offset - 12
  262. for port_type in port_types:
  263. for port in port_list:
  264. if port.port_mode == PORT_MODE_INPUT:
  265. port.widget.setX(inX)
  266. port.widget.setPortWidth(max_in_width)
  267. elif port.port_mode == PORT_MODE_OUTPUT:
  268. port.widget.setX(outX)
  269. port.widget.setPortWidth(max_out_width)
  270. self.p_height = max(last_in_pos, last_out_pos)
  271. self.p_height += max(canvas.theme.port_spacing, canvas.theme.port_spacingT) - canvas.theme.port_spacing
  272. self.p_height += canvas.theme.box_pen.widthF()
  273. self.repaintLines(True)
  274. self.update()
  275. def repaintLines(self, forced=False):
  276. if self.pos() != self.m_last_pos or forced:
  277. for connection in self.m_connection_lines:
  278. connection.line.updateLinePos()
  279. self.m_last_pos = self.pos()
  280. def resetLinesZValue(self):
  281. for connection in canvas.connection_list:
  282. if connection.port_out_id in self.m_port_list_ids and connection.port_in_id in self.m_port_list_ids:
  283. z_value = canvas.last_z_value
  284. else:
  285. z_value = canvas.last_z_value - 1
  286. connection.widget.setZValue(z_value)
  287. def type(self):
  288. return CanvasBoxType
  289. def contextMenuEvent(self, event):
  290. event.accept()
  291. menu = QMenu()
  292. # Conenct menu stuff
  293. connMenu = QMenu("Connect", menu)
  294. our_port_types = []
  295. our_port_outs = {
  296. PORT_TYPE_AUDIO_JACK: [],
  297. PORT_TYPE_MIDI_JACK: [],
  298. PORT_TYPE_MIDI_ALSA: [],
  299. }
  300. for port in canvas.port_list:
  301. if port.group_id != self.m_group_id:
  302. continue
  303. if port.port_mode != PORT_MODE_OUTPUT:
  304. continue
  305. if port.port_id not in self.m_port_list_ids:
  306. continue
  307. if port.port_type not in our_port_types:
  308. our_port_types.append(port.port_type)
  309. our_port_outs[port.port_type].append((port.group_id, port.port_id))
  310. if len(our_port_types) != 0:
  311. act_x_conn = None
  312. for group in canvas.group_list:
  313. if self.m_group_id == group.group_id:
  314. continue
  315. has_ports = False
  316. target_ports = {
  317. PORT_TYPE_AUDIO_JACK: [],
  318. PORT_TYPE_MIDI_JACK: [],
  319. PORT_TYPE_MIDI_ALSA: [],
  320. }
  321. for port in canvas.port_list:
  322. if port.group_id != group.group_id:
  323. continue
  324. if port.port_mode != PORT_MODE_INPUT:
  325. continue
  326. if port.port_type not in our_port_types:
  327. continue
  328. has_ports = True
  329. target_ports[port.port_type].append((port.group_id, port.port_id))
  330. if not has_ports:
  331. continue
  332. act_x_conn = connMenu.addAction(group.group_name)
  333. act_x_conn.setData((our_port_outs, target_ports))
  334. act_x_conn.triggered.connect(canvas.qobject.PortContextMenuConnect)
  335. if act_x_conn is None:
  336. act_x_disc = connMenu.addAction("Nothing to connect to")
  337. act_x_disc.setEnabled(False)
  338. else:
  339. act_x_disc = connMenu.addAction("No output ports")
  340. act_x_disc.setEnabled(False)
  341. # Disconnect menu stuff
  342. discMenu = QMenu("Disconnect", menu)
  343. conn_list = []
  344. conn_list_ids = []
  345. for port_id in self.m_port_list_ids:
  346. tmp_conn_list = CanvasGetPortConnectionList(self.m_group_id, port_id)
  347. for tmp_conn_id, tmp_group_id, tmp_port_id in tmp_conn_list:
  348. if tmp_conn_id not in conn_list_ids:
  349. conn_list.append((tmp_conn_id, tmp_group_id, tmp_port_id))
  350. conn_list_ids.append(tmp_conn_id)
  351. if len(conn_list) > 0:
  352. for conn_id, group_id, port_id in conn_list:
  353. act_x_disc = discMenu.addAction(CanvasGetFullPortName(group_id, port_id))
  354. act_x_disc.setData(conn_id)
  355. act_x_disc.triggered.connect(canvas.qobject.PortContextMenuDisconnect)
  356. else:
  357. act_x_disc = discMenu.addAction("No connections")
  358. act_x_disc.setEnabled(False)
  359. menu.addMenu(connMenu)
  360. menu.addMenu(discMenu)
  361. act_x_disc_all = menu.addAction("Disconnect &All")
  362. act_x_sep1 = menu.addSeparator()
  363. act_x_info = menu.addAction("Info")
  364. act_x_rename = menu.addAction("Rename")
  365. act_x_sep2 = menu.addSeparator()
  366. act_x_split_join = menu.addAction("Join" if self.m_splitted else "Split")
  367. if not features.group_info:
  368. act_x_info.setVisible(False)
  369. if not features.group_rename:
  370. act_x_rename.setVisible(False)
  371. if not (features.group_info and features.group_rename):
  372. act_x_sep1.setVisible(False)
  373. if self.m_plugin_id >= 0 and self.m_plugin_id <= MAX_PLUGIN_ID_ALLOWED:
  374. menu.addSeparator()
  375. act_p_edit = menu.addAction("Edit")
  376. act_p_ui = menu.addAction("Show Custom UI")
  377. menu.addSeparator()
  378. act_p_clone = menu.addAction("Clone")
  379. act_p_rename = menu.addAction("Rename...")
  380. act_p_replace = menu.addAction("Replace...")
  381. act_p_remove = menu.addAction("Remove")
  382. if not self.m_plugin_ui:
  383. act_p_ui.setVisible(False)
  384. else:
  385. act_p_edit = act_p_ui = None
  386. act_p_clone = act_p_rename = None
  387. act_p_replace = act_p_remove = None
  388. haveIns = haveOuts = False
  389. for port in canvas.port_list:
  390. if port.group_id == self.m_group_id and port.port_id in self.m_port_list_ids:
  391. if port.port_mode == PORT_MODE_INPUT:
  392. haveIns = True
  393. elif port.port_mode == PORT_MODE_OUTPUT:
  394. haveOuts = True
  395. if not (self.m_splitted or bool(haveIns and haveOuts)):
  396. act_x_sep2.setVisible(False)
  397. act_x_split_join.setVisible(False)
  398. act_selected = menu.exec_(event.screenPos())
  399. if act_selected is None:
  400. pass
  401. elif act_selected == act_x_disc_all:
  402. for conn_id in conn_list_ids:
  403. canvas.callback(ACTION_PORTS_DISCONNECT, conn_id, 0, "")
  404. elif act_selected == act_x_info:
  405. canvas.callback(ACTION_GROUP_INFO, self.m_group_id, 0, "")
  406. elif act_selected == act_x_rename:
  407. canvas.callback(ACTION_GROUP_RENAME, self.m_group_id, 0, "")
  408. elif act_selected == act_x_split_join:
  409. if self.m_splitted:
  410. canvas.callback(ACTION_GROUP_JOIN, self.m_group_id, 0, "")
  411. else:
  412. canvas.callback(ACTION_GROUP_SPLIT, self.m_group_id, 0, "")
  413. elif act_selected == act_p_edit:
  414. canvas.callback(ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "")
  415. elif act_selected == act_p_ui:
  416. canvas.callback(ACTION_PLUGIN_SHOW_UI, self.m_plugin_id, 0, "")
  417. elif act_selected == act_p_clone:
  418. canvas.callback(ACTION_PLUGIN_CLONE, self.m_plugin_id, 0, "")
  419. elif act_selected == act_p_rename:
  420. canvas.callback(ACTION_PLUGIN_RENAME, self.m_plugin_id, 0, "")
  421. elif act_selected == act_p_replace:
  422. canvas.callback(ACTION_PLUGIN_REPLACE, self.m_plugin_id, 0, "")
  423. elif act_selected == act_p_remove:
  424. canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
  425. def keyPressEvent(self, event):
  426. if self.m_plugin_id >= 0 and event.key() == Qt.Key_Delete:
  427. event.accept()
  428. canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
  429. return
  430. QGraphicsItem.keyPressEvent(self, event)
  431. def hoverEnterEvent(self, event):
  432. if options.auto_select_items:
  433. if len(canvas.scene.selectedItems()) > 0:
  434. canvas.scene.clearSelection()
  435. self.setSelected(True)
  436. QGraphicsItem.hoverEnterEvent(self, event)
  437. def mouseDoubleClickEvent(self, event):
  438. if self.m_plugin_id >= 0:
  439. event.accept()
  440. canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "")
  441. return
  442. QGraphicsItem.mouseDoubleClickEvent(self, event)
  443. def mousePressEvent(self, event):
  444. canvas.last_z_value += 1
  445. self.setZValue(canvas.last_z_value)
  446. self.resetLinesZValue()
  447. self.m_cursor_moving = False
  448. if event.button() == Qt.RightButton:
  449. event.accept()
  450. canvas.scene.clearSelection()
  451. self.setSelected(True)
  452. self.m_mouse_down = False
  453. return
  454. elif event.button() == Qt.LeftButton:
  455. if self.sceneBoundingRect().contains(event.scenePos()):
  456. self.m_mouse_down = True
  457. else:
  458. # FIXME: Check if still valid: Fix a weird Qt behaviour with right-click mouseMove
  459. self.m_mouse_down = False
  460. event.ignore()
  461. return
  462. else:
  463. self.m_mouse_down = False
  464. QGraphicsItem.mousePressEvent(self, event)
  465. def mouseMoveEvent(self, event):
  466. if self.m_mouse_down:
  467. if not self.m_cursor_moving:
  468. self.setCursor(QCursor(Qt.SizeAllCursor))
  469. self.m_cursor_moving = True
  470. self.repaintLines()
  471. QGraphicsItem.mouseMoveEvent(self, event)
  472. def mouseReleaseEvent(self, event):
  473. if self.m_cursor_moving:
  474. self.unsetCursor()
  475. QTimer.singleShot(0, self.fixPos)
  476. self.m_mouse_down = False
  477. self.m_cursor_moving = False
  478. QGraphicsItem.mouseReleaseEvent(self, event)
  479. def fixPos(self):
  480. self.setX(round(self.x()))
  481. self.setY(round(self.y()))
  482. def boundingRect(self):
  483. return QRectF(0, 0, self.p_width, self.p_height)
  484. def paint(self, painter, option, widget):
  485. painter.save()
  486. painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
  487. rect = QRectF(0, 0, self.p_width, self.p_height)
  488. # Draw rectangle
  489. pen = QPen(canvas.theme.box_pen_sel if self.isSelected() else canvas.theme.box_pen)
  490. pen.setWidthF(pen.widthF() + 0.00001)
  491. painter.setPen(pen)
  492. lineHinting = pen.widthF() / 2
  493. if canvas.theme.box_bg_type == Theme.THEME_BG_GRADIENT:
  494. box_gradient = QLinearGradient(0, 0, 0, self.p_height)
  495. box_gradient.setColorAt(0, canvas.theme.box_bg_1)
  496. box_gradient.setColorAt(1, canvas.theme.box_bg_2)
  497. painter.setBrush(box_gradient)
  498. else:
  499. painter.setBrush(canvas.theme.box_bg_1)
  500. rect.adjust(lineHinting, lineHinting, -lineHinting, -lineHinting)
  501. painter.drawRect(rect)
  502. # Draw plugin inline display if supported
  503. if self.m_plugin_id >= 0 and self.m_plugin_id <= MAX_PLUGIN_ID_ALLOWED and self.m_plugin_inline:
  504. inwidth = self.p_width - self.p_width_in - self.p_width_out - 16
  505. inheight = self.p_height - canvas.theme.box_header_height
  506. scaling = canvas.scene.getScaleFactor() * canvas.scene.getDevicePixelRatioF()
  507. size = "%i:%i" % (int(inwidth*scaling), int(inheight*scaling))
  508. data = canvas.callback(ACTION_INLINE_DISPLAY, self.m_plugin_id, 0, size)
  509. if data is not None:
  510. image = QImage(data['data'], data['width'], data['height'], data['stride'], QImage.Format_ARGB32)
  511. srcx = self.p_width_in + 7
  512. srcy = int(canvas.theme.box_header_height
  513. + (self.p_height - canvas.theme.box_header_height) / 2
  514. - data['height'] / 2 / scaling - 1)
  515. painter.drawImage(QRectF(srcx, srcy, inwidth, inheight), image)
  516. # Draw pixmap header
  517. rect.setHeight(canvas.theme.box_header_height)
  518. if canvas.theme.box_header_pixmap:
  519. painter.setPen(Qt.NoPen)
  520. painter.setBrush(canvas.theme.box_bg_2)
  521. # outline
  522. rect.adjust(lineHinting, lineHinting, -lineHinting, -lineHinting)
  523. painter.drawRect(rect)
  524. rect.adjust(1, 1, -1, 0)
  525. painter.drawTiledPixmap(rect, canvas.theme.box_header_pixmap, rect.topLeft())
  526. # Draw text
  527. painter.setFont(self.m_font_name)
  528. if self.isSelected():
  529. painter.setPen(canvas.theme.box_text_sel)
  530. else:
  531. painter.setPen(canvas.theme.box_text)
  532. if canvas.theme.box_use_icon:
  533. textPos = QPointF(25, canvas.theme.box_text_ypos)
  534. else:
  535. appNameSize = QFontMetrics(self.m_font_name).width(self.m_group_name)
  536. rem = self.p_width - appNameSize
  537. textPos = QPointF(rem/2, canvas.theme.box_text_ypos)
  538. painter.drawText(textPos, self.m_group_name)
  539. self.repaintLines()
  540. painter.restore()
  541. # ------------------------------------------------------------------------------------------------------------