Cleanup
[carla.git] / source / frontend / patchcanvas / canvasbox.py
blobf7ee2c158bfc3ee02659293f413f7ab1b776b7f9
1 #!/usr/bin/env python3
2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ------------------------------------------------------------------------------------------------------------
6 # Imports (Global)
8 from qt_compat import qt_config
10 if qt_config == 5:
11 from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, QT_VERSION, Qt, QPointF, QRectF, QTimer
12 from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen
13 from PyQt5.QtSvg import QGraphicsSvgItem
14 from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu
15 elif qt_config == 6:
16 from PyQt6.QtCore import pyqtSignal, pyqtSlot, qCritical, QT_VERSION, Qt, QPointF, QRectF, QTimer
17 from PyQt6.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen
18 from PyQt6.QtSvgWidgets import QGraphicsSvgItem
19 from PyQt6.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu
21 # ------------------------------------------------------------------------------------------------------------
22 # Backwards-compatible horizontalAdvance/width call, depending on Qt version
24 def fontHorizontalAdvance(font, string):
25 if QT_VERSION >= 0x50b00:
26 return QFontMetrics(font).horizontalAdvance(string)
27 return QFontMetrics(font).width(string)
29 # ------------------------------------------------------------------------------------------------------------
30 # Imports (Custom)
32 from . import (
33 canvas,
34 features,
35 options,
36 port_dict_t,
37 CanvasBoxType,
38 ANTIALIASING_FULL,
39 ACTION_PLUGIN_EDIT,
40 ACTION_PLUGIN_SHOW_UI,
41 ACTION_PLUGIN_CLONE,
42 ACTION_PLUGIN_REMOVE,
43 ACTION_PLUGIN_RENAME,
44 ACTION_PLUGIN_REPLACE,
45 ACTION_GROUP_INFO,
46 ACTION_GROUP_JOIN,
47 ACTION_GROUP_SPLIT,
48 ACTION_GROUP_RENAME,
49 ACTION_PORTS_DISCONNECT,
50 ACTION_INLINE_DISPLAY,
51 EYECANDY_FULL,
52 PORT_MODE_NULL,
53 PORT_MODE_INPUT,
54 PORT_MODE_OUTPUT,
55 PORT_TYPE_NULL,
56 PORT_TYPE_AUDIO_JACK,
57 PORT_TYPE_MIDI_ALSA,
58 PORT_TYPE_MIDI_JACK,
59 PORT_TYPE_PARAMETER,
60 MAX_PLUGIN_ID_ALLOWED,
63 from .canvasboxshadow import CanvasBoxShadow
64 from .canvasicon import CanvasIcon
65 from .canvasport import CanvasPort
66 from .theme import Theme
67 from .utils import CanvasItemFX, CanvasGetFullPortName, CanvasGetPortConnectionList
69 # ------------------------------------------------------------------------------------------------------------
71 class cb_line_t(object):
72 def __init__(self, line, connection_id):
73 self.line = line
74 self.connection_id = connection_id
76 # ------------------------------------------------------------------------------------------------------------
78 class CanvasBox(QGraphicsObject):
79 # signals
80 positionChanged = pyqtSignal(int, bool, int, int)
82 # enums
83 INLINE_DISPLAY_DISABLED = 0
84 INLINE_DISPLAY_ENABLED = 1
85 INLINE_DISPLAY_CACHED = 2
87 def __init__(self, group_id, group_name, icon, parent=None):
88 QGraphicsObject.__init__(self)
89 self.setParentItem(parent)
91 # Save Variables, useful for later
92 self.m_group_id = group_id
93 self.m_group_name = group_name
95 # plugin Id, < 0 if invalid
96 self.m_plugin_id = -1
97 self.m_plugin_ui = False
98 self.m_plugin_inline = self.INLINE_DISPLAY_DISABLED
100 # Base Variables
101 self.p_width = 50
102 self.p_width_in = 0
103 self.p_width_out = 0
104 self.p_height = canvas.theme.box_header_height + canvas.theme.box_header_spacing + 1
106 self.m_last_pos = QPointF()
107 self.m_split = False
108 self.m_split_mode = PORT_MODE_NULL
110 self.m_cursor_moving = False
111 self.m_forced_split = False
112 self.m_mouse_down = False
113 self.m_inline_image = None
114 self.m_inline_scaling = 1.0
115 self.m_inline_first = True
116 self.m_will_signal_pos_change = False
118 self.m_port_list_ids = []
119 self.m_connection_lines = []
121 # Set Font
122 self.m_font_name = QFont()
123 self.m_font_name.setFamily(canvas.theme.box_font_name)
124 self.m_font_name.setPixelSize(canvas.theme.box_font_size)
125 self.m_font_name.setWeight(canvas.theme.box_font_state)
127 self.m_font_port = QFont()
128 self.m_font_port.setFamily(canvas.theme.port_font_name)
129 self.m_font_port.setPixelSize(canvas.theme.port_font_size)
130 self.m_font_port.setWeight(canvas.theme.port_font_state)
132 # Icon
133 if canvas.theme.box_use_icon:
134 self.icon_svg = CanvasIcon(icon, self.m_group_name, self)
135 else:
136 self.icon_svg = None
138 # Shadow
139 if options.eyecandy and QT_VERSION >= 0x50c00:
140 self.shadow = CanvasBoxShadow(self.toGraphicsObject())
141 self.shadow.setFakeParent(self)
142 self.setGraphicsEffect(self.shadow)
143 else:
144 self.shadow = None
146 # Final touches
147 self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
149 # Wait for at least 1 port
150 if options.auto_hide_groups:
151 self.setVisible(False)
153 if options.auto_select_items:
154 self.setAcceptHoverEvents(True)
156 self.updatePositions()
158 self.visibleChanged.connect(self.slot_signalPositionChangedLater)
159 self.xChanged.connect(self.slot_signalPositionChangedLater)
160 self.yChanged.connect(self.slot_signalPositionChangedLater)
162 canvas.scene.addItem(self)
163 QTimer.singleShot(0, self.fixPos)
165 def getGroupId(self):
166 return self.m_group_id
168 def getGroupName(self):
169 return self.m_group_name
171 def isSplit(self):
172 return self.m_split
174 def getSplitMode(self):
175 return self.m_split_mode
177 def getPortCount(self):
178 return len(self.m_port_list_ids)
180 def getPortList(self):
181 return self.m_port_list_ids
183 def redrawInlineDisplay(self):
184 if self.m_plugin_inline == self.INLINE_DISPLAY_CACHED:
185 self.m_plugin_inline = self.INLINE_DISPLAY_ENABLED
186 self.update()
188 def removeAsPlugin(self):
189 #del self.m_inline_image
190 #self.m_inline_image = None
191 #self.m_inline_scaling = 1.0
193 self.m_plugin_id = -1
194 self.m_plugin_ui = False
195 self.m_plugin_inline = self.INLINE_DISPLAY_DISABLED
197 def setAsPlugin(self, plugin_id, hasUI, hasInlineDisplay):
198 if hasInlineDisplay and not options.inline_displays:
199 hasInlineDisplay = False
201 if not hasInlineDisplay:
202 self.m_inline_image = None
203 self.m_inline_scaling = 1.0
205 self.m_plugin_id = plugin_id
206 self.m_plugin_ui = hasUI
207 self.m_plugin_inline = self.INLINE_DISPLAY_ENABLED if hasInlineDisplay else self.INLINE_DISPLAY_DISABLED
208 self.update()
210 def setIcon(self, icon):
211 if self.icon_svg is not None:
212 self.icon_svg.setIcon(icon, self.m_group_name)
214 def setSplit(self, split, mode=PORT_MODE_NULL):
215 self.m_split = split
216 self.m_split_mode = mode
218 def setGroupName(self, group_name):
219 self.m_group_name = group_name
220 self.updatePositions()
222 def setShadowOpacity(self, opacity):
223 if self.shadow is not None:
224 self.shadow.setOpacity(opacity)
226 def addPortFromGroup(self, port_id, port_mode, port_type, port_name, is_alternate):
227 if len(self.m_port_list_ids) == 0:
228 if options.auto_hide_groups:
229 if options.eyecandy == EYECANDY_FULL:
230 CanvasItemFX(self, True, False)
231 self.blockSignals(True)
232 self.setVisible(True)
233 self.blockSignals(False)
235 new_widget = CanvasPort(self.m_group_id, port_id, port_name, port_mode, port_type, is_alternate, self)
237 port_dict = port_dict_t()
238 port_dict.group_id = self.m_group_id
239 port_dict.port_id = port_id
240 port_dict.port_name = port_name
241 port_dict.port_mode = port_mode
242 port_dict.port_type = port_type
243 port_dict.is_alternate = is_alternate
244 port_dict.widget = new_widget
246 self.m_port_list_ids.append(port_id)
248 return new_widget
250 def removePortFromGroup(self, port_id):
251 if port_id in self.m_port_list_ids:
252 self.m_port_list_ids.remove(port_id)
253 else:
254 qCritical("PatchCanvas::CanvasBox.removePort(%i) - unable to find port to remove" % port_id)
255 return
257 if len(self.m_port_list_ids) > 0:
258 self.updatePositions()
260 elif self.isVisible():
261 if options.auto_hide_groups:
262 if options.eyecandy == EYECANDY_FULL:
263 CanvasItemFX(self, False, False)
264 else:
265 self.blockSignals(True)
266 self.setVisible(False)
267 self.blockSignals(False)
269 def addLineFromGroup(self, line, connection_id):
270 new_cbline = cb_line_t(line, connection_id)
271 self.m_connection_lines.append(new_cbline)
273 def removeLineFromGroup(self, connection_id):
274 for connection in self.m_connection_lines:
275 if connection.connection_id == connection_id:
276 self.m_connection_lines.remove(connection)
277 return
278 qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id)
280 def checkItemPos(self):
281 if canvas.size_rect.isNull():
282 return
284 pos = self.scenePos()
285 if (canvas.size_rect.contains(pos) and
286 canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))):
287 return
289 if pos.x() < canvas.size_rect.x():
290 self.setPos(canvas.size_rect.x(), pos.y())
291 elif pos.x() + self.p_width > canvas.size_rect.width():
292 self.setPos(canvas.size_rect.width() - self.p_width, pos.y())
294 pos = self.scenePos()
295 if pos.y() < canvas.size_rect.y():
296 self.setPos(pos.x(), canvas.size_rect.y())
297 elif pos.y() + self.p_height > canvas.size_rect.height():
298 self.setPos(pos.x(), canvas.size_rect.height() - self.p_height)
300 def removeIconFromScene(self):
301 if self.icon_svg is None:
302 return
304 item = self.icon_svg
305 self.icon_svg = None
306 canvas.scene.removeItem(item)
307 del item
309 def updatePositions(self):
310 self.prepareGeometryChange()
312 # Check Text Name size
313 app_name_size = fontHorizontalAdvance(self.m_font_name, self.m_group_name) + 30
314 self.p_width = max(50, app_name_size)
316 # Get Port List
317 port_list = []
318 for port in canvas.port_list:
319 if port.group_id == self.m_group_id and port.port_id in self.m_port_list_ids:
320 port_list.append(port)
322 if len(port_list) == 0:
323 self.p_height = canvas.theme.box_header_height
324 self.p_width_in = 0
325 self.p_width_out = 0
326 else:
327 max_in_width = max_out_width = 0
328 port_spacing = canvas.theme.port_height + canvas.theme.port_spacing
330 # Get Max Box Width, vertical ports re-positioning
331 port_types = (PORT_TYPE_AUDIO_JACK, PORT_TYPE_MIDI_JACK, PORT_TYPE_MIDI_ALSA, PORT_TYPE_PARAMETER)
332 last_in_type = last_out_type = PORT_TYPE_NULL
333 last_in_pos = last_out_pos = canvas.theme.box_header_height + canvas.theme.box_header_spacing
335 for port_type in port_types:
336 for port in port_list:
337 if port.port_type != port_type:
338 continue
340 size = fontHorizontalAdvance(self.m_font_port, port.port_name)
342 if port.port_mode == PORT_MODE_INPUT:
343 max_in_width = max(max_in_width, size)
344 if port.port_type != last_in_type:
345 if last_in_type != PORT_TYPE_NULL:
346 last_in_pos += canvas.theme.port_spacingT
347 last_in_type = port.port_type
348 port.widget.setY(last_in_pos)
349 last_in_pos += port_spacing
351 elif port.port_mode == PORT_MODE_OUTPUT:
352 max_out_width = max(max_out_width, size)
353 if port.port_type != last_out_type:
354 if last_out_type != PORT_TYPE_NULL:
355 last_out_pos += canvas.theme.port_spacingT
356 last_out_type = port.port_type
357 port.widget.setY(last_out_pos)
358 last_out_pos += port_spacing
360 self.p_width = max(self.p_width, 30 + max_in_width + max_out_width)
361 self.p_width_in = max_in_width
362 self.p_width_out = max_out_width
364 self.p_height = max(last_in_pos, last_out_pos)
365 self.p_height += max(canvas.theme.port_spacing, canvas.theme.port_spacingT) - canvas.theme.port_spacing
366 self.p_height += canvas.theme.box_pen.width()
368 self.repositionPorts(port_list)
370 self.repaintLines(True)
371 self.update()
373 def repositionPorts(self, port_list = None):
374 if port_list is None:
375 port_list = []
376 for port in canvas.port_list:
377 if port.group_id == self.m_group_id and port.port_id in self.m_port_list_ids:
378 port_list.append(port)
380 # Horizontal ports re-positioning
381 inX = canvas.theme.port_offset
382 outX = self.p_width - self.p_width_out - canvas.theme.port_offset - 12
383 for port in port_list:
384 if port.port_mode == PORT_MODE_INPUT:
385 port.widget.setX(inX)
386 port.widget.setPortWidth(self.p_width_in)
388 elif port.port_mode == PORT_MODE_OUTPUT:
389 port.widget.setX(outX)
390 port.widget.setPortWidth(self.p_width_out)
392 def repaintLines(self, forced=False):
393 if self.pos() != self.m_last_pos or forced:
394 for connection in self.m_connection_lines:
395 connection.line.updateLinePos()
397 self.m_last_pos = self.pos()
399 def resetLinesZValue(self):
400 for connection in canvas.connection_list:
401 if connection.port_out_id in self.m_port_list_ids and connection.port_in_id in self.m_port_list_ids:
402 z_value = canvas.last_z_value
403 else:
404 z_value = canvas.last_z_value - 1
406 connection.widget.setZValue(z_value)
408 def triggerSignalPositionChanged(self):
409 self.positionChanged.emit(self.m_group_id, self.m_split, int(self.x()), int(self.y()))
410 self.m_will_signal_pos_change = False
412 @pyqtSlot()
413 def slot_signalPositionChangedLater(self):
414 if self.m_will_signal_pos_change:
415 return
416 self.m_will_signal_pos_change = True
417 QTimer.singleShot(0, self.triggerSignalPositionChanged)
419 def type(self):
420 return CanvasBoxType
422 def contextMenuEvent(self, event):
423 event.accept()
424 menu = QMenu()
426 # Conenct menu stuff
427 connMenu = QMenu("Connect", menu)
429 our_port_types = []
430 our_port_outs = {
431 PORT_TYPE_AUDIO_JACK: [],
432 PORT_TYPE_MIDI_JACK: [],
433 PORT_TYPE_MIDI_ALSA: [],
434 PORT_TYPE_PARAMETER: [],
436 for port in canvas.port_list:
437 if port.group_id != self.m_group_id:
438 continue
439 if port.port_mode != PORT_MODE_OUTPUT:
440 continue
441 if port.port_id not in self.m_port_list_ids:
442 continue
443 if port.port_type not in our_port_types:
444 our_port_types.append(port.port_type)
445 our_port_outs[port.port_type].append((port.group_id, port.port_id))
447 if len(our_port_types) != 0:
448 act_x_conn = None
449 for group in canvas.group_list:
450 if self.m_group_id == group.group_id:
451 continue
453 has_ports = False
454 target_ports = {
455 PORT_TYPE_AUDIO_JACK: [],
456 PORT_TYPE_MIDI_JACK: [],
457 PORT_TYPE_MIDI_ALSA: [],
458 PORT_TYPE_PARAMETER: [],
461 for port in canvas.port_list:
462 if port.group_id != group.group_id:
463 continue
464 if port.port_mode != PORT_MODE_INPUT:
465 continue
466 if port.port_type not in our_port_types:
467 continue
468 has_ports = True
469 target_ports[port.port_type].append((port.group_id, port.port_id))
471 if not has_ports:
472 continue
474 act_x_conn = connMenu.addAction(group.group_name)
475 act_x_conn.setData((our_port_outs, target_ports))
476 act_x_conn.triggered.connect(canvas.qobject.PortContextMenuConnect)
478 if act_x_conn is None:
479 act_x_disc = connMenu.addAction("Nothing to connect to")
480 act_x_disc.setEnabled(False)
482 else:
483 act_x_disc = connMenu.addAction("No output ports")
484 act_x_disc.setEnabled(False)
486 # Disconnect menu stuff
487 discMenu = QMenu("Disconnect", menu)
489 conn_list = []
490 conn_list_ids = []
492 for port_id in self.m_port_list_ids:
493 tmp_conn_list = CanvasGetPortConnectionList(self.m_group_id, port_id)
494 for tmp_conn_id, tmp_group_id, tmp_port_id in tmp_conn_list:
495 if tmp_conn_id not in conn_list_ids:
496 conn_list.append((tmp_conn_id, tmp_group_id, tmp_port_id))
497 conn_list_ids.append(tmp_conn_id)
499 if len(conn_list) > 0:
500 for conn_id, group_id, port_id in conn_list:
501 act_x_disc = discMenu.addAction(CanvasGetFullPortName(group_id, port_id))
502 act_x_disc.setData(conn_id)
503 act_x_disc.triggered.connect(canvas.qobject.PortContextMenuDisconnect)
504 else:
505 act_x_disc = discMenu.addAction("No connections")
506 act_x_disc.setEnabled(False)
508 menu.addMenu(connMenu)
509 menu.addMenu(discMenu)
510 act_x_disc_all = menu.addAction("Disconnect &All")
511 act_x_sep1 = menu.addSeparator()
512 act_x_info = menu.addAction("Info")
513 act_x_rename = menu.addAction("Rename")
514 act_x_sep2 = menu.addSeparator()
515 act_x_split_join = menu.addAction("Join" if self.m_split else "Split")
517 if not features.group_info:
518 act_x_info.setVisible(False)
520 if not features.group_rename:
521 act_x_rename.setVisible(False)
523 if not (features.group_info and features.group_rename):
524 act_x_sep1.setVisible(False)
526 if self.m_plugin_id >= 0 and self.m_plugin_id <= MAX_PLUGIN_ID_ALLOWED:
527 menu.addSeparator()
528 act_p_edit = menu.addAction("Edit")
529 act_p_ui = menu.addAction("Show Custom UI")
530 menu.addSeparator()
531 act_p_clone = menu.addAction("Clone")
532 act_p_rename = menu.addAction("Rename...")
533 act_p_replace = menu.addAction("Replace...")
534 act_p_remove = menu.addAction("Remove")
536 if not self.m_plugin_ui:
537 act_p_ui.setVisible(False)
539 else:
540 act_p_edit = act_p_ui = None
541 act_p_clone = act_p_rename = None
542 act_p_replace = act_p_remove = None
544 haveIns = haveOuts = False
545 for port in canvas.port_list:
546 if port.group_id == self.m_group_id and port.port_id in self.m_port_list_ids:
547 if port.port_mode == PORT_MODE_INPUT:
548 haveIns = True
549 elif port.port_mode == PORT_MODE_OUTPUT:
550 haveOuts = True
552 if not (self.m_split or bool(haveIns and haveOuts)):
553 act_x_sep2.setVisible(False)
554 act_x_split_join.setVisible(False)
556 act_selected = menu.exec_(event.screenPos())
558 if act_selected is None:
559 pass
561 elif act_selected == act_x_disc_all:
562 for conn_id in conn_list_ids:
563 canvas.callback(ACTION_PORTS_DISCONNECT, conn_id, 0, "")
565 elif act_selected == act_x_info:
566 canvas.callback(ACTION_GROUP_INFO, self.m_group_id, 0, "")
568 elif act_selected == act_x_rename:
569 canvas.callback(ACTION_GROUP_RENAME, self.m_group_id, 0, "")
571 elif act_selected == act_x_split_join:
572 if self.m_split:
573 canvas.callback(ACTION_GROUP_JOIN, self.m_group_id, 0, "")
574 else:
575 canvas.callback(ACTION_GROUP_SPLIT, self.m_group_id, 0, "")
577 elif act_selected == act_p_edit:
578 canvas.callback(ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "")
580 elif act_selected == act_p_ui:
581 canvas.callback(ACTION_PLUGIN_SHOW_UI, self.m_plugin_id, 0, "")
583 elif act_selected == act_p_clone:
584 canvas.callback(ACTION_PLUGIN_CLONE, self.m_plugin_id, 0, "")
586 elif act_selected == act_p_rename:
587 canvas.callback(ACTION_PLUGIN_RENAME, self.m_plugin_id, 0, "")
589 elif act_selected == act_p_replace:
590 canvas.callback(ACTION_PLUGIN_REPLACE, self.m_plugin_id, 0, "")
592 elif act_selected == act_p_remove:
593 canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
595 def keyPressEvent(self, event):
596 if self.m_plugin_id >= 0 and event.key() == Qt.Key_Delete:
597 event.accept()
598 canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "")
599 return
600 QGraphicsObject.keyPressEvent(self, event)
602 def hoverEnterEvent(self, event):
603 if options.auto_select_items:
604 if len(canvas.scene.selectedItems()) > 0:
605 canvas.scene.clearSelection()
606 self.setSelected(True)
607 QGraphicsObject.hoverEnterEvent(self, event)
609 def mouseDoubleClickEvent(self, event):
610 if self.m_plugin_id >= 0:
611 event.accept()
612 canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "")
613 return
615 QGraphicsObject.mouseDoubleClickEvent(self, event)
617 def mousePressEvent(self, event):
618 if event.button() == Qt.MiddleButton or event.source() == Qt.MouseEventSynthesizedByApplication:
619 event.ignore()
620 return
622 canvas.last_z_value += 1
623 self.setZValue(canvas.last_z_value)
624 self.resetLinesZValue()
625 self.m_cursor_moving = False
627 if event.button() == Qt.RightButton:
628 event.accept()
629 canvas.scene.clearSelection()
630 self.setSelected(True)
631 self.m_mouse_down = False
632 return
634 elif event.button() == Qt.LeftButton:
635 if self.sceneBoundingRect().contains(event.scenePos()):
636 self.m_mouse_down = True
637 else:
638 # FIXME: Check if still valid: Fix a weird Qt behaviour with right-click mouseMove
639 self.m_mouse_down = False
640 event.ignore()
641 return
643 else:
644 self.m_mouse_down = False
646 QGraphicsObject.mousePressEvent(self, event)
648 def mouseMoveEvent(self, event):
649 if self.m_mouse_down:
650 if not self.m_cursor_moving:
651 self.setCursor(QCursor(Qt.SizeAllCursor))
652 self.m_cursor_moving = True
653 self.repaintLines()
654 QGraphicsObject.mouseMoveEvent(self, event)
656 def mouseReleaseEvent(self, event):
657 if self.m_cursor_moving:
658 self.unsetCursor()
659 QTimer.singleShot(0, self.fixPos)
660 self.m_mouse_down = False
661 self.m_cursor_moving = False
662 QGraphicsObject.mouseReleaseEvent(self, event)
664 def fixPos(self):
665 self.blockSignals(True)
666 self.setX(round(self.x()))
667 self.setY(round(self.y()))
668 self.blockSignals(False)
670 def boundingRect(self):
671 return QRectF(0, 0, self.p_width, self.p_height)
673 def paint(self, painter, option, widget):
674 painter.save()
675 painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
676 rect = QRectF(0, 0, self.p_width, self.p_height)
678 # Draw rectangle
679 pen = QPen(canvas.theme.box_pen_sel if self.isSelected() else canvas.theme.box_pen)
680 pen.setWidthF(pen.widthF() + 0.00001)
681 painter.setPen(pen)
682 lineHinting = pen.widthF() / 2
684 if canvas.theme.box_bg_type == Theme.THEME_BG_GRADIENT:
685 box_gradient = QLinearGradient(0, 0, 0, self.p_height)
686 box_gradient.setColorAt(0, canvas.theme.box_bg_1)
687 box_gradient.setColorAt(1, canvas.theme.box_bg_2)
688 painter.setBrush(box_gradient)
689 else:
690 painter.setBrush(canvas.theme.box_bg_1)
692 rect.adjust(lineHinting, lineHinting, -lineHinting, -lineHinting)
693 painter.drawRect(rect)
695 # Draw plugin inline display if supported
696 self.paintInlineDisplay(painter)
698 # Draw pixmap header
699 rect.setHeight(canvas.theme.box_header_height)
700 if canvas.theme.box_header_pixmap:
701 painter.setPen(Qt.NoPen)
702 painter.setBrush(canvas.theme.box_bg_2)
704 # outline
705 rect.adjust(lineHinting, lineHinting, -lineHinting, -lineHinting)
706 painter.drawRect(rect)
708 rect.adjust(1, 1, -1, 0)
709 painter.drawTiledPixmap(rect, canvas.theme.box_header_pixmap, rect.topLeft())
711 # Draw text
712 painter.setFont(self.m_font_name)
714 if self.isSelected():
715 painter.setPen(canvas.theme.box_text_sel)
716 else:
717 painter.setPen(canvas.theme.box_text)
719 if canvas.theme.box_use_icon:
720 textPos = QPointF(25, canvas.theme.box_text_ypos)
721 else:
722 appNameSize = fontHorizontalAdvance(self.m_font_name, self.m_group_name)
723 rem = self.p_width - appNameSize
724 textPos = QPointF(rem/2, canvas.theme.box_text_ypos)
726 painter.drawText(textPos, self.m_group_name)
728 self.repaintLines()
730 painter.restore()
732 def paintInlineDisplay(self, painter):
733 if self.m_plugin_inline == self.INLINE_DISPLAY_DISABLED:
734 return
735 if not options.inline_displays:
736 return
738 inwidth = self.p_width - 16 - self.p_width_in - self.p_width_out
739 inheight = self.p_height - 3 - canvas.theme.box_header_height - canvas.theme.box_header_spacing - canvas.theme.port_spacing
741 scaling = canvas.scene.getScaleFactor() * canvas.scene.getDevicePixelRatioF()
743 if self.m_plugin_id >= 0 and self.m_plugin_id <= MAX_PLUGIN_ID_ALLOWED and (
744 self.m_plugin_inline == self.INLINE_DISPLAY_ENABLED or self.m_inline_scaling != scaling):
745 if self.m_inline_first:
746 size = "%i:%i" % (int(50*scaling), int(50*scaling))
747 else:
748 size = "%i:%i" % (int(inwidth*scaling), int(inheight*scaling))
749 data = canvas.callback(ACTION_INLINE_DISPLAY, self.m_plugin_id, 0, size)
750 if data is None:
751 return
753 self.m_inline_image = QImage(data['data'], data['width'], data['height'], data['stride'],
754 QImage.Format_ARGB32)
755 self.m_inline_scaling = scaling
756 self.m_plugin_inline = self.INLINE_DISPLAY_CACHED
758 # make room for inline display, in a square shape
759 if self.m_inline_first:
760 self.m_inline_first = False
761 aspectRatio = data['width'] / data['height']
762 self.p_height = int(max(50*scaling, self.p_height))
763 self.p_width += int(max(0, min((80 - 14)*scaling, (inheight-inwidth) * aspectRatio * scaling)))
764 self.repositionPorts()
765 self.repaintLines(True)
766 self.update()
767 return
769 if self.m_inline_image is None:
770 print("ERROR: inline display image is None for", self.m_plugin_id, self.m_group_name)
771 return
773 swidth = self.m_inline_image.width() / scaling
774 sheight = self.m_inline_image.height() / scaling
776 srcx = int(self.p_width_in + (self.p_width - self.p_width_in - self.p_width_out) / 2 - swidth / 2)
777 srcy = int(canvas.theme.box_header_height + canvas.theme.box_header_spacing + 1 + (inheight - sheight) / 2)
779 painter.drawImage(QRectF(srcx, srcy, swidth, sheight), self.m_inline_image)
781 # ------------------------------------------------------------------------------------------------------------