takuroooのブログ

勉強したこととか

Video.jsでHLS/MPEG-DASH対応Playerを作る

JavaScriptのVideo.jsを使うとHLSとMPEG-DASHに対応したplayerが簡単に作れそうだったので作ってみた。
今回使ったVideo.jsの基本機能をまとめていく。

github.com

目次

作ったplayer

f:id:takuroooooo:20200809152120p:plain

Video URLにHLS or MPEG-DASHのManifestファイルのURLを指定してLoadボタンを押すと再生を開始する。デフォルトではHLSのURLを読み込むようにしている。

Video.js CDN

以下のように書くとCDN経由でVideo.jsを読み込むことができる。

<head>
  <link href="https://vjs.zencdn.net/7.8.4/video-js.css" rel="stylesheet" />
  <script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script>
</head>

Player生成

video-jsタグでplayerを作ることができる。ここに動画が表示される。

<video-js id=videoPlayer class="vjs-fluid  vjs-default-skin vjs-big-play-centered mx-auto"></video-js>
  • vjs-fluidはロードしたビデオのアスペクト比を自動で計算してplayerに反映してくれる。
  • vjs-big-play-centeredはプレイボタンをplayerの中心に配置してくれる。
  • mx-autoはBootStrapのクラスで画面の中央にplayerを配置してくれる。

classの他にも色々なattributeを付けることでplayerを定義できる。
例えば、

  • controlsを定義するとplayerにコントロールパネルが付与される。
  • width=640 height=480のように書くとplayerの大きさを定義できる。

これらのパラメータはJavaScript側でも設定することができる。

playerはvideoタグでも作れるがVideo.jsの公式ドキュメトによるとvideo-jsタグを使う方が望ましいとのこと。

You can use a video-js element instead of video. Using a video element is undesirable in some circumstances, as the browser may show unstyled controls or try to load a source in the moments before the player initialises, which does not happen with the video-js custom element.

Tutorial: setup | Video.js Documentation

Playerオブジェクト生成

JavaScript側でplayerを制御するためにオブジェクトを生成する。

var player = videojs('videoPlayer', {
    autoplay      : false,
    loop          : false,
    controls      : true,
    preload       : 'auto',
    playbackRates : [0.5, 1, 1.5, 2]
});
  • videojsの第一引数はvideo-jsタグのidを指定する。
  • 第二引数には先に説明したplayerに関するattributeを付与することができる。
    • autoplayは動画をロードした直後にすぐ再生を開始するかを指定
    • loopは動画をループするかを指定
    • controlsはplayerにコントロールパネルをつけるかどうかを指定
    • preloadは動画の先行してロードするかを指定
    • playbackRatesはコントロールパネルに動画の再生速度を制御するボタンをつけるかどうかを指定

Playerに再生する動画を設定

src()を使うと再生する動画を指定できる。

player.src({
    type: "application/x-mpegURL",
    src: "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8"
});
  • typeは再生する動画のmimeTypeを指定
  • srcはHLSもしくはMPEG-DASHのManifestファイルのパスを指定

HLSで再生する場合、mimeTypeはapplication/x-mpegURLMPEG-DASHで再生する場合はapplication/dash+xmlを設定する。

Playerで動画を再生

play()を呼ぶと動画の再生が開始される。

player.play()

play()を呼ばないと動画のロードだけ実行して再生待ち状態になる。

参考

videojs.com

github.com


Go stringsで文字列を操作する

Goの文字列操作を勉強したのでそのまとめ。

目次

stringsパッケージに文字列操作のためな便利な関数があるのでまずはこれをimportする。

import (
    "strings"
)

golang.org

大文字小文字変換

// 大文字小文字変換
fmt.Println(strings.ToUpper("toupper")) // TOUPPER
fmt.Println(strings.ToLower("TOLOWER")) // tolower

タイトルケースに変換

fmt.Println(strings.Title("hey"))     // "Hey"
fmt.Println(strings.Title("heyhey"))  // "Heyhey"
fmt.Println(strings.Title("hey hey")) // "Hey Hey"

文字列を比較

fmt.Println(strings.Compare("a", "a")) // 0
fmt.Println(strings.Compare("a", "b")) // -1
fmt.Println(strings.Compare("b", "a")) // 1
fmt.Println("abc" == "abc")            // true

文字列の比較。一緒なら0を返す。
strings.Compare(s1, s2)としたとき、

  • s1とs2が一致しているなら0を返す。
  • 辞書順でs1 < s2なら-1を返す。
  • 辞書順でs1 > s2なら1を返す。

文字列の中に特定の文字列が存在するか

fmt.Println(strings.Contains("abcdef\n", "\n")) // true

指定した文字数をカウント

fmt.Println(strings.Count("aaa", "a")) // 3

空白文字でsplit

fmt.Println(strings.Fields("ab cd ef")) // [ab cd ef]

[ab cd ef]はslice型で返ってくる。

先頭/終端文字列の一致確認

fmt.Println(strings.HasPrefix("prefix_abc", "prefix")) // true
fmt.Println(strings.HasPrefix("prefix_abc", "abc"))    // false
fmt.Println(strings.HasSuffix("prefix_abc", "prefix")) // false
fmt.Println(strings.HasSuffix("prefix_abc", "abc"))    // true

文字列のインデックスを取得

fmt.Println(strings.Index("abcc", "c"))     // 2
fmt.Println(strings.Index("abcc", "z"))     // -1
fmt.Println(strings.LastIndex("abcc", "c")) // 3

strings.Index(s, substr)sの先頭からsubstrを探して最初に見つかったインデックスを返す。
strings.LastIndex(s, substr)sの終端からsubstrを探して最初に見つかったインデックスを返す。

slice内の文字列を連結

s := []string{"a", "b", "c"}
fmt.Println(strings.Join(s, ",")) // "a,b,c"

文字列置換

fmt.Println(strings.Replace("abc abc", "a", "z", 1)) // "zbc abc"
fmt.Println(strings.ReplaceAll("abc abc", "a", "z")) // "zbc zbc"

strings.Replace()の最後の引数は置換する文字数。
strings.ReplaceAll()は一致する文字列を全て置換する。

文字列を分割

fmt.Println(strings.Split("a,b,c", ","))      // [a b c]
fmt.Println(strings.SplitAfter("a,b,c", ",")) // [a, b, c]

strings.SplitAfter()は指定した区切り文字の後でSplitする。

文字列の削除

fmt.Println(strings.Trim("aaxaa", "a"))        // "x"
fmt.Println(strings.TrimLeft("aaxaa", "a"))    // "xaa"
fmt.Println(strings.TrimRight("aaxaa", "a"))   // "aax"
fmt.Println(strings.TrimRight("aaxaa", "xa"))  // ""
fmt.Println(strings.TrimPrefix("aaxaa", "aa")) // "xaa"
fmt.Println(strings.TrimSuffix("aaxaa", "aa")) // "aax"
fmt.Println(strings.TrimSpace("  aaxaa  "))    // "aaxaa"

strings.Trim()指定した文字列を両端から削除していく。 strings.Trim()strings.TrimLeft()strings.TrimRight()は例えば第二引数に"ab"と指定したらabを削除する。

一方、strings.TrimPrefix()strings.TrimSuffix()は第二引数に"ab"と指定したらabを探して削除しようとする。

github.com


GoのArrayとSliceの違い

先週からGoを勉強し始めた。
blog.golang.org
ArrayとSliceがいまいちわからなかったが、この記事を読んでArrayとSliceの違いを学んだので自分なりにまとめてみる。

目次

ArrayとSliceの違い(記事を読む前の認識)

  • Array
    • 固定長の配列
    • C言語の配列と同じように変数は配列の先頭アドレスを保持する。
  • Slice
    • 可変長の配列。

と思っていたけど調べてみると間違っている部分があった。

以下ArrayとSliceが何者なのかをまとめていく。

Array

Array型とはなにか

Array型は配列全体を示すデータ型とのこと。これは配列の長さまでを含めて型として定義しているということ。*1

なので同じArray型でも長さが異なれば違う型として判定される。 つまり、以下のコードはエラーになる。

package main

import (
    "fmt"
)

func doSomething(arr [4]int) {
    fmt.Println(arr)
}

func main() {
    arr := [3]int{1, 2, 3}

    // [3]int と [4]intは型が違うためエラーになる
    doSomething(arr) 
}

arrの型は、int型の値を持つ配列ではなく「int型の値を持つ長さ3の配列」という意味になる。関数の引数の型は「int型の値を持つ長さ4の配列」なので型が一致せずエラーになる。

Array型の変数は何を意味しているのか

Array型の変数は、C言語の配列のように先頭のアドレスを保持しているわけではない。 固定長の配列という観点ではC言語の配列と同じだが、この点が違う。

以下のコードではArray型の変数の値をアドレス表記としてプリントしようとしているが、正しくプリントされない。
Array型の変数はC言語の配列のように配列の先頭アドレスを保持しているわけではないので、変数をアドレスとしてプリントしようとすると意図しない表示になる。

package main

import (
    "fmt"
)

func main() {
    arr := [3]int{1, 2, 3}
    // アドレスが表示されるのが期待だけど...
    fmt.Printf("%p\n", arr) // %!p([3]int=[1 2 3])
}

一方、Sliceは同じやり方でアドレスが正しく表示される。詳細は後述する。

Array型の変数は値渡し

Array型の変数は先頭のアドレスを保持した変数ではないため関数に私たりや変数の代入すると実態のコピーが発生する。

package main

import (
    "fmt"
)

func main() {
    arr1 := [3]int{1, 2, 3}
    arr2 := arr1 // arr2のために新しいメモリを確保してarr1の値がコピーされる
    arr2[0] = 0

    // arr1とarr2が同じ配列を共有していないためarr2を変更してもarr1に影響がない
    fmt.Println(arr1, arr2) // [1 2 3] [0 2 3]
}

関数の引数としてArrayを渡した場合もコピーが渡される。

package main

import (
    "fmt"
)

func doSomething(arr [3]int) {
    arr[0] = 0 // Arrayのコピーの値を変えているためarr1には影響がない
}

func main() {
    arr1 := [3]int{1, 2, 3}
    doSomething(arr1)
    fmt.Println(arr1) //[1 2 3]
}

もちろんArrayのアドレスを引数として渡せば、Arrayのコピーは発生せずにdoSomethingの中で行なった処理はarr1に影響するようになる。

Slice

Slice型とはなにか

Slice型は配列を指すデータ型である。どの型のArrayを指すかだけを定義している。なのでSlice型の宣言には配列の長さを含めない。

package main

import (
    "fmt"
)

func main() {
    slice := []int{1, 2, 3}
    arr := [3]int{1, 2, 3}
    fmt.Printf("slice %T\n", slice) # slice []int
    fmt.Printf("arr   %T\n", arr)   # arr   [3]int
}

%Tで型を表示してみるとSlice型は長さの情報が表示されないが、Array型は長さが表示されているのがわかる。

Slice型の変数は何を意味しているのか

Slice型の変数はArrayの先頭アドレスを保持している構造体みたいなもの。これはC言語の配列を保持する変数と似ている。
Arrayにもポインタがあるがこれは固定長の配列を指すアドレスである。サイズが異なる配列を指すことはできない。Slice型の変数はArrayの長さに制限されない。

package main

import (
    "fmt"
)

func main() {
    slice := []int{1, 2, 3}
    fmt.Printf("%p\n", slice) // 16進数で配列アドレスが表示される
}

Slice型の変数の値を%pで表示するとArray型のときとは異なりエラーしないでアドレスが表示される。
slice := []int{1, 2, 3}この宣言ではsliceにArrayの先頭アドレスが保持される。このとき背後で長さが3の配列が生成されている。

下記コードではArrayからSliceを生成しているが、Sliceが保持しているアドレスがArrayのアドレスを保持していることがわかる。同じアドレスを見ているので、arrもしくはsliceの変更はお互いに影響しあう。

package main

import (
    "fmt"
)

func main() {
    arr := [3]int{1, 2, 3} // Array型
    var slice []int // Slice型

    slice = arr[:] // arr[:]はArrayからSliceを生成する

    // 同じアドレスが表示される
    fmt.Printf("%p %v\n", slice, slice) // 0xc0000141a0 [1 2 3]
    fmt.Printf("%p %v\n", &arr, arr) // 0xc0000141a0 [1 2 3]

    arr[0] = 0

    // 同じアドレスを指しているのでarrの変更はsliceにも反映される
    fmt.Printf("%p %v\n", slice, slice) // 0xc0000141a0 [0 2 3]
    fmt.Printf("%p %v\n", &arr, arr) // 0xc0000141a0 [0 2 3]
}

Slice型の変数も値渡し

Slice型はArray型の先頭アドレスを保持している構造体のようなもの。これを関数の引数に渡すとスライスが持つ情報はコピーされる(参照渡しにならない)。しかしスライスが持つ配列のアドレスはそのまま同じものが関数に渡されるので、関数内でスライスを操作すると(スライスが指している配列が操作されると)、関数の呼び出し元のスライス(が指している配列)にも影響がでる。

package main

import (
    "fmt"
)

func doSomething(slice []int) {
    slice[0] = 0
    fmt.Printf("%p %d\n", slice, slice) // 0xc0000141a0 [0 2 3]
    fmt.Printf("%p\n", &slice) // スライスの情報を保持しているアドレスは関数外のものと異なる

}
func main() {
    slice := []int{1, 2, 3}
    doSomething(slice)
    fmt.Printf("%p %d\n", slice, slice) // 0xc0000141a0 [0 2 3]
    fmt.Printf("%p\n", &slice) //  // スライスの情報を保持しているアドレスは関数内のものと異なる
}

*1:GoのブログではArrayはインデックス付きのフィールドを持った構造体としても考えられると書いてある。

Qt for Python Exif Viewer

QTreeWidgetとQTextEditとQSplitterとQStackedWidgetを使ってExifViewerを作った。

f:id:takuroooooo:20200729223518p:plain

github.com

以前作ったViewerはQTreeWidgetメインでGUIを作成したが、今回はQSplitterとQStackedWidgetを使ってページが切り替わるようなUIにした。

自作ExifParser

UI以外の変更点として、前回はPILの_getexif()を使ってExifを取得していたが、今回はExifのParserを自作した。

ExifViewer/exif_reader.py at master · takurooo/ExifViewer · GitHub

下記のように書くとExifのtagの名前と値をプリントすることができる。

from common.exif_reader import ExifReader
exif_reader = ExifReader(path_to_your_jpegfile) # Parse exif tags
ifds =exif_reader.get_exif()
for ifd_name, ifd in ifds.items():
    for tag_name, tag in ifd.items():
        print(tag_name, tag.val)

またテキストにExif情報を出力することもできる。

from common.exif_reader import ExifReader
exif_reader = ExifReader(path_to_your_jpegfile) # Parse exif tags
exif_reader.save_log(text_file_path)

このテキスト保存機能はExifViewerのメニューバーにあるSaveボタンを押すと実行するようにしてある。

PySide2 メニューバーとアイコン

Qtではメニューアイコンがデフォルトで用意されている。
メニューバーとアイコンの設定は_make_toolbar()で実行している。

ExifViewer/exif_viewer.py at master · takurooo/ExifViewer · GitHub

    def _make_toolbar(self):
        save_icon = QApplication.style().standardIcon(QStyle.SP_DialogSaveButton)
        save_action = QAction(icon=save_icon, text='save', parent=self)
        save_action.triggered.connect(self._save_text)

        exit_icon = QApplication.style().standardIcon(QStyle.SP_DialogCloseButton)
        exit_action = QAction(icon=exit_icon, text='exit', parent=self)
        exit_action.triggered.connect(self._quit)

        # ツールバー作成
        self.toolbar = self.addToolBar('tool bar')
        self.toolbar.setIconSize(QSize(35, 35))
        self.toolbar.setFixedHeight(35)
        self.toolbar.addAction(save_action)
        self.toolbar.addAction(exit_action)

save_action.triggered.connect(self._save_text)でボタンが押された時に実行される関数を設定している。
QtではSignalとSlotという考え方がある。ユーザーがボタンを押したりするとSignalが発行され、Signalに紐づいたSlot(関数)が実行される。
save_action.triggered.connect(self._save_text)はSignalとSlotの紐付けを行なっている。

Qt for Python Signals and Slots - Qt Wiki

QStackedWidgetの基本的なこと

Qt for Python(PySide2)のQStackedWidgetについて勉強したのでまとめていく。

目次

QStackedWidgetとは

QStackedWidgetを使うとWidgetを重ねることができる。

f:id:takuroooooo:20200723132848p:plain

QStackedWidgetでまとめられたWidgetは常に1つしか表示されない。そのためQStackedWidgetを使用すると、ある時はWidget1を表示して、ある操作をされたらWidget2に切り替えるみたいな制御が可能となる。

これにより状況に応じて必要なWidgetだけを表示でき、UIの表示スペースを小さくすることができる。

StackedWidgetを表示する

f:id:takuroooooo:20200723133647p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit, QStackedWidget

app = QApplication(sys.argv)

qw_text_edit_1 = QTextEdit()
qw_text_edit_1.append('1')

qw_text_edit_2 = QTextEdit()
qw_text_edit_2.append('2')

qw_stack = QStackedWidget()
 # QStackedWidgetにTextEditを2つ追加する
qw_stack.addWidget(qw_text_edit_1)
qw_stack.addWidget(qw_text_edit_2)
print(qw_stack.currentIndex())

qw_stack.show() # 最初に追加したTextEditが表示される

sys.exit(app.exec_())
  • addWidgetを使ってQTextEditを2つQStackedWidgetに追加している。
  • この場合最初に追加したQTextEditが表示される。

StackedWidgetに登録されたwidgetをインデックスで指定して表示する

f:id:takuroooooo:20200723133702p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit, QStackedWidget

app = QApplication(sys.argv)

qw_text_edit_1 = QTextEdit()
qw_text_edit_1.append('1')

qw_text_edit_2 = QTextEdit()
qw_text_edit_2.append('2')

qw_stack = QStackedWidget()
idx_qw_text_edit_1 = qw_stack.addWidget(qw_text_edit_1)
idx_qw_text_edit_2 = qw_stack.addWidget(qw_text_edit_2)
print(idx_qw_text_edit_1, idx_qw_text_edit_2)

 # 表示するwidgetをインデックスで指定する
qw_stack.setCurrentIndex(idx_qw_text_edit_2)
print(qw_stack.currentIndex())

qw_stack.show()

sys.exit(app.exec_())
  • setCurrentIndexを使って表示するwidgetをインデックスで指定する。
  • インデックスはaddWidgetの戻り値で取得できる。
  • 現在設定されているインデックスはcurrentIndexを使って取得できる。

StackedWidgetに登録されたwidgetwidgetインスタンスで指定して表示する

f:id:takuroooooo:20200723133702p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit, QStackedWidget

app = QApplication(sys.argv)

qw_text_edit_1 = QTextEdit()
qw_text_edit_1.append('1')

qw_text_edit_2 = QTextEdit()
qw_text_edit_2.append('2')

qw_stack = QStackedWidget()
qw_stack.addWidget(qw_text_edit_1)
qw_stack.addWidget(qw_text_edit_2)

 # 表示するwidgetをwidgetのインスタンスで指定する
qw_stack.setCurrentWidget(qw_text_edit_2)
print(qw_stack.currentWidget())

qw_stack.show()

sys.exit(app.exec_())

TreeをクリックしてTextEditを切り替える

f:id:takuroooooo:20200723134408p:plainf:id:takuroooooo:20200723134411p:plainf:id:takuroooooo:20200723134414p:plain

import sys
from PySide2.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem, QTextEdit, QStackedWidget, QSplitter

app = QApplication(sys.argv)

qw_stack = QStackedWidget()

qw_tree = QTreeWidget()
qw_tree.setHeaderLabels(["test"])


def tree_item_clicked(item, column):
    print(item, column, item.type())
    # TreeがクリックされたときにTextEditを切り替える
    qw_stack.setCurrentIndex(item.type())


for i in range(3):
    text = 'page No.' + str(i+1)
    qw_tree_item = QTreeWidgetItem([text], type=i)
    qw_tree.addTopLevelItem(qw_tree_item)

    qw_text_edit = QTextEdit()
    qw_text_edit.append(text)

    stack_idx = qw_stack.addWidget(qw_text_edit)
    # print(stack_idx)

# Treeがクリックされたときに呼ばれる関数を登録
qw_tree.itemClicked.connect(tree_item_clicked)

qw_splitter = QSplitter()
qw_splitter.addWidget(qw_tree)
qw_splitter.addWidget(qw_stack)

qw_splitter.show()

sys.exit(app.exec_())

ソースコード

github.com

QSplitterの基本的なこと

Qt for Python(PySide2)のQSplitterについて勉強したのでまとめていく。
QSplitterを使うと複数のwidgetの間にサイズを調整できるスプリッターを置くことができる。

目次

SplitterでTextEditを水平に2つ表示する

f:id:takuroooooo:20200723113219p:plain

import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit
from PySide2.QtCore import Qt

app = QApplication(sys.argv)

qw_text_edit_left = QTextEdit()
qw_text_edit_left.append('left')

qw_text_edit_right = QTextEdit()
qw_text_edit_right.append('right')

qw_splitter = QSplitter()
qw_splitter.addWidget(qw_text_edit_left) # Splitterにwidgetを追加
qw_splitter.addWidget(qw_text_edit_right) # Splitterにwidgetを追加

qw_splitter.show()

sys.exit(app.exec_())
  • addWidgetを使うとwidgetをQSplitterに追加できる。
  • QSplitterは何も設定しないと水平のスプリッターを生成する。
  • QSplitterに追加されるwidget(この例ではQTextEdit)はshow()を呼ぶ必要はない。

SplitterでTextEditを垂直に2つ表示する

f:id:takuroooooo:20200723113231p:plain

import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit
from PySide2.QtCore import Qt

app = QApplication(sys.argv)

qw_text_edit_top = QTextEdit()
qw_text_edit_top.append('top')

qw_text_edit_bottom = QTextEdit()
qw_text_edit_bottom.append('bottom')

qw_splitter = QSplitter()
qw_splitter.setOrientation(Qt.Orientation.Vertical)  # Splitterを垂直に設定
print(qw_splitter.orientation())
qw_splitter.addWidget(qw_text_edit_top)
qw_splitter.addWidget(qw_text_edit_bottom)

qw_splitter.show()

sys.exit(app.exec_())
  • setOrientationで水平に分割するか垂直に分割するかを選択できる。

Splitterの左右の比率を変える

f:id:takuroooooo:20200723113246p:plain

import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit

app = QApplication(sys.argv)

qw_text_edit_left = QTextEdit()
qw_text_edit_left.append('left')

qw_text_edit_right = QTextEdit()
qw_text_edit_right.append('right')

qw_splitter = QSplitter() # Orientationの初期値は水平
qw_splitter.addWidget(qw_text_edit_left)
qw_splitter.addWidget(qw_text_edit_right)

qw_splitter_size = qw_splitter.size() # Splitterのサイズを取得する
qw_splitter_size_width = qw_splitter_size.width() # Splitterの横サイズを取得する
qw_splitter.setSizes([qw_splitter_size_width*0.1, qw_splitter_size_width*0.9]) # Splitterの横の比率を1:9に変更する
print(qw_splitter.size()) # Splitter全体のサイズ
print(qw_splitter.sizes()) # Splitterの子widgetごとのサイズ

qw_splitter.show()

sys.exit(app.exec_())
  • setSizesでリストを渡すとQSplitterに登録されている各widgetのサイズを設定できる。
  • この例ではQSplitterに登録されているwidgetが2つなので、要素が2つあるリストを設定している。

Splitterの子widgetのサイズ調整をしたときに調整完了後にサイズを変更する

f:id:takuroooooo:20200723114248g:plain

import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit

app = QApplication(sys.argv)

qw_text_edit_left = QTextEdit()
qw_text_edit_left.append('left')

qw_text_edit_right = QTextEdit()
qw_text_edit_right.append('right')

qw_splitter = QSplitter()
qw_splitter.addWidget(qw_text_edit_left)
qw_splitter.addWidget(qw_text_edit_right)

qw_splitter.setOpaqueResize(False) # 子widgetのサイズ調整を操作完了後に行う
print(qw_splitter.opaqueResize())

qw_splitter.show()

sys.exit(app.exec_())
  • setOpaqueResizeでTrueを設定するとスプリッターを操作したときに即時に変更が反映される。
  • setOpaqueResizeでFalseを設定すると上記Gifのようにスプリッターを操作した後にスプリッターが実際に移動する。

Splitterにwidgetをインデックスで指定した位置に追加する

f:id:takuroooooo:20200723113313p:plain

import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit

app = QApplication(sys.argv)

qw_text_edit_left = QTextEdit()
qw_text_edit_left.append('left')

qw_text_edit_right = QTextEdit()
qw_text_edit_right.append('right')

qw_splitter = QSplitter()
qw_splitter.addWidget(qw_text_edit_left)
qw_splitter.addWidget(qw_text_edit_right)

qw_text_edit_center = QTextEdit()
qw_text_edit_center.append('center')
qw_splitter.insertWidget(1, qw_text_edit_center) # index 1 の位置にwidgetを追加

print(qw_splitter.indexOf(qw_text_edit_left))
print(qw_splitter.indexOf(qw_text_edit_center))
print(qw_splitter.indexOf(qw_text_edit_right))

qw_splitter.show()

sys.exit(app.exec_())
  • insertWidgetを使うとインデックスで指定した位置にwidgetを追加することができる。

ソースコード

github.com


QTextEditの基本的なこと

前回に引き続きQt for Python(PySide2)のQTextEditについて勉強したのでまとめていく。

QTextEditを使うとテキストを入力できるフォームを作成することができる。

目次

TextEditを表示する

f:id:takuroooooo:20200126102928p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qw_text_edit.show()

sys.exit(app.exec_())

ユーザー自身が文字を入力することができる。 f:id:takuroooooo:20200126102947p:plain

TextEditに文字をを表示する

f:id:takuroooooo:20200126172845p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qw_text_edit.append('This is a text edit widget.') # 文字を表示する
qw_text_edit.show()

sys.exit(app.exec_())

appendを使うことで最初に表示するテキストを設定できる。

編集禁止にする

f:id:takuroooooo:20200126173748p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qw_text_edit.setReadOnly(True)  # 編集禁止にする
qw_text_edit.append('This is a text edit widget.')
qw_text_edit.show()

sys.exit(app.exec_())

文字の色を変える

f:id:takuroooooo:20200126173312p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit
from PySide2.QtGui import QColor

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qw_text_edit.setTextColor(QColor(255, 0, 0, 255)) # 文字の色を赤色にする
qw_text_edit.append('This is a text edit widget.')
qw_text_edit.show()

sys.exit(app.exec_())

文字の大きさを変える

f:id:takuroooooo:20200126173515p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qw_text_edit.setFontPointSize(30)  # 文字の大きさを変える
qw_text_edit.append('This is a text edit widget.')
qw_text_edit.show()

sys.exit(app.exec_())

フォントを設定する

f:id:takuroooooo:20200126175240p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit
from PySide2.QtGui import QFont

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
qt_font = QFont("Monaco")  # monaco fontを生成
qt_font.setStyleHint(QFont.Helvetica)  # monacoが使用できない場合はHelveticaにする
qw_text_edit.setFont(qt_font)  # fontを設定
qw_text_edit.append('This is a text edit widget.')
qw_text_edit.show()

sys.exit(app.exec_())

もしMonacoがなければsetStyleHintで設定されたフォントが適用される。

カーソルの位置を先頭行に設定する

f:id:takuroooooo:20200126184910p:plain

import sys
from PySide2.QtWidgets import QApplication, QTextEdit
from PySide2.QtGui import QTextCursor

app = QApplication(sys.argv)

qw_text_edit = QTextEdit()
for i in range(20):
    qw_text_edit.append(str(i))

first_row_block = qw_text_edit.document().findBlockByLineNumber(0)  # 1行目のblockを取得
text_cursor = QTextCursor(first_row_block)  # カーソルを取得
qw_text_edit.setTextCursor(text_cursor)  # cursorを1行目に設定

qw_text_edit.show()

sys.exit(app.exec_())

カーソルに関する設定をしないと最後にappendした行にカーソルがあった状態で起動する。

ソースコード

github.com