読者です 読者をやめる 読者になる 読者になる

はんぎょねこの憂鬱

耳から変な汁が出てきた

PySide ドラッグ&ドロップ

PySide

忘れがちなポイント

  • setAcceptDrops(True)
  • 各イベントでevent.accept
  • ファイルパスはTextじゃなくてURL

f:id:dungeonneko:20170307160033g:plain

from PySide import QtCore, QtGui
import sys, os

class DragAndDrop(QtGui.QLabel):
    def __init__(self):
        super().__init__()
        self.setText('Drag and drop here')
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        filename = os.path.basename(event.mimeData().urls()[0].path())
        self.setText(filename)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    w = DragAndDrop()
    w.show()
    sys.exit(app.exec_())

QMessageBoxでチョロっとダイアログ表示

PySide

f:id:dungeonneko:20170222145049g:plain

import sys
from PySide import QtGui

app = QtGui.QApplication(sys.argv)
icon = None
QtGui.QMessageBox.about(icon, 'About', 'Hello.')
QtGui.QMessageBox.question(icon, 'Question', 'Are you dead?', QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
QtGui.QMessageBox.information(icon, 'Info', 'You are dead!', QtGui.QMessageBox.Ok)
QtGui.QMessageBox.warning(icon, 'Warn', 'You are dead!', QtGui.QMessageBox.Ok)
QtGui.QMessageBox.critical(icon, 'Error', 'You are dead!', QtGui.QMessageBox.Ok)

戻り値は君の眼で確かめてみてくれ!

Pythonでウインドウハンドル取得してGetClientRectしてみる

PySide

WIN32から離れられない貴方へ

import sys
from PySide import QtGui
import ctypes


class RECT(ctypes.Structure):
    _fields_ = [('left', ctypes.c_long),
                ('top', ctypes.c_long),
                ('right', ctypes.c_long),
                ('bottom', ctypes.c_long)]


class TestWidget(QtGui.QWidget):
    def __init__(self):
        super().__init__()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        
        # ここから!
        ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
        ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
        hwnd = ctypes.pythonapi.PyCapsule_GetPointer(self.winId(), None)
        rect = RECT()
        ctypes.windll.user32.GetClientRect(hwnd, ctypes.byref(rect))
        print(rect.left, rect.top, rect.right, rect.bottom)

if __name__ == '__main__':
    a = QtGui.QApplication(sys.argv)
    w = TestWidget()
    w.show()
    sys.exit(a.exec_())

参考記事

stackoverflow.com

Widgetを等倍で中央寄せさせておきたい!

PySide

QScrollAreaのresizeEventを継承してゴニョゴニョしたら割と簡単にできた

f:id:dungeonneko:20170216171205g:plain

import sys
from PySide import QtCore, QtGui

class AlwaysCenterAlign(QtGui.QScrollArea):
    def __init__(self):
        super().__init__()
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        v = self.verticalScrollBar()
        h = self.horizontalScrollBar()
        v.setValue(v.minimum() + (v.maximum() - v.minimum()) / 2)
        h.setValue(h.minimum() + (h.maximum() - h.minimum()) / 2)

    def wheelEvent(self, _):
        pass

    def keyPressEvent(self, _):
        pass

if __name__ == '__main__':
    a = QtGui.QApplication(sys.argv)
    s = AlwaysCenterAlign()
    l = QtGui.QLabel()
    l.setPixmap(QtGui.QPixmap('lena.jpg'))
    s.setWidget(l)
    s.show()
    sys.exit(a.exec_())

RaspberryPi 3 RPIOでサーボモーターを動かす

Raspberry Pi

RPIOをインストール

モーターは SG90 Digital を使いました

下記サイトを参考に github.com

こんな感じでインストール

cd ~
git clone https://github.com/metachris/RPIO.git --branch v2 --single-branch
cd RPIO
sudo python3.4 setup.py install

スクリプトを書く

root権限で実行しないといけないっぽい

from RPIO import PWM
import time

servo = PWM.Servo()
pin = 14


def deg_to_width(deg):
    ''' 角度からパルス幅に変換

    :param: deg 角度(-90~+90)
    :return: パルス幅
    '''
    center = 127
    margin = 45
    step = (center - margin) / 90.0
    deg = max(-90.0, min(deg, 90.0))
    return int(center + deg * step) * 10

while True:
    for x in [-90, 0, 90, 0]:
        servo.set_servo(pin, deg_to_width(x))
        time.sleep(1)

ちなみにPWMとPCMは同時に使えないのでサーボ制御しながら音も出したいときはBluetoothスピーカーとかUSBサウンドカードを使ったほうがよさげ

QActionでコードもすっきり

PySide

メニューやツールバーを実装するときはQPushButtonやコールバックをガリガリ書くのではなくQActionを作る。QActionオブジェクトを作成しておけば、複数のメニューやツールバーをまたいでも、ひとつのQActionオブジェクトで挙動を定義できるし、QActionGroupを使えばラジオボタンも作れる。ショートカットキーやIconも設定できる。

f:id:dungeonneko:20170125160034g:plain

import sys
from PySide.QtCore import *
from PySide.QtGui import *

# ラジオボタン的な挙動をするメニューを作る例
if __name__ == '__main__':

    myapp = QApplication(sys.argv)
    window = QMainWindow()

    # まずはExclusiveなグループを作成
    exclusive_group = QActionGroup(window)
    exclusive_group.setExclusive(True)

    # グループに属するアクションを作る
    def createExclusiveAction(in_name, in_icon, in_shortcut):
        action = QAction(in_name, window)
        action.setActionGroup(exclusive_group)
        action.setIcon(myapp.style().standardIcon(in_icon))
        action.setShortcut(in_shortcut)
        action.setCheckable(True)
        return action

    data = [
        ('Play', QStyle.SP_MediaPlay, 'Q'),
        ('Pause', QStyle.SP_MediaPause, 'W'),
        ('Stop', QStyle.SP_MediaStop, 'E'),
        ('Forward', QStyle.SP_MediaSeekForward, 'R'),
        ('Backward', QStyle.SP_MediaSeekBackward, 'T'),
    ]

    actions = []
    for name, icon, shortcut in data:
        actions.append(createExclusiveAction(name, icon, shortcut))

    # メニューバーとツールバー両方に追加して動作テスト
    menubar = window.menuBar()
    mediamenu = menubar.addMenu('Media')
    toolbar = window.addToolBar('ToolBar')
    for x in actions:
        mediamenu.addAction(x)
        toolbar.addAction(x)

    window.show()
    sys.exit(myapp.exec_())

ラズパイ起動時にBluetooth接続してwav再生

Raspberry Pi

自分の環境でちゃんと動く記事が無かったのでメモ。設定ファイルをあちこち編集するのは好きじゃないのでbash_profileで頑張った。Linuxめんどい。

環境

1. モジュールインストール

sudo apt-get install pulseaudio pavucontrol pulseaudio-module-bluetooth
reboot

2. 準備(GUIからTerminal起動してやった)

pulseaudioを起動

pulseaudio -D

ペアリング

bluetoothctl
> scan on
> (BluetoothスピーカーをペアリングモードにしてMACアドレスが表示されるまで待つ)
> scan off
> pair FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> connect  FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> trust  FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> quit

バイスの設定

  • メニュー ⇒ Sound & Video ⇒ PulseAudio Volume Control
  • ConfigurationメニューからALSAの出力先をOffにしておく

3. 起動スクリプトを用意

.bash_profileをユーザーのホームディレクトリに作成

pulseaudio -D
sleep 5  # 適当にウェイト
bluetoothctl << EOF
power on
connect FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
quit
EOF
sleep 5  # 適当にウェイト
pacmd set-sink-volume 1 32767  # 音量調節(デバイス番号、音量~65565)
aplay test.wav  # wavを再生

CUIモードにしてreboot