Cleanup
[carla.git] / source / frontend / patchcanvas / canvasport.py
blob98f89ad34597434d934714cdcf3d747c58115aa0
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 math import floor
10 from qt_compat import qt_config
12 if qt_config == 5:
13 from PyQt5.QtCore import qCritical, Qt, QLineF, QPointF, QRectF, QTimer
14 from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QPainter, QPainterPath, QPen, QPolygonF
15 from PyQt5.QtWidgets import QGraphicsItem, QMenu
16 elif qt_config == 6:
17 from PyQt6.QtCore import qCritical, Qt, QLineF, QPointF, QRectF, QTimer
18 from PyQt6.QtGui import QCursor, QFont, QFontMetrics, QPainter, QPainterPath, QPen, QPolygonF
19 from PyQt6.QtWidgets import QGraphicsItem, QMenu
21 # ------------------------------------------------------------------------------------------------------------
22 # Imports (Custom)
24 from . import (
25 canvas,
26 features,
27 options,
28 port_mode2str,
29 port_type2str,
30 CanvasPortType,
31 ANTIALIASING_FULL,
32 ACTION_PORT_INFO,
33 ACTION_PORT_RENAME,
34 ACTION_PORTS_CONNECT,
35 ACTION_PORTS_DISCONNECT,
36 PORT_MODE_INPUT,
37 PORT_MODE_OUTPUT,
38 PORT_TYPE_AUDIO_JACK,
39 PORT_TYPE_MIDI_ALSA,
40 PORT_TYPE_MIDI_JACK,
41 PORT_TYPE_PARAMETER,
44 from .canvasbezierlinemov import CanvasBezierLineMov
45 from .canvaslinemov import CanvasLineMov
46 from .theme import Theme
47 from .utils import CanvasGetFullPortName, CanvasGetPortConnectionList
49 # ------------------------------------------------------------------------------------------------------------
51 class CanvasPort(QGraphicsItem):
52 def __init__(self, group_id, port_id, port_name, port_mode, port_type, is_alternate, parent):
53 QGraphicsItem.__init__(self)
54 self.setParentItem(parent)
56 # Save Variables, useful for later
57 self.m_group_id = group_id
58 self.m_port_id = port_id
59 self.m_port_mode = port_mode
60 self.m_port_type = port_type
61 self.m_port_name = port_name
62 self.m_is_alternate = is_alternate
64 # Base Variables
65 self.m_port_width = 15
66 self.m_port_height = canvas.theme.port_height
67 self.m_port_font = QFont()
68 self.m_port_font.setFamily(canvas.theme.port_font_name)
69 self.m_port_font.setPixelSize(canvas.theme.port_font_size)
70 self.m_port_font.setWeight(canvas.theme.port_font_state)
72 self.m_line_mov = None
73 self.m_hover_item = None
75 self.m_mouse_down = False
76 self.m_cursor_moving = False
78 self.setFlags(QGraphicsItem.ItemIsSelectable)
80 if options.auto_select_items:
81 self.setAcceptHoverEvents(True)
83 def getGroupId(self):
84 return self.m_group_id
86 def getPortId(self):
87 return self.m_port_id
89 def getPortMode(self):
90 return self.m_port_mode
92 def getPortType(self):
93 return self.m_port_type
95 def getPortName(self):
96 return self.m_port_name
98 def getFullPortName(self):
99 return self.parentItem().getGroupName() + ":" + self.m_port_name
101 def getPortWidth(self):
102 return self.m_port_width
104 def getPortHeight(self):
105 return self.m_port_height
107 def setPortMode(self, port_mode):
108 self.m_port_mode = port_mode
109 self.update()
111 def setPortType(self, port_type):
112 self.m_port_type = port_type
113 self.update()
115 def setPortName(self, port_name):
116 metrics = QFontMetrics(self.m_port_font)
118 if QT_VERSION >= 0x50b00:
119 width1 = metrics.horizontalAdvance(port_name)
120 width2 = metrics.horizontalAdvance(self.m_port_name)
121 else:
122 width1 = metrics.width(port_name)
123 width2 = metrics.width(self.m_port_name)
125 if width1 < width2:
126 QTimer.singleShot(0, canvas.scene.update)
128 self.m_port_name = port_name
129 self.update()
131 def setPortWidth(self, port_width):
132 if port_width < self.m_port_width:
133 QTimer.singleShot(0, canvas.scene.update)
135 self.m_port_width = port_width
136 self.update()
138 def type(self):
139 return CanvasPortType
141 def hoverEnterEvent(self, event):
142 if options.auto_select_items:
143 self.setSelected(True)
144 QGraphicsItem.hoverEnterEvent(self, event)
146 def hoverLeaveEvent(self, event):
147 if options.auto_select_items:
148 self.setSelected(False)
149 QGraphicsItem.hoverLeaveEvent(self, event)
151 def mousePressEvent(self, event):
152 if event.button() == Qt.MiddleButton or event.source() == Qt.MouseEventSynthesizedByApplication:
153 event.ignore()
154 return
156 if self.m_mouse_down:
157 self.handleMouseRelease()
158 self.m_hover_item = None
159 self.m_mouse_down = bool(event.button() == Qt.LeftButton)
160 self.m_cursor_moving = False
161 QGraphicsItem.mousePressEvent(self, event)
163 def mouseMoveEvent(self, event):
164 if not self.m_mouse_down:
165 QGraphicsItem.mouseMoveEvent(self, event)
166 return
168 event.accept()
170 if not self.m_cursor_moving:
171 self.setCursor(QCursor(Qt.CrossCursor))
172 self.m_cursor_moving = True
174 for connection in canvas.connection_list:
175 if (
176 (connection.group_out_id == self.m_group_id and
177 connection.port_out_id == self.m_port_id)
179 (connection.group_in_id == self.m_group_id and
180 connection.port_in_id == self.m_port_id)
182 connection.widget.setLocked(True)
184 if not self.m_line_mov:
185 if options.use_bezier_lines:
186 self.m_line_mov = CanvasBezierLineMov(self.m_port_mode, self.m_port_type, self)
187 else:
188 self.m_line_mov = CanvasLineMov(self.m_port_mode, self.m_port_type, self)
190 canvas.last_z_value += 1
191 self.m_line_mov.setZValue(canvas.last_z_value)
192 canvas.last_z_value += 1
193 self.parentItem().setZValue(canvas.last_z_value)
195 item = None
196 items = canvas.scene.items(event.scenePos(), Qt.ContainsItemShape, Qt.AscendingOrder)
197 #for i in range(len(items)):
198 for _, itemx in enumerate(items):
199 if itemx.type() != CanvasPortType:
200 continue
201 if itemx == self:
202 continue
203 if item is None or itemx.parentItem().zValue() > item.parentItem().zValue():
204 item = itemx
206 if self.m_hover_item and self.m_hover_item != item:
207 self.m_hover_item.setSelected(False)
209 if item is not None:
210 if item.getPortMode() != self.m_port_mode and item.getPortType() == self.m_port_type:
211 item.setSelected(True)
212 self.m_hover_item = item
213 else:
214 self.m_hover_item = None
215 else:
216 self.m_hover_item = None
218 self.m_line_mov.updateLinePos(event.scenePos())
220 def handleMouseRelease(self):
221 if self.m_mouse_down:
222 if self.m_line_mov is not None:
223 item = self.m_line_mov
224 self.m_line_mov = None
225 canvas.scene.removeItem(item)
226 del item
228 for connection in canvas.connection_list:
229 if (
230 (connection.group_out_id == self.m_group_id and
231 connection.port_out_id == self.m_port_id)
233 (connection.group_in_id == self.m_group_id and
234 connection.port_in_id == self.m_port_id)
236 connection.widget.setLocked(False)
238 if self.m_hover_item:
239 # TODO: a better way to check already existing connection
240 for connection in canvas.connection_list:
241 hover_group_id = self.m_hover_item.getGroupId()
242 hover_port_id = self.m_hover_item.getPortId()
244 # FIXME clean this big if stuff
245 if (
246 (connection.group_out_id == self.m_group_id and
247 connection.port_out_id == self.m_port_id and
248 connection.group_in_id == hover_group_id and
249 connection.port_in_id == hover_port_id)
251 (connection.group_out_id == hover_group_id and
252 connection.port_out_id == hover_port_id and
253 connection.group_in_id == self.m_group_id and
254 connection.port_in_id == self.m_port_id)
256 canvas.callback(ACTION_PORTS_DISCONNECT, connection.connection_id, 0, "")
257 break
258 else:
259 if self.m_port_mode == PORT_MODE_OUTPUT:
260 conn = "%i:%i:%i:%i" % (self.m_group_id, self.m_port_id,
261 self.m_hover_item.getGroupId(), self.m_hover_item.getPortId())
262 canvas.callback(ACTION_PORTS_CONNECT, 0, 0, conn)
263 else:
264 conn = "%i:%i:%i:%i" % (self.m_hover_item.getGroupId(),
265 self.m_hover_item.getPortId(), self.m_group_id, self.m_port_id)
266 canvas.callback(ACTION_PORTS_CONNECT, 0, 0, conn)
268 canvas.scene.clearSelection()
270 if self.m_cursor_moving:
271 self.unsetCursor()
273 self.m_hover_item = None
274 self.m_mouse_down = False
275 self.m_cursor_moving = False
277 def mouseReleaseEvent(self, event):
278 if event.button() == Qt.LeftButton:
279 self.handleMouseRelease()
280 QGraphicsItem.mouseReleaseEvent(self, event)
282 def contextMenuEvent(self, event):
283 event.accept()
285 canvas.scene.clearSelection()
286 self.setSelected(True)
288 menu = QMenu()
289 discMenu = QMenu("Disconnect", menu)
291 conn_list = CanvasGetPortConnectionList(self.m_group_id, self.m_port_id)
293 if len(conn_list) > 0:
294 for conn_id, group_id, port_id in conn_list:
295 act_x_disc = discMenu.addAction(CanvasGetFullPortName(group_id, port_id))
296 act_x_disc.setData(conn_id)
297 act_x_disc.triggered.connect(canvas.qobject.PortContextMenuDisconnect)
298 else:
299 act_x_disc = discMenu.addAction("No connections")
300 act_x_disc.setEnabled(False)
302 menu.addMenu(discMenu)
303 act_x_disc_all = menu.addAction("Disconnect &All")
304 act_x_sep_1 = menu.addSeparator()
305 act_x_info = menu.addAction("Get &Info")
306 act_x_rename = menu.addAction("&Rename")
308 if not features.port_info:
309 act_x_info.setVisible(False)
311 if not features.port_rename:
312 act_x_rename.setVisible(False)
314 if not (features.port_info and features.port_rename):
315 act_x_sep_1.setVisible(False)
317 act_selected = menu.exec_(event.screenPos())
319 if act_selected == act_x_disc_all:
320 self.triggerDisconnect(conn_list)
322 elif act_selected == act_x_info:
323 canvas.callback(ACTION_PORT_INFO, self.m_group_id, self.m_port_id, "")
325 elif act_selected == act_x_rename:
326 canvas.callback(ACTION_PORT_RENAME, self.m_group_id, self.m_port_id, "")
328 def setPortSelected(self, yesno):
329 for connection in canvas.connection_list:
330 if (
331 (connection.group_out_id == self.m_group_id and
332 connection.port_out_id == self.m_port_id)
334 (connection.group_in_id == self.m_group_id and
335 connection.port_in_id == self.m_port_id)
337 connection.widget.updateLineSelected()
339 def itemChange(self, change, value):
340 if change == QGraphicsItem.ItemSelectedHasChanged:
341 self.setPortSelected(value)
342 return QGraphicsItem.itemChange(self, change, value)
344 def triggerDisconnect(self, conn_list=None):
345 if not conn_list:
346 conn_list = CanvasGetPortConnectionList(self.m_group_id, self.m_port_id)
347 for conn_id, group_id, port_id in conn_list:
348 canvas.callback(ACTION_PORTS_DISCONNECT, conn_id, 0, "")
350 def boundingRect(self):
351 return QRectF(0, 0, self.m_port_width + 12, self.m_port_height)
353 def paint(self, painter, option, widget):
354 painter.save()
355 painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
357 selected = self.isSelected()
358 theme = canvas.theme
359 if self.m_port_type == PORT_TYPE_AUDIO_JACK:
360 poly_color = theme.port_audio_jack_bg_sel if selected else theme.port_audio_jack_bg
361 poly_pen = theme.port_audio_jack_pen_sel if selected else theme.port_audio_jack_pen
362 text_pen = theme.port_audio_jack_text_sel if selected else theme.port_audio_jack_text
363 conn_pen = QPen(theme.port_audio_jack_pen_sel)
364 elif self.m_port_type == PORT_TYPE_MIDI_JACK:
365 poly_color = theme.port_midi_jack_bg_sel if selected else theme.port_midi_jack_bg
366 poly_pen = theme.port_midi_jack_pen_sel if selected else theme.port_midi_jack_pen
367 text_pen = theme.port_midi_jack_text_sel if selected else theme.port_midi_jack_text
368 conn_pen = QPen(theme.port_midi_jack_pen_sel)
369 elif self.m_port_type == PORT_TYPE_MIDI_ALSA:
370 poly_color = theme.port_midi_alsa_bg_sel if selected else theme.port_midi_alsa_bg
371 poly_pen = theme.port_midi_alsa_pen_sel if selected else theme.port_midi_alsa_pen
372 text_pen = theme.port_midi_alsa_text_sel if selected else theme.port_midi_alsa_text
373 conn_pen = QPen(theme.port_midi_alsa_pen_sel)
374 elif self.m_port_type == PORT_TYPE_PARAMETER:
375 poly_color = theme.port_parameter_bg_sel if selected else theme.port_parameter_bg
376 poly_pen = theme.port_parameter_pen_sel if selected else theme.port_parameter_pen
377 text_pen = theme.port_parameter_text_sel if selected else theme.port_parameter_text
378 conn_pen = QPen(theme.port_parameter_pen_sel)
379 else:
380 qCritical("PatchCanvas::CanvasPort.paint() - invalid port type '%s'" % port_type2str(self.m_port_type))
381 painter.restore()
382 return
384 # To prevent quality worsening
385 poly_pen = QPen(poly_pen)
386 poly_pen.setWidthF(poly_pen.widthF() + 0.00001)
388 if self.m_is_alternate:
389 poly_color = poly_color.darker(180)
390 #poly_pen.setColor(poly_pen.color().darker(110))
391 #text_pen.setColor(text_pen.color()) #.darker(150))
392 #conn_pen.setColor(conn_pen.color()) #.darker(150))
394 lineHinting = poly_pen.widthF() / 2
396 poly_locx = [0, 0, 0, 0, 0]
397 poly_corner_xhinting = (float(canvas.theme.port_height)/2) % floor(float(canvas.theme.port_height)/2)
398 if poly_corner_xhinting == 0:
399 poly_corner_xhinting = 0.5 * (1 - 7 / (float(canvas.theme.port_height)/2))
401 if self.m_port_mode == PORT_MODE_INPUT:
402 text_pos = QPointF(3, canvas.theme.port_text_ypos)
404 if canvas.theme.port_mode == Theme.THEME_PORT_POLYGON:
405 poly_locx[0] = lineHinting
406 poly_locx[1] = self.m_port_width + 5 - lineHinting
407 poly_locx[2] = self.m_port_width + 12 - poly_corner_xhinting
408 poly_locx[3] = self.m_port_width + 5 - lineHinting
409 poly_locx[4] = lineHinting
410 elif canvas.theme.port_mode == Theme.THEME_PORT_SQUARE:
411 poly_locx[0] = lineHinting
412 poly_locx[1] = self.m_port_width + 5 - lineHinting
413 poly_locx[2] = self.m_port_width + 5 - lineHinting
414 poly_locx[3] = self.m_port_width + 5 - lineHinting
415 poly_locx[4] = lineHinting
416 else:
417 qCritical("PatchCanvas::CanvasPort.paint() - invalid theme port mode '%s'" % canvas.theme.port_mode)
418 painter.restore()
419 return
421 elif self.m_port_mode == PORT_MODE_OUTPUT:
422 text_pos = QPointF(9, canvas.theme.port_text_ypos)
424 if canvas.theme.port_mode == Theme.THEME_PORT_POLYGON:
425 poly_locx[0] = self.m_port_width + 12 - lineHinting
426 poly_locx[1] = 7 + lineHinting
427 poly_locx[2] = 0 + poly_corner_xhinting
428 poly_locx[3] = 7 + lineHinting
429 poly_locx[4] = self.m_port_width + 12 - lineHinting
430 elif canvas.theme.port_mode == Theme.THEME_PORT_SQUARE:
431 poly_locx[0] = self.m_port_width + 12 - lineHinting
432 poly_locx[1] = 5 + lineHinting
433 poly_locx[2] = 5 + lineHinting
434 poly_locx[3] = 5 + lineHinting
435 poly_locx[4] = self.m_port_width + 12 - lineHinting
436 else:
437 qCritical("PatchCanvas::CanvasPort.paint() - invalid theme port mode '%s'" % canvas.theme.port_mode)
438 painter.restore()
439 return
441 else:
442 qCritical("PatchCanvas::CanvasPort.paint() - invalid port mode '%s'" % port_mode2str(self.m_port_mode))
443 painter.restore()
444 return
446 polygon = QPolygonF()
447 polygon += QPointF(poly_locx[0], lineHinting)
448 polygon += QPointF(poly_locx[1], lineHinting)
449 polygon += QPointF(poly_locx[2], float(canvas.theme.port_height)/2)
450 polygon += QPointF(poly_locx[3], canvas.theme.port_height - lineHinting)
451 polygon += QPointF(poly_locx[4], canvas.theme.port_height - lineHinting)
452 polygon += QPointF(poly_locx[0], lineHinting)
454 if canvas.theme.port_bg_pixmap:
455 portRect = polygon.boundingRect().adjusted(-lineHinting+1, -lineHinting+1, lineHinting-1, lineHinting-1)
456 portPos = portRect.topLeft()
457 painter.drawTiledPixmap(portRect, canvas.theme.port_bg_pixmap, portPos)
458 else:
459 painter.setBrush(poly_color) #.lighter(200))
461 painter.setPen(poly_pen)
462 painter.drawPolygon(polygon)
464 painter.setPen(text_pen)
465 painter.setFont(self.m_port_font)
466 painter.drawText(text_pos, self.m_port_name)
468 if canvas.theme.idx == Theme.THEME_OOSTUDIO and canvas.theme.port_bg_pixmap:
469 conn_pen.setCosmetic(True)
470 conn_pen.setWidthF(0.4)
471 painter.setPen(conn_pen)
473 if self.m_port_mode == PORT_MODE_INPUT:
474 connLineX = portRect.left()+1
475 else:
476 connLineX = portRect.right()-1
477 conn_path = QPainterPath()
478 conn_path.addRect(QRectF(connLineX-1, portRect.top(), 2, portRect.height()))
479 painter.fillPath(conn_path, conn_pen.brush())
480 painter.drawLine(QLineF(connLineX, portRect.top(), connLineX, portRect.bottom()))
482 painter.restore()
484 # ------------------------------------------------------------------------------------------------------------