2016/11/09(水) [n年前の日記]
#1 [python] PySideでツールパネルっぽいソレを作成できないかテスト
Photoshop や GIMP等の、ツールパネル?っぽいソレをPySideで作れるかどうかテスト。…ツールパネルって呼び方でいいのかな? ツールパレット? なんて呼ぶんだろアレ。要は、どれか一つ、ツールを選ぶと ―― どれか一つボタンを押すと、他のボタンがOFFになるアレ。
_Photoshop のツールギャラリー
こんな感じだろうか。
_button_test.py

どれか一つボタンを押すと、他のボタンがOFFになってる。更に、IDも変わってる。目的は果たせそう。
こういう動作にする際は、 _QButtonGroup を使う。 _QPushButton (フツーのボタン)でも、 _QRadioButton (ラジオボタン)でも、とにかく QButtonGroup に追加登録してやれば、こういう動作にすることができる模様。また、各ボタンを登録する際にIDを設定しておいてやると、後で変化があった際、押されたボタンのIDを取得することもできる。
QPushButtonは、そのままだとクリックした時しかONにならないけど。クリックするたびにON/OFFを切り替えるトグルボタン(?)にしたい時は、 _setCheckable(True) を呼んで設定してやる。
それと、最初に QPushButton のどれかしらが既に押されている状態にしたいわけだけど。その場合は、 _setChecked(True) を呼んでやれば、ON状態に設定できる。
QButtonGroup に登録されたボタンの状態が変わったら、QButtonGroup は _buttonClicked というシグナルを出すので、そのシグナルにメソッドを割り当ててやれば、どのIDのボタンが押されたのかが分かる。
_Photoshop のツールギャラリー
こんな感じだろうか。
_button_test.py
u"""押したままの状態になるボタンを並べてツールパネル相当を作るテスト."""
import sys
from PySide.QtCore import * # NOQA
from PySide.QtGui import * # NOQA
class ToolPanel(QWidget):
u"""ツールパネル用Widget."""
button_clicked = Signal(int) # シグナルを用意
def __init__(self, *argv, **keywords):
u"""初期化."""
super(ToolPanel, self).__init__(*argv, **keywords)
self.grp = QButtonGroup()
gb = QGridLayout()
dt = [
(0, 0, 0, "Brush"),
(1, 0, 1, "Erase"),
(0, 1, 2, "Box"),
(1, 1, 3, "Box Fill"),
(0, 2, 4, "Select"),
(1, 2, 5, "Fill"),
]
for x, y, id, s in dt:
btn = QPushButton(s, self)
btn.setCheckable(True) # トグルボタンにする
self.grp.addButton(btn, id) # グループ登録してラジオボタン風に使う
gb.addWidget(btn, y, x)
self.setLayout(gb)
self.grp.setExclusive(True) # 排他的なボタン処理にする
self.grp.button(0).setChecked(True) # 最初のボタンを押しておく
self.grp.buttonClicked.connect(self.changed_button)
def changed_button(self):
u"""ボタンが押された時に呼ばれる処理."""
self.button_clicked.emit(self.grp.checkedId()) # シグナルを発生
def get_id(self):
u"""選択状態(ID)を返す."""
return self.grp.checkedId()
class MyMainWidget(QWidget):
u"""メインウインドウ."""
def __init__(self, *argv, **keywords):
u"""初期化."""
super(MyMainWidget, self).__init__(*argv, **keywords)
self.tb = ToolPanel(self)
self.tb.button_clicked.connect(self.changed_toolbox)
self.lbl = QLabel("----", self)
self.lbl.setText("ID=%d" % self.tb.get_id())
l = QVBoxLayout()
l.addWidget(self.tb)
l.addWidget(self.lbl)
self.setLayout(l)
def changed_toolbox(self, id):
u"""ツールの選択状態が切り替わった時に呼ばれる処理."""
self.lbl.setText("ID=%d" % id)
def main():
u"""メイン処理."""
app = QApplication(sys.argv)
w = MyMainWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

どれか一つボタンを押すと、他のボタンがOFFになってる。更に、IDも変わってる。目的は果たせそう。
こういう動作にする際は、 _QButtonGroup を使う。 _QPushButton (フツーのボタン)でも、 _QRadioButton (ラジオボタン)でも、とにかく QButtonGroup に追加登録してやれば、こういう動作にすることができる模様。また、各ボタンを登録する際にIDを設定しておいてやると、後で変化があった際、押されたボタンのIDを取得することもできる。
QPushButtonは、そのままだとクリックした時しかONにならないけど。クリックするたびにON/OFFを切り替えるトグルボタン(?)にしたい時は、 _setCheckable(True) を呼んで設定してやる。
それと、最初に QPushButton のどれかしらが既に押されている状態にしたいわけだけど。その場合は、 _setChecked(True) を呼んでやれば、ON状態に設定できる。
QButtonGroup に登録されたボタンの状態が変わったら、QButtonGroup は _buttonClicked というシグナルを出すので、そのシグナルにメソッドを割り当ててやれば、どのIDのボタンが押されたのかが分かる。
◎ アイコン画像を表示してみた。 :
各ボタンにテキストが表示されてる状態ではツールパネルっぽくないので、試しにアイコン画像を表示してみたり。
_button_test_with_icon.py.txt

_toolbar_32x32x8.png
こんな見た目になった。

_QIcon - PySide v1.0.7 documentation
_QGridLayout - PySide v1.0.7 documentation
_QWidget - PySide v1.0.7 documentation
_QApplication - PySide v1.0.7 documentation
_QStyleFactory - PySide v1.0.7 documentation
_button_test_with_icon.py.txt
u"""
押したままの状態になるボタンを並べてツールパネル相当を作るテスト.
アイコン画像をボタン上に表示してみる版。
"""
import sys
from PySide.QtCore import * # NOQA
from PySide.QtGui import * # NOQA
class ToolPanel(QWidget):
u"""ツールパネル用Widget."""
button_clicked = Signal(int) # シグナルを用意
def __init__(self, *argv, **keywords):
u"""初期化."""
super(ToolPanel, self).__init__(*argv, **keywords)
# 画像を読み込んで分割
im = QImage("./toolbar_32x32x8.png")
h = im.size().height()
imgs = []
for i in range(8):
imgs.append(im.copy(i * h, 0, h, h))
self.grp = QButtonGroup()
gb = QGridLayout()
gb.setHorizontalSpacing(0) # GridLayoutの水平余白を設定
gb.setVerticalSpacing(0) # GridLayoutの垂直余白を設定
dt = [
(0, 0, 0, "Brush"),
(1, 0, 1, "Erase"),
(2, 0, 2, "Line"),
(3, 0, 3, "Box Line"),
(4, 0, 4, "Box Fill"),
(0, 1, 5, "Fill"),
(1, 1, 6, "Text"),
(2, 1, 7, "Select"),
]
for x, y, id, s in dt:
icon = QIcon(QPixmap.fromImage(imgs[id])) # アイコン画像を生成
btn = QPushButton(icon, "", self)
# アイコンサイズを画像の元サイズにする
btn.setIconSize(imgs[id].size())
btn.setToolTip(s) # ツールチップを指定
btn.setCheckable(True) # トグルボタンにする
self.grp.addButton(btn, id) # グループ登録してラジオボタン風に使う
gb.addWidget(btn, y, x)
self.setLayout(gb)
self.grp.setExclusive(True) # 排他的なボタン処理にする
self.grp.button(0).setChecked(True) # 最初のボタンを押しておく
self.grp.buttonClicked.connect(self.changed_button)
def changed_button(self):
u"""ボタンが押された時に呼ばれる処理."""
self.button_clicked.emit(self.grp.checkedId()) # シグナルを発生
def get_id(self):
u"""選択状態(ID)を返す."""
return self.grp.checkedId()
class MyMainWidget(QWidget):
u"""メインウインドウ."""
def __init__(self, *argv, **keywords):
u"""初期化."""
super(MyMainWidget, self).__init__(*argv, **keywords)
self.tb = ToolPanel(self)
self.tb.button_clicked.connect(self.changed_toolbox)
self.lbl = QLabel("----", self)
self.lbl.setText("ID=%d" % self.tb.get_id())
l = QVBoxLayout()
l.addWidget(self.tb)
l.addWidget(self.lbl)
self.setLayout(l)
def changed_toolbox(self, id):
u"""ツールの選択状態が切り替わった時に呼ばれる処理."""
self.lbl.setText("ID=%d" % id)
def main():
u"""メイン処理."""
app = QApplication(sys.argv)
app.setStyle(QStyleFactory.create("cleanlooks"))
# app.setStyle(QStyleFactory.create("plastique"))
# app.setStyle(QStyleFactory.create("windows"))
# app.setStyle(QStyleFactory.create("windowsvista"))
w = MyMainWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
アイコン画像として、以下を自作した。スクリプトソースも含めて、CC0 / Public Domain ってことで。こんな見た目になった。
- アイコンの指定には、QIcon を使う。QPixmap を渡すと作れるっぽいので、元画像が QImage なら、QIcon(QPixmap.fromImage(qimg)) てな記述になる。
- QIcon は、自動で画像を縮小して適切なサイズにしてしまうので、元画像サイズで表示したい場合は setIconSize() を呼んで元画像サイズを指定してやる。
- QGridLayout の水平余白、垂直余白は、setHorizontalSpacing()、setVerticalSpacing() で指定できる。
- ボタン上にマウスカーソルを合わせた際にピョコッと表示されるツールチップは、setToolTip() で指定できる。
- 各Widgetの全体的な見た目は、QApplication.setStyle() で変更できる。QStyleFactory.create("cleanlooks") や QStyleFactory.create("plastique") を渡してやると見た目が変わる。指定できる文字列としては、windows、motif、cde、plastique、cleanlooks があるらしい。
_QIcon - PySide v1.0.7 documentation
_QGridLayout - PySide v1.0.7 documentation
_QWidget - PySide v1.0.7 documentation
_QApplication - PySide v1.0.7 documentation
_QStyleFactory - PySide v1.0.7 documentation
[ ツッコむ ]
#2 [python] PySideのQKeySequenceについてメモ
PySideでメインウインドウにメニューバーをつけた際、各メニューに、setShortcut() を使って、ショートカットキーを割り当てたりすると思うのだけど。
今頃になって、 _QKeySequence なるものがあると知ったのでメモ。
QKeySequence には、あらかじめ決まったショートカットキーが定義されてたりするようで、ソレを使うと分かりやすく…なるのかな? どうかな? どうなんだろう。かえって分かりづらくなったりしないか。どうなんだ。そのへん判断できないけど、とりあえずそういう指定もできるようですね、とメモ。
ただ、一部、シンボルはあるけど中身が定義されてない組み合わせもあるようで。例えば、QKeySequence.SaveAs には何も割り当てられていなかった。
_QKeySequence - PySide v1.0.7 documentation を眺めた感じでは、SaveAs は、Mac OS X と GNOME なら Ctrl+Shift+S が割り当てられてるけど、Windows では何も割り当てられてないっぽい。このキーの組み合わせは、OS側で何かしらに使ってたりするのだろうか? ちょっとよく分からない。
他にも、"Ctrl+S" といった記述の他に、Qt.CTRL + Qt.Key_S、といった記述も可能らしい。
zoomactual_action = view_menu.addAction("Zoom &1:1")
zoomactual_action.setShortcut("Ctrl+1")
zoomactual_action.triggered.connect(self.zoom_actual_pixels)
今頃になって、 _QKeySequence なるものがあると知ったのでメモ。
QKeySequence には、あらかじめ決まったショートカットキーが定義されてたりするようで、ソレを使うと分かりやすく…なるのかな? どうかな? どうなんだろう。かえって分かりづらくなったりしないか。どうなんだ。そのへん判断できないけど、とりあえずそういう指定もできるようですね、とメモ。
open_action = QAction("&Open", self)
open_action.setShortcut(QKeySequence(QKeySequence.Open))
open_action.setStatusTip("Open Image File")
open_action.triggered.connect(self.show_open_dialog)
file_menu.addAction(open_action)
...
zoomin_action = view_menu.addAction("Zoom &In")
zoomin_action.setShortcut(QKeySequence(QKeySequence.ZoomIn))
zoomin_action.triggered.connect(self.zoom_in)
zoomout_action = view_menu.addAction("Zoom &Out")
zoomout_action.setShortcut(QKeySequence(QKeySequence.ZoomOut))
zoomout_action.triggered.connect(self.zoom_out)
ただ、一部、シンボルはあるけど中身が定義されてない組み合わせもあるようで。例えば、QKeySequence.SaveAs には何も割り当てられていなかった。
_QKeySequence - PySide v1.0.7 documentation を眺めた感じでは、SaveAs は、Mac OS X と GNOME なら Ctrl+Shift+S が割り当てられてるけど、Windows では何も割り当てられてないっぽい。このキーの組み合わせは、OS側で何かしらに使ってたりするのだろうか? ちょっとよく分からない。
他にも、"Ctrl+S" といった記述の他に、Qt.CTRL + Qt.Key_S、といった記述も可能らしい。
save_as_action = QAction("Save &As...", self)
save_as_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S))
save_as_action.setStatusTip("Save Canvas Image")
save_as_action.triggered.connect(self.show_save_dialog)
file_menu.addAction(save_as_action)
[ ツッコむ ]
以上、1 日分です。