#!/usr/bin/env python3 # -*- coding: utf-8 -*- # QGraphicsEmbedScene class, based on Qt3D C++ code # Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). # Copyright (C) 2014 Filipe Coelho # # 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 (Config) from carla_config import * # ------------------------------------------------------------------------------------------------------------ # Imports (Global) if config_UseQt5: from PyQt5.QtCore import pyqtSignal, pyqtSlot, qRound, Qt, QPoint, QPointF, QRectF, QSize from PyQt5.QtGui import QPainter from PyQt5.QtOpenGL import QGLFramebufferObject, QGLFramebufferObjectFormat from PyQt5.QtWidgets import QApplication, QEvent, QGraphicsItem, QGraphicsScene else: from PyQt4.QtCore import pyqtSignal, pyqtSlot, qRound, Qt, QEvent, QObject, QPoint, QPointF, QRectF, QSize from PyQt4.QtOpenGL import QGLFramebufferObject, QGLFramebufferObjectFormat from PyQt4.QtGui import QApplication, QPainter, QGraphicsItem, QGraphicsScene # ------------------------------------------------------------------------------------------------------------ # Returns the next power of two that is greater than or # equal to @a value. The @a value must be positive or the # result is undefined. # # This is a convenience function for use with GL texture # handling code. def nextPowerOfTwo(value): value -= 1 value |= value >> 1 value |= value >> 2 value |= value >> 4 value |= value >> 8 value |= value >> 16 value += 1 return value # ------------------------------------------------------------------------------------------------------------ # Widget Class class QGraphicsEmbedScene(QGraphicsScene): def __init__(self, parent): QGraphicsScene.__init__(self, parent) self.dirty = True self.fbo = None self.format = QGLFramebufferObjectFormat() self.format.setAttachment(QGLFramebufferObject.CombinedDepthStencil) self.pressedPos = QPoint() self.changed.connect(self.slot_update) self.sceneRectChanged.connect(self.slot_update) def format(self): return self.format def setFormat(self, format): self.format = format def renderToTexture(self, levelOfDetail = 1.0): # Determine the fbo size we will need. size = (self.sceneRect().size() * levelOfDetail).toSize() fboSize = nextPowerOfTwo(size) if fboSize.isEmpty(): fboSize = QSize(16, 16) # Create or re-create the fbo. if self.fbo is None or self.fbo.size() != fboSize: #del self.fbo self.fbo = QGLFramebufferObject(fboSize, self.format) if not self.fbo.isValid(): #del self.fbo self.fbo = None return 0 self.dirty = True # Return the previous texture contents if the scene hasn't changed. if self.fbo is not None and not self.dirty: return self.fbo.texture() # Render the scene into the fbo, scaling the QPainter's view # transform up to the power-of-two fbo size. painter = QPainter(self.fbo) painter.setWindow(0, 0, size.width(), size.height()) painter.setViewport(0, 0, fboSize.width(), fboSize.height()) self.render(painter) painter.end() self.dirty = False return self.fbo.texture() def deliverEvent(self, event, texCoord): # Map the texture co-ordinate into "screen" co-ordinates. # Mouse move and release events can extend beyond the boundaries # of the scene, for "click and drag off-screen" operations. # Mouse press and double-click events need to be constrained. bounds = self.sceneRect() screenX = qRound(texCoord.x() * bounds.width()) screenY = qRound((1.0 - texCoord.y()) * bounds.height()) if event.type() in (QEvent.GraphicsSceneMousePress, QEvent.GraphicsSceneMouseDoubleClick, QEvent.MouseButtonPress, QEvent.MouseButtonDblClick): if screenX < 0: screenX = 0 elif screenX >= bounds.width(): screenX = qRound(bounds.width() - 1) if screenY < 0: screenY = 0 elif screenY >= bounds.height(): screenY = qRound(bounds.height() - 1) self.pressedPos = QPoint(screenX, screenY) # Convert the event and deliver it to the scene. eventType = event.type() if eventType in (QEvent.GraphicsSceneMouseMove, QEvent.GraphicsSceneMousePress, QEvent.GraphicsSceneMouseRelease, QEvent.GraphicsSceneMouseDoubleClick): pass #QGraphicsSceneMouseEvent *ev = #static_cast(event) #QGraphicsSceneMouseEvent e(ev->type()) #e.setPos(QPointF(screenX, screenY)) #e.setScenePos(QPointF(screenX + bounds.x(), screenY + bounds.y())) #e.setScreenPos(QPoint(screenX, screenY)) #e.setButtonDownScreenPos(ev->button(), d->pressedPos) #e.setButtonDownScenePos #(ev->button(), QPointF(d->pressedPos.x() + bounds.x(), #d->pressedPos.y() + bounds.y())) #e.setButtons(ev->buttons()) #e.setButton(ev->button()) #e.setModifiers(ev->modifiers()) #e.setAccepted(false) #QApplication::sendEvent(this, &e) elif eventType == QEvent.GraphicsSceneWheel: pass #QGraphicsSceneWheelEvent *ev = #static_cast(event) #QGraphicsSceneWheelEvent e(QEvent::GraphicsSceneWheel) #e.setPos(QPointF(screenX, screenY)) #e.setScenePos(QPointF(screenX + bounds.x(), screenY + bounds.y())) #e.setScreenPos(QPoint(screenX, screenY)) #e.setButtons(ev->buttons()) #e.setModifiers(ev->modifiers()) #e.setDelta(ev->delta()) #e.setOrientation(ev->orientation()) #e.setAccepted(false) #QApplication::sendEvent(this, &e) elif eventType in (QEvent.MouseButtonPress, QEvent.MouseButtonRelease, QEvent.MouseButtonDblClick, QEvent.MouseMove): pass #QMouseEvent *ev = static_cast(event) #QEvent::Type type #if (ev->type() == QEvent::MouseButtonPress) #type = QEvent::GraphicsSceneMousePress #else if (ev->type() == QEvent::MouseButtonRelease) #type = QEvent::GraphicsSceneMouseRelease #else if (ev->type() == QEvent::MouseButtonDblClick) #type = QEvent::GraphicsSceneMouseDoubleClick #else #type = QEvent::GraphicsSceneMouseMove #QGraphicsSceneMouseEvent e(type) #e.setPos(QPointF(screenX, screenY)) #e.setScenePos(QPointF(screenX + bounds.x(), screenY + bounds.y())) #e.setScreenPos(QPoint(screenX, screenY)) #e.setButtonDownScreenPos(ev->button(), d->pressedPos) #e.setButtonDownScenePos #(ev->button(), QPointF(d->pressedPos.x() + bounds.x(), #d->pressedPos.y() + bounds.y())) #e.setButtons(ev->buttons()) #e.setButton(ev->button()) #e.setModifiers(ev->modifiers()) #e.setAccepted(false) #QApplication::sendEvent(this, &e) elif eventType == QEvent.Wheel: pass #QWheelEvent *ev = static_cast(event) #QGraphicsSceneWheelEvent e(QEvent::GraphicsSceneWheel) #e.setPos(QPointF(screenX, screenY)) #e.setScenePos(QPointF(screenX + bounds.x(), screenY + bounds.y())) #e.setScreenPos(QPoint(screenX, screenY)) #e.setButtons(ev->buttons()) #e.setModifiers(ev->modifiers()) #e.setDelta(ev->delta()) #e.setOrientation(ev->orientation()) #e.setAccepted(false) #QApplication::sendEvent(this, &e) #else: # Send the event directly without any conversion. # Typically used for keyboard, focus, and enter/leave events. #QApplication.sendEvent(self, event) QApplication.sendEvent(self, event) def drawBackground(self, painter, rect): if self.backgroundBrush().style() == Qt.NoBrush: # Fill the fbo with the transparent color as there won't # be a window or graphics item drawing a previous background. painter.save() painter.setCompositionMode(QPainter.CompositionMode_Source) painter.fillRect(rect, Qt.transparent) painter.restore() else: QGraphicsScene.drawBackground(painter, rect) @pyqtSlot() def slot_update(self): self.dirty = True # ------------------------------------------------------------------------------------------------------------ # PatchScene compatible class # object types CanvasBoxType = QGraphicsItem.UserType + 1 CanvasIconType = QGraphicsItem.UserType + 2 CanvasPortType = QGraphicsItem.UserType + 3 CanvasLineType = QGraphicsItem.UserType + 4 CanvasBezierLineType = QGraphicsItem.UserType + 5 CanvasLineMovType = QGraphicsItem.UserType + 6 CanvasBezierLineMovType = QGraphicsItem.UserType + 7 class PatchScene3D(QGraphicsEmbedScene): scaleChanged = pyqtSignal(float) sceneGroupMoved = pyqtSignal(int, int, QPointF) pluginSelected = pyqtSignal(list) def __init__(self, parent, view): QGraphicsEmbedScene.__init__(self, parent) self.m_ctrl_down = False self.m_mouse_down_init = False self.m_mouse_rubberband = False self.addRubberBand() self.m_view = view #if not self.m_view: #qFatal("PatchCanvas::PatchScene() - invalid view") self.selectionChanged.connect(self.slot_selectionChanged) def addRubberBand(self): self.m_rubberband = self.addRect(QRectF(0, 0, 0, 0)) self.m_rubberband.setZValue(-1) self.m_rubberband.hide() self.m_rubberband_selection = False self.m_rubberband_orig_point = QPointF(0, 0) def clear(self): QGraphicsEmbedScene.clear(self) # Re-add rubberband, that just got deleted self.addRubberBand() def fixScaleFactor(self): pass def updateTheme(self): pass def zoom_fit(self): pass def zoom_in(self): pass def zoom_out(self): pass def zoom_reset(self): pass @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_list.append(group_item.m_plugin_id) self.pluginSelected.emit(plugin_list)