はんぎょねこの憂鬱

耳から変な汁が出てきた

PySide2 ビルドしてみる

ここ見たら大体書いてあるよ

https://wiki.qt.io/PySide2_GettingStarted

環境

  • Windows10
  • Python 3.6 (64bit)
  • Qt 5.6
  • OpenSSLは使わないよ

インストールしておくもの

ビルド

  1. pyside-setupリポジトリの5.6ブランチをrecursiveでcloneする
  2. 一応virtualenvでビルド環境つくってactivate
  3. sphinxモジュールをインストール
  4. VCコンパイラのパス通す
  5. wheelビルド実行
こんな感じ
> git clone -b 5.6 http://code.qt.io/cgit/pyside/pyside-setup.git/ --recursive
> pip install virtualenv
> virtualenv hogehoge
> hogehoge/Scripts/activate.bat
> pip install sphinx
> cd pyside-setup
> "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
> python setup.py bdist_wheel --ignore-git --qmake="C:\Qt\Qt5.6.2\5.6\msvc2015_64\bin\qmake.exe" --cmake="C:\Program Files\CMake\bin\cmake.exe"
fcntlを呼ぼうとしてエラーが出る?

popenasync.pyのdecode関数を適当に修正
(たぶん日本語表示しようとして例外出てる)

def decode(b):
    try:
        return b.decode('utf-8')
    except UnicodeDecodeError:
        return b.decode('shift-jis')

英語圏へ対応する気はなさそうだ

RC Pass 1 failed to run. というエラーが出る?

rc.exeにパスが通っていないか別プラットフォームのrc.exeを読んで失敗している

set PATH=%PATH%;C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x64

こんな感じでrc.exeのある場所にパスを通すか、WindowsSDKをインストールしなおすと良いと思う

bdist_wheelコマンドが見つからない?
pip install wheel

もしくはこのあたりを参考に

リンクエラーが発生する?

prebuiltなものとビルドターゲットがズレてると出る気がする。32bit版のPythonとかQtを使ってるとか、vcvarsall.bat呼ぶときにamd64つけてないとか

whlのサイズが大きいのが気になる?

setup.pyのdllコピーの規則がQt4時代から変わってないのが原因

  • デバッグ用のdllも含まれてしまっている
  • setup.pyの d4.dll, d?.dll となっている箇所を *d.dll に修正する

PySide Custom Dialog

Dialogを自分で実装するときのポイント
  • QDialogを継承する
  • ボタン表示にはQDialogButtonBoxを使う
こんなかんじ
from PySide import QtCore, QtGui
import sys


class CustomDialog(QtGui.QDialog):
    def __init__(self):
        super().__init__()
        self._buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self._buttons.accepted.connect(self.accept)
        self._buttons.rejected.connect(self.reject)
        mainLayout = QtGui.QVBoxLayout()
        mainLayout.addWidget(self._buttons)
        self.setLayout(mainLayout)
        self.setWindowTitle('Custom Dialog')


app = QtGui.QApplication(sys.argv)
mainwindow = QtGui.QMainWindow()
mainwindow.show()
dialog = CustomDialog()
if QtGui.QDialog.Accepted == dialog.exec_():
    QtGui.QMessageBox.information(None, 'Info', 'accepted!', QtGui.QMessageBox.Ok)
else:
    QtGui.QMessageBox.critical(None, 'Info', 'rejected!', QtGui.QMessageBox.Ok)
広告を非表示にする

ねんがんの GPD Pocket をてにいれたぞ

とりあえず一台ゲットしたので適当レビュー
使っているうちに意見をコロコロ変える可能性あります

スペックおさらい

OS Ubuntu 16.04 LTS or Windows 10 Home
CPU 🤔 Quad-core 1.6GHz
GPU Integrated processor
RAM 8GB
ストレージ容量 128GB (😬 Samsung MDGAGC)
画面の大きさ 😀 1920×1200,7 inch, 323.45PPI
画面の素材 Corning Gorilla Glass 3
冷却 Active Cooling(意外とうるさい)
サイズ 😀 180×106×18.5mm
素材 CNC precise all-in-one body made of magnesium and alloy
MacBookみたいなやつ)
Weight 😀 480g
バッテリー 😀 12時間
価格 😀 Indiegogo price $399, Retail price $599

詳細はIndiegogoのプロジェクトページを見てよ

気に入っているところ

  • 安い($399でゲットした)!頑丈!持ちやすい!小さい!
  • USB Type-Aポートがついてる
  • ディスプレイが綺麗
  • 持ち歩いてると「おっ、何それ?」って言われる

気に入らない点

  • CPUパフォーマンス全然足りない
  • 本体右側が熱い。タイピングしてると熱いな~ってなる
  • えっ?USB Type-Cポート1個しかないのに電源ケーブルに使うの?
  • Capsキー小さくて押しづらい
  • スペースキーとクリックを間違えることがよくある
  • スペースキーが二つに分かれてるのがキモい
  • トラックポイントはGとHの間に欲しい
  • 純正トラックポイントがツルツルしてて滑る
  • スピーカーの音質が気に食わない&イヤホンジャックのノイズが多い
  • ストレージがeMMC(SSDよりもっさりする)

f:id:dungeonneko:20170713002414p:plain

メモ

  • 次のモデルはSIMカード対応するという噂
  • 無線LAN802.11acしか対応してないっぽい
  • キー配置は慣れれば思ったより気にならない

まとめ

まぁこんなもんかなという感じ。僕は今からコールドスリープするから
transbook t90の後継機が出たら起こしてくれ

PySide 編集可能なQTreeWidgetを作る

f:id:dungeonneko:20170622151448g:plain

import sys
from PySide import QtCore, QtGui


class MyTreeWidget(QtGui.QTreeWidget):
    def __init__(self):
        super().__init__()
        self.setHeaderHidden(True)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenuRequested)

        # アイテムの移動を有効にする
        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.setDragEnabled(True)
        self.viewport().setAcceptDrops(True)

        # 挿入位置カーソルを表示する
        self.setDropIndicatorShown(True)

    def contextMenuRequested(self, pos):
        """ コンテキストメニュー
        """
        menu = QtGui.QMenu()
        item = self.itemAt(pos)
        if item:
            action = QtGui.QAction('Delete Item', self)
            action.triggered.connect(lambda: self.removeItem(item))
            menu.addAction(action)
        else:
            action = QtGui.QAction('New Item', self)
            action.triggered.connect(self.createNewItem)
            menu.addAction(action)
        menu.exec_(self.mapToGlobal(pos))

    def createNewItem(self):
        """ 新規項目
        """
        item = QtGui.QTreeWidgetItem()
        item.setText(0, 'Item')

        # アイテムを編集可能にする
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)

        self.addTopLevelItem(item)

    def removeItem(self, item):
        """ 項目削除
        """
        parent = item.parent()
        if parent:
            parent.takeChild(parent.indexOfChild(item))
        else:
            self.takeTopLevelItem(self.indexOfTopLevelItem(item))


app = QtGui.QApplication(sys.argv)
tr = MyTreeWidget()
tr.show()
sys.exit(app.exec_())

PySide movable and resizable widget without frame

f:id:dungeonneko:20170619180539g:plain

import sys, enum
from PySide import QtCore, QtGui

class Item(QtGui.QLabel):
    Manipilate = enum.Enum('Manipilate', 'none move resize_l resize_r')
    
    def __init__(self, text):
        super().__init__(text)
        self.setStyleSheet('background-color: white; border: 1px solid black; padding: 4px;')
        self.resize(128, 64)
        self._mani = Item.Manipilate.none
        self._offset = QtCore.QPoint(0, 0)
        self._rect = self.rect()
        self.setMouseTracking(True)

    def mousePressEvent(self, event):
        pos = event.pos()
        if self._mani == Item.Manipilate.none:
            self._mani = self.get_manipulation(pos)
        self._offset = event.pos()
        self._rect = self.geometry()

    def mouseReleaseEvent(self, event):
        self._mani = Item.Manipilate.none

    def mouseMoveEvent(self, event):
        pos = event.pos()
        if self._mani == Item.Manipilate.none:
            self.setCursor({
                Item.Manipilate.none: QtCore.Qt.ArrowCursor,
                Item.Manipilate.move: QtCore.Qt.ArrowCursor,
                Item.Manipilate.resize_l: QtCore.Qt.SizeHorCursor,
                Item.Manipilate.resize_r: QtCore.Qt.SizeHorCursor
            }[self.get_manipulation(pos)])
        elif self._mani == Item.Manipilate.move:
            self.move(self.mapToParent(pos - self._offset))
        elif self._mani == Item.Manipilate.resize_l:
            sub = pos - self._offset
            self.setGeometry(self._rect.x() + sub.x(), self._rect.y(), self._rect.width() - sub.x(), self._rect.height())
            self._rect = self.geometry()
        elif self._mani == Item.Manipilate.resize_r:
            sub = pos - self._offset
            self.setGeometry(self._rect.x(), self._rect.y(), self._rect.width() + sub.x(), self._rect.height())
        self.update()

    def get_manipulation(self, pos):
        if pos.x() < 8:
            return Item.Manipilate.resize_l
        if pos.x() > (self.width() - 8):
            return Item.Manipilate.resize_r
        else:
            return Item.Manipilate.move

app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
widget.setFixedSize(640, 480)
item = Item('item')
item.setParent(widget)
item.move(0, 0)
widget.show()
sys.exit(app.exec_())

PySide Progressbarを表示してみる

QProgressDialogを使うと一番簡単であります

f:id:dungeonneko:20170612143019g:plain

import sys
from PySide import QtGui

app = QtGui.QApplication(sys.argv)
prog = QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 100, None, 0)
prog.show()
prog.setValue(50)
sys.exit(app.exec_())
キャンセルボタンなくしたい

コンストラクタのキャンセルボタンのテキストにNoneを指定

QtGui.QProgressDialog('何か処理しています...', None, 0, 100, None, 0)
ウインドウのボタンを消したい

コンストラクタの最後の引数でウインドウフラグを指定

QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 100, None,
    QtCore.Qt.Window | QtCore.Qt.WindowTitleHint | QtCore.Qt.CustomizeWindowHint)
リサイズなくしたい

setFixedSizeを呼ぶ

prog.setFixedSize(prog.sizeHint())
Marqueeスタイル(進捗表示しないで処理の終了だけ待つやつ)にしたい

f:id:dungeonneko:20170612153620g:plain

プログレスバーの範囲を 0 ~ 0 にする

QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 0, None, 0)

バー横の空白が残る場合は進捗表示テキストをセンター寄せすればいい

prog.setStyleSheet('QProgressBar {text-align: center;}')

QScrollAreaに固定されたヘッダーを表示する

f:id:dungeonneko:20170605143912g:plain

import sys
from PySide import QtGui


class MyTable(QtGui.QWidget):
    def __init__(self):
        super().__init__()
        lo = QtGui.QHBoxLayout()
        lo.setContentsMargins(0, 0, 0, 0)
        lo.setSpacing(0)
        self.setLayout(lo)

        self._header = QtGui.QLabel('header')
        self._header.setStyleSheet('border-right: 1px solid black;\
            border-bottom: 1px solid black;\
            background-color: red;')
        self._header.setFixedSize(128, 32)
        lo.addWidget(self._header)

        for i in range(0, 8):
            item = QtGui.QLabel('cell {0}'.format(i))
            item.setStyleSheet('border-right: 1px solid black;\
                border-bottom: 1px solid black;\
                background-color: white;')
            item.setFixedSize(128, 32)
            lo.addWidget(item)

    def setHeaderPos(self, x, y):
        self._header.move(x, y)
        self._header.raise_()


app = QtGui.QApplication(sys.argv)
table = MyTable()
scroll = QtGui.QScrollArea()
scroll.setWidget(table)

def moveHeader():
    x = scroll.horizontalScrollBar().value()
    table.setHeaderPos(x, 0)

scroll.horizontalScrollBar().valueChanged.connect(moveHeader)
scroll.show()
sys.exit(app.exec_())

スライダーの移動量を固定する

f:id:dungeonneko:20170605163459g:plain

下記コードを追加する

def scrollControll(x):
    x = int(x / 128) * 128
    scroll.horizontalScrollBar().setValue(x)

scroll.horizontalScrollBar().setSingleStep(128)  # ボタン押された時の移動量
scroll.horizontalScrollBar().sliderMoved.connect(scrollControll)