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.

420 lines
14KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. from math import floor
  7. from qt_compat import qt_config
  8. if qt_config == 5:
  9. from PyQt5.QtCore import QT_VERSION, pyqtSignal, pyqtSlot, qFatal, Qt, QPointF, QRectF
  10. from PyQt5.QtGui import QCursor, QPixmap, QPolygonF
  11. from PyQt5.QtWidgets import QGraphicsRectItem, QGraphicsScene
  12. elif qt_config == 6:
  13. from PyQt6.QtCore import QT_VERSION, pyqtSignal, pyqtSlot, qFatal, Qt, QPointF, QRectF
  14. from PyQt6.QtGui import QCursor, QPixmap, QPolygonF
  15. from PyQt6.QtWidgets import QGraphicsRectItem, QGraphicsScene
  16. # ------------------------------------------------------------------------------------------------------------
  17. # Imports (Custom)
  18. from . import (
  19. canvas,
  20. CanvasBoxType,
  21. CanvasIconType,
  22. CanvasPortType,
  23. CanvasLineType,
  24. CanvasBezierLineType,
  25. CanvasRubberbandType,
  26. ACTION_BG_RIGHT_CLICK,
  27. MAX_PLUGIN_ID_ALLOWED,
  28. )
  29. # ------------------------------------------------------------------------------------------------------------
  30. class RubberbandRect(QGraphicsRectItem):
  31. def __init__(self, scene):
  32. QGraphicsRectItem.__init__(self, QRectF(0, 0, 0, 0))
  33. self.setZValue(-1)
  34. self.hide()
  35. scene.addItem(self)
  36. def type(self):
  37. return CanvasRubberbandType
  38. # ------------------------------------------------------------------------------------------------------------
  39. class PatchScene(QGraphicsScene):
  40. scaleChanged = pyqtSignal(float)
  41. pluginSelected = pyqtSignal(list)
  42. def __init__(self, parent, view):
  43. QGraphicsScene.__init__(self, parent)
  44. self.m_connection_cut_mode = False
  45. self.m_scale_area = False
  46. self.m_mouse_down_init = False
  47. self.m_mouse_rubberband = False
  48. self.m_scale_min = 0.1
  49. self.m_scale_max = 4.0
  50. self.m_rubberband = RubberbandRect(self)
  51. self.m_rubberband_selection = False
  52. self.m_rubberband_orig_point = QPointF(0, 0)
  53. self.m_view = view
  54. if not self.m_view:
  55. qFatal("PatchCanvas::PatchScene() - invalid view")
  56. self.m_cursor_cut = None
  57. self.m_cursor_zoom = None
  58. self.setItemIndexMethod(QGraphicsScene.NoIndex)
  59. self.selectionChanged.connect(self.slot_selectionChanged)
  60. def getDevicePixelRatioF(self):
  61. if QT_VERSION < 0x50600:
  62. return 1.0
  63. return self.m_view.devicePixelRatioF()
  64. def getScaleFactor(self):
  65. return self.m_view.transform().m11()
  66. def getView(self):
  67. return self.m_view
  68. def fixScaleFactor(self, transform=None):
  69. fix, set_view = False, False
  70. if not transform:
  71. set_view = True
  72. view = self.m_view
  73. transform = view.transform()
  74. scale = transform.m11()
  75. if scale > self.m_scale_max:
  76. fix = True
  77. transform.reset()
  78. transform.scale(self.m_scale_max, self.m_scale_max)
  79. elif scale < self.m_scale_min:
  80. fix = True
  81. transform.reset()
  82. transform.scale(self.m_scale_min, self.m_scale_min)
  83. if set_view:
  84. if fix:
  85. view.setTransform(transform)
  86. self.scaleChanged.emit(transform.m11())
  87. return fix
  88. def updateLimits(self):
  89. w0 = canvas.size_rect.width()
  90. h0 = canvas.size_rect.height()
  91. w1 = self.m_view.width()
  92. h1 = self.m_view.height()
  93. self.m_scale_min = w1/w0 if w0/h0 > w1/h1 else h1/h0
  94. def updateTheme(self):
  95. self.setBackgroundBrush(canvas.theme.canvas_bg)
  96. self.m_rubberband.setPen(canvas.theme.rubberband_pen)
  97. self.m_rubberband.setBrush(canvas.theme.rubberband_brush)
  98. cur_color = "black" if canvas.theme.canvas_bg.blackF() < 0.5 else "white"
  99. self.m_cursor_cut = QCursor(QPixmap(":/cursors/cut_"+cur_color+".png"), 1, 1)
  100. self.m_cursor_zoom = QCursor(QPixmap(":/cursors/zoom-area_"+cur_color+".png"), 8, 7)
  101. def zoom_fit(self):
  102. min_x = min_y = max_x = max_y = None
  103. first_value = True
  104. items_list = self.items()
  105. if len(items_list) > 0:
  106. for item in items_list:
  107. if item and item.isVisible() and item.type() == CanvasBoxType:
  108. pos = item.scenePos()
  109. rect = item.boundingRect()
  110. x = pos.x()
  111. y = pos.y()
  112. if first_value:
  113. first_value = False
  114. min_x, min_y = x, y
  115. max_x = x + rect.width()
  116. max_y = y + rect.height()
  117. else:
  118. min_x = min(min_x, x)
  119. min_y = min(min_y, y)
  120. max_x = max(max_x, x + rect.width())
  121. max_y = max(max_y, y + rect.height())
  122. if not first_value:
  123. self.m_view.fitInView(min_x, min_y, abs(max_x - min_x), abs(max_y - min_y), Qt.KeepAspectRatio)
  124. self.fixScaleFactor()
  125. def zoom_in(self):
  126. view = self.m_view
  127. transform = view.transform()
  128. if transform.m11() < self.m_scale_max:
  129. transform.scale(1.2, 1.2)
  130. if transform.m11() > self.m_scale_max:
  131. transform.reset()
  132. transform.scale(self.m_scale_max, self.m_scale_max)
  133. view.setTransform(transform)
  134. self.scaleChanged.emit(transform.m11())
  135. def zoom_out(self):
  136. view = self.m_view
  137. transform = view.transform()
  138. if transform.m11() > self.m_scale_min:
  139. transform.scale(0.833333333333333, 0.833333333333333)
  140. if transform.m11() < self.m_scale_min:
  141. transform.reset()
  142. transform.scale(self.m_scale_min, self.m_scale_min)
  143. view.setTransform(transform)
  144. self.scaleChanged.emit(transform.m11())
  145. def zoom_reset(self):
  146. self.m_view.resetTransform()
  147. self.scaleChanged.emit(1.0)
  148. def handleMouseRelease(self):
  149. rubberband_active = self.m_rubberband_selection
  150. if self.m_scale_area and not self.m_rubberband_selection:
  151. self.m_scale_area = False
  152. self.m_view.viewport().unsetCursor()
  153. if self.m_rubberband_selection:
  154. if self.m_scale_area:
  155. self.m_scale_area = False
  156. self.m_view.viewport().unsetCursor()
  157. rect = self.m_rubberband.rect()
  158. self.m_view.fitInView(rect.x(), rect.y(), rect.width(), rect.height(), Qt.KeepAspectRatio)
  159. self.fixScaleFactor()
  160. else:
  161. items_list = self.items()
  162. for item in items_list:
  163. if item and item.isVisible() and item.type() == CanvasBoxType:
  164. item_rect = item.sceneBoundingRect()
  165. item_top_left = QPointF(item_rect.x(), item_rect.y())
  166. item_bottom_right = QPointF(item_rect.x() + item_rect.width(),
  167. item_rect.y() + item_rect.height())
  168. if self.m_rubberband.contains(item_top_left) and self.m_rubberband.contains(item_bottom_right):
  169. item.setSelected(True)
  170. self.m_rubberband.hide()
  171. self.m_rubberband.setRect(0, 0, 0, 0)
  172. self.m_rubberband_selection = False
  173. self.m_mouse_rubberband = False
  174. self.stopConnectionCut()
  175. return rubberband_active
  176. def startConnectionCut(self):
  177. if self.m_cursor_cut:
  178. self.m_connection_cut_mode = True
  179. self.m_view.viewport().setCursor(self.m_cursor_cut)
  180. def stopConnectionCut(self):
  181. if self.m_connection_cut_mode:
  182. self.m_connection_cut_mode = False
  183. self.m_view.viewport().unsetCursor()
  184. def triggerRubberbandScale(self):
  185. self.m_scale_area = True
  186. if self.m_cursor_zoom:
  187. self.m_view.viewport().setCursor(self.m_cursor_zoom)
  188. @pyqtSlot()
  189. def slot_selectionChanged(self):
  190. items_list = self.selectedItems()
  191. if len(items_list) == 0:
  192. self.pluginSelected.emit([])
  193. return
  194. plugin_list = []
  195. for item in items_list:
  196. if item and item.isVisible():
  197. group_item = None
  198. if item.type() == CanvasBoxType:
  199. group_item = item
  200. elif item.type() == CanvasPortType:
  201. group_item = item.parentItem()
  202. #elif item.type() in (CanvasLineType, CanvasBezierLineType, CanvasLineMovType, CanvasBezierLineMovType):
  203. #plugin_list = []
  204. #break
  205. if group_item is not None and group_item.m_plugin_id >= 0:
  206. plugin_id = group_item.m_plugin_id
  207. if plugin_id > MAX_PLUGIN_ID_ALLOWED:
  208. plugin_id = 0
  209. plugin_list.append(plugin_id)
  210. self.pluginSelected.emit(plugin_list)
  211. def keyPressEvent(self, event):
  212. if not self.m_view:
  213. event.ignore()
  214. return
  215. if event.key() == Qt.Key_Home:
  216. event.accept()
  217. self.zoom_fit()
  218. return
  219. if event.modifiers() & Qt.ControlModifier:
  220. if event.key() == Qt.Key_Plus:
  221. event.accept()
  222. self.zoom_in()
  223. return
  224. if event.key() == Qt.Key_Minus:
  225. event.accept()
  226. self.zoom_out()
  227. return
  228. if event.key() == Qt.Key_1:
  229. event.accept()
  230. self.zoom_reset()
  231. return
  232. QGraphicsScene.keyPressEvent(self, event)
  233. def keyReleaseEvent(self, event):
  234. self.stopConnectionCut()
  235. QGraphicsScene.keyReleaseEvent(self, event)
  236. def mousePressEvent(self, event):
  237. ctrlDown = bool(event.modifiers() & Qt.ControlModifier)
  238. self.m_mouse_down_init = (
  239. (event.button() == Qt.LeftButton) or ((event.button() == Qt.RightButton) and ctrlDown)
  240. )
  241. self.m_mouse_rubberband = False
  242. if event.button() == Qt.MiddleButton and ctrlDown:
  243. self.startConnectionCut()
  244. items = self.items(event.scenePos())
  245. for item in items:
  246. if item and item.type() in (CanvasLineType, CanvasBezierLineType, CanvasPortType):
  247. item.triggerDisconnect()
  248. QGraphicsScene.mousePressEvent(self, event)
  249. def mouseMoveEvent(self, event):
  250. if self.m_mouse_down_init:
  251. self.m_mouse_down_init = False
  252. items = self.items(event.scenePos())
  253. for item in items:
  254. if item and item.type() in (CanvasBoxType, CanvasIconType, CanvasPortType):
  255. self.m_mouse_rubberband = False
  256. break
  257. else:
  258. self.m_mouse_rubberband = True
  259. if self.m_mouse_rubberband:
  260. event.accept()
  261. pos = event.scenePos()
  262. pos_x = pos.x()
  263. pos_y = pos.y()
  264. if not self.m_rubberband_selection:
  265. self.m_rubberband.show()
  266. self.m_rubberband_selection = True
  267. self.m_rubberband_orig_point = pos
  268. rubberband_orig_point = self.m_rubberband_orig_point
  269. x = min(pos_x, rubberband_orig_point.x())
  270. y = min(pos_y, rubberband_orig_point.y())
  271. lineHinting = canvas.theme.rubberband_pen.widthF() / 2
  272. self.m_rubberband.setRect(x+lineHinting,
  273. y+lineHinting,
  274. abs(pos_x - rubberband_orig_point.x()),
  275. abs(pos_y - rubberband_orig_point.y()))
  276. return
  277. if self.m_connection_cut_mode:
  278. trail = QPolygonF([event.scenePos(), event.lastScenePos(), event.scenePos()])
  279. items = self.items(trail)
  280. for item in items:
  281. if item and item.type() in (CanvasLineType, CanvasBezierLineType):
  282. item.triggerDisconnect()
  283. QGraphicsScene.mouseMoveEvent(self, event)
  284. def mouseReleaseEvent(self, event):
  285. self.m_mouse_down_init = False
  286. if not self.handleMouseRelease():
  287. items_list = self.selectedItems()
  288. needs_update = False
  289. for item in items_list:
  290. if item and item.isVisible() and item.type() == CanvasBoxType:
  291. item.checkItemPos()
  292. needs_update = True
  293. if needs_update:
  294. canvas.scene.update()
  295. QGraphicsScene.mouseReleaseEvent(self, event)
  296. def zoom_wheel(self, delta):
  297. transform = self.m_view.transform()
  298. scale = transform.m11()
  299. if (delta > 0 and scale < self.m_scale_max) or (delta < 0 and scale > self.m_scale_min):
  300. factor = 1.41 ** (delta / 240.0)
  301. transform.scale(factor, factor)
  302. self.fixScaleFactor(transform)
  303. self.m_view.setTransform(transform)
  304. self.scaleChanged.emit(transform.m11())
  305. def wheelEvent(self, event):
  306. if not self.m_view:
  307. event.ignore()
  308. return
  309. if event.modifiers() & Qt.ControlModifier:
  310. event.accept()
  311. self.zoom_wheel(event.delta())
  312. return
  313. QGraphicsScene.wheelEvent(self, event)
  314. def contextMenuEvent(self, event):
  315. if self.handleMouseRelease():
  316. self.m_mouse_down_init = False
  317. QGraphicsScene.contextMenuEvent(self, event)
  318. return
  319. if event.modifiers() & Qt.ControlModifier:
  320. event.accept()
  321. self.triggerRubberbandScale()
  322. return
  323. if len(self.selectedItems()) == 0:
  324. self.m_mouse_down_init = False
  325. event.accept()
  326. canvas.callback(ACTION_BG_RIGHT_CLICK, 0, 0, "")
  327. return
  328. self.m_mouse_down_init = False
  329. QGraphicsScene.contextMenuEvent(self, event)
  330. # ------------------------------------------------------------------------------------------------------------