ffmpegの処理をtkinterでGUI化（その２: コンソール風出力表示）

＊[前回](https://qiita.com/ytakeda-paleo/items/c3d33aef9d1c715702de)の続きです．
＊この記事の続きは[こちら](https://qiita.com/ytakeda-paleo/items/07013d25814d656b5cb7)

# やりたいこと
- 前回は一行ずつffmpegからの標準出力が表示されるプログレスバーを作った↓
![before.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/736226/a35d301c-e5d9-2339-e1dd-b66fe72662d4.gif)
- これだと，通しで出力を確認することができない．出力内容をコピペすることもできない．
- そこで，コンソールで表示されるときと同じようにテキストボックスに表示させる．
- 完成したもの↓
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/736226/6c99cab5-294b-621d-b521-c7d429679462.gif)

## GUIの内容
- ffmpegで処理中に出力されるメッセージが一行ずつテキストボックスに追加される．
- テキストボックスは一番下の最新行が表示されるよう自動にスクロールする．
- テキストボックスの右側にスクロールバーをつける．
- テキストボックスはキーボードからのキー入力を受け付けない（出力の中身は編集できないようにする）
- ただし，Ctrl+Cは有効として，テキストボックス内の出力をマウスで選択してコピー可能とする．

## 環境
Windows 10 Pro, Python 3.7, miniconda(conda 4.11.0)

# コード
```python
import os, subprocess
import tkinter as tk
from tkinter import ttk, messagebox
import threading

command = "ffmpeg hogehoge" # ffmpegの処理コマンド

def start():
    progress = tk.Toplevel()
    progress.geometry("750x350")
    progress.title("processing...")
    progress.grab_set()
    progress.focus_set()
    bar = ttk.Progressbar(progress,mode='indeterminate')
    bar.pack(side = tk.TOP, fill = tk.X)

    info = tk.Text(progress)
    info.bind("<Key>", lambda e: ctrlEvent(e))
    scrollbar = tk.Scrollbar(progress, orient=tk.VERTICAL, command=info.yview)
    scrollbar.pack(side = tk.RIGHT, fill = tk.Y)
    info["yscrollcommand"] = scrollbar.set
    info.pack(side = tk.TOP, anchor = tk.W, fill = tk.BOTH)

    bar.start()

    def ctrlEvent(event):
        if(event.state & 2**2 == 4 and event.keysym=='c' ):
            return
        else:
            return "break"

    def process():
        p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True,universal_newlines=True)
        
        for line in p.stdout:
            info.insert(tk.END, line)
            info.see("end")

        try:
            outs, errs = p.communicate()
        except subprocess.TimeoutExpired:
            pass
        else:
            p.terminate()
            bar.stop()

    th1 = threading.Thread(target=process)
    th1.start()


root = tk.Tk()
button = tk.Button(root, text = "start", command = start)
button.pack()

root.mainloop()
```

# ポイント
## テキストボックスは一番下の最新行が表示されるよう自動スクロール
- `.see()`を使う．
```python
info.see("end")
```
- 参考: [teratail - 【python】スクロールバーが自動で下がってほしい。](https://teratail.com/questions/199144)

## テキストボックスの右側にスクロールバー
- スクロールバーウィジェットを`.pack()`してからテキストウィジェットを`.pack()`しないとスクロールバーが正しい位置に表示されない．

## テキストボックスはキーボードからのキー入力を受け付けない
- テキストウィジェットを読み取り専用にする方法（`textbox.configure(state='disabled')`）では，ffmpegからの出力も受け付けてくれない．
- そこで，任意のキーをbreak関数にバインドして読み取り専用にする．
参考: [DelftStack - Tkinter Text ウィジェットを読み取り専用にする方法](https://www.delftstack.com/ja/howto/python-tkinter/how-to-make-tkinter-text-widget-read-only/)

## ただし，Ctrl+Cは有効
- ただし，[上記ページ](https://www.delftstack.com/ja/howto/python-tkinter/how-to-make-tkinter-text-widget-read-only/)に書かれているように，この方法（`textbox.bind("<Key>", lambda a: "break")`）だとCtrl+Cを受け付けてくれないので，例外として Ctrl+Cをバインドする．
- [上記ページ](https://www.delftstack.com/ja/howto/python-tkinter/how-to-make-tkinter-text-widget-read-only/)に記載されている条件`if(12==event.state and event.keysym=='c' ):`だと，上手くいかなかったので，`if(event.state & 2**2 == 4 and event.keysym=='c' ):`に変更した．
参考: [Python GUIプログラミング tkinter Canvas state Mod3](https://www.ishikawasekkei.com/index.php/2020/11/01/python-gui-programing-tkinter-canvas-state-mod3/)
```python
def ctrlEvent(event):
        if(event.state & 2**2 == 4 and event.keysym=='c' ):
            return
        else:
            return "break"
```
```python
info.bind("<Key>", lambda e: ctrlEvent(e))
```

- コピー動作の様子
![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/736226/5456064b-22b3-9198-fddb-55b1d85ee7c2.gif)

最後までお読みいただきありがとうございました．