2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ------------------------------------------------------------------------------------------------------------
8 from qt_compat
import qt_config
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
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 # ------------------------------------------------------------------------------------------------------------
40 ACTION_PLUGIN_SHOW_UI
,
44 ACTION_PLUGIN_REPLACE
,
49 ACTION_PORTS_DISCONNECT
,
50 ACTION_INLINE_DISPLAY
,
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
):
74 self
.connection_id
= connection_id
76 # ------------------------------------------------------------------------------------------------------------
78 class CanvasBox(QGraphicsObject
):
80 positionChanged
= pyqtSignal(int, bool, int, int)
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
97 self
.m_plugin_ui
= False
98 self
.m_plugin_inline
= self
.INLINE_DISPLAY_DISABLED
104 self
.p_height
= canvas
.theme
.box_header_height
+ canvas
.theme
.box_header_spacing
+ 1
106 self
.m_last_pos
= QPointF()
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
= []
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
)
133 if canvas
.theme
.box_use_icon
:
134 self
.icon_svg
= CanvasIcon(icon
, self
.m_group_name
, self
)
139 if options
.eyecandy
and QT_VERSION
>= 0x50c00:
140 self
.shadow
= CanvasBoxShadow(self
.toGraphicsObject())
141 self
.shadow
.setFakeParent(self
)
142 self
.setGraphicsEffect(self
.shadow
)
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
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
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
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
):
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
)
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
)
254 qCritical("PatchCanvas::CanvasBox.removePort(%i) - unable to find port to remove" % port_id
)
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)
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
)
278 qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id
)
280 def checkItemPos(self
):
281 if canvas
.size_rect
.isNull():
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
))):
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:
306 canvas
.scene
.removeItem(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
)
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
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
:
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)
373 def repositionPorts(self
, port_list
= None):
374 if port_list
is None:
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
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
413 def slot_signalPositionChangedLater(self
):
414 if self
.m_will_signal_pos_change
:
416 self
.m_will_signal_pos_change
= True
417 QTimer
.singleShot(0, self
.triggerSignalPositionChanged
)
422 def contextMenuEvent(self
, event
):
427 connMenu
= QMenu("Connect", menu
)
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
:
439 if port
.port_mode
!= PORT_MODE_OUTPUT
:
441 if port
.port_id
not in self
.m_port_list_ids
:
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:
449 for group
in canvas
.group_list
:
450 if self
.m_group_id
== group
.group_id
:
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
:
464 if port
.port_mode
!= PORT_MODE_INPUT
:
466 if port
.port_type
not in our_port_types
:
469 target_ports
[port
.port_type
].append((port
.group_id
, port
.port_id
))
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)
483 act_x_disc
= connMenu
.addAction("No output ports")
484 act_x_disc
.setEnabled(False)
486 # Disconnect menu stuff
487 discMenu
= QMenu("Disconnect", menu
)
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
)
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
:
528 act_p_edit
= menu
.addAction("Edit")
529 act_p_ui
= menu
.addAction("Show Custom UI")
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)
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
:
549 elif port
.port_mode
== PORT_MODE_OUTPUT
:
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:
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
:
573 canvas
.callback(ACTION_GROUP_JOIN
, self
.m_group_id
, 0, "")
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
:
598 canvas
.callback(ACTION_PLUGIN_REMOVE
, self
.m_plugin_id
, 0, "")
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:
612 canvas
.callback(ACTION_PLUGIN_SHOW_UI
if self
.m_plugin_ui
else ACTION_PLUGIN_EDIT
, self
.m_plugin_id
, 0, "")
615 QGraphicsObject
.mouseDoubleClickEvent(self
, event
)
617 def mousePressEvent(self
, event
):
618 if event
.button() == Qt
.MiddleButton
or event
.source() == Qt
.MouseEventSynthesizedByApplication
:
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
:
629 canvas
.scene
.clearSelection()
630 self
.setSelected(True)
631 self
.m_mouse_down
= False
634 elif event
.button() == Qt
.LeftButton
:
635 if self
.sceneBoundingRect().contains(event
.scenePos()):
636 self
.m_mouse_down
= True
638 # FIXME: Check if still valid: Fix a weird Qt behaviour with right-click mouseMove
639 self
.m_mouse_down
= False
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
654 QGraphicsObject
.mouseMoveEvent(self
, event
)
656 def mouseReleaseEvent(self
, event
):
657 if self
.m_cursor_moving
:
659 QTimer
.singleShot(0, self
.fixPos
)
660 self
.m_mouse_down
= False
661 self
.m_cursor_moving
= False
662 QGraphicsObject
.mouseReleaseEvent(self
, event
)
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
):
675 painter
.setRenderHint(QPainter
.Antialiasing
, bool(options
.antialiasing
== ANTIALIASING_FULL
))
676 rect
= QRectF(0, 0, self
.p_width
, self
.p_height
)
679 pen
= QPen(canvas
.theme
.box_pen_sel
if self
.isSelected() else canvas
.theme
.box_pen
)
680 pen
.setWidthF(pen
.widthF() + 0.00001)
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
)
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
)
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
)
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())
712 painter
.setFont(self
.m_font_name
)
714 if self
.isSelected():
715 painter
.setPen(canvas
.theme
.box_text_sel
)
717 painter
.setPen(canvas
.theme
.box_text
)
719 if canvas
.theme
.box_use_icon
:
720 textPos
= QPointF(25, canvas
.theme
.box_text_ypos
)
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
)
732 def paintInlineDisplay(self
, painter
):
733 if self
.m_plugin_inline
== self
.INLINE_DISPLAY_DISABLED
:
735 if not options
.inline_displays
:
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
))
748 size
= "%i:%i" % (int(inwidth
*scaling
), int(inheight
*scaling
))
749 data
= canvas
.callback(ACTION_INLINE_DISPLAY
, self
.m_plugin_id
, 0, size
)
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)
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
)
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 # ------------------------------------------------------------------------------------------------------------