Last active
October 28, 2016 07:02
-
-
Save mieki256/3c32427bacc009b89d4db40e67bc7636 to your computer and use it in GitHub Desktop.
PySide+QGraphicsViewでズーム表示する例。gview_zoom2.pyとの違いは、QGraphicsScene内のItemを拡大縮小することでズーム表示している点。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python | |
# -*- mode: python; Encoding: utf-8; coding: utf-8 -*- | |
# Last updated: <2016/10/28 09:10:18 +0900> | |
""" | |
PySide + QGraphicsView上でズーム表示する。 | |
ホイールを回すか、ステータスバー上のボタンを押すとズームが変わる。 | |
マウスカーソル位置にブラシ画像も表示する。 | |
QGraphicsScene 内の各Itemを拡大縮小して対応する例。 | |
動作確認環境 : Windows10 x64 + Python 2.7.11 + PySide 1.2.4 | |
""" | |
import sys | |
from PySide.QtCore import * | |
from PySide.QtGui import * | |
brushFile = "brush.png" | |
bgFile = "bg.jpg" | |
canvasSize = (640, 480) | |
padding = 48 | |
# ズーム倍率 | |
zoomValue = 1.0 | |
zoomValues = [1.0 / 32, 1.0 / 24, 1.0 / 20, 1.0 / 16, 1.0 / 12, | |
1.0 / 8, 1.0 / 6, 1.0 / 4, 1.0 / 3, 1.0 / 2, | |
1.0, | |
2.0, 3.0, 4.0, 6.0, 8.0, 12.0, 16.0, 20.0, 24.0, 32.0] | |
zoomIndex = 10 | |
status = None | |
zoomDisp = None | |
gView = None | |
myApp = None | |
class DrawAreaScene(QGraphicsScene): | |
""" 描画ウインドウ用Scene """ | |
def __init__(self, *argv, **keywords): | |
super(DrawAreaScene, self).__init__(*argv, **keywords) | |
global brushFile | |
global padding | |
global bgFile | |
global canvasSize | |
self.zoomv = 1.0 | |
self.bgPixmap = QPixmap(bgFile) | |
canvasSize = (self.bgPixmap.width(), self.bgPixmap.height()) | |
self.bgItem = QGraphicsPixmapItem(self.bgPixmap) | |
self.addItem(self.bgItem) | |
self.bgItem.setOffset(padding, padding) | |
# Scene にブラシ画像を追加 | |
self.brushPixmap = QPixmap(brushFile) | |
self.brushItem = QGraphicsPixmapItem(self.brushPixmap) | |
self.addItem(self.brushItem) | |
def setVisibleBrush(self, flag): | |
""" ブラシ表示の有効無効切り替え """ | |
self.brushItem.setVisible(flag) | |
def changeScale(self, scale): | |
""" ズーム変更 """ | |
global padding | |
self.zoomv = scale | |
t = QTransform() | |
t.scale(self.zoomv, self.zoomv) # スケールだけ反映 | |
self.bgItem.setTransform(t) | |
bt = QTransform() | |
bt.scale(self.zoomv, self.zoomv) | |
self.brushItem.setTransform(bt) | |
def changeBrushPos(self, x, y): | |
""" ブラシの表示位置を変更 """ | |
global zoomValue | |
x = float(x) / zoomValue | |
y = float(y) / zoomValue | |
# ブラシが非表示なら表示を有効化 | |
if not self.brushItem.isVisible(): | |
self.setVisibleBrush(True) | |
# ブラシの表示位置を変更 | |
pm = self.brushItem.pixmap() | |
xd = (pm.width() / 2) | |
yd = (pm.height() / 2) | |
self.brushItem.setOffset(int(x - xd), int(y - yd)) | |
class DrawAreaView(QGraphicsView): | |
""" メインになるQGraphicsView """ | |
def __init__(self, *argv, **keywords): | |
super(DrawAreaView, self).__init__(*argv, **keywords) | |
self.setBackgroundBrush(QColor(64, 64, 64, 255)) # 背景色を設定 | |
self.setCacheMode(QGraphicsView.CacheBackground) | |
# 描画更新の仕方を選択 | |
self.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate) | |
# self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) | |
# self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) | |
self.oldCurPos = (0, 0) | |
self.button = Qt.NoButton | |
# Sceneを登録 | |
scene = DrawAreaScene(self) | |
self.setScene(scene) | |
self.setSceneNewRect() | |
# 子のSceneに対してマウストラッキングを有効にすると | |
# マウスカーソル移動時に常時 mouseMoveEvent() が呼ばれるようになる | |
vp = self.viewport().setMouseTracking(True) | |
def mousePressEvent(self, event): | |
""" マウスボタンを押した """ | |
x, y = self.getMousePos(event, "Click") | |
self.button = event.button() | |
self.oldCurPos = (x, y) | |
if event.buttons() & Qt.MidButton: | |
# 中ボタンならマウスカーソル変更 | |
global myApp | |
myApp.setOverrideCursor(Qt.ClosedHandCursor) | |
def mouseReleaseEvent(self, event): | |
""" マウスボタンを離した """ | |
x, y = self.getMousePos(event, "Release") | |
self.button = Qt.NoButton | |
# マウスカーソルを元に戻す | |
global myApp | |
myApp.restoreOverrideCursor() | |
def mouseMoveEvent(self, event): | |
""" マウスを動かしてる時に呼ばれる処理 """ | |
if self.button == Qt.NoButton: | |
# マウスカーソル移動のみ | |
x, y = self.getMousePos(event, "Move") | |
self.changeBrushPos(x, y) | |
elif self.button == Qt.LeftButton: | |
# 左ドラッグ | |
x, y = self.getMousePos(event, "Drag") | |
self.changeBrushPos(x, y) | |
elif self.button == Qt.MidButton: | |
# 中ボタンドラッグ | |
x, y = self.getMousePos(event, "Drag") | |
# 前回の座標位置との差を求める | |
dx = self.oldCurPos[0] - x | |
dy = self.oldCurPos[1] - y | |
self.oldCurPos = (x, y) | |
# スクロールバーの位置を変更 | |
ox = self.horizontalScrollBar().value() | |
oy = self.verticalScrollBar().value() | |
self.horizontalScrollBar().setValue(ox + dx) | |
self.verticalScrollBar().setValue(oy + dy) | |
else: | |
super(DrawAreaScene, self).mouseMoveEvent() | |
def getMousePos(self, event, msg): | |
""" マウス座標を取得 """ | |
x = event.pos().x() | |
y = event.pos().y() | |
kind = "" | |
if event.buttons() & Qt.LeftButton: | |
kind = "Left " | |
if event.button() & Qt.MidButton: | |
kind += "Mid " | |
if event.button() & Qt.RightButton: | |
kind += "Right " | |
global status | |
status.showMessage("(%d , %d) %s %s" % (x, y, kind, msg)) | |
return (x, y) | |
def changeBrushPos(self, x, y): | |
""" ブラシ表示位置を変更 """ | |
vr = self.viewport().rect() | |
vw, vh = vr.width(), vr.height() | |
sr = self.scene().sceneRect() | |
sw, sh = sr.width(), sr.height() | |
if vw - sw < 0: | |
# Hスクロールバーが有る | |
x += self.horizontalScrollBar().value() | |
else: | |
# Hスクロールバーが無いのでviewportの真ん中を基準にして座標算出 | |
x = (sw / 2) + (x - (vw / 2)) | |
if vh - sh < 0: | |
# Vスクロールバーが有る | |
y += self.verticalScrollBar().value() | |
else: | |
# Vスクロールバーが無いのでviewportの真ん中を基準にして座標算出 | |
y = (sh / 2) + (y - (vh / 2)) | |
self.scene().changeBrushPos(int(x), int(y)) | |
def resizeEvent(self, event): | |
""" リサイズ時に呼ばれる処理 """ | |
super(DrawAreaView, self).resizeEvent(event) | |
self.setSceneNewRect() | |
def scrollContentsBy(self, dx, dy): | |
""" スクロールバー操作時に呼ばれる処理 """ | |
# スクロール中、Scene内にブラシがあると | |
# 何故かゴミが残るので、ブラシを非表示にしている | |
self.scene().setVisibleBrush(False) | |
super(DrawAreaView, self).scrollContentsBy(dx, dy) | |
def setSceneNewRect(self): | |
""" Sceneの矩形を更新。キャンバス周辺に余白を設けたサイズを設定 """ | |
global canvasSize | |
global padding | |
global zoomValue | |
w, h = canvasSize | |
w += padding * 2 | |
h += padding * 2 | |
w *= zoomValue | |
h *= zoomValue | |
rect = QRectF(0, 0, int(w), int(h)) | |
# Sceneの矩形を更新。自動でスクロールバーの長さも変わってくれる | |
self.scene().setSceneRect(rect) | |
def wheelEvent(self, event): | |
""" マウスホイール回転時 """ | |
d = 0 | |
if event.delta() < 0: | |
# 下に回転 | |
d = -1 | |
else: | |
# 上に回転 | |
d = 1 | |
self.changeZoom(d) | |
def changeZoom(self, d): | |
""" ズーム変更 """ | |
global zoomValues | |
global zoomIndex | |
global zoomValue | |
vr = self.viewport().rect() | |
vw, vh = vr.width(), vr.height() | |
sr = self.scene().sceneRect() | |
sw, sh = sr.width(), sr.height() | |
# 今までのスクロールバーの位置を取得 | |
ox = float(self.horizontalScrollBar().value()) | |
oy = float(self.verticalScrollBar().value()) | |
w = sw - vw | |
h = sh - vh | |
ox /= w | |
oy /= h | |
# 新しいズーム値を得る | |
oldZoomIndex = zoomIndex | |
zoomIndex += d | |
if zoomIndex < 0: | |
zoomIndex = 0 | |
if zoomIndex >= len(zoomValues): | |
zoomIndex = len(zoomValues) - 1 | |
if zoomIndex == oldZoomIndex: | |
return | |
zoomValue = zoomValues[zoomIndex] | |
global zoomDisp | |
zoomDisp.setText("%d%s" % (zoomValue * 100, "%")) | |
self.scene().setVisibleBrush(False) | |
self.scene().changeScale(zoomValue) | |
self.setSceneNewRect() | |
self.viewport().update() | |
# 新しいスクロールバーの位置を設定 | |
sr = self.scene().sceneRect() | |
sw, sh = sr.width(), sr.height() | |
if w < 0: | |
nx = 0.5 * (sw - vw) | |
else: | |
nx = ox * (sw - vw) | |
self.horizontalScrollBar().setValue(int(nx)) | |
if h < 0: | |
ny = 0.5 * (sh - vh) | |
else: | |
ny = oy * (sh - vh) | |
self.verticalScrollBar().setValue(int(ny)) | |
class MyMainWindow(QMainWindow): | |
""" メインウインドウ """ | |
def __init__(self, *argv, **keywords): | |
super(MyMainWindow, self).__init__(*argv, **keywords) | |
self.setWindowTitle("Zoom and Mouse Tracking Test") | |
self.resize(640, 480) | |
self.initMenuBar() # メニューバー | |
self.initStatusBar() # ステータスバー | |
# 中央Widget | |
global gView | |
gView = DrawAreaView(self) | |
self.gView = gView | |
self.setCentralWidget(gView) | |
def initMenuBar(self): | |
""" メニューバー初期化 """ | |
mb = QMenuBar() | |
file_menu = QMenu("&File", self) | |
exit_action = file_menu.addAction("&Close") | |
exit_action.setShortcut('Ctrl+Q') | |
exit_action.triggered.connect(qApp.quit) | |
mb.addMenu(file_menu) | |
self.setMenuBar(mb) | |
def initStatusBar(self): | |
""" ステータスバー初期化 """ | |
global status | |
global zoomDisp | |
status = QStatusBar(self) | |
self.setStatusBar(status) | |
# 縮小ボタン | |
zoomOutBtn = QPushButton("-", self.statusBar()) | |
zoomOutBtn.setFixedSize(24, 24) | |
# 拡大ボタン | |
zoomInBtn = QPushButton("+", self.statusBar()) | |
zoomInBtn.setFixedSize(24, 24) | |
# 倍率表示 | |
zoomDisp = QLabel("100%", self.statusBar()) | |
zoomDisp.setFixedWidth(80) | |
zoomDisp.setFrameStyle(QFrame.Box | QFrame.Sunken) | |
zoomDisp.setAlignment(Qt.AlignRight | Qt.AlignVCenter) | |
# ステータスバーに追加 | |
self.statusBar().addPermanentWidget(zoomOutBtn) | |
self.statusBar().addPermanentWidget(zoomDisp) | |
self.statusBar().addPermanentWidget(zoomInBtn) | |
status.showMessage("Status Bar") | |
# ボタンが押されたときの処理を登録 | |
zoomOutBtn.clicked.connect(self.zoomOut) | |
zoomInBtn.clicked.connect(self.zoomIn) | |
def zoomOut(self): | |
""" 縮小ボタンを押した時の処理 """ | |
self.gView.changeZoom(-1) | |
def zoomIn(self): | |
""" 拡大ボタンを押した時の処理 """ | |
self.gView.changeZoom(1) | |
def main(): | |
""" メイン処理 """ | |
# このあたりを指定すると描画が速くなるという話を見かけたが、 | |
# "native"、"raster"、"opengl" を指定しても結果は変わらなかった… | |
QApplication.setGraphicsSystem("raster") | |
global myApp | |
myApp = QApplication(sys.argv) | |
w = MyMainWindow() | |
w.show() | |
sys.exit(myApp.exec_()) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment