diff options
73 files changed, 574 insertions, 476 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3feb49f5..50940246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.13..3.21) option(APPVEYOR_BUILD "Build on appveyor" OFF) option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF) @@ -395,8 +395,6 @@ set(SRC_FILES src/ui/NhekoCursorShape.h src/ui/NhekoDropArea.cpp src/ui/NhekoDropArea.h - src/ui/NhekoEventObserver.cpp - src/ui/NhekoEventObserver.h src/ui/NhekoGlobalObject.cpp src/ui/NhekoGlobalObject.h src/ui/RoomSettings.cpp @@ -499,8 +497,6 @@ set(SRC_FILES src/SingleImagePackModel.h src/TrayIcon.cpp src/TrayIcon.h - src/UserDirectoryModel.cpp - src/UserDirectoryModel.h src/UserSettingsPage.cpp src/UserSettingsPage.h src/UsersModel.cpp @@ -679,10 +675,10 @@ if(ASAN) endif() if(WIN32) - add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS}) + qt_add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS}) target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601 NOMINMAX WIN32_LEAN_AND_MEAN STRICT) else() - add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) + qt_add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) if (HAVE_BACKTRACE_SYMBOLS_FD AND NOT CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(nheko PROPERTIES ENABLE_EXPORTS ON) @@ -700,6 +696,130 @@ set_target_properties(nheko file(GLOB LANG_TS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/langs/*.ts") qt_add_translations(nheko RESOURCE_PREFIX "/translations" TS_FILES ${LANG_TS_SRC}) + +# +# Add qml files +# + +set(QML_SOURCES + resources/qml/Root.qml + resources/qml/ChatPage.qml + resources/qml/CommunitiesList.qml + resources/qml/RoomList.qml + resources/qml/TimelineView.qml + resources/qml/Avatar.qml + resources/qml/Completer.qml + resources/qml/EncryptionIndicator.qml + resources/qml/ImageButton.qml + resources/qml/ElidedLabel.qml + resources/qml/MatrixText.qml + resources/qml/MatrixTextField.qml + resources/qml/ToggleButton.qml + resources/qml/UploadBox.qml + resources/qml/MessageInput.qml + resources/qml/MessageView.qml + resources/qml/PrivacyScreen.qml + resources/qml/Reactions.qml + resources/qml/ReplyPopup.qml + resources/qml/StatusIndicator.qml + resources/qml/TimelineRow.qml + resources/qml/TopBar.qml + resources/qml/QuickSwitcher.qml + resources/qml/ForwardCompleter.qml + resources/qml/SelfVerificationCheck.qml + resources/qml/TypingIndicator.qml + resources/qml/MessageInputWarning.qml + resources/qml/components/AdaptiveLayout.qml + resources/qml/components/AdaptiveLayoutElement.qml + resources/qml/components/AvatarListTile.qml + resources/qml/components/FlatButton.qml + resources/qml/components/MainWindowDialog.qml + resources/qml/components/NhekoTabButton.qml + resources/qml/components/NotificationBubble.qml + resources/qml/components/ReorderableListview.qml + resources/qml/components/SpaceMenuLevel.qml + resources/qml/components/TextButton.qml + resources/qml/components/UserListRow.qml + resources/qml/delegates/Encrypted.qml + resources/qml/delegates/FileMessage.qml + resources/qml/delegates/ImageMessage.qml + resources/qml/delegates/MessageDelegate.qml + resources/qml/delegates/NoticeMessage.qml + resources/qml/delegates/Pill.qml + resources/qml/delegates/Placeholder.qml + resources/qml/delegates/PlayableMediaMessage.qml + resources/qml/delegates/Redacted.qml + resources/qml/delegates/Reply.qml + resources/qml/delegates/TextMessage.qml + resources/qml/device-verification/DeviceVerification.qml + resources/qml/device-verification/DigitVerification.qml + resources/qml/device-verification/EmojiVerification.qml + resources/qml/device-verification/Failed.qml + resources/qml/device-verification/NewVerificationRequest.qml + resources/qml/device-verification/Success.qml + resources/qml/device-verification/Waiting.qml + resources/qml/dialogs/AliasEditor.qml + resources/qml/dialogs/ConfirmJoinRoomDialog.qml + resources/qml/dialogs/CreateDirect.qml + resources/qml/dialogs/CreateRoom.qml + resources/qml/dialogs/HiddenEventsDialog.qml + resources/qml/dialogs/ImageOverlay.qml + resources/qml/dialogs/ImagePackEditorDialog.qml + resources/qml/dialogs/ImagePackSettingsDialog.qml + resources/qml/dialogs/InputDialog.qml + resources/qml/dialogs/InviteDialog.qml + resources/qml/dialogs/JoinRoomDialog.qml + resources/qml/dialogs/LeaveRoomDialog.qml + resources/qml/dialogs/LogoutDialog.qml + resources/qml/dialogs/PhoneNumberInputDialog.qml + resources/qml/dialogs/PowerLevelEditor.qml + resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml + resources/qml/dialogs/RawMessageDialog.qml + resources/qml/dialogs/ReadReceipts.qml + resources/qml/dialogs/RoomDirectory.qml + resources/qml/dialogs/RoomMembers.qml + resources/qml/dialogs/AllowedRoomsSettingsDialog.qml + resources/qml/dialogs/RoomSettings.qml + resources/qml/dialogs/UserProfile.qml + resources/qml/emoji/StickerPicker.qml + resources/qml/pages/LoginPage.qml + resources/qml/pages/RegisterPage.qml + resources/qml/pages/UserSettingsPage.qml + resources/qml/pages/WelcomePage.qml + resources/qml/ui/NhekoSlider.qml + resources/qml/ui/Ripple.qml + resources/qml/ui/Snackbar.qml + resources/qml/ui/Spinner.qml + resources/qml/ui/animations/BlinkAnimation.qml + resources/qml/ui/media/MediaControls.qml + resources/qml/voip/ActiveCallBar.qml + resources/qml/voip/CallDevices.qml + resources/qml/voip/CallInvite.qml + resources/qml/voip/CallInviteBar.qml + resources/qml/voip/DeviceError.qml + resources/qml/voip/PlaceCall.qml + resources/qml/voip/ScreenShare.qml + resources/qml/voip/VideoCall.qml + resources/qml/delegates/EncryptionEnabled.qml + resources/qml/ui/TimelineEffects.qml +) +qt_add_qml_module(nheko + URI im.nheko + NO_RESOURCE_TARGET_PATH + RESOURCE_PREFIX "/" + VERSION 1.1 + DEPENDENCIES QtQml QtQuick # https://bugreports.qt.io/browse/QTBUG-102554 + QML_FILES + ${QML_SOURCES} + SOURCES + src/UserDirectoryModel.cpp + src/UserDirectoryModel.h + ) + #qt_target_qml_sources(nheko + # #PREFIX "/" + #) + + if(WIN32) target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN) if(MSVC) @@ -712,7 +832,7 @@ else() endif() endif() -target_include_directories(nheko PRIVATE src includes) +target_include_directories(nheko PRIVATE src includes src/timeline/ src/ui/ src/encryption/ src/voip/) if (USE_BUNDLED_CPPHTTPLIB) target_include_directories(nheko PRIVATE third_party/cpp-httplib-0.5.12) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 5a28b5de..39061d2b 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -102,7 +102,7 @@ AbstractButton { target: Presence } } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index 4115cd0a..e2e1b914 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -31,7 +31,7 @@ AbstractButton { sourceSize.height: button.height sourceSize.width: button.width } - CursorShape { + NhekoCursorShape { id: mouseArea anchors.fill: parent diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 6d611311..7a71182b 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -44,7 +44,7 @@ TextArea { onPressAndHold: (event) => event.accepted = false onPressed: (event) => event.accepted = (event.button == Qt.LeftButton) - CursorShape { + NhekoCursorShape { id: cs anchors.fill: parent diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 3eed4c48..af3a3371 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -332,7 +332,7 @@ Item { sourceSize.height: button.height sourceSize.width: button.width } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } @@ -590,7 +590,7 @@ Item { elideWidth: userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3) text: userName } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index b869f95a..85c10ddd 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -15,7 +15,6 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import im.nheko -import im.nheko.EmojiModel Pane { id: timelineRoot @@ -93,14 +92,13 @@ Pane { id: fontMetrics } - RoomDirectoryModel { - id: publicRooms - - } UserDirectoryModel { id: userDirectory } + RoomDirectoryModel { + id: publicRooms + } Component { id: readReceiptsDialog diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 9c8ebdc6..16a31a3c 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -173,7 +173,7 @@ AbstractButton { Layout.row: 0 blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" - callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? "" + callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? "" duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0 encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0 eventId: fromModel(Room.EventId) ?? "" diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 18085f28..7b89eb4b 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -8,14 +8,13 @@ import "./device-verification" import "./emoji" import "./ui" import "./voip" -import Qt.labs.platform 1.1 as Platform -import QtQuick 2.15 -import QtQuick.Controls 2.5 -import QtQuick.Layouts 1.3 -import QtQuick.Particles 2.15 -import QtQuick.Window 2.13 -import im.nheko 1.0 -import im.nheko.EmojiModel 1.0 +import Qt.labs.platform as Platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Particles +import QtQuick.Window +import im.nheko Item { id: timelineView @@ -123,7 +122,7 @@ Item { searchString: topBar.searchString } Loader { - source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : "" + source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : "" onLoaded: TimelineManager.setVideoCallItem() } @@ -249,7 +248,7 @@ Item { onLinkActivated: Nheko.openLink(link) - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 3f2d8d2a..699595e6 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -369,7 +369,7 @@ Pane { onAccepted: topBar.searchString = text } } - CursorShape { + NhekoCursorShape { anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0) anchors.fill: parent cursorShape: Qt.PointingHandCursor diff --git a/resources/qml/components/AdaptiveLayout.qml b/resources/qml/components/AdaptiveLayout.qml index 9fc27055..eea74006 100644 --- a/resources/qml/components/AdaptiveLayout.qml +++ b/resources/qml/components/AdaptiveLayout.qml @@ -87,7 +87,7 @@ Container { x: parent.preferredWidth z: 3 - CursorShape { + NhekoCursorShape { height: parent.height width: container.splitterGrabMargin * 2 x: -container.splitterGrabMargin diff --git a/resources/qml/components/TextButton.qml b/resources/qml/components/TextButton.qml index 0b1ac270..b6153f22 100644 --- a/resources/qml/components/TextButton.qml +++ b/resources/qml/components/TextButton.qml @@ -32,7 +32,7 @@ AbstractButton { horizontalAlignment: Text.AlignHCenter } - CursorShape { + NhekoCursorShape { id: mouseArea anchors.fill: parent diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index 48546592..82b82c1b 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -49,7 +49,7 @@ Item { gesturePolicy: TapHandler.ReleaseWithinBounds } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 94128960..4d4983ac 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -43,7 +43,7 @@ AbstractButton { implicitHeight: replyContainer.height implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 1237180b..1eb5e2c0 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -46,7 +46,7 @@ MatrixText { enabled: !Settings.mobileMode font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize - CursorShape { + NhekoCursorShape { enabled: isReply anchors.fill: parent cursorShape: Qt.PointingHandCursor diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml index f5d467db..58bae7fd 100644 --- a/resources/qml/dialogs/InviteDialog.qml +++ b/resources/qml/dialogs/InviteDialog.qml @@ -212,7 +212,7 @@ ApplicationWindow { onClicked: invitees.removeUser(model.mxid) } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml index 523b47cb..d65de73c 100644 --- a/resources/qml/dialogs/ReadReceipts.qml +++ b/resources/qml/dialogs/ReadReceipts.qml @@ -110,7 +110,7 @@ ApplicationWindow { } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml index 916c6c86..bbf1605d 100644 --- a/resources/qml/dialogs/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -220,7 +220,7 @@ ApplicationWindow { } - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml index 0fa98fff..1f011bf1 100644 --- a/resources/qml/dialogs/RoomSettings.qml +++ b/resources/qml/dialogs/RoomSettings.qml @@ -213,7 +213,7 @@ ApplicationWindow { horizontalAlignment: TextEdit.AlignHCenter onLinkActivated: Nheko.openLink(link) - CursorShape { + NhekoCursorShape { anchors.fill: parent cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index c10e57e7..b7721db6 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -7,7 +7,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import im.nheko -import im.nheko.EmojiModel Menu { id: stickerPopup diff --git a/resources/qml/ui/animations/qmldir b/resources/qml/ui/animations/qmldir deleted file mode 100644 index 14f9ad86..00000000 --- a/resources/qml/ui/animations/qmldir +++ /dev/null @@ -1,2 +0,0 @@ -module im.nheko.UI.Animations -BlinkAnimation 1.0 BlinkAnimation.qml diff --git a/resources/qml/ui/media/qmldir b/resources/qml/ui/media/qmldir deleted file mode 100644 index 143b603d..00000000 --- a/resources/qml/ui/media/qmldir +++ /dev/null @@ -1,3 +0,0 @@ -module im.nheko.UI.Media -VolumeSlider 1.0 VolumeSlider.qml -MediaControls 1.0 MediaControls.qml \ No newline at end of file diff --git a/resources/qml/ui/qmldir b/resources/qml/ui/qmldir deleted file mode 100644 index a2ce7514..00000000 --- a/resources/qml/ui/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module im.nheko.UI -NhekoSlider 1.0 NhekoSlider.qml -Ripple 1.0 Ripple.qml -Spinner 1.0 Spinner.qml \ No newline at end of file diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index d055c013..45afedab 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -16,7 +16,7 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - if (CallManager.callType != CallType.VOICE) + if (CallManager.callType != Voip.VOICE) stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; } @@ -58,7 +58,7 @@ Rectangle { states: [ State { name: "VOICE" - when: CallManager.callType == CallType.VOICE + when: CallManager.callType == Voip.VOICE PropertyChanges { target: callTypeIcon @@ -68,7 +68,7 @@ Rectangle { }, State { name: "VIDEO" - when: CallManager.callType == CallType.VIDEO + when: CallManager.callType == Voip.VIDEO PropertyChanges { target: callTypeIcon @@ -78,7 +78,7 @@ Rectangle { }, State { name: "SCREEN" - when: CallManager.callType == CallType.SCREEN + when: CallManager.callType == Voip.SCREEN PropertyChanges { target: callTypeIcon @@ -100,7 +100,7 @@ Rectangle { states: [ State { name: "OFFERSENT" - when: CallManager.callState == WebRTCState.OFFERSENT + when: CallManager.callState == Voip.OFFERSENT PropertyChanges { target: callStateLabel @@ -110,7 +110,7 @@ Rectangle { }, State { name: "CONNECTING" - when: CallManager.callState == WebRTCState.CONNECTING + when: CallManager.callState == Voip.CONNECTING PropertyChanges { target: callStateLabel @@ -120,7 +120,7 @@ Rectangle { }, State { name: "ANSWERSENT" - when: CallManager.callState == WebRTCState.ANSWERSENT + when: CallManager.callState == Voip.ANSWERSENT PropertyChanges { target: callStateLabel @@ -130,7 +130,7 @@ Rectangle { }, State { name: "CONNECTED" - when: CallManager.callState == WebRTCState.CONNECTED + when: CallManager.callState == Voip.CONNECTED PropertyChanges { target: callStateLabel @@ -144,13 +144,13 @@ Rectangle { PropertyChanges { target: stackLayout - currentIndex: CallManager.callType != CallType.VOICE ? 1 : 0 + currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 } }, State { name: "DISCONNECTED" - when: CallManager.callState == WebRTCState.DISCONNECTED + when: CallManager.callState == Voip.DISCONNECTED PropertyChanges { target: callStateLabel @@ -176,7 +176,7 @@ Rectangle { } interval: 1000 - running: CallManager.callState == WebRTCState.CONNECTED + running: CallManager.callState == Voip.CONNECTED repeat: true onTriggered: { var d = new Date(); @@ -190,7 +190,7 @@ Rectangle { Label { Layout.leftMargin: 16 - visible: CallManager.callType == CallType.SCREEN && CallManager.callState == WebRTCState.CONNECTED + visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED text: qsTr("You are screen sharing") font.pointSize: fontMetrics.font.pointSize * 1.1 color: "#000000" diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml index d4c554dc..46c8cde3 100644 --- a/resources/qml/voip/CallDevices.qml +++ b/resources/qml/voip/CallDevices.qml @@ -43,7 +43,7 @@ Popup { } RowLayout { - visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 + visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 Image { Layout.preferredWidth: 22 diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml index 25aa0818..8a609c32 100644 --- a/resources/qml/voip/CallInvite.qml +++ b/resources/qml/voip/CallInvite.qml @@ -62,7 +62,7 @@ Popup { Layout.bottomMargin: callInv.height / 25 Image { - property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" + property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" Layout.alignment: Qt.AlignCenter Layout.preferredWidth: callInv.height / 10 @@ -72,7 +72,7 @@ Popup { Label { Layout.alignment: Qt.AlignCenter - text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") + text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") font.pointSize: fontMetrics.font.pointSize * 2 color: palette.windowText } @@ -106,7 +106,7 @@ Popup { } RowLayout { - visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 + visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 Layout.alignment: Qt.AlignCenter Image { @@ -169,7 +169,7 @@ Popup { RoundButton { id: acceptButton - property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" + property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" implicitWidth: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 3c7426cc..d82bd143 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -57,12 +57,12 @@ Rectangle { Layout.leftMargin: 4 Layout.preferredWidth: 24 Layout.preferredHeight: 24 - source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" + source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" } Label { font.pointSize: fontMetrics.font.pointSize * 1.1 - text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") + text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") color: "#000000" } @@ -88,7 +88,7 @@ Rectangle { Button { Layout.rightMargin: 4 - icon.source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" + icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" text: qsTr("Accept") onClicked: { if (CallManager.mics.length == 0) { @@ -108,7 +108,7 @@ Rectangle { timelineRoot.destroyOnClose(dialog); return ; } - if (CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { + if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { var dialog = deviceError.createObject(timelineRoot, { "errorString": qsTr("Unknown camera: %1").arg(Settings.camera), "image": ":/icons/icons/ui/video.svg" diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 5d9387c3..2d4c46f8 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -81,7 +81,7 @@ Popup { onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; - CallManager.sendInvite(room.roomId, CallType.VOICE); + CallManager.sendInvite(room.roomId, Voip.VOICE); close(); } } @@ -95,7 +95,7 @@ Popup { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - CallManager.sendInvite(room.roomId, CallType.VIDEO); + CallManager.sendInvite(room.roomId, Voip.VIDEO); close(); } } diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index ce998299..2337f6d0 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -47,7 +47,7 @@ Popup { Layout.fillWidth: true model: CallManager.screenShareTypeList() - onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex); + onCurrentIndexChanged: CallManager.setVoip(currentIndex); } } @@ -63,7 +63,7 @@ Popup { } ComboBox { - visible: CallManager.screenShareType == ScreenShareType.X11 + visible: CallManager.screenShareType == Voip.X11 id: windowCombo Layout.fillWidth: true @@ -71,7 +71,7 @@ Popup { } Button { - visible: CallManager.screenShareType == ScreenShareType.XDP + visible: CallManager.screenShareType == Voip.XDP highlighted: !CallManager.screenShareReady text: qsTr("Request screencast") onClicked: { @@ -165,7 +165,7 @@ Popup { Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.sendInvite(room.roomId, CallType.SCREEN, windowCombo.currentIndex); + CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex); close(); } } diff --git a/resources/res.qrc b/resources/res.qrc index ed32b2bf..fb857d4a 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -92,107 +92,7 @@ </qresource> <qresource prefix="/"> <file>qtquickcontrols2.conf</file> - <file>qml/Root.qml</file> - <file>qml/ChatPage.qml</file> - <file>qml/CommunitiesList.qml</file> - <file>qml/RoomList.qml</file> - <file>qml/TimelineView.qml</file> - <file>qml/Avatar.qml</file> - <file>qml/Completer.qml</file> - <file>qml/EncryptionIndicator.qml</file> - <file>qml/ImageButton.qml</file> - <file>qml/ElidedLabel.qml</file> - <file>qml/MatrixText.qml</file> - <file>qml/MatrixTextField.qml</file> - <file>qml/ToggleButton.qml</file> - <file>qml/UploadBox.qml</file> - <file>qml/MessageInput.qml</file> - <file>qml/MessageView.qml</file> - <file>qml/PrivacyScreen.qml</file> - <file>qml/Reactions.qml</file> - <file>qml/ReplyPopup.qml</file> - <file>qml/StatusIndicator.qml</file> - <file>qml/TimelineRow.qml</file> - <file>qml/TopBar.qml</file> - <file>qml/QuickSwitcher.qml</file> - <file>qml/ForwardCompleter.qml</file> - <file>qml/SelfVerificationCheck.qml</file> - <file>qml/TypingIndicator.qml</file> - <file>qml/MessageInputWarning.qml</file> - <file>qml/components/AdaptiveLayout.qml</file> - <file>qml/components/AdaptiveLayoutElement.qml</file> - <file>qml/components/AvatarListTile.qml</file> - <file>qml/components/FlatButton.qml</file> - <file>qml/components/MainWindowDialog.qml</file> - <file>qml/components/NhekoTabButton.qml</file> - <file>qml/components/NotificationBubble.qml</file> - <file>qml/components/ReorderableListview.qml</file> - <file>qml/components/SpaceMenuLevel.qml</file> - <file>qml/components/TextButton.qml</file> - <file>qml/components/UserListRow.qml</file> - <file>qml/delegates/Encrypted.qml</file> - <file>qml/delegates/FileMessage.qml</file> - <file>qml/delegates/ImageMessage.qml</file> - <file>qml/delegates/MessageDelegate.qml</file> - <file>qml/delegates/NoticeMessage.qml</file> - <file>qml/delegates/Pill.qml</file> - <file>qml/delegates/Placeholder.qml</file> - <file>qml/delegates/PlayableMediaMessage.qml</file> - <file>qml/delegates/Redacted.qml</file> - <file>qml/delegates/Reply.qml</file> - <file>qml/delegates/TextMessage.qml</file> - <file>qml/device-verification/DeviceVerification.qml</file> - <file>qml/device-verification/DigitVerification.qml</file> - <file>qml/device-verification/EmojiVerification.qml</file> - <file>qml/device-verification/Failed.qml</file> - <file>qml/device-verification/NewVerificationRequest.qml</file> - <file>qml/device-verification/Success.qml</file> - <file>qml/device-verification/Waiting.qml</file> - <file>qml/dialogs/AliasEditor.qml</file> - <file>qml/dialogs/ConfirmJoinRoomDialog.qml</file> - <file>qml/dialogs/CreateDirect.qml</file> - <file>qml/dialogs/CreateRoom.qml</file> - <file>qml/dialogs/HiddenEventsDialog.qml</file> - <file>qml/dialogs/ImageOverlay.qml</file> - <file>qml/dialogs/ImagePackEditorDialog.qml</file> - <file>qml/dialogs/ImagePackSettingsDialog.qml</file> - <file>qml/dialogs/InputDialog.qml</file> - <file>qml/dialogs/InviteDialog.qml</file> - <file>qml/dialogs/JoinRoomDialog.qml</file> - <file>qml/dialogs/LeaveRoomDialog.qml</file> - <file>qml/dialogs/LogoutDialog.qml</file> - <file>qml/dialogs/PhoneNumberInputDialog.qml</file> - <file>qml/dialogs/PowerLevelEditor.qml</file> - <file>qml/dialogs/PowerLevelSpacesApplyDialog.qml</file> - <file>qml/dialogs/RawMessageDialog.qml</file> - <file>qml/dialogs/ReadReceipts.qml</file> - <file>qml/dialogs/RoomDirectory.qml</file> - <file>qml/dialogs/RoomMembers.qml</file> - <file>qml/dialogs/AllowedRoomsSettingsDialog.qml</file> - <file>qml/dialogs/RoomSettings.qml</file> - <file>qml/dialogs/UserProfile.qml</file> - <file>qml/emoji/StickerPicker.qml</file> - <file>qml/pages/LoginPage.qml</file> - <file>qml/pages/RegisterPage.qml</file> - <file>qml/pages/UserSettingsPage.qml</file> - <file>qml/pages/WelcomePage.qml</file> - <file>qml/ui/NhekoSlider.qml</file> - <file>qml/ui/Ripple.qml</file> - <file>qml/ui/Snackbar.qml</file> - <file>qml/ui/Spinner.qml</file> - <file>qml/ui/animations/BlinkAnimation.qml</file> - <file>qml/ui/media/MediaControls.qml</file> - <file>qml/voip/ActiveCallBar.qml</file> - <file>qml/voip/CallDevices.qml</file> - <file>qml/voip/CallInvite.qml</file> - <file>qml/voip/CallInviteBar.qml</file> - <file>qml/voip/DeviceError.qml</file> - <file>qml/voip/PlaceCall.qml</file> - <file>qml/voip/ScreenShare.qml</file> - <file>qml/voip/VideoCall.qml</file> <file>confettiparticle.svg</file> - <file>qml/delegates/EncryptionEnabled.qml</file> - <file>qml/ui/TimelineEffects.qml</file> </qresource> <qresource prefix="/media"> <file>media/ring.ogg</file> diff --git a/src/AliasEditModel.h b/src/AliasEditModel.h index 2263659b..04de5016 100644 --- a/src/AliasEditModel.h +++ b/src/AliasEditModel.h @@ -5,6 +5,7 @@ #pragma once #include <QAbstractListModel> +#include <QQmlEngine> #include <QVector> #include <mtx/events/canonical_alias.hpp> @@ -29,6 +30,9 @@ signals: class AliasEditingModel final : public QAbstractListModel { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Please use editAliases to create the models") + Q_PROPERTY(bool canAdvertize READ canAdvertize CONSTANT) public: diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 96fc35ec..2a5b895f 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -5,6 +5,7 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <map> #include <mutex> @@ -16,6 +17,8 @@ namespace crypto { Q_NAMESPACE +QML_NAMED_ELEMENT(Crypto) + //! How much a participant is trusted. enum Trust { diff --git a/src/Clipboard.h b/src/Clipboard.h index bad9fd10..8bf89c22 100644 --- a/src/Clipboard.h +++ b/src/Clipboard.h @@ -5,11 +5,15 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <QString> class Clipboard final : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) public: diff --git a/src/LoginPage.h b/src/LoginPage.h index f20ba0c6..e3d0d2e0 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -5,13 +5,10 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <QVariantList> -namespace mtx { -namespace responses { -struct Login; -} -} +#include <mtx/responses/login.hpp> struct SSOProvider { @@ -33,6 +30,7 @@ public: class LoginPage : public QObject { Q_OBJECT + QML_ELEMENT Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged) Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 51b23e0f..d06171de 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -25,21 +25,15 @@ #include "InviteesModel.h" #include "JdenticonProvider.h" #include "Logging.h" -#include "LoginPage.h" #include "MainWindow.h" #include "MatrixClient.h" #include "MemberList.h" #include "MxcImageProvider.h" #include "PowerlevelsEditModels.h" -#include "ReadReceiptsModel.h" -#include "RegisterPage.h" -#include "RoomDirectoryModel.h" -#include "RoomsModel.h" #include "SingleImagePackModel.h" #include "TrayIcon.h" #include "UserDirectoryModel.h" #include "UserSettingsPage.h" -#include "UsersModel.h" #include "Utils.h" #include "dock/Dock.h" #include "emoji/Provider.h" @@ -48,12 +42,6 @@ #include "timeline/DelegateChooser.h" #include "timeline/TimelineFilter.h" #include "timeline/TimelineViewManager.h" -#include "ui/HiddenEvents.h" -#include "ui/MxcAnimatedImage.h" -#include "ui/MxcMediaProxy.h" -#include "ui/NhekoCursorShape.h" -#include "ui/NhekoDropArea.h" -#include "ui/NhekoEventObserver.h" #include "ui/NhekoGlobalObject.h" #include "ui/RoomSummary.h" #include "ui/UIA.h" @@ -83,7 +71,7 @@ MainWindow::MainWindow(QWindow *parent) registerQmlTypes(); setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color()); - setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml"))); + setSource(QUrl(QStringLiteral("qrc:///resources/qml/Root.qml"))); trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this); @@ -132,156 +120,57 @@ MainWindow::MainWindow(QWindow *parent) void MainWindow::registerQmlTypes() { - qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, - "im.nheko", - 1, - 0, - "MtxEvent", - QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject( - olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject(crypto::staticMetaObject, - "im.nheko", - 1, - 0, - "Crypto", - QStringLiteral("Can't instantiate enum!")); - qmlRegisterUncreatableMetaObject(verification::staticMetaObject, - "im.nheko", - 1, - 0, - "VerificationStatus", - QStringLiteral("Can't instantiate enum!")); - - qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); - qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); - qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); - qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); - qmlRegisterType<NhekoEventObserver>("im.nheko", 1, 0, "EventObserver"); - qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage"); - qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); - qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); - qmlRegisterType<UserDirectoryModel>("im.nheko", 1, 0, "UserDirectoryModel"); - qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login"); - qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration"); - qmlRegisterType<HiddenEvents>("im.nheko", 1, 0, "HiddenEvents"); - qmlRegisterType<TimelineFilter>("im.nheko", 1, 0, "TimelineFilter"); - qmlRegisterUncreatableType<RoomSummary>( - "im.nheko", - 1, - 0, - "RoomSummary", - QStringLiteral("Please use joinRoom to create a room summary.")); - qmlRegisterUncreatableType<AliasEditingModel>( - "im.nheko", - 1, - 0, - "AliasEditingModel", - QStringLiteral("Please use editAliases to create the models")); - - qmlRegisterUncreatableType<PowerlevelEditingModels>( - "im.nheko", - 1, - 0, - "PowerlevelEditingModels", - QStringLiteral("Please use editPowerlevels to create the models")); - qmlRegisterUncreatableType<DeviceVerificationFlow>( - "im.nheko", - 1, - 0, - "DeviceVerificationFlow", - QStringLiteral("Can't create verification flow from QML!")); - qmlRegisterUncreatableType<UserProfile>( - "im.nheko", - 1, - 0, - "UserProfileModel", - QStringLiteral("UserProfile needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<MemberList>( - "im.nheko", - 1, - 0, - "MemberList", - QStringLiteral("MemberList needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<RoomSettings>( - "im.nheko", - 1, - 0, - "RoomSettingsModel", - QStringLiteral("Room Settings needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<TimelineModel>( - "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<ImagePackListModel>( - "im.nheko", - 1, - 0, - "ImagePackListModel", - QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<SingleImagePackModel>( - "im.nheko", - 1, - 0, - "SingleImagePackModel", - QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<InviteesModel>( - "im.nheko", - 1, - 0, - "InviteesModel", - QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); - qmlRegisterUncreatableType<ReadReceiptsProxy>( - "im.nheko", - 1, - 0, - "ReadReceiptsProxy", - QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side")); - - qmlRegisterSingletonType<Clipboard>( - "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Clipboard(); - }); - qmlRegisterSingletonType<Nheko>( - "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Nheko(); - }); - qmlRegisterSingletonType<UserSettingsModel>( - "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new UserSettingsModel(); - }); - - qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data()); - - qmlRegisterUncreatableType<FilteredCommunitiesModel>( - "im.nheko", - 1, - 0, - "FilteredCommunitiesModel", - QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel")); - - qmlRegisterUncreatableType<MediaUpload>( - "im.nheko", 1, 0, "MediaUpload", QStringLiteral("MediaUploads can not be created in Qml")); - qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, - "im.nheko.EmojiModel", - 1, - 0, - "EmojiCategory", - QStringLiteral("Error: Only enums")); - - qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); - - qmlRegisterSingletonType<SelfVerificationStatus>( - "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = new SelfVerificationStatus(); - QObject::connect(ChatPage::instance(), - &ChatPage::initializeEmptyViews, - ptr, - &SelfVerificationStatus::invalidate); - return ptr; - }); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", this); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance()); - qmlRegisterSingletonInstance( - "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager()); + // qmlRegisterUncreatableType<DeviceVerificationFlow>( + // "im.nheko", + // 1, + // 0, + // "DeviceVerificationFlow", + // QStringLiteral("Can't create verification flow from QML!")); + // qmlRegisterUncreatableType<UserProfile>( + // "im.nheko", + // 1, + // 0, + // "UserProfileModel", + // QStringLiteral("UserProfile needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<MemberList>( + // "im.nheko", + // 1, + // 0, + // "MemberList", + // QStringLiteral("MemberList needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<RoomSettings>( + // "im.nheko", + // 1, + // 0, + // "RoomSettingsModel", + // QStringLiteral("Room Settings needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<TimelineModel>( + // "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<ImagePackListModel>( + // "im.nheko", + // 1, + // 0, + // "ImagePackListModel", + // QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<SingleImagePackModel>( + // "im.nheko", + // 1, + // 0, + // "SingleImagePackModel", + // QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side")); + // qmlRegisterUncreatableType<InviteesModel>( + // "im.nheko", + // 1, + // 0, + // "InviteesModel", + // QStringLiteral("InviteesModel needs to be instantiated on the C++ side")); + + // qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, + // "im.nheko.EmojiModel", + // 1, + // 0, + // "EmojiCategory", + // QStringLiteral("Error: Only enums")); imgProvider = new MxcImageProvider(); engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider); diff --git a/src/MainWindow.h b/src/MainWindow.h index 0a5f9433..20e81efc 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -50,14 +50,35 @@ public: bool eventFilter(QObject *obj, QEvent *event) override; }; -class MainWindow final : public QQuickView +class MainWindow : public QQuickView { Q_OBJECT + QML_ELEMENT + QML_SINGLETON public: - explicit MainWindow(QWindow *parent = nullptr); + explicit MainWindow(QWindow *parent); static MainWindow *instance() { return instance_; } + static MainWindow *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; + } + void saveCurrentWindowSize(); void openJoinRoomDialog(std::function<void(const QString &room_id)> callback); diff --git a/src/PowerlevelsEditModels.h b/src/PowerlevelsEditModels.h index fe9735d3..c9d262d8 100644 --- a/src/PowerlevelsEditModels.h +++ b/src/PowerlevelsEditModels.h @@ -5,6 +5,7 @@ #pragma once #include <QAbstractListModel> +#include <QQmlEngine> #include <QSortFilterProxyModel> #include <mtx/events/power_levels.hpp> @@ -196,6 +197,9 @@ class PowerlevelEditingModels final : public QObject { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Please use editPowerlevels to create the models") + Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT) Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT) Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces CONSTANT) diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index b870061a..56f67509 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -8,6 +8,7 @@ #include <QAbstractListModel> #include <QDateTime> #include <QObject> +#include <QQmlEngine> #include <QSortFilterProxyModel> #include <QString> @@ -54,6 +55,9 @@ class ReadReceiptsProxy final : public QSortFilterProxyModel { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_PROPERTY(QString eventId READ eventId CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 7c58b40c..dcf61489 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -5,6 +5,7 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <QString> #include <mtx/user_interactive.hpp> @@ -13,6 +14,7 @@ class RegisterPage : public QObject { Q_OBJECT + QML_ELEMENT Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_PROPERTY(QString hsError READ hsError NOTIFY hsErrorChanged) diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 8a367e2e..a5103112 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -5,6 +5,7 @@ #pragma once #include <QAbstractListModel> +#include <QQmlEngine> #include <QString> #include <string> #include <vector> @@ -32,6 +33,7 @@ signals: class RoomDirectoryModel : public QAbstractListModel { Q_OBJECT + QML_ELEMENT Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) Q_PROPERTY( diff --git a/src/UserDirectoryModel.h b/src/UserDirectoryModel.h index f0416ecf..ffa9ae93 100644 --- a/src/UserDirectoryModel.h +++ b/src/UserDirectoryModel.h @@ -5,6 +5,7 @@ #pragma once #include <QAbstractListModel> +#include <QQmlEngine> #include <QString> #include <string> #include <vector> @@ -26,6 +27,7 @@ signals: class UserDirectoryModel : public QAbstractListModel { Q_OBJECT + QML_ELEMENT Q_PROPERTY(bool searchingUsers READ searchingUsers NOTIFY searchingUsersChanged) diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 657a362d..301a1b67 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -6,6 +6,7 @@ #include <QAbstractListModel> #include <QProcessEnvironment> +#include <QQmlEngine> #include <QSettings> #include <QSharedPointer> @@ -23,6 +24,8 @@ class QVBoxLayout; class UserSettings final : public QObject { Q_OBJECT + QML_NAMED_ELEMENT(Settings) + QML_SINGLETON Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight @@ -131,6 +134,24 @@ class UserSettings final : public QObject public: static QSharedPointer<UserSettings> instance(); static void initialize(std::optional<QString> profile); + static UserSettings *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance()); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance()->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance().get(), QJSEngine::CppOwnership); + return instance().get(); + } QSettings *qsettings() { return &settings; } @@ -431,9 +452,10 @@ private: static QSharedPointer<UserSettings> instance_; }; -class UserSettingsModel final : public QAbstractListModel +class UserSettingsModel : public QAbstractListModel { Q_OBJECT + QML_ELEMENT enum Indices { diff --git a/src/encryption/Olm.h b/src/encryption/Olm.h index f0e51070..726b9590 100644 --- a/src/encryption/Olm.h +++ b/src/encryption/Olm.h @@ -9,10 +9,13 @@ #include <mtx/events/encrypted.hpp> #include <mtxclient/crypto/client.hpp> +#include <QQmlEngine> + #include <CacheCryptoStructs.h> namespace olm { Q_NAMESPACE +QML_NAMED_ELEMENT(Olm) enum DecryptionErrorCode { diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index 6fa737d4..d9d3d787 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -29,6 +29,11 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o) Qt::UniqueConnection); cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()}); }); + + connect(ChatPage::instance(), + &ChatPage::initializeEmptyViews, + this, + &SelfVerificationStatus::invalidate); } void diff --git a/src/encryption/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h index ea790c8b..c65fffd0 100644 --- a/src/encryption/SelfVerificationStatus.h +++ b/src/encryption/SelfVerificationStatus.h @@ -5,11 +5,15 @@ #pragma once #include <QObject> +#include <QQmlEngine> class SelfVerificationStatus final : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(Status status READ status NOTIFY statusChanged) Q_PROPERTY(bool hasSSSS READ hasSSSS NOTIFY hasSSSSChanged) diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp index 802a8177..d1248755 100644 --- a/src/encryption/VerificationManager.cpp +++ b/src/encryption/VerificationManager.cpp @@ -15,6 +15,7 @@ VerificationManager::VerificationManager(TimelineViewManager *o) : QObject(o) , rooms_(o->rooms()) { + instance_ = this; } static bool diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h index 7b32bc98..cdc8af30 100644 --- a/src/encryption/VerificationManager.h +++ b/src/encryption/VerificationManager.h @@ -6,6 +6,7 @@ #include <QHash> #include <QObject> +#include <QQmlEngine> #include <QSharedPointer> #include <mtx/events.hpp> @@ -21,8 +22,30 @@ class VerificationManager final : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON + public: - VerificationManager(TimelineViewManager *o = nullptr); + VerificationManager(TimelineViewManager *o); + + static VerificationManager *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; + } Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); @@ -45,4 +68,6 @@ private: QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList; bool isInitialSync_ = false; RoomlistModel *rooms_; + + inline static VerificationManager *instance_ = nullptr; }; diff --git a/src/main.cpp b/src/main.cpp index da67ca43..07397d62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -346,7 +346,7 @@ main(int argc, char *argv[]) QStringLiteral(":/translations"))) app.installTranslator(&appTranslator); - MainWindow w; + MainWindow w(nullptr); // QQuickView w; // Move the MainWindow to the center diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index b04fd7a9..3c09d747 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -22,6 +22,7 @@ CommunitiesModel::CommunitiesModel(QObject *parent) , hiddenTagIds_{UserSettings::instance()->hiddenTags()} , mutedTagIds_{UserSettings::instance()->mutedTags()} { + instance_ = this; } QHash<int, QByteArray> diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index a90fa6a2..d0841f4b 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -6,6 +6,7 @@ #include <QAbstractListModel> #include <QHash> +#include <QQmlEngine> #include <QSortFilterProxyModel> #include <QString> #include <QStringList> @@ -21,6 +22,8 @@ class CommunitiesModel; class FilteredCommunitiesModel final : public QSortFilterProxyModel { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Use Communities.filtered() to create a FilteredCommunitiesModel") public: explicit FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent = nullptr); @@ -73,6 +76,9 @@ public: class CommunitiesModel final : public QAbstractListModel { Q_OBJECT + QML_NAMED_ELEMENT(Communities) + QML_SINGLETON + Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY currentTagIdChanged RESET resetCurrentTagId) Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged) @@ -149,6 +155,26 @@ public: }; CommunitiesModel(QObject *parent = nullptr); + + static CommunitiesModel *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; + } + QHash<int, QByteArray> roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { @@ -221,4 +247,6 @@ private: mtx::responses::UnreadNotifications dmUnreads{}; friend class FilteredCommunitiesModel; + + inline static CommunitiesModel *instance_ = nullptr; }; diff --git a/src/timeline/DelegateChooser.h b/src/timeline/DelegateChooser.h index c27f2c43..ac227382 100644 --- a/src/timeline/DelegateChooser.h +++ b/src/timeline/DelegateChooser.h @@ -19,6 +19,7 @@ class QQmlAdaptorModel; class DelegateChoice : public QObject { Q_OBJECT + QML_ELEMENT Q_CLASSINFO("DefaultProperty", "delegate") public: @@ -45,6 +46,7 @@ private: class DelegateChooser : public QQuickItem { Q_OBJECT + QML_ELEMENT Q_CLASSINFO("DefaultProperty", "choices") public: diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index f03e6019..3cd65524 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -7,11 +7,13 @@ #include <QIODevice> #include <QImage> #include <QObject> +#include <QQmlEngine> #include <QSize> #include <QStringList> #include <QTimer> #include <QUrl> #include <QVariantList> + #include <deque> #include <memory> @@ -43,6 +45,10 @@ enum class MarkdownOverride class MediaUpload final : public QObject { Q_OBJECT + + QML_ELEMENT + QML_UNCREATABLE("") + Q_PROPERTY(int mediaType READ type NOTIFY mediaTypeChanged) // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646 Q_PROPERTY(QUrl thumbnail READ thumbnailDataUrl NOTIFY thumbnailChanged) diff --git a/src/timeline/PresenceEmitter.h b/src/timeline/PresenceEmitter.h index e89fb316..09ad1301 100644 --- a/src/timeline/PresenceEmitter.h +++ b/src/timeline/PresenceEmitter.h @@ -5,6 +5,7 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <vector> @@ -15,10 +16,33 @@ class PresenceEmitter final : public QObject { Q_OBJECT + QML_NAMED_ELEMENT(Presence) + QML_SINGLETON + public: PresenceEmitter(QObject *p = nullptr) : QObject(p) { + instance_ = this; + } + + static PresenceEmitter *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; } void sync(const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences); @@ -28,4 +52,7 @@ public: signals: void presenceChanged(QString userid); + +private: + inline static PresenceEmitter *instance_ = nullptr; }; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index ec41cc12..8d8d2977 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -909,6 +909,8 @@ FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *pare : QSortFilterProxyModel(parent) , roomlistmodel(model) { + instance_ = this; + this->sortByImportance = UserSettings::instance()->sortByImportance(); this->sortByAlphabet = UserSettings::instance()->sortByAlphabet(); setSourceModel(model); diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index c06ab67d..34bf3f9a 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -167,12 +167,36 @@ private: class FilteredRoomlistModel final : public QSortFilterProxyModel { Q_OBJECT + + QML_NAMED_ELEMENT(Rooms) + QML_SINGLETON + Q_PROPERTY( TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom) Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged RESET resetCurrentRoom) public: FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr); + + static FilteredRoomlistModel *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; + } + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; @@ -249,4 +273,6 @@ private: FilterBy filterType = FilterBy::Nothing; QStringList hiddenTags, hiddenSpaces; bool hideDMs = false; + + inline static FilteredRoomlistModel *instance_ = nullptr; }; diff --git a/src/timeline/TimelineFilter.h b/src/timeline/TimelineFilter.h index 1c92c89a..658a8c57 100644 --- a/src/timeline/TimelineFilter.h +++ b/src/timeline/TimelineFilter.h @@ -4,6 +4,7 @@ #pragma once +#include <QQmlEngine> #include <QSortFilterProxyModel> #include <QString> @@ -14,6 +15,7 @@ class TimelineFilter : public QSortFilterProxyModel { Q_OBJECT + QML_ELEMENT Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged) Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index a232b4ee..fd1a4396 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -11,6 +11,7 @@ #include <QTimer> #include <QVariant> +#include <mtx/responses/common.hpp> #include <mtxclient/http/errors.hpp> #include "CacheCryptoStructs.h" @@ -36,6 +37,7 @@ struct RelatedInfo; namespace qml_mtx_events { Q_NAMESPACE +QML_NAMED_ELEMENT(MtxEvent) enum EventType { @@ -193,6 +195,9 @@ class TimelineViewManager; class TimelineModel final : public QAbstractListModel { Q_OBJECT + QML_NAMED_ELEMENT(Room) + QML_UNCREATABLE("") + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY typingUsersChanged) diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index a3b91ce7..2f6553e5 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -96,29 +96,21 @@ TimelineViewManager::userColor(QString id, QColor background) TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent) : QObject(parent) , rooms_(new RoomlistModel(this)) + , frooms_(new FilteredRoomlistModel(this->rooms_)) , communities_(new CommunitiesModel(this)) , verificationManager_(new VerificationManager(this)) , presenceEmitter(new PresenceEmitter(this)) { - static auto self = this; - qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self); - qmlRegisterSingletonType<RoomlistModel>( - "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = new FilteredRoomlistModel(self->rooms_); - - connect(self->communities_, - &CommunitiesModel::currentTagIdChanged, - ptr, - &FilteredRoomlistModel::updateFilterTag); - connect(self->communities_, - &CommunitiesModel::hiddenTagsChanged, - ptr, - &FilteredRoomlistModel::updateHiddenTagsAndSpaces); - return ptr; - }); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_); - qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter); + instance_ = this; + + connect(this->communities_, + &CommunitiesModel::currentTagIdChanged, + frooms_, + &FilteredRoomlistModel::updateFilterTag); + connect(this->communities_, + &CommunitiesModel::hiddenTagsChanged, + frooms_, + &FilteredRoomlistModel::updateHiddenTagsAndSpaces); updateColorPalette(); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 303e2af2..a4bc6c41 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -34,6 +34,9 @@ class TimelineViewManager final : public QObject { Q_OBJECT + QML_NAMED_ELEMENT(TimelineManager) + QML_SINGLETON + Q_PROPERTY( bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged) @@ -41,6 +44,25 @@ class TimelineViewManager final : public QObject public: TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); + static TimelineViewManager *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance_); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance_->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership); + return instance_; + } + void sync(const mtx::responses::Sync &sync_); VerificationManager *verificationManager() { return verificationManager_; } @@ -123,6 +145,7 @@ private: bool isConnected_ = true; RoomlistModel *rooms_ = nullptr; + FilteredRoomlistModel *frooms_ = nullptr; CommunitiesModel *communities_ = nullptr; // don't move this above the rooms_ @@ -130,4 +153,6 @@ private: PresenceEmitter *presenceEmitter = nullptr; QHash<QPair<QString, quint64>, QColor> userColors; + + inline static TimelineViewManager *instance_ = nullptr; }; diff --git a/src/ui/HiddenEvents.h b/src/ui/HiddenEvents.h index bb68e0fa..4f0d23b4 100644 --- a/src/ui/HiddenEvents.h +++ b/src/ui/HiddenEvents.h @@ -5,6 +5,7 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <QString> #include <QVariantList> @@ -13,6 +14,7 @@ class HiddenEvents : public QObject { Q_OBJECT + QML_ELEMENT Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged REQUIRED) Q_PROPERTY(QVariantList hiddenEvents READ hiddenEvents NOTIFY hiddenEventsChanged) public: diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h index bc53e711..c9f89764 100644 --- a/src/ui/MxcAnimatedImage.h +++ b/src/ui/MxcAnimatedImage.h @@ -15,6 +15,7 @@ class MxcAnimatedImage : public QQuickItem { Q_OBJECT + QML_ELEMENT Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged) diff --git a/src/ui/MxcMediaProxy.h b/src/ui/MxcMediaProxy.h index 5c2eac33..d245dcae 100644 --- a/src/ui/MxcMediaProxy.h +++ b/src/ui/MxcMediaProxy.h @@ -8,6 +8,7 @@ #include <QMediaPlayer> #include <QObject> #include <QPointer> +#include <QQuickItem> #include <QString> #include <QUrl> #include <QVideoSink> @@ -21,6 +22,8 @@ class TimelineModel; class MxcMediaProxy : public QMediaPlayer { Q_OBJECT + QML_NAMED_ELEMENT(MxcMedia) + Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) diff --git a/src/ui/NhekoCursorShape.h b/src/ui/NhekoCursorShape.h index 84d56fad..123852f9 100644 --- a/src/ui/NhekoCursorShape.h +++ b/src/ui/NhekoCursorShape.h @@ -12,7 +12,7 @@ class NhekoCursorShape : public QQuickItem { Q_OBJECT - + QML_ELEMENT Q_PROPERTY( Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY cursorShapeChanged) diff --git a/src/ui/NhekoDropArea.h b/src/ui/NhekoDropArea.h index 91116844..46a02da5 100644 --- a/src/ui/NhekoDropArea.h +++ b/src/ui/NhekoDropArea.h @@ -7,6 +7,7 @@ class NhekoDropArea : public QQuickItem { Q_OBJECT + QML_ELEMENT Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged) public: NhekoDropArea(QQuickItem *parent = nullptr); diff --git a/src/ui/NhekoEventObserver.cpp b/src/ui/NhekoEventObserver.cpp deleted file mode 100644 index 713a0733..00000000 --- a/src/ui/NhekoEventObserver.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "NhekoEventObserver.h" - -#include <QMouseEvent> - -#include "Logging.h" - -NhekoEventObserver::NhekoEventObserver(QQuickItem *parent) - : QQuickItem(parent) -{ - setFiltersChildMouseEvents(true); -} - -bool -NhekoEventObserver::childMouseEventFilter(QQuickItem * /*item*/, QEvent *event) -{ - // nhlog::ui()->debug("Touched {}", item->metaObject()->className()); - - auto setTouched = [this](bool touched) { - if (touched != this->wasTouched_) { - this->wasTouched_ = touched; - emit wasTouchedChanged(); - } - }; - - // see - // https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quicktemplates2/qquickscrollview.cpp?id=7f29e89c26ae2babc358b1c4e6f965af6ec759f4#n471 - switch (event->type()) { - case QEvent::TouchBegin: - case QEvent::TouchEnd: - setTouched(true); - break; - - case QEvent::MouseButtonPress: - if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) { - setTouched(false); - } - break; - - case QEvent::MouseMove: - case QEvent::MouseButtonRelease: - if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) - setTouched(false); - break; - - case QEvent::HoverEnter: - case QEvent::HoverMove: - case QEvent::Wheel: - setTouched(false); - break; - - default: - break; - } - - return false; -} diff --git a/src/ui/NhekoEventObserver.h b/src/ui/NhekoEventObserver.h deleted file mode 100644 index 63739d4a..00000000 --- a/src/ui/NhekoEventObserver.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include <QQuickItem> - -class NhekoEventObserver : public QQuickItem -{ - Q_OBJECT - - Q_PROPERTY(bool wasTouched READ wasTouched NOTIFY wasTouchedChanged) - -public: - explicit NhekoEventObserver(QQuickItem *parent = 0); - - bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; - -private: - bool wasTouched() { return wasTouched_; } - - bool wasTouched_ = false; - -signals: - void wasTouchedChanged(); -}; diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index b7a7a637..91210c54 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -7,6 +7,7 @@ #include <QFontDatabase> #include <QObject> #include <QPalette> +#include <QQmlEngine> #include <QWindow> #include "AliasEditModel.h" @@ -19,6 +20,9 @@ class Nheko final : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(QPalette colors READ colors NOTIFY colorsChanged) Q_PROPERTY(QPalette inactiveColors READ inactiveColors NOTIFY colorsChanged) Q_PROPERTY(Theme theme READ theme NOTIFY colorsChanged) diff --git a/src/ui/RoomSummary.h b/src/ui/RoomSummary.h index c02ea5d5..8225f0ae 100644 --- a/src/ui/RoomSummary.h +++ b/src/ui/RoomSummary.h @@ -7,6 +7,7 @@ #include <optional> #include <QObject> +#include <QQmlEngine> #include <mtx/responses/public_rooms.hpp> @@ -25,6 +26,9 @@ class RoomSummary final : public QObject { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Please use joinRoom to create a room summary.") + Q_PROPERTY(QString reason READ reason WRITE setReason NOTIFY reasonChanged) Q_PROPERTY(QString roomid READ roomid NOTIFY loaded) diff --git a/src/ui/UIA.h b/src/ui/UIA.h index 7d23d88e..414cb804 100644 --- a/src/ui/UIA.h +++ b/src/ui/UIA.h @@ -5,6 +5,7 @@ #pragma once #include <QObject> +#include <QQmlEngine> #include <mtxclient/http/client.hpp> @@ -12,10 +13,31 @@ class UIA final : public QObject { Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(QString title READ title NOTIFY titleChanged) public: static UIA *instance(); + static UIA *create(QQmlEngine *qmlEngine, QJSEngine *) + { + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(instance()); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance()->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance(), QJSEngine::CppOwnership); + return instance(); + } UIA(QObject *parent = nullptr) : QObject(parent) diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index a880f320..d8e06aa1 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -6,6 +6,7 @@ #include <QAbstractListModel> #include <QObject> +#include <QQmlEngine> #include <QString> #include <QVector> #include <mtx/responses.hpp> @@ -16,6 +17,7 @@ namespace verification { Q_NAMESPACE +QML_NAMED_ELEMENT(VerificationStatus) enum Status { diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp index feb06835..5479ba31 100644 --- a/src/voip/CallManager.cpp +++ b/src/voip/CallManager.cpp @@ -54,6 +54,27 @@ std::vector<std::string> getTurnURIs(const mtx::responses::TurnServer &turnServer); } +CallManager * +CallManager::create(QQmlEngine *qmlEngine, QJSEngine *) +{ + // The instance has to exist before it is used. We cannot replace it. + auto instance = ChatPage::instance()->callManager(); + Q_ASSERT(instance); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == instance->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(instance, QJSEngine::CppOwnership); + return instance; +} + CallManager::CallManager(QObject *parent) : QObject(parent) , session_(WebRTCSession::instance()) diff --git a/src/voip/CallManager.h b/src/voip/CallManager.h index bbc7a903..e84b79c9 100644 --- a/src/voip/CallManager.h +++ b/src/voip/CallManager.h @@ -9,6 +9,7 @@ #include <QMediaPlayer> #include <QObject> +#include <QQmlEngine> #include <QString> #include <QStringList> #include <QTimer> @@ -29,6 +30,10 @@ class QUrl; class CallManager final : public QObject { Q_OBJECT + + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) Q_PROPERTY(bool isOnCallOnOtherDevice READ isOnCallOnOtherDevice NOTIFY newCallDeviceState) @@ -49,6 +54,8 @@ class CallManager final : public QObject public: CallManager(QObject *); + static CallManager *create(QQmlEngine *qmlEngine, QJSEngine *); + bool haveCallInvite() const { return haveCallInvite_; } bool isOnCall() const { return (session_.state() != webrtc::State::DISCONNECTED); } bool isOnCallOnOtherDevice() const { return (isOnCallOnOtherDevice_ != ""); } diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index c40b39a4..ff459bf9 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -48,26 +48,26 @@ using webrtc::State; WebRTCSession::WebRTCSession() : devices_(CallDevices::instance()) { - qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, - "im.nheko", - 1, - 0, - "CallType", - QStringLiteral("Can't instantiate enum")); - - qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, - "im.nheko", - 1, - 0, - "ScreenShareType", - QStringLiteral("Can't instantiate enum")); - - qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, - "im.nheko", - 1, - 0, - "WebRTCState", - QStringLiteral("Can't instantiate enum")); + // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, + // "im.nheko", + // 1, + // 0, + // "CallType", + // QStringLiteral("Can't instantiate enum")); + + // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, + // "im.nheko", + // 1, + // 0, + // "ScreenShareType", + // QStringLiteral("Can't instantiate enum")); + + // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject, + // "im.nheko", + // 1, + // 0, + // "WebRTCState", + // QStringLiteral("Can't instantiate enum")); connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); init(); diff --git a/src/voip/WebRTCSession.h b/src/voip/WebRTCSession.h index 82753372..3357bff7 100644 --- a/src/voip/WebRTCSession.h +++ b/src/voip/WebRTCSession.h @@ -8,6 +8,7 @@ #include <vector> #include <QObject> +#include <QQmlEngine> #include "mtx/events/voip.hpp" @@ -17,6 +18,7 @@ class QQuickItem; namespace webrtc { Q_NAMESPACE +QML_NAMED_ELEMENT(Voip) enum class CallType { |