|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
-
- # PatchBay Canvas engine using QGraphicsView/Scene
- # Copyright (C) 2010-2019 Filipe Coelho <falktx@falktx.com>
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License as
- # published by the Free Software Foundation; either version 2 of
- # the License, or any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # For a full copy of the GNU General Public License see the doc/GPL.txt file.
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (Global)
-
- from math import floor
-
- from PyQt5.QtCore import QT_VERSION, pyqtSignal, pyqtSlot, qFatal, Qt, QPointF, QRectF
- from PyQt5.QtGui import QCursor, QPixmap, QPolygonF
- from PyQt5.QtWidgets import QGraphicsRectItem, QGraphicsScene
-
- # ------------------------------------------------------------------------------------------------------------
- # Imports (Custom)
-
- from . import (
- canvas,
- CanvasBoxType,
- CanvasIconType,
- CanvasPortType,
- CanvasLineType,
- CanvasBezierLineType,
- CanvasRubberbandType,
- ACTION_BG_RIGHT_CLICK,
- MAX_PLUGIN_ID_ALLOWED,
- )
-
- # ------------------------------------------------------------------------------------------------------------
-
- class RubberbandRect(QGraphicsRectItem):
- def __init__(self, scene):
- QGraphicsRectItem.__init__(self, QRectF(0, 0, 0, 0))
-
- self.setZValue(-1)
- self.hide()
-
- scene.addItem(self)
-
- def type(self):
- return CanvasRubberbandType
-
- # ------------------------------------------------------------------------------------------------------------
-
- class PatchScene(QGraphicsScene):
- scaleChanged = pyqtSignal(float)
- pluginSelected = pyqtSignal(list)
-
- def __init__(self, parent, view):
- QGraphicsScene.__init__(self, parent)
-
- self.m_connection_cut_mode = False
- self.m_scale_area = False
- self.m_mouse_down_init = False
- self.m_mouse_rubberband = False
- self.m_scale_min = 0.1
- self.m_scale_max = 4.0
-
- self.m_rubberband = RubberbandRect(self)
- self.m_rubberband_selection = False
- self.m_rubberband_orig_point = QPointF(0, 0)
-
- self.m_view = view
- if not self.m_view:
- qFatal("PatchCanvas::PatchScene() - invalid view")
-
- self.m_cursor_cut = None
- self.m_cursor_zoom = None
-
- self.selectionChanged.connect(self.slot_selectionChanged)
-
- def getDevicePixelRatioF(self):
- if QT_VERSION < 0x50600:
- return 1.0
-
- return self.m_view.devicePixelRatioF()
-
- def getScaleFactor(self):
- return self.m_view.transform().m11()
-
- def getView(self):
- return self.m_view
-
- def fixScaleFactor(self, transform=None):
- fix, set_view = False, False
- if not transform:
- set_view = True
- view = self.m_view
- transform = view.transform()
-
- scale = transform.m11()
- if scale > self.m_scale_max:
- fix = True
- transform.reset()
- transform.scale(self.m_scale_max, self.m_scale_max)
- elif scale < self.m_scale_min:
- fix = True
- transform.reset()
- transform.scale(self.m_scale_min, self.m_scale_min)
-
- if set_view:
- if fix:
- view.setTransform(transform)
- self.scaleChanged.emit(transform.m11())
-
- return fix
-
- def updateLimits(self):
- w0 = canvas.size_rect.width()
- h0 = canvas.size_rect.height()
- w1 = self.m_view.width()
- h1 = self.m_view.height()
- self.m_scale_min = w1/w0 if w0/h0 > w1/h1 else h1/h0
-
- def updateTheme(self):
- self.setBackgroundBrush(canvas.theme.canvas_bg)
- self.m_rubberband.setPen(canvas.theme.rubberband_pen)
- self.m_rubberband.setBrush(canvas.theme.rubberband_brush)
-
- cur_color = "black" if canvas.theme.canvas_bg.blackF() < 0.5 else "white"
- self.m_cursor_cut = QCursor(QPixmap(":/cursors/cut_"+cur_color+".png"), 1, 1)
- self.m_cursor_zoom = QCursor(QPixmap(":/cursors/zoom-area_"+cur_color+".png"), 8, 7)
-
- def zoom_fit(self):
- min_x = min_y = max_x = max_y = None
- first_value = True
-
- items_list = self.items()
-
- if len(items_list) > 0:
- for item in items_list:
- if item and item.isVisible() and item.type() == CanvasBoxType:
- pos = item.scenePos()
- rect = item.boundingRect()
-
- x = pos.x()
- y = pos.y()
- if first_value:
- first_value = False
- min_x, min_y = x, y
- max_x = x + rect.width()
- max_y = y + rect.height()
- else:
- min_x = min(min_x, x)
- min_y = min(min_y, y)
- max_x = max(max_x, x + rect.width())
- max_y = max(max_y, y + rect.height())
-
- if not first_value:
- self.m_view.fitInView(min_x, min_y, abs(max_x - min_x), abs(max_y - min_y), Qt.KeepAspectRatio)
- self.fixScaleFactor()
-
- def zoom_in(self):
- view = self.m_view
- transform = view.transform()
- if transform.m11() < self.m_scale_max:
- transform.scale(1.2, 1.2)
- if transform.m11() > self.m_scale_max:
- transform.reset()
- transform.scale(self.m_scale_max, self.m_scale_max)
- view.setTransform(transform)
- self.scaleChanged.emit(transform.m11())
-
- def zoom_out(self):
- view = self.m_view
- transform = view.transform()
- if transform.m11() > self.m_scale_min:
- transform.scale(0.833333333333333, 0.833333333333333)
- if transform.m11() < self.m_scale_min:
- transform.reset()
- transform.scale(self.m_scale_min, self.m_scale_min)
- view.setTransform(transform)
- self.scaleChanged.emit(transform.m11())
-
- def zoom_reset(self):
- self.m_view.resetTransform()
- self.scaleChanged.emit(1.0)
-
- def handleMouseRelease(self):
- rubberband_active = self.m_rubberband_selection
-
- if self.m_scale_area and not self.m_rubberband_selection:
- self.m_scale_area = False
- self.m_view.viewport().unsetCursor()
-
- if self.m_rubberband_selection:
- if self.m_scale_area:
- self.m_scale_area = False
- self.m_view.viewport().unsetCursor()
-
- rect = self.m_rubberband.rect()
- self.m_view.fitInView(rect.x(), rect.y(), rect.width(), rect.height(), Qt.KeepAspectRatio)
- self.fixScaleFactor()
-
- else:
- items_list = self.items()
- for item in items_list:
- if item and item.isVisible() and item.type() == CanvasBoxType:
- item_rect = item.sceneBoundingRect()
- item_top_left = QPointF(item_rect.x(), item_rect.y())
- item_bottom_right = QPointF(item_rect.x() + item_rect.width(),
- item_rect.y() + item_rect.height())
-
- if self.m_rubberband.contains(item_top_left) and self.m_rubberband.contains(item_bottom_right):
- item.setSelected(True)
-
- self.m_rubberband.hide()
- self.m_rubberband.setRect(0, 0, 0, 0)
- self.m_rubberband_selection = False
-
- self.m_mouse_rubberband = False
-
- self.stopConnectionCut()
-
- return rubberband_active
-
- def startConnectionCut(self):
- if self.m_cursor_cut:
- self.m_connection_cut_mode = True
- self.m_view.viewport().setCursor(self.m_cursor_cut)
-
- def stopConnectionCut(self):
- if self.m_connection_cut_mode:
- self.m_connection_cut_mode = False
- self.m_view.viewport().unsetCursor()
-
- def triggerRubberbandScale(self):
- self.m_scale_area = True
- if self.m_cursor_zoom:
- self.m_view.viewport().setCursor(self.m_cursor_zoom)
-
- @pyqtSlot()
- def slot_selectionChanged(self):
- items_list = self.selectedItems()
-
- if len(items_list) == 0:
- self.pluginSelected.emit([])
- return
-
- plugin_list = []
-
- for item in items_list:
- if item and item.isVisible():
- group_item = None
-
- if item.type() == CanvasBoxType:
- group_item = item
- elif item.type() == CanvasPortType:
- group_item = item.parentItem()
- #elif item.type() in (CanvasLineType, CanvasBezierLineType, CanvasLineMovType, CanvasBezierLineMovType):
- #plugin_list = []
- #break
-
- if group_item is not None and group_item.m_plugin_id >= 0:
- plugin_id = group_item.m_plugin_id
- if plugin_id > MAX_PLUGIN_ID_ALLOWED:
- plugin_id = 0
- plugin_list.append(plugin_id)
-
- self.pluginSelected.emit(plugin_list)
-
- def keyPressEvent(self, event):
- if not self.m_view:
- event.ignore()
- return
-
- if event.key() == Qt.Key_Home:
- event.accept()
- self.zoom_fit()
- return
-
- if event.modifiers() & Qt.ControlModifier:
- if event.key() == Qt.Key_Plus:
- event.accept()
- self.zoom_in()
- return
-
- if event.key() == Qt.Key_Minus:
- event.accept()
- self.zoom_out()
- return
-
- if event.key() == Qt.Key_1:
- event.accept()
- self.zoom_reset()
- return
-
- QGraphicsScene.keyPressEvent(self, event)
-
- def keyReleaseEvent(self, event):
- self.stopConnectionCut()
- QGraphicsScene.keyReleaseEvent(self, event)
-
- def mousePressEvent(self, event):
- ctrlDown = bool(event.modifiers() & Qt.ControlModifier)
-
- self.m_mouse_down_init = (
- (event.button() == Qt.LeftButton) or ((event.button() == Qt.RightButton) and ctrlDown)
- )
- self.m_mouse_rubberband = False
-
- if event.button() == Qt.MidButton and ctrlDown:
- self.startConnectionCut()
- items = self.items(event.scenePos())
- for item in items:
- if item and item.type() in (CanvasLineType, CanvasBezierLineType, CanvasPortType):
- item.triggerDisconnect()
-
- QGraphicsScene.mousePressEvent(self, event)
-
- def mouseMoveEvent(self, event):
- if self.m_mouse_down_init:
- self.m_mouse_down_init = False
- items = self.items(event.scenePos())
- for item in items:
- if item and item.type() in (CanvasBoxType, CanvasIconType, CanvasPortType):
- self.m_mouse_rubberband = False
- break
- else:
- self.m_mouse_rubberband = True
-
- if self.m_mouse_rubberband:
- event.accept()
- pos = event.scenePos()
- pos_x = pos.x()
- pos_y = pos.y()
- if not self.m_rubberband_selection:
- self.m_rubberband.show()
- self.m_rubberband_selection = True
- self.m_rubberband_orig_point = pos
- rubberband_orig_point = self.m_rubberband_orig_point
-
- x = min(pos_x, rubberband_orig_point.x())
- y = min(pos_y, rubberband_orig_point.y())
-
- lineHinting = canvas.theme.rubberband_pen.widthF() / 2
- self.m_rubberband.setRect(x+lineHinting,
- y+lineHinting,
- abs(pos_x - rubberband_orig_point.x()),
- abs(pos_y - rubberband_orig_point.y()))
- return
-
- if self.m_connection_cut_mode:
- trail = QPolygonF([event.scenePos(), event.lastScenePos(), event.scenePos()])
- items = self.items(trail)
- for item in items:
- if item and item.type() in (CanvasLineType, CanvasBezierLineType):
- item.triggerDisconnect()
-
- QGraphicsScene.mouseMoveEvent(self, event)
-
- def mouseReleaseEvent(self, event):
- self.m_mouse_down_init = False
-
- if not self.handleMouseRelease():
- items_list = self.selectedItems()
- needs_update = False
- for item in items_list:
- if item and item.isVisible() and item.type() == CanvasBoxType:
- item.checkItemPos()
- needs_update = True
-
- if needs_update:
- canvas.scene.update()
-
- QGraphicsScene.mouseReleaseEvent(self, event)
-
- def zoom_wheel(self, delta):
- transform = self.m_view.transform()
- scale = transform.m11()
-
- if (delta > 0 and scale < self.m_scale_max) or (delta < 0 and scale > self.m_scale_min):
- factor = 1.41 ** (delta / 240.0)
- transform.scale(factor, factor)
- self.fixScaleFactor(transform)
- self.m_view.setTransform(transform)
- self.scaleChanged.emit(transform.m11())
-
- def wheelEvent(self, event):
- if not self.m_view:
- event.ignore()
- return
-
- if event.modifiers() & Qt.ControlModifier:
- event.accept()
- self.zoom_wheel(event.delta())
- return
-
- QGraphicsScene.wheelEvent(self, event)
-
- def contextMenuEvent(self, event):
- if self.handleMouseRelease():
- self.m_mouse_down_init = False
- QGraphicsScene.contextMenuEvent(self, event)
- return
-
- if event.modifiers() & Qt.ControlModifier:
- event.accept()
- self.triggerRubberbandScale()
- return
-
- if len(self.selectedItems()) == 0:
- self.m_mouse_down_init = False
- event.accept()
- canvas.callback(ACTION_BG_RIGHT_CLICK, 0, 0, "")
- return
-
- self.m_mouse_down_init = False
- QGraphicsScene.contextMenuEvent(self, event)
-
- # ------------------------------------------------------------------------------------------------------------
|