JUCE6.1以降でも使えるダイアログ

JUCE Advent Calendar 2021 16日目のエントリーです。3回目なので(!)軽めにいきます。

今年リリースされたJUCE 6.1には、いくつかの破壊的変更が加えられています。JUCEでは公式に破壊的変更とみなしているものを全てトップレベルのBREAKING_CHANGES.txtに記録してあって、どんな破壊的変更が加えられたのかを手作業の更新として確認できますが、今回はその中からモーダルダイアログの扱いについてちょっと書きます。

The default value of JUCE_MODAL_LOOPS_PERMITTED has been changed from 1 to 0.

JUCE 6.0.9までデフォルトで許容されていたモーダルダイアログが、JUCE 6.1から無効になります。無効になったものは、利用できるAPIから消えてなくなります。具体的にはこの辺のAPIが使えなくなります:

  • PopupMenu::show()
  • AlertWindow::showYesNoCancelBox() のうちCallback引数がないもの
  • FileChooser::browseForFileToOpen(), FileChooser::browseforFileToSave()
  • AlertDialogAlertWindowComponent::runModalLoop()

Why?

根本的な疑問として、なぜ2021年の今このモーダルダイアログをサポート対象外にする必要があるんでしょうか? 憶測ではありますが、これはJUCEチームによる今後の開発への布石なんじゃないかと考えられます。

そもそも、モーダルループが使えるのはデスクトップ環境など一部のJUCEサポート対象環境のみでした。たとえばAndroid上では今回の変更で使えなくなったような関数はもともと使えませんでした。JUCEアプリケーションをAndroidに移植しているとき、さり気なく面倒になるやつです。これが理由でたとえばaap-juce-dexedではdexedを独自にforkしたandroidブランチを作って利用しています。

Androidと同様にモーダルダイアログをサポートできないであろうプラットフォームとしてはWebブラウザがあります。はい、われわれは一度やっているやつですね。juce_emscriptenでもモーダルダイアログは動きませんでした。

atsushieno.hatenablog.com

JUCE公式ではやっていないはずだし気にしていないのでは…と思うじゃないですか。そういえばこれも今年のcommitなんですよね。(いやマージされたのが今年というだけでコミット自体は12ヶ月前とか出ているな…??)

github.com

emscriptenサポートに向けて内部実装をいろいろ整理していると考えると、今回の動きにも納得感が出てくるのではないでしょうか。

あと、モーダルダイアログは、現代的なUIアーキテクチャとは割と相性の悪い存在です。MVVM and dialogでぐぐるとさまざまな独自ソリューションが実装されてきたっぽいさまが垣間見えます(同じMVVMでもWindows方面とAndroid方面でだいぶ違う話になったりもするので気をつけて読む必要があります)。

デスクトップ向けやっつけ回避策

cflagsに-DJUCE_MODAL_LOOPS_PERMITTED=1を追加すれば従来どおりのモーダルダイアログを使い続けられます。

ただしこの回避策を使うということは、上記のような方針…と考えられるもの…とは相容れないということです。今後のことを考えると、非同期ダイアログの処理フローに転換しておいたほうがよいでしょう。

ちゃんとした対応策

ふわっとした説明になってしまいますが、それぞれのコンポーネントに存在する非同期メソッドを使って書き換えるとよいでしょう。

sync async
PopupMenu::show() showMenuAsync()
AlertWindow::showYesNoCancelBox() callback引数あり版を使う
FileChooser::browseForFileToOpen(), FileChooser::browseforFileToSave() FileChooser::launchAsync()

多くの場合は、既存のダイアログ表示以降の関数の内容をそのままstd::function変数の内容にしてしまって、それをcallbackに継続として渡すなり呼び出すなりしてしまえばよさそうです。

AlertWindow::showYesNoCancelBox()のcallback引数みたいに独自クラスを作らないといけないものはちょっと面倒ですね。個人的にはその場でclassを定義してしまって、コンストラクタではcallbackを渡し、オーバーライドが必要な関数(たとえばModalComponentManager::Callback::modalStateFinished())の中でcallbackを呼び出す、という感じにしています。

AlertDialogComponent::runModalLoop() だけはそうシンプルには書き換えられなそうですが、enterModalState()ModalComponentManager::Callbackを使って書き換えられるのではないかと思います(ちょっと確認するのに時間がかかりそう)。

追記:

書き換えはご安全に…!

まとめ

今後のJUCEの利用場面を考えて、適宜非同期ダイアログを使うやり方に書き換えていきましょう。