Ctrl E ChatSpoolerにショートカットキーと
「Next Now」を足した

ChatSpoolerを使い続けていると、「毎回マウスで押すのは地味に面倒だな」と感じる場面が少しずつ増えてきました。
特に Reload や Start(Emit)は、作業の流れの中で何度も触る操作です。

そこで今回は、ショートカットキーの追加とウェイト中だけ待機を飛ばす「Next Now」ボタンを実装しました。

関連記事

1. なぜショートカットキーを入れたくなったのか

ChatSpoolerは「プロンプトを積んで流す」道具なので、操作自体はとても単純です。
その分、短い時間に同じ操作を何度も繰り返します。

Reload → 少し修正 → Reload → Start

この往復が増えてくると、マウス移動が少しずつ気になってきました。

そこで、次の割り当てを考えました。

Reload は Ctrl+R
Start(Emit)は Ctrl+E

1.1. PyQt6でショートカットをどう入れるか考える

PyQt6には、ショートカットを設定する方法がいくつかあります1

PyQt6での実装方法の選択 QShortcut シンプルな キーバインド専用 QAction メニュー・ツールバー 統合管理可能 QActionを選択した理由 将来メニューも追加する可能性 → 最初から統合的に設計 メニュー ツールバー ショートカット QAction

QShortcut を使う方法
QAction に shortcut を設定する方法

今回は QAction を選びました。
理由は単純で、「あとからメニューも追加しそうだ」と思ったからです2

ショートカットだけを先に足して、あとでメニューを作り直すより、
最初から QAction を中心に据えた方が整理しやすいと捉えました。

実際のコードは次のようになりました。

def _setup_menu(self):
    from PyQt6.QtGui import QAction, QKeySequence

    menubar = self.menuBar()

    file_menu = menubar.addMenu("File")
    run_menu = menubar.addMenu("Run")

    reload_action = QAction("Reload Folder", self)
    reload_action.setShortcut(QKeySequence("Ctrl+R"))
    reload_action.triggered.connect(self._on_reload_folder)
    file_menu.addAction(reload_action)
    self.addAction(reload_action)

    emit_action = QAction("Start / Emit", self)
    emit_action.setShortcut(QKeySequence("Ctrl+E"))
    emit_action.triggered.connect(self._on_start)
    run_menu.addAction(emit_action)
    self.addAction(emit_action)Code language: PHP (php)

`

同じ QAction を、メニューに追加し、ウィンドウ自体にも登録、という形で共有しています3

この構成にしておくと、メニューから操作しても、ショートカットで呼んでも、必ず同じ処理が実行されます。

2. 次に気になったのが「待機時間」

ChatSpoolerでは、プロンプト送信後に数秒から数分待つことがあります。
この待機は、タイトル末尾の数字で制御しています。

Next Now:待機スキップ機能 数秒〜数分の 待機時間 「もう次に 行っていいな」 → スキップしたい 実装の3ステップ 1 スキップフラグ workerに追加 2 待機ループで フラグ確認 3 フラグON時 待機終了

ただ、実際に使っていると、「もう次に行っていいな」と思う瞬間が出てきました。

Next Now は、待機だけをスキップして次に進みます。

2.1. 待機処理を読んで考えた実装

コードを読み直すと、待機は worker スレッドの中で、1秒ごとにスリープ、残り時間を通知、という形で実装していました4

そこで、「待機をスキップしたい」というフラグを追加することにしました。
やったことはシンプルです。

  • worker に待機スキップ用のフラグを持たせる
  • 待機ループの中でそのフラグを確認する
  • 立っていたら、その待機を終了する

3. 実際に使ってみて思うこと

ショートカットと Next Now を入れたことで、操作の引っかかりはかなり減りました。
一方で、待機スキップは最大で1秒ほど遅れることがあります5

それでも、日常的な使い勝手は明確に改善しました。

道具は完成させるものというより、
使いながら少しずつ整えていくものだと感じています。

今の ChatSpooler は、そんな感覚で付き合っているツールです。

  1. QActionとQShortcutはいずれもショートカットキーを実装できますが、QActionはメニューやツールバーとの統合が容易で、UI要素間で一貫した動作を維持できる利点があります。一方、QShortcutは単純なキーバインドに特化しており、メニューなしでショートカットのみを実装する場合に適しています。 – Using PyQt6 Actions, Toolbars and Menus
  2. QActionは、メニュー、ツールバー、ショートカットキーを1つのオブジェクトで統合管理できる抽象クラスです。例えばEdit→Cutという機能を、メニュー項目、ツールバーボタン、Ctrl-Xショートカットの3箇所で定義せずに、1つのQActionで実装できます。これにより、UI要素間での動作の一貫性が保証されます。 – PySide6.QtGui.QAction – Qt for Python
  3. ウィンドウへのaddAction()呼び出しは、メニューバーが存在しない場合やメニューからアクセスできない場合でも、ショートカットキーがグローバルに機能するために必要です。これにより、フォーカスがどのウィジェットにあってもショートカットが動作します。 – Menus and toolbars in PyQt6
  4. Pythonのtime.sleep()は中断できないため、待機をスキップ可能にするには、1秒ごとにフラグをチェックするループ構造が一般的です。より洗練された実装としては、threading.Eventのwait()メソッドを使うことで、Event.set()が呼ばれた瞬間に待機を即座に終了できます。 – Use Python’s threading.Event for interruptable sleep
  5. この遅延は、スリープループが1秒間隔でフラグをチェックする実装に起因します。threading.Eventのwait(timeout)を使用すれば、Event.set()呼び出し時に即座に反応できるため、遅延をほぼゼロにすることが可能です。 – Python – Interrupting a Thread