Apple SiliconでQt6
Apple SiliconでQt5 の続きです。
続きにしたかったのでこのタイトルですが、もはやApple Siliconはほとんど関係ありませんし、Qt6対応と言うよりは QtQuick Controls(2)対応の話です。
Qt6 対応
前回の前置きで、quazipとQt6がhomebrewでインストールできることを書きました。
Qt5は開発終了で、5.15 LTSのEOLが2023年5月らしいので、Qt6対応できるならやっておいた方が良いかもしれません。
(とは言え、現在homebrewでインストールされるQt6.3.0のEOLは2022年10月です。6.2 LTSで2024年9月)
※ 以下のサイトで確認しました。
とりあえずビルド
自分でビルドした Qt5.15.2 と quazip 1.3 の組み合わせだと、ComicsViewerを起動したときに落ちていたのですが、homebrewでインストールしたQt6とquazipの組み合わせだと落ちずに起動しました。
- homebrew で quazip をインストール。
- quazip 1.3 がインストールされます。依存関係で qt6.3.0もインストールされます。
- qtのインストール先は /opt/homebrew
- quazip は /opt/homebrew/include/quazip, /opt/homebrew/lib/quazip/libquazip1-qt6.dylib に入ります。(シンボリックリンクです)
- ComicsViewer.pro では libquazip.dylib をリンクしようとするので、ファイル名を変更します。
diff --git a/ComicsViewer.pro b/ComicsViewer.pro
index ae7f1a5..0006ec1 100644
--- a/ComicsViewer.pro
+++ b/ComicsViewer.pro
@@ -13,7 +13,7 @@ CONFIG += c++11
QMAKE_INCDIR += $$QUAZIP_DIR/include
QMAKE_LIBDIR += $$QUAZIP_DIR/lib
QMAKE_RPATHDIR += $$QUAZIP_DIR/lib
-QMAKE_LIBS += -lquazip
+QMAKE_LIBS += -lquazip1-qt6
macx: ICON = ComicsViewer.icns
win32: RC_FILE = ComicsViewer.rc
- Qt6 からはビルドシステムがcmakeに移行していますが、まだqmakeも残っているのでqmakeでビルドします。
% qmake QUAZIP_DIR=/opt/homebrew
% make
あっさりビルドできました。
ぷちぷち潰す
Qt5 から Qt6 への移行方法は、以下に書いてあります。
死ぬほど項目があって読む気がしないので、実際に実行して問題を潰していきます。
ただ、後から見返すと必要な情報はだいたい以下の二つだったようです。
- Qt Quick Controls の Qt6での変更点
https://doc.qt.io/qt-6/qtquickcontrols-changes-qt6.html - Qt Quick Controls 1 と Qt Quick Controls の違い
https://doc.qt.io/qt-5/qtquickcontrols2-differences.html
実行時のエラーはコンソールに出るので、コマンドラインから直接起動します。
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:2:1: module "QtQuick.Controls" version 1.3 is not installed
上のリンク先に書いてありますが、QtQuick.Controls 1.3 がないと言われます。Qt5.11以降では Qt Quick Controls 1系がdeprecatedになり、Qt6で削除されたようです。
最近のQMLの書き方では、バージョンを書かなくて良いようなので、バージョンを外していきます。
diff --git a/main.qml b/main.qml
index d5ed22c..e2e323b 100644
--- a/main.qml
+++ b/main.qml
@@ -1,9 +1,9 @@
-import QtQuick 2.4
-import QtQuick.Controls 1.3
-import QtQuick.Window 2.2
-import QtQuick.Dialogs 1.2
-import Qt.labs.settings 1.0
-import ComicsViewer 1.0
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Window
+import QtQuick.Dialogs
+import Qt.labs.settings
+import ComicsViewer
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:55:16: StatusBar is not a type
Qt Quick Controls 1 から 2(のちに2は取れた)になったときに、StatusBarはなくなったようです。(かつ、ApplicationWindowのstatusBar は footer に変わったようです。)
- statusBar: StatusBar {
- anchors.fill: parent
+ footer: ToolBar {
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:94:13: Cannot assign to non-existent property "maximumValue"
Slider の minimumValue, maximumValue が from, to に変わったようです。(ビキビキ)
- minimumValue: 0
- maximumValue: comicModel.maxPage < 2 ? 1 : comicModel.maxPage - 1
+ from: 0
+ to: comicModel.maxPage < 2 ? 1 : comicModel.maxPage - 1
ここまでで、やっと画面が出ますが、まだ以下のメッセージが出ます。
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
qt.qml.context: qrc:/main.qml:34:21 Parameter "index" is not declared. Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead.
なんのことやら良くわからなかったのですが、エラーメッセージでググったら qt - Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead - Stack Overflow に辿り着きました。
@ -31,8 +31,8 @@ ApplicationWindow {
title: qsTr("Open &recent")
Instantiator {
model: comicModel.recentFiles
- onObjectAdded: recentFileMenu.insertItem(index, object)
- onObjectRemoved: recentFileMenu.removeItem(object)
+ onObjectAdded: (index, object) => recentFileMenu.insertItem(index, object)
+ onObjectRemoved: object => recentFileMenu.removeItem(object)
delegate: MenuItem {
text: modelData
onTriggered: comicModel.open("file://" + modelData)
@@ -61,7 +60,7 @@ ApplicationWindow {
ComicModel {
id: comicModel
- onCurrentPageChanged: {
+ onCurrentPageChanged: function(currentPage) {
pageSlider.pageNumber = currentPage
}
}
これで、起動時にはエラーが出なくなりました。
続いて、メニューからファイルを開くと、qrc:/main.qml:149: ReferenceError: fileUrl is not defined
と言われます。
FileDialog の fileUrl が、selectedFile に変わったようです。
@@ -147,7 +146,7 @@ ApplicationWindow {
FileDialog {
id: fileDialog
onAccepted: {
- comicModel.open(fileUrl)
+ comicModel.open(selectedFile)
//comicModel.currentPage = 1
}
}
これで、ファイルを開いて読めるようになりました。
しかし、マウスクリックでページ送りができるのですが、カーソルキーが効きません。
どうも、ファイルダイアログを開くとキーが効かなくなるようです。(ファイルダイアログを開かずに、「最近開いたファイル」から開くとキーが効きます)
これもググると、 QT/QML , After Dialog is open , Back button not working on Android? - Stack Overflow に辿り着きました。ダイアログが閉じたとき(visibleがfalseになったとか)に、FocusScopeにforceActiveFocus()してあげると良いみたいです。
@@ -129,6 +128,7 @@ ApplicationWindow {
}
FocusScope {
+ id: focus
focus: true
Keys.onRightPressed: {
comicModel.previousPage()
@@ -147,8 +147,13 @@ ApplicationWindow {
FileDialog {
id: fileDialog
onAccepted: {
comicModel.open(selectedFile)
//comicModel.currentPage = 1
}
+ onVisibleChanged: {
+ if (!visible) {
+ focus.forceActiveFocus()
+ }
+ }
}
}
これで、キーも効くようになりました。(ちょっとまだたまにおかしかったりするようですが)
これでだいたい動くようになったかなと思ったのですが、何か違和感があります。
良く見ると、今までMacのメニューバーに表示されていたメニューバーが、アプリケーションウィンドウ内に表示されているのです。
- Qt5のとき
- Qt6のとき
Qt6 になったときと言うか、Qt Controls 1 から 2 になったときに、platformのメニューバーを使うのをやめて、ウィンドウ内にQtが描画するようになったらしいです。
で、QtQuick.Controls の代わりに Qt.labs.platform を使うと、昔のメニューバーが出せるようです。
ただし、Qt.labs.platform は仕様が変わったり、なくなったりする可能性があるとのことで、いつまで使えるかわかりません。
Note: Types in Qt.labs modules are not guaranteed to remain compatible in future versions.
あと、QtQuick.Controls と Qt.labs.platform に同名の Type があるので、そのまま import できません。import Qt.labs.platform as Platform
のように as を使って別名をつけてあげます。(完全修飾名的なものはないのかな?)
さらに、QtQuick.Controls.MenuBar は ApplicationWindow の menuBar プロパティに設定しますが、Qt.labs.platform.MenuBar は ApplicationWindow の子要素にします。
% ./ComicsViewer.app/Contents/MacOS/ComicsViewer
QQmlApplicationEngine failed to load component
qrc:/main.qml:23:14: Cannot assign object of type "Platform.MenuBar" to property of type "QQuickItem*" as the former is neither the same as the latter nor a sub-class of it.
- menuBar: MenuBar {
- Menu {
+ Platform.MenuBar {
+ Platform.Menu {
title: qsTr("&File")
- MenuItem {
+ Platform.MenuItem {
text: qsTr("&Open...")
onTriggered: fileDialog.open()
}
- Menu {
+ Platform.Menu {
id: recentFileMenu
title: qsTr("Open &recent")
Instantiator {
model: comicModel.recentFiles
onObjectAdded: (index, object) => recentFileMenu.insertItem(index, object)
onObjectRemoved: object => recentFileMenu.removeItem(object)
- delegate: MenuItem {
+ delegate: Platform.MenuItem {
text: modelData
onTriggered: comicModel.open("file://" + modelData)
}
}
- MenuSeparator {
+ Platform.MenuSeparator {
}
- MenuItem {
+ Platform.MenuItem {
text: qsTr("&Delete all")
onTriggered: comicModel.clearRecentFiles()
}
}
- MenuItem {
+ Platform.MenuItem {
text: qsTr("E&xit")
onTriggered: Qt.quit();
}
}
}
ここまでの内容を github のqt6ブランチに入れました。
commit はこちら。
元々のコードを書いたのが7年前の Qt 5.4の頃で、QMLもまだ始まったばかりと言う感じだったので、いろいろ変更があるのはしょうがないのですが、え、そこ変える?って言うのが結構あるような気がします。
iPhoneとかAndroidのアプリを作ってる人はもっと大変なんでしょうねえ。
追記(2022.6.6)
どうも、Qt6でビルドしたものだと表示されない画像があることに気づきました。
コンソールから実行してみると、以下のエラーが出ています。
qt.gui.imageio: QImageIOHandler: Rejecting image as it exceeds the current allocation limit of 128 megabytes
エラーメッセージでぐぐると、QImageReaderのドキュメントに辿り着きました。
QImageReaderのクラスメソッドに allocationLimit()
, setAllocationLimit()
が追加されていて、このサイズを超える画像は読み込まなくなったようです。
と、言うことで、QImageReader::setAllocationLimit(0)
を追加して無制限に戻してあげたら無事に画像が読み込めるようになりました。
Discussion