commit
b706e272e5
@ -331,24 +331,13 @@ set(SRC_FILES
|
||||
src/timeline/RoomlistModel.cpp
|
||||
|
||||
# UI components
|
||||
src/ui/DropShadow.cpp
|
||||
src/ui/FlatButton.cpp
|
||||
src/ui/Label.cpp
|
||||
src/ui/LoadingIndicator.cpp
|
||||
src/ui/MxcAnimatedImage.cpp
|
||||
src/ui/MxcMediaProxy.cpp
|
||||
src/ui/NhekoCursorShape.cpp
|
||||
src/ui/NhekoDropArea.cpp
|
||||
src/ui/NhekoGlobalObject.cpp
|
||||
src/ui/OverlayModal.cpp
|
||||
src/ui/OverlayWidget.cpp
|
||||
src/ui/RaisedButton.cpp
|
||||
src/ui/Ripple.cpp
|
||||
src/ui/RippleOverlay.cpp
|
||||
src/ui/RoomSettings.cpp
|
||||
src/ui/SnackBar.cpp
|
||||
src/ui/TextField.cpp
|
||||
src/ui/TextLabel.cpp
|
||||
src/ui/Theme.cpp
|
||||
src/ui/ThemeManager.cpp
|
||||
src/ui/ToggleButton.cpp
|
||||
@ -395,7 +384,6 @@ set(SRC_FILES
|
||||
src/RoomDirectoryModel.cpp
|
||||
src/RoomsModel.cpp
|
||||
src/Utils.cpp
|
||||
src/WelcomePage.cpp
|
||||
src/main.cpp
|
||||
|
||||
third_party/blurhash/blurhash.cpp
|
||||
@ -414,7 +402,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||
FetchContent_Declare(
|
||||
MatrixClient
|
||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||
GIT_TAG 6a7eaa5006b1a18e132be7655e490d9819158dca
|
||||
GIT_TAG 9781553b0186f2db9036d2abbde83c28828eb2b9
|
||||
)
|
||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||
@ -538,23 +526,13 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/timeline/RoomlistModel.h
|
||||
|
||||
# UI components
|
||||
src/ui/FlatButton.h
|
||||
src/ui/Label.h
|
||||
src/ui/LoadingIndicator.h
|
||||
src/ui/MxcAnimatedImage.h
|
||||
src/ui/MxcMediaProxy.h
|
||||
src/ui/Menu.h
|
||||
src/ui/NhekoCursorShape.h
|
||||
src/ui/NhekoDropArea.h
|
||||
src/ui/NhekoGlobalObject.h
|
||||
src/ui/OverlayWidget.h
|
||||
src/ui/RaisedButton.h
|
||||
src/ui/Ripple.h
|
||||
src/ui/RippleOverlay.h
|
||||
src/ui/RoomSettings.h
|
||||
src/ui/SnackBar.h
|
||||
src/ui/TextField.h
|
||||
src/ui/TextLabel.h
|
||||
src/ui/Theme.h
|
||||
src/ui/ThemeManager.h
|
||||
src/ui/ToggleButton.h
|
||||
@ -596,7 +574,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/UsersModel.h
|
||||
src/RoomDirectoryModel.h
|
||||
src/RoomsModel.h
|
||||
src/WelcomePage.h
|
||||
src/ReadReceiptsModel.h
|
||||
)
|
||||
|
||||
|
@ -191,7 +191,7 @@ modules:
|
||||
buildsystem: cmake-ninja
|
||||
name: mtxclient
|
||||
sources:
|
||||
- commit: 6a7eaa5006b1a18e132be7655e490d9819158dca
|
||||
- commit: 9781553b0186f2db9036d2abbde83c28828eb2b9
|
||||
#tag: v0.6.1
|
||||
type: git
|
||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||
|
@ -319,7 +319,7 @@
|
||||
<message>
|
||||
<location line="+66"/>
|
||||
<source>Failed to kick %1 from %2: %3</source>
|
||||
<translation>Kontte %1 nicht aus %2 entfernen: %3</translation>
|
||||
<translation>Konnte %1 nicht aus %2 entfernen: %3</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -97,6 +97,7 @@ Rectangle {
|
||||
|
||||
implicitHeight: chatPage.height
|
||||
collapsed: parent.collapsed
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Binding {
|
||||
|
@ -8,29 +8,139 @@ import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import im.nheko 1.0
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
|
||||
property alias backgroundColor: backgroundRect.color
|
||||
ColumnLayout {
|
||||
id: c
|
||||
property color backgroundColor: Nheko.colors.base
|
||||
property alias color: labelC.color
|
||||
property alias textPadding: input.padding
|
||||
property alias text: input.text
|
||||
property alias label: labelC.text
|
||||
property alias placeholderText: input.placeholderText
|
||||
property alias font: input.font
|
||||
property alias echoMode: input.echoMode
|
||||
property alias selectByMouse: input.selectByMouse
|
||||
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.text
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 350
|
||||
onTriggered: editingFinished()
|
||||
}
|
||||
|
||||
onTextChanged: timer.restart()
|
||||
|
||||
signal textEdited
|
||||
signal accepted
|
||||
signal editingFinished
|
||||
|
||||
function forceActiveFocus() {
|
||||
input.forceActiveFocus();
|
||||
}
|
||||
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.visible: hover.hovered
|
||||
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: labelC.contentHeight
|
||||
Layout.margins: input.padding
|
||||
Layout.bottomMargin: Nheko.paddingSmall
|
||||
visible: labelC.text
|
||||
|
||||
z: 1
|
||||
|
||||
Label {
|
||||
id: labelC
|
||||
|
||||
y: contentHeight + input.padding + Nheko.paddingSmall
|
||||
enabled: false
|
||||
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.text
|
||||
font.pixelSize: input.font.pixelSize
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: input.font.pixelSize * 0.02
|
||||
width: parent.width
|
||||
|
||||
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
|
||||
|
||||
states: State {
|
||||
name: "focused"
|
||||
|
||||
PropertyChanges {
|
||||
target: labelC
|
||||
y: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: input
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
from: ""
|
||||
to: "focused"
|
||||
reversible: true
|
||||
|
||||
NumberAnimation {
|
||||
target: labelC
|
||||
properties: "y"
|
||||
duration: 210
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: input
|
||||
properties: "opacity"
|
||||
duration: 210
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
Layout.fillWidth: true
|
||||
|
||||
palette: Nheko.colors
|
||||
color: labelC.color
|
||||
opacity: labelC.text ? 0 : 1
|
||||
|
||||
onTextEdited: c.textEdited()
|
||||
onAccepted: c.accepted()
|
||||
onEditingFinished: c.editingFinished()
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
id: backgroundRect
|
||||
|
||||
color: labelC.text ? "transparent" : backgroundColor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: blueBar
|
||||
|
||||
anchors.top: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: Nheko.colors.highlight
|
||||
height: 1
|
||||
width: parent.width
|
||||
|
||||
Rectangle {
|
||||
id: blackBar
|
||||
|
||||
anchors.verticalCenter: blueBar.verticalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: parent.height + 1
|
||||
height: parent.height*2
|
||||
width: 0
|
||||
color: Nheko.colors.text
|
||||
|
||||
@ -50,11 +160,12 @@ TextField {
|
||||
to: "focused"
|
||||
reversible: true
|
||||
|
||||
|
||||
NumberAnimation {
|
||||
target: blackBar
|
||||
properties: "width"
|
||||
duration: 500
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: 310
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
@ -64,10 +175,8 @@ TextField {
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: backgroundRect
|
||||
|
||||
color: Nheko.colors.base
|
||||
HoverHandler {
|
||||
id: hover
|
||||
enabled: c.ToolTip.text
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.2
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
@ -15,7 +16,7 @@ Item {
|
||||
|
||||
Connections {
|
||||
function onFocusChanged() {
|
||||
if (TimelineManager.isWindowFocused) {
|
||||
if (MainWindow.active) {
|
||||
screenSaverTimer.stop();
|
||||
screenSaver.state = "Invisible";
|
||||
} else {
|
||||
@ -32,7 +33,7 @@ Item {
|
||||
id: screenSaverTimer
|
||||
|
||||
interval: screenTimeout * 1000
|
||||
running: true
|
||||
running: !MainWindow.active
|
||||
onTriggered: {
|
||||
screenSaver.state = "Visible";
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ Popup {
|
||||
id: quickSwitcher
|
||||
|
||||
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
||||
property int textMargin: Math.round(textHeight / 8)
|
||||
|
||||
background: null
|
||||
width: Math.round(parent.width / 2)
|
||||
@ -34,7 +33,6 @@ Popup {
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||
padding: textMargin
|
||||
color: Nheko.colors.text
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
@ -60,7 +58,7 @@ Popup {
|
||||
id: completerPopup
|
||||
|
||||
x: roomTextInput.x
|
||||
y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
|
||||
y: roomTextInput.y + quickSwitcher.textHeight
|
||||
visible: roomTextInput.length > 0
|
||||
width: parent.width
|
||||
completerName: "room"
|
||||
|
@ -385,7 +385,7 @@ Page {
|
||||
header: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
Pane {
|
||||
id: userInfoPanel
|
||||
|
||||
function openUserProfile() {
|
||||
@ -396,12 +396,15 @@ Page {
|
||||
userProfile.show();
|
||||
}
|
||||
|
||||
color: Nheko.colors.window
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
|
||||
//Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
|
||||
padding: Nheko.paddingMedium
|
||||
Layout.minimumHeight: 40
|
||||
|
||||
background: Rectangle {color: Nheko.colors.window}
|
||||
|
||||
InputDialog {
|
||||
id: statusDialog
|
||||
|
||||
@ -442,14 +445,12 @@ Page {
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
contentItem: RowLayout {
|
||||
id: userInfoGrid
|
||||
|
||||
property var profile: Nheko.currentUser
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
@ -614,19 +615,17 @@ Page {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: Nheko.colors.window
|
||||
Pane {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.preferredHeight: buttonRow.implicitHeight
|
||||
Layout.minimumHeight: 40
|
||||
|
||||
RowLayout {
|
||||
id: buttonRow
|
||||
horizontalPadding: Nheko.paddingMedium
|
||||
verticalPadding: 0
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
background: Rectangle {color: Nheko.colors.window}
|
||||
contentItem: RowLayout {
|
||||
id: buttonRow
|
||||
|
||||
ImageButton {
|
||||
Layout.fillWidth: true
|
||||
|
@ -9,6 +9,7 @@ import "./dialogs"
|
||||
import "./emoji"
|
||||
import "./pages"
|
||||
import "./voip"
|
||||
import "./ui"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
@ -17,10 +18,12 @@ import QtQuick.Window 2.15
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
Page {
|
||||
Pane {
|
||||
id: timelineRoot
|
||||
|
||||
palette: Nheko.colors
|
||||
background: null
|
||||
padding: 0
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
@ -153,11 +156,15 @@ Page {
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Quit
|
||||
onActivated: Qt.quit()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
onActivated: {
|
||||
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
|
||||
TimelineManager.focusTimeline();
|
||||
quickSwitch.open();
|
||||
}
|
||||
}
|
||||
@ -165,7 +172,6 @@ Page {
|
||||
Shortcut {
|
||||
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
||||
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
||||
context: Qt.ApplicationShortcut
|
||||
onActivated: Rooms.nextRoomWithActivity()
|
||||
}
|
||||
|
||||
@ -366,9 +372,51 @@ Page {
|
||||
id: mainWindow
|
||||
|
||||
anchors.fill: parent
|
||||
initialItem: ChatPage {
|
||||
//anchors.fill: parent
|
||||
initialItem: welcomePage
|
||||
}
|
||||
|
||||
Component {
|
||||
id: welcomePage
|
||||
|
||||
WelcomePage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chatPage
|
||||
|
||||
ChatPage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loginPage
|
||||
|
||||
LoginPage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: registerPage
|
||||
|
||||
RegisterPage {
|
||||
}
|
||||
}
|
||||
|
||||
Snackbar { id: snackbar }
|
||||
|
||||
Connections {
|
||||
function onSwitchToChatPage() {
|
||||
mainWindow.replace(null, chatPage);
|
||||
}
|
||||
function onSwitchToLoginPage(error) {
|
||||
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
|
||||
}
|
||||
function onShowNotification(msg) {
|
||||
snackbar.showNotification(msg);
|
||||
console.log("New snack: " + msg);
|
||||
}
|
||||
target: MainWindow
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import im.nheko 1.0
|
||||
|
||||
import "./delegates"
|
||||
|
||||
Rectangle {
|
||||
Pane {
|
||||
id: topBar
|
||||
|
||||
property bool showBackButton: false
|
||||
@ -28,7 +28,11 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||
z: 3
|
||||
color: Nheko.colors.window
|
||||
|
||||
padding: 0
|
||||
background: Rectangle {
|
||||
color: Nheko.colors.window
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onSingleTapped: {
|
||||
@ -65,248 +69,250 @@ Rectangle {
|
||||
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: topLayout
|
||||
contentItem: Item {
|
||||
GridLayout {
|
||||
id: topLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
columnSpacing: Nheko.paddingSmall
|
||||
rowSpacing: Nheko.paddingSmall
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
columnSpacing: Nheko.paddingSmall
|
||||
rowSpacing: Nheko.paddingSmall
|
||||
|
||||
ImageButton {
|
||||
id: backToRoomsButton
|
||||
ImageButton {
|
||||
id: backToRoomsButton
|
||||
|
||||
Layout.column: 0
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
visible: showBackButton
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
onClicked: Rooms.resetCurrentRoom()
|
||||
}
|
||||
|
||||
Avatar {
|
||||
Layout.column: 1
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: roomId
|
||||
userid: isDirect ? directChatOtherUserId : ""
|
||||
displayName: roomName
|
||||
enabled: false
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.row: 0
|
||||
color: Nheko.colors.text
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: roomName
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
id: roomTopicC
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.row: 1
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||
selectByMouse: false
|
||||
enabled: false
|
||||
clip: true
|
||||
text: roomTopic
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
Layout.column: 3
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio
|
||||
sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio
|
||||
visible: isEncrypted
|
||||
encrypted: isEncrypted
|
||||
trust: trustlevel
|
||||
ToolTip.text: {
|
||||
if (!encrypted)
|
||||
return qsTr("This room is not encrypted!");
|
||||
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return qsTr("This room contains only verified devices.");
|
||||
case Crypto.TOFU:
|
||||
return qsTr("This room contains verified devices and devices which have never changed their master key.");
|
||||
default:
|
||||
return qsTr("This room contains unverified devices!");
|
||||
}
|
||||
Layout.column: 0
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
visible: showBackButton
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
onClicked: Rooms.resetCurrentRoom()
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
Avatar {
|
||||
Layout.column: 1
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: roomId
|
||||
userid: isDirect ? directChatOtherUserId : ""
|
||||
displayName: roomName
|
||||
enabled: false
|
||||
}
|
||||
|
||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.row: 0
|
||||
color: Nheko.colors.text
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: roomName
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0
|
||||
Layout.column: 4
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||
onClicked: {
|
||||
var ps = Settings.hiddenPins;
|
||||
if (pinsShown) {
|
||||
ps.push(roomId);
|
||||
} else {
|
||||
const index = ps.indexOf(roomId);
|
||||
if (index > -1) {
|
||||
ps.splice(index, 1);
|
||||
MatrixText {
|
||||
id: roomTopicC
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.row: 1
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||
selectByMouse: false
|
||||
enabled: false
|
||||
clip: true
|
||||
text: roomTopic
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
Layout.column: 3
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
sourceSize.height: Layout.preferredHeight * Screen.devicePixelRatio
|
||||
sourceSize.width: Layout.preferredWidth * Screen.devicePixelRatio
|
||||
visible: isEncrypted
|
||||
encrypted: isEncrypted
|
||||
trust: trustlevel
|
||||
ToolTip.text: {
|
||||
if (!encrypted)
|
||||
return qsTr("This room is not encrypted!");
|
||||
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return qsTr("This room contains only verified devices.");
|
||||
case Crypto.TOFU:
|
||||
return qsTr("This room contains verified devices and devices which have never changed their master key.");
|
||||
default:
|
||||
return qsTr("This room contains unverified devices!");
|
||||
}
|
||||
}
|
||||
Settings.hiddenPins = ps;
|
||||
}
|
||||
|
||||
}
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
|
||||
ImageButton {
|
||||
id: roomOptionsButton
|
||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||
|
||||
visible: !!room
|
||||
Layout.column: 5
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: ":/icons/icons/ui/options.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Room options")
|
||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||
|
||||
Platform.Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
Platform.MenuItem {
|
||||
visible: room ? room.permissions.canInvite() : false
|
||||
text: qsTr("Invite users")
|
||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Members")
|
||||
onTriggered: TimelineManager.openRoomMembers(room)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Settings")
|
||||
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||
visible: !!room && room.pinnedMessages.length > 0
|
||||
Layout.column: 4
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||
onClicked: {
|
||||
var ps = Settings.hiddenPins;
|
||||
if (pinsShown) {
|
||||
ps.push(roomId);
|
||||
} else {
|
||||
const index = ps.indexOf(roomId);
|
||||
if (index > -1) {
|
||||
ps.splice(index, 1);
|
||||
}
|
||||
}
|
||||
Settings.hiddenPins = ps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
ImageButton {
|
||||
id: roomOptionsButton
|
||||
|
||||
ScrollView {
|
||||
id: pinnedMessages
|
||||
visible: !!room
|
||||
Layout.column: 5
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: ":/icons/icons/ui/options.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Room options")
|
||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||
|
||||
Layout.row: 2
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 3
|
||||
Platform.Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
||||
clip: true
|
||||
|
||||
palette: Nheko.colors
|
||||
ScrollBar.horizontal.visible: false
|
||||
|
||||
ListView {
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
model: room ? room.pinnedMessages : undefined
|
||||
delegate: RowLayout {
|
||||
required property string modelData
|
||||
|
||||
width: ListView.view.width
|
||||
height: implicitHeight
|
||||
|
||||
Reply {
|
||||
property var e: room ? room.getDump(modelData, "") : {}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: height
|
||||
|
||||
userColor: TimelineManager.userColor(e.userId, Nheko.colors.window)
|
||||
blurhash: e.blurhash ?? ""
|
||||
body: e.body ?? ""
|
||||
formattedBody: e.formattedBody ?? ""
|
||||
eventId: e.eventId ?? ""
|
||||
filename: e.filename ?? ""
|
||||
filesize: e.filesize ?? ""
|
||||
proportionalHeight: e.proportionalHeight ?? 1
|
||||
type: e.type ?? MtxEvent.UnknownMessage
|
||||
typeString: e.typeString ?? ""
|
||||
url: e.url ?? ""
|
||||
originalWidth: e.originalWidth ?? 0
|
||||
isOnlyEmoji: e.isOnlyEmoji ?? false
|
||||
userId: e.userId ?? ""
|
||||
userName: e.userName ?? ""
|
||||
encryptionError: e.encryptionError ?? ""
|
||||
Platform.MenuItem {
|
||||
visible: room ? room.permissions.canInvite() : false
|
||||
text: qsTr("Invite users")
|
||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: deletePinButton
|
||||
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
||||
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Unpin")
|
||||
|
||||
onClicked: room.unpin(modelData)
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Members")
|
||||
onTriggered: TimelineManager.openRoomMembers(room)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Settings")
|
||||
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
enabled: !Settings.mobileMode
|
||||
ScrollView {
|
||||
id: pinnedMessages
|
||||
|
||||
Layout.row: 2
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 3
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
||||
clip: true
|
||||
|
||||
palette: Nheko.colors
|
||||
ScrollBar.horizontal.visible: false
|
||||
|
||||
ListView {
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
model: room ? room.pinnedMessages : undefined
|
||||
delegate: RowLayout {
|
||||
required property string modelData
|
||||
|
||||
width: ListView.view.width
|
||||
height: implicitHeight
|
||||
|
||||
Reply {
|
||||
property var e: room ? room.getDump(modelData, "") : {}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: height
|
||||
|
||||
userColor: TimelineManager.userColor(e.userId, Nheko.colors.window)
|
||||
blurhash: e.blurhash ?? ""
|
||||
body: e.body ?? ""
|
||||
formattedBody: e.formattedBody ?? ""
|
||||
eventId: e.eventId ?? ""
|
||||
filename: e.filename ?? ""
|
||||
filesize: e.filesize ?? ""
|
||||
proportionalHeight: e.proportionalHeight ?? 1
|
||||
type: e.type ?? MtxEvent.UnknownMessage
|
||||
typeString: e.typeString ?? ""
|
||||
url: e.url ?? ""
|
||||
originalWidth: e.originalWidth ?? 0
|
||||
isOnlyEmoji: e.isOnlyEmoji ?? false
|
||||
userId: e.userId ?? ""
|
||||
userName: e.userName ?? ""
|
||||
encryptionError: e.encryptionError ?? ""
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: deletePinButton
|
||||
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
||||
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Unpin")
|
||||
|
||||
onClicked: room.unpin(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
enabled: !Settings.mobileMode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import im.nheko 1.0
|
||||
Button {
|
||||
id: control
|
||||
|
||||
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.5)
|
||||
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
|
||||
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
|
||||
hoverEnabled: true
|
||||
|
||||
@ -42,7 +42,7 @@ Button {
|
||||
background: Rectangle {
|
||||
//height: control.contentItem.implicitHeight * 2
|
||||
//width: control.contentItem.implicitWidth * 2
|
||||
radius: height / 6
|
||||
radius: height / 8
|
||||
color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ ApplicationWindow {
|
||||
minimumHeight: stack.implicitHeight
|
||||
width: stack.implicitWidth
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(dialog)
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
|
@ -12,8 +12,6 @@ import QtQuick.Layouts 1.12
|
||||
import im.nheko 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
//Component.onCompleted: Nheko.reparent(win)
|
||||
|
||||
id: win
|
||||
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
|
||||
@ -175,39 +173,37 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
visible: imagePack.roomid
|
||||
text: qsTr("State key")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: statekeyField
|
||||
|
||||
visible: imagePack.roomid
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("State key")
|
||||
text: imagePack.statekey
|
||||
onTextEdited: imagePack.statekey = text
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Packname")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("Packname")
|
||||
text: imagePack.packname
|
||||
onTextEdited: imagePack.packname = text
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Attribution")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("Attribution")
|
||||
text: imagePack.attribution
|
||||
onTextEdited: imagePack.attribution = text
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: statekeyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
text: qsTr("Use as Emoji")
|
||||
}
|
||||
|
||||
@ -218,6 +214,9 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: statekeyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
text: qsTr("Use as Sticker")
|
||||
}
|
||||
|
||||
@ -253,27 +252,28 @@ ApplicationWindow {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Shortcode")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("Shortcode")
|
||||
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
|
||||
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Body")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: bodyField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("Body")
|
||||
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
|
||||
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
text: qsTr("Use as Emoji")
|
||||
}
|
||||
|
||||
@ -284,6 +284,9 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
text: qsTr("Use as Sticker")
|
||||
}
|
||||
|
||||
@ -294,6 +297,9 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
text: qsTr("Remove from pack")
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ ApplicationWindow {
|
||||
color: Nheko.colors.base
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(win)
|
||||
|
||||
Component {
|
||||
id: packEditor
|
||||
|
@ -18,7 +18,6 @@ ApplicationWindow {
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog
|
||||
Component.onCompleted: Nheko.reparent(inputDialog)
|
||||
width: 350
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
|
||||
|
@ -37,7 +37,6 @@ ApplicationWindow {
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(inviteDialogRoot)
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+Enter"
|
||||
|
@ -17,7 +17,6 @@ ApplicationWindow {
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
Component.onCompleted: Nheko.reparent(joinRoomRoot)
|
||||
width: 350
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
|
||||
|
@ -19,7 +19,6 @@ ApplicationWindow {
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog
|
||||
Component.onCompleted: Nheko.reparent(inputDialog)
|
||||
width: 350
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
|
||||
|
@ -17,7 +17,6 @@ ApplicationWindow {
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(rawMessageRoot)
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
@ -22,7 +22,6 @@ ApplicationWindow {
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(readReceiptsRoot)
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
@ -22,7 +22,6 @@ ApplicationWindow {
|
||||
color: Nheko.colors.window
|
||||
modality: Qt.WindowModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(roomDirectoryWindow)
|
||||
title: qsTr("Explore Public Rooms")
|
||||
|
||||
Shortcut {
|
||||
@ -189,7 +188,6 @@ ApplicationWindow {
|
||||
Layout.fillWidth: true
|
||||
selectByMouse: true
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
padding: Nheko.paddingMedium
|
||||
color: Nheko.colors.text
|
||||
placeholderText: qsTr("Search for public rooms")
|
||||
onTextChanged: searchTimer.restart()
|
||||
@ -200,7 +198,6 @@ ApplicationWindow {
|
||||
|
||||
Layout.minimumWidth: 0.3 * header.width
|
||||
Layout.maximumWidth: 0.3 * header.width
|
||||
padding: Nheko.paddingMedium
|
||||
color: Nheko.colors.text
|
||||
placeholderText: qsTr("Choose custom homeserver")
|
||||
onTextChanged: publicRooms.setMatrixServer(text)
|
||||
|
@ -24,7 +24,6 @@ ApplicationWindow {
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(roomMembersRoot)
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
@ -23,7 +23,6 @@ ApplicationWindow {
|
||||
color: Nheko.colors.window
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(roomSettingsDialog)
|
||||
title: qsTr("Room Settings")
|
||||
|
||||
Shortcut {
|
||||
|
@ -13,9 +13,6 @@ import QtQuick.Window 2.13
|
||||
import im.nheko 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
// this does not work in ApplicationWindow, just in Window
|
||||
//transientParent: Nheko.mainwindow()
|
||||
|
||||
id: userProfileDialog
|
||||
|
||||
property var profile
|
||||
@ -29,7 +26,6 @@ ApplicationWindow {
|
||||
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
Component.onCompleted: Nheko.reparent(userProfileDialog)
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
182
resources/qml/pages/LoginPage.qml
Normal file
182
resources/qml/pages/LoginPage.qml
Normal file
@ -0,0 +1,182 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.15
|
||||
import im.nheko 1.0
|
||||
import "../components/"
|
||||
import "../ui/"
|
||||
import "../"
|
||||
|
||||
Item {
|
||||
id: loginPage
|
||||
property int maxExpansion: 400
|
||||
|
||||
property string error: login.error
|
||||
|
||||
Login {
|
||||
id: login
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scroll
|
||||
|
||||
clip: false
|
||||
palette: Nheko.colors
|
||||
ScrollBar.horizontal.visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: Math.min(loginPage.height, col.implicitHeight)
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/login.png"
|
||||
height: 128
|
||||
width: 128
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
id: matrixIdLabel
|
||||
label: qsTr("Matrix ID")
|
||||
placeholderText: qsTr("e.g @joe:matrix.org")
|
||||
onEditingFinished: login.mxid = text
|
||||
|
||||
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
|
||||
Keys.forwardTo: [pwBtn, ssoBtn]
|
||||
}
|
||||
|
||||
|
||||
Spinner {
|
||||
height: matrixIdLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: login.lookingUpHs
|
||||
foreground: Nheko.colors.mid
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: login.mxidError
|
||||
visible: text
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: passwordLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Password")
|
||||
echoMode: TextInput.Password
|
||||
ToolTip.text: qsTr("Your password.")
|
||||
visible: login.passwordSupported
|
||||
Keys.forwardTo: [pwBtn, ssoBtn]
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: deviceNameLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Device name")
|
||||
placeholderText: login.initialDeviceName()
|
||||
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
|
||||
Keys.forwardTo: [pwBtn, ssoBtn]
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: hsLabel
|
||||
enabled: visible
|
||||
visible: login.homeserverNeeded
|
||||
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Homeserver address")
|
||||
placeholderText: qsTr("server.my:8787")
|
||||
text: login.homeserver
|
||||
onEditingFinished: login.homeserver = text
|
||||
ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
|
||||
Keys.forwardTo: [pwBtn, ssoBtn]
|
||||
}
|
||||
|
||||
Item {
|
||||
height: Nheko.avatarSize
|
||||
Layout.fillWidth: true
|
||||
|
||||
Spinner {
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: running
|
||||
running: login.loggingIn
|
||||
foreground: Nheko.colors.mid
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: loginPage.error
|
||||
visible: text
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
id: pwBtn
|
||||
visible: login.passwordSupported
|
||||
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("LOGIN")
|
||||
function pwLogin() {
|
||||
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
|
||||
}
|
||||
onClicked: pwBtn.pwLogin()
|
||||
Keys.onEnterPressed: pwBtn.pwLogin()
|
||||
Keys.onReturnPressed: pwBtn.pwLogin()
|
||||
Keys.enabled: pwBtn.enabled && login.passwordSupported
|
||||
}
|
||||
FlatButton {
|
||||
id: ssoBtn
|
||||
visible: login.ssoSupported
|
||||
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("SSO LOGIN")
|
||||
function ssoLogin() {
|
||||
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
|
||||
}
|
||||
onClicked: ssoBtn.ssoLogin()
|
||||
Keys.onEnterPressed: ssoBtn.ssoLogin()
|
||||
Keys.onReturnPressed: ssoBtn.ssoLogin()
|
||||
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
}
|
215
resources/qml/pages/RegisterPage.qml
Normal file
215
resources/qml/pages/RegisterPage.qml
Normal file
@ -0,0 +1,215 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.15
|
||||
import im.nheko 1.0
|
||||
import "../components/"
|
||||
import "../ui/"
|
||||
import "../"
|
||||
|
||||
Item {
|
||||
id: registrationPage
|
||||
property int maxExpansion: 400
|
||||
|
||||
property string error: regis.error
|
||||
|
||||
Registration {
|
||||
id: regis
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scroll
|
||||
|
||||
clip: false
|
||||
palette: Nheko.colors
|
||||
ScrollBar.horizontal.visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: Math.min(registrationPage.height, col.implicitHeight)
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/login.png"
|
||||
height: 128
|
||||
width: 128
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
id: hsLabel
|
||||
label: qsTr("Homeserver")
|
||||
placeholderText: qsTr("your.server")
|
||||
onEditingFinished: regis.setServer(text)
|
||||
|
||||
ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.")
|
||||
}
|
||||
|
||||
|
||||
Spinner {
|
||||
height: hsLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: regis.lookingUpHs
|
||||
foreground: Nheko.colors.mid
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: regis.hsError
|
||||
visible: text
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
visible: regis.supported
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
id: usernameLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Username")
|
||||
ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.")
|
||||
onEditingFinished: regis.checkUsername(text)
|
||||
}
|
||||
Spinner {
|
||||
height: usernameLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: regis.lookingUpUsername
|
||||
foreground: Nheko.colors.mid
|
||||
}
|
||||
|
||||
Image {
|
||||
width: usernameLabel.height/2
|
||||
height: width
|
||||
Layout.preferredHeight: usernameLabel.height/2
|
||||
Layout.preferredWidth: usernameLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error)
|
||||
visible: regis.usernameAvailable || regis.usernameUnavailable
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
sourceSize.height: height * Screen.devicePixelRatio
|
||||
sourceSize.width: width * Screen.devicePixelRatio
|
||||
HoverHandler {
|
||||
id: ma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: regis.usernameError
|
||||
visible: text
|
||||
}
|
||||
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: passwordLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Password")
|
||||
echoMode: TextInput.Password
|
||||
ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: passwordConfirmationLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Password confirmation")
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
visible: regis.supported
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: deviceNameLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Device name")
|
||||
placeholderText: regis.initialDeviceName()
|
||||
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
|
||||
}
|
||||
|
||||
Item {
|
||||
height: Nheko.avatarSize
|
||||
Layout.fillWidth: true
|
||||
|
||||
Spinner {
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: running
|
||||
running: regis.registering
|
||||
foreground: Nheko.colors.mid
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: registrationPage.error
|
||||
visible: text
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
id: regisBtn
|
||||
visible: regis.supported
|
||||
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("REGISTER")
|
||||
function register() {
|
||||
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text)
|
||||
}
|
||||
onClicked: regisBtn.register()
|
||||
Keys.onEnterPressed: regisBtn.register()
|
||||
Keys.onReturnPressed: regisBtn.register()
|
||||
Keys.enabled: regisBtn.enabled && regis.supported
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
}
|
||||
|
73
resources/qml/pages/WelcomePage.qml
Normal file
73
resources/qml/pages/WelcomePage.qml
Normal file
@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.15
|
||||
import im.nheko 1.0
|
||||
import "../components/"
|
||||
|
||||
ColumnLayout {
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/splash.png"
|
||||
height: 256
|
||||
width: 256
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.bottomMargin: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
|
||||
color: Nheko.colors.text
|
||||
font.pointSize: fontMetrics.font.pointSize*2
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Enjoy your stay!")
|
||||
color: Nheko.colors.text
|
||||
font.pointSize: fontMetrics.font.pointSize*1.5
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
FlatButton {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("REGISTER")
|
||||
onClicked: {
|
||||
mainWindow.push(registerPage);
|
||||
}
|
||||
}
|
||||
FlatButton {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("LOGIN")
|
||||
onClicked: {
|
||||
mainWindow.push(loginPage);
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
98
resources/qml/ui/Snackbar.qml
Normal file
98
resources/qml/ui/Snackbar.qml
Normal file
@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import im.nheko 1.0
|
||||
|
||||
Popup {
|
||||
id: snackbar
|
||||
|
||||
property var messages: []
|
||||
property string currentMessage: ""
|
||||
|
||||
function showNotification(msg) {
|
||||
messages.push(msg);
|
||||
currentMessage = messages[0];
|
||||
if (!visible) {
|
||||
open();
|
||||
dismissTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
interval: 10000
|
||||
onTriggered: snackbar.close()
|
||||
}
|
||||
|
||||
onAboutToHide: {
|
||||
messages.shift();
|
||||
}
|
||||
onClosed: {
|
||||
if (messages.length > 0) {
|
||||
currentMessage = messages[0];
|
||||
open();
|
||||
dismissTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
parent: Overlay.overlay
|
||||
opacity: 0
|
||||
y: -100
|
||||
x: (parent.width - width)/2
|
||||
padding: Nheko.paddingLarge
|
||||
|
||||
contentItem: Label {
|
||||
color: Nheko.colors.light
|
||||
width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400)
|
||||
text: snackbar.currentMessage
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: Nheko.paddingLarge
|
||||
color: Nheko.colors.dark
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
target: snackbar
|
||||
property: "opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
NumberAnimation {
|
||||
target: snackbar
|
||||
properties: "y"
|
||||
from: -100
|
||||
to: 100
|
||||
duration: 1000
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
target: snackbar
|
||||
property: "opacity"
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
duration: 300
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
NumberAnimation {
|
||||
target: snackbar
|
||||
properties: "y"
|
||||
to: -100
|
||||
from: 100
|
||||
duration: 300
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,9 @@
|
||||
<file>qml/TypingIndicator.qml</file>
|
||||
<file>qml/NotificationWarning.qml</file>
|
||||
<file>qml/pages/UserSettingsPage.qml</file>
|
||||
<file>qml/pages/WelcomePage.qml</file>
|
||||
<file>qml/pages/LoginPage.qml</file>
|
||||
<file>qml/pages/RegisterPage.qml</file>
|
||||
<file>qml/components/AdaptiveLayout.qml</file>
|
||||
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
||||
<file>qml/components/AvatarListTile.qml</file>
|
||||
@ -154,6 +157,7 @@
|
||||
<file>qml/ui/NhekoSlider.qml</file>
|
||||
<file>qml/ui/Ripple.qml</file>
|
||||
<file>qml/ui/Spinner.qml</file>
|
||||
<file>qml/ui/Snackbar.qml</file>
|
||||
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
||||
<file>qml/ui/media/MediaControls.qml</file>
|
||||
<file>qml/voip/ActiveCallBar.qml</file>
|
||||
|
@ -325,7 +325,7 @@ static void
|
||||
fatalSecretError()
|
||||
{
|
||||
QMessageBox::critical(
|
||||
ChatPage::instance(),
|
||||
nullptr,
|
||||
QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
|
||||
QCoreApplication::translate(
|
||||
"SecretStorage",
|
||||
@ -391,6 +391,7 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
|
||||
&QKeychain::ReadPasswordJob::finished,
|
||||
this,
|
||||
[this, name, toLoad, job](QKeychain::Job *) mutable {
|
||||
nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first);
|
||||
const QString secret = job->textData();
|
||||
if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
|
||||
nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
|
||||
@ -413,6 +414,7 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
|
||||
// You can't start a job from the finish signal of a job.
|
||||
QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); });
|
||||
});
|
||||
nhlog::db()->debug("Reading '{}'", name_);
|
||||
job->start();
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include "Utils.h"
|
||||
#include "encryption/DeviceVerificationFlow.h"
|
||||
#include "encryption/Olm.h"
|
||||
#include "ui/OverlayModal.h"
|
||||
#include "ui/Theme.h"
|
||||
#include "ui/UserProfile.h"
|
||||
#include "voip/CallManager.h"
|
||||
@ -44,8 +43,8 @@ Q_DECLARE_METATYPE(mtx::presence::PresenceState)
|
||||
Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
|
||||
Q_DECLARE_METATYPE(SecretsToDecrypt)
|
||||
|
||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
|
||||
: QObject(parent)
|
||||
, isConnected_(true)
|
||||
, userSettings_{userSettings}
|
||||
, notificationsManager(this)
|
||||
@ -61,14 +60,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
|
||||
qRegisterMetaType<SecretsToDecrypt>();
|
||||
|
||||
topLayout_ = new QHBoxLayout(this);
|
||||
topLayout_->setSpacing(0);
|
||||
topLayout_->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
view_manager_ = new TimelineViewManager(callManager_, this);
|
||||
|
||||
topLayout_->addWidget(view_manager_->getWidget());
|
||||
|
||||
connect(this,
|
||||
&ChatPage::downloadedSecrets,
|
||||
this,
|
||||
@ -154,7 +147,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
[this](const QString &roomid, const QString &eventid) {
|
||||
Q_UNUSED(eventid)
|
||||
view_manager_->rooms()->setCurrentRoom(roomid);
|
||||
activateWindow();
|
||||
MainWindow::instance()->requestActivate();
|
||||
});
|
||||
connect(¬ificationsManager,
|
||||
&NotificationsManager::sendNotificationReply,
|
||||
@ -162,17 +155,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
[this](const QString &roomid, const QString &eventid, const QString &body) {
|
||||
view_manager_->rooms()->setCurrentRoom(roomid);
|
||||
view_manager_->queueReply(roomid, eventid, body);
|
||||
activateWindow();
|
||||
MainWindow::instance()->requestActivate();
|
||||
});
|
||||
|
||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
|
||||
// ensure the qml context is shutdown before we destroy all other singletons
|
||||
// Otherwise Qml will try to access the room list or settings, after they have been
|
||||
// destroyed
|
||||
topLayout_->removeWidget(view_manager_->getWidget());
|
||||
delete view_manager_->getWidget();
|
||||
});
|
||||
|
||||
connect(
|
||||
this,
|
||||
&ChatPage::initializeViews,
|
||||
@ -183,8 +168,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
&ChatPage::initializeEmptyViews,
|
||||
view_manager_,
|
||||
&TimelineViewManager::initializeRoomlist);
|
||||
connect(
|
||||
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
|
||||
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
|
||||
view_manager_->sync(sync);
|
||||
|
||||
@ -201,7 +184,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
// TODO: Replace this once we have proper pushrules support. This is a horrible hack
|
||||
if (prevNotificationCount < notificationCount) {
|
||||
if (userSettings_->hasAlertOnNotification())
|
||||
QApplication::alert(this);
|
||||
MainWindow::instance()->alert(0);
|
||||
}
|
||||
prevNotificationCount = notificationCount;
|
||||
|
||||
@ -331,7 +314,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
||||
} else if (cacheVersion == cache::CacheVersion::Older) {
|
||||
if (!cache::runMigrations()) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
nullptr,
|
||||
tr("Cache migration failed!"),
|
||||
tr("Migrating the cache to the current version failed. "
|
||||
"This can have different reasons. Please open an "
|
||||
@ -344,7 +327,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
||||
return;
|
||||
} else if (cacheVersion == cache::CacheVersion::Newer) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
nullptr,
|
||||
tr("Incompatible cache version"),
|
||||
tr("The cache on your disk is newer than this version of Nheko "
|
||||
"supports. Please update Nheko or clear your cache."));
|
||||
@ -690,7 +673,7 @@ ChatPage::joinRoomVia(const std::string &room_id,
|
||||
if (promptForConfirmation &&
|
||||
QMessageBox::Yes !=
|
||||
QMessageBox::question(
|
||||
this,
|
||||
nullptr,
|
||||
tr("Confirm join"),
|
||||
tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
|
||||
return;
|
||||
@ -776,7 +759,7 @@ ChatPage::inviteUser(QString userid, QString reason)
|
||||
{
|
||||
auto room = currentRoom();
|
||||
|
||||
if (QMessageBox::question(this,
|
||||
if (QMessageBox::question(nullptr,
|
||||
tr("Confirm invite"),
|
||||
tr("Do you really want to invite %1 (%2)?")
|
||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||
@ -787,6 +770,8 @@ ChatPage::inviteUser(QString userid, QString reason)
|
||||
userid.toStdString(),
|
||||
[this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->error(
|
||||
"Failed to invite {} to {}: {}", userid.toStdString(), room.toStdString(), *err);
|
||||
emit showNotification(
|
||||
tr("Failed to invite %1 to %2: %3")
|
||||
.arg(userid, room, QString::fromStdString(err->matrix_error.error)));
|
||||
@ -800,7 +785,7 @@ ChatPage::kickUser(QString userid, QString reason)
|
||||
{
|
||||
auto room = currentRoom();
|
||||
|
||||
if (QMessageBox::question(this,
|
||||
if (QMessageBox::question(nullptr,
|
||||
tr("Confirm kick"),
|
||||
tr("Do you really want to kick %1 (%2)?")
|
||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||
@ -825,7 +810,7 @@ ChatPage::banUser(QString userid, QString reason)
|
||||
auto room = currentRoom();
|
||||
|
||||
if (QMessageBox::question(
|
||||
this,
|
||||
nullptr,
|
||||
tr("Confirm ban"),
|
||||
tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) !=
|
||||
QMessageBox::Yes)
|
||||
@ -849,7 +834,7 @@ ChatPage::unbanUser(QString userid, QString reason)
|
||||
{
|
||||
auto room = currentRoom();
|
||||
|
||||
if (QMessageBox::question(this,
|
||||
if (QMessageBox::question(nullptr,
|
||||
tr("Confirm unban"),
|
||||
tr("Do you really want to unban %1 (%2)?")
|
||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||
@ -1064,8 +1049,6 @@ ChatPage::initiateLogout()
|
||||
|
||||
emit loggedOut();
|
||||
});
|
||||
|
||||
emit showOverlayProgressBar();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@ -1083,7 +1066,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
|
||||
const SecretsToDecrypt &secrets)
|
||||
{
|
||||
QString text = QInputDialog::getText(
|
||||
ChatPage::instance(),
|
||||
nullptr,
|
||||
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
|
||||
keyDesc.name.empty()
|
||||
? QCoreApplication::translate(
|
||||
@ -1115,7 +1098,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
|
||||
|
||||
if (!decryptionKey) {
|
||||
QMessageBox::information(
|
||||
ChatPage::instance(),
|
||||
nullptr,
|
||||
QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
|
||||
QCoreApplication::translate("CrossSigningSecrets",
|
||||
"Failed to decrypt secrets with the "
|
||||
@ -1209,7 +1192,7 @@ ChatPage::startChat(QString userid)
|
||||
|
||||
if (QMessageBox::Yes !=
|
||||
QMessageBox::question(
|
||||
this,
|
||||
nullptr,
|
||||
tr("Confirm invite"),
|
||||
tr("Do you really want to start a private chat with %1?").arg(userid)))
|
||||
return;
|
||||
@ -1395,7 +1378,7 @@ ChatPage::handleMatrixUri(const QUrl &uri)
|
||||
bool
|
||||
ChatPage::isRoomActive(const QString &room_id)
|
||||
{
|
||||
return isActiveWindow() && currentRoom() == room_id;
|
||||
return MainWindow::instance()->isActive() && currentRoom() == room_id;
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <variant>
|
||||
|
||||
#include <mtx/common.hpp>
|
||||
@ -18,17 +17,15 @@
|
||||
#include <mtx/events/presence.hpp>
|
||||
#include <mtx/secret_storage.hpp>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMap>
|
||||
#include <QPoint>
|
||||
#include <QSharedPointer>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "CacheCryptoStructs.h"
|
||||
#include "CacheStructs.h"
|
||||
#include "notifications/Manager.h"
|
||||
|
||||
class OverlayModal;
|
||||
class TimelineViewManager;
|
||||
class UserSettings;
|
||||
class NotificationsManager;
|
||||
@ -51,12 +48,12 @@ struct Rooms;
|
||||
|
||||
using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
|
||||
|
||||
class ChatPage : public QWidget
|
||||
class ChatPage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
|
||||
ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent = nullptr);
|
||||
|
||||
// Initialize all the components of the UI.
|
||||
void bootstrap(QString userid, QString homeserver, QString token);
|
||||
@ -112,7 +109,6 @@ signals:
|
||||
void showNotification(const QString &msg);
|
||||
void showLoginPage(const QString &msg);
|
||||
void showUserSettingsPage();
|
||||
void showOverlayProgressBar();
|
||||
|
||||
void ownProfileOk();
|
||||
void setUserDisplayName(const QString &name);
|
||||
@ -143,7 +139,6 @@ signals:
|
||||
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
||||
void themeChanged();
|
||||
void decryptSidebarChanged();
|
||||
void chatFocusChanged(const bool focused);
|
||||
|
||||
//! Signals for device verificaiton
|
||||
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
|
||||
@ -201,8 +196,6 @@ private:
|
||||
template<typename T>
|
||||
void connectCallMessage();
|
||||
|
||||
QHBoxLayout *topLayout_;
|
||||
|
||||
TimelineViewManager *view_manager_;
|
||||
|
||||
QTimer connectivityTimer_;
|
||||
|
@ -5,11 +5,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QFontMetrics>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QtMath>
|
||||
|
||||
#include <mtx/identifiers.hpp>
|
||||
#include <mtx/requests.hpp>
|
||||
@ -18,247 +13,94 @@
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
#include "LoginPage.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "SSOHandler.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "ui/FlatButton.h"
|
||||
#include "ui/LoadingIndicator.h"
|
||||
#include "ui/OverlayModal.h"
|
||||
#include "ui/RaisedButton.h"
|
||||
#include "ui/TextField.h"
|
||||
|
||||
Q_DECLARE_METATYPE(LoginPage::LoginMethod)
|
||||
|
||||
using namespace mtx::identifiers;
|
||||
|
||||
LoginPage::LoginPage(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
LoginPage::LoginPage(QObject *parent)
|
||||
: QObject(parent)
|
||||
, inferredServerAddress_()
|
||||
{
|
||||
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
|
||||
|
||||
top_layout_ = new QVBoxLayout();
|
||||
|
||||
top_bar_layout_ = new QHBoxLayout();
|
||||
top_bar_layout_->setSpacing(0);
|
||||
top_bar_layout_->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
|
||||
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
top_bar_layout_->addStretch(1);
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
|
||||
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
|
||||
QIcon logo;
|
||||
logo.addFile(QStringLiteral(":/logos/login.png"));
|
||||
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setContentsMargins(0, 0, 0, 20);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 200));
|
||||
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
|
||||
matrixid_input_ = new TextField(this);
|
||||
matrixid_input_->setLabel(tr("Matrix ID"));
|
||||
matrixid_input_->setRegexp(QRegularExpression(QStringLiteral("@.+?:.{3,}")));
|
||||
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
|
||||
matrixid_input_->setToolTip(
|
||||
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
|
||||
"id you need to include your server name after a :.\nYou can also put your homeserver "
|
||||
"address there, if your server doesn't support .well-known lookup.\nExample: "
|
||||
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
|
||||
"field to enter the server manually."));
|
||||
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(40);
|
||||
spinner_->setFixedWidth(40);
|
||||
spinner_->hide();
|
||||
|
||||
errorIcon_ = new QLabel(this);
|
||||
errorIcon_->setPixmap(QPixmap(QStringLiteral(":/icons/icons/error.png")));
|
||||
errorIcon_->hide();
|
||||
|
||||
matrixidLayout_ = new QHBoxLayout();
|
||||
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
|
||||
|
||||
QFont font;
|
||||
|
||||
error_matrixid_label_ = new QLabel(this);
|
||||
error_matrixid_label_->setFont(font);
|
||||
error_matrixid_label_->setWordWrap(true);
|
||||
|
||||
password_input_ = new TextField(this);
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Your password."));
|
||||
|
||||
deviceName_ = new TextField(this);
|
||||
deviceName_->setLabel(tr("Device name"));
|
||||
deviceName_->setToolTip(
|
||||
tr("A name for this device, which will be shown to others, when verifying your devices. "
|
||||
"If none is provided a default is used."));
|
||||
|
||||
serverInput_ = new TextField(this);
|
||||
serverInput_->setLabel(tr("Homeserver address"));
|
||||
serverInput_->setPlaceholderText(tr("server.my:8787"));
|
||||
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
|
||||
"client API.\nExample: https://server.my:8787"));
|
||||
serverInput_->hide();
|
||||
|
||||
serverLayout_ = new QHBoxLayout();
|
||||
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
|
||||
|
||||
form_layout_->addLayout(matrixidLayout_);
|
||||
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_);
|
||||
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
|
||||
form_layout_->addLayout(serverLayout_);
|
||||
|
||||
error_matrixid_label_->hide();
|
||||
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(20);
|
||||
button_layout_->setContentsMargins(0, 0, 0, 30);
|
||||
|
||||
login_button_ = new RaisedButton(tr("LOGIN"), this);
|
||||
login_button_->setMinimumSize(150, 65);
|
||||
login_button_->setFontSize(20);
|
||||
login_button_->setCornerRadius(3);
|
||||
|
||||
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
|
||||
sso_login_button_->setMinimumSize(150, 65);
|
||||
sso_login_button_->setFontSize(20);
|
||||
sso_login_button_->setCornerRadius(3);
|
||||
sso_login_button_->setVisible(false);
|
||||
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(login_button_);
|
||||
button_layout_->addWidget(sso_login_button_);
|
||||
button_layout_->addStretch(1);
|
||||
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setFont(font);
|
||||
error_label_->setWordWrap(true);
|
||||
|
||||
top_layout_->addLayout(top_bar_layout_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
|
||||
setLayout(top_layout_);
|
||||
[[maybe_unused]] static auto ignored =
|
||||
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
|
||||
|
||||
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
|
||||
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
|
||||
connect(
|
||||
this,
|
||||
&LoginPage::loginOk,
|
||||
this,
|
||||
[this](const mtx::responses::Login &res) {
|
||||
loggingIn_ = false;
|
||||
emit loggingInChanged();
|
||||
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
|
||||
});
|
||||
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
|
||||
onLoginButtonClicked(LoginMethod::SSO);
|
||||
});
|
||||
connect(this,
|
||||
&LoginPage::showErrorMessage,
|
||||
this,
|
||||
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
|
||||
Qt::QueuedConnection);
|
||||
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
|
||||
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
|
||||
http::client()->set_user(res.user_id);
|
||||
MainWindow::instance()->showChatPage();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
void
|
||||
LoginPage::showError(const QString &msg)
|
||||
{
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
loggingIn_ = false;
|
||||
emit loggingInChanged();
|
||||
|
||||
error_ = msg;
|
||||
emit errorOccurred();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::showError(QLabel *label, const QString &msg)
|
||||
LoginPage::setHomeserver(QString hs)
|
||||
{
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
if (hs != homeserver_) {
|
||||
homeserver_ = hs;
|
||||
homeserverValid_ = false;
|
||||
emit homeserverChanged();
|
||||
http::client()->set_server(hs.toStdString());
|
||||
checkHomeserverVersion();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onMatrixIdEntered()
|
||||
{
|
||||
error_label_->setText(QLatin1String(""));
|
||||
clearErrors();
|
||||
|
||||
homeserverValid_ = false;
|
||||
emit homeserverChanged();
|
||||
|
||||
User user;
|
||||
try {
|
||||
user = parse<User>(mxid_.toStdString());
|
||||
} catch (const std::exception &) {
|
||||
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||
emit mxidErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
if (user.hostname().empty() || user.localpart().empty()) {
|
||||
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||
emit mxidErrorChanged();
|
||||
return;
|
||||
} else {
|
||||
error_matrixid_label_->setText(QLatin1String(""));
|
||||
error_matrixid_label_->hide();
|
||||
nhlog::net()->debug("hostname: {}", user.hostname());
|
||||
}
|
||||
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString homeServer = QString::fromStdString(user.hostname());
|
||||
if (homeServer != inferredServerAddress_) {
|
||||
serverInput_->hide();
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
if (serverInput_->isVisible()) {
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
} else {
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
}
|
||||
|
||||
inferredServerAddress_ = homeServer;
|
||||
serverInput_->setText(homeServer);
|
||||
if (user.hostname() != inferredServerAddress_.toStdString()) {
|
||||
homeserverNeeded_ = false;
|
||||
lookingUpHs_ = true;
|
||||
emit lookingUpHsChanged();
|
||||
|
||||
http::client()->set_server(user.hostname());
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
homeserver_ = QString::fromStdString(user.hostname());
|
||||
emit homeserverChanged();
|
||||
|
||||
http::client()->well_known(
|
||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||
@ -286,6 +128,7 @@ LoginPage::onMatrixIdEntered()
|
||||
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
emit homeserverChanged();
|
||||
checkHomeserverVersion();
|
||||
});
|
||||
}
|
||||
@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered()
|
||||
void
|
||||
LoginPage::checkHomeserverVersion()
|
||||
{
|
||||
clearErrors();
|
||||
|
||||
try {
|
||||
User user = parse<User>(mxid_.toStdString());
|
||||
} catch (const std::exception &) {
|
||||
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||
emit mxidErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
@ -318,107 +171,78 @@ LoginPage::checkHomeserverVersion()
|
||||
if (err || flows.flows.empty())
|
||||
emit versionOkCb(true, false);
|
||||
|
||||
bool ssoSupported_ = false;
|
||||
bool passwordSupported_ = false;
|
||||
bool ssoSupported = false;
|
||||
bool passwordSupported = false;
|
||||
for (const auto &flow : flows.flows) {
|
||||
if (flow.type == mtx::user_interactive::auth_types::sso) {
|
||||
ssoSupported_ = true;
|
||||
ssoSupported = true;
|
||||
} else if (flow.type == mtx::user_interactive::auth_types::password) {
|
||||
passwordSupported_ = true;
|
||||
passwordSupported = true;
|
||||
}
|
||||
}
|
||||
emit versionOkCb(passwordSupported_, ssoSupported_);
|
||||
emit versionOkCb(passwordSupported, ssoSupported);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onServerAddressEntered()
|
||||
{
|
||||
error_label_->setText(QLatin1String(""));
|
||||
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
|
||||
http::client()->set_server(serverInput_->text().toStdString());
|
||||
checkHomeserverVersion();
|
||||
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
spinner_->start();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::versionError(const QString &error)
|
||||
{
|
||||
showError(error_label_, error);
|
||||
serverInput_->show();
|
||||
showError(error);
|
||||
|
||||
spinner_->stop();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
||||
errorIcon_->show();
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
homeserverNeeded_ = true;
|
||||
lookingUpHs_ = false;
|
||||
homeserverValid_ = false;
|
||||
emit lookingUpHsChanged();
|
||||
emit versionLookedUp();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
|
||||
LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
|
||||
{
|
||||
passwordSupported = passwordSupported_;
|
||||
ssoSupported = ssoSupported_;
|
||||
passwordSupported_ = passwordSupported;
|
||||
ssoSupported_ = ssoSupported;
|
||||
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
spinner_->stop();
|
||||
|
||||
password_input_->setVisible(passwordSupported);
|
||||
password_input_->setEnabled(passwordSupported);
|
||||
sso_login_button_->setVisible(ssoSupported);
|
||||
login_button_->setVisible(passwordSupported);
|
||||
|
||||
if (serverInput_->isVisible())
|
||||
serverInput_->hide();
|
||||
lookingUpHs_ = false;
|
||||
homeserverValid_ = true;
|
||||
emit homeserverChanged();
|
||||
emit lookingUpHsChanged();
|
||||
emit versionLookedUp();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
||||
LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
|
||||
QString userid,
|
||||
QString password,
|
||||
QString deviceName)
|
||||
{
|
||||
error_label_->setText(QLatin1String(""));
|
||||
clearErrors();
|
||||
|
||||
User user;
|
||||
|
||||
if (!matrixid_input_->isValid()) {
|
||||
error_matrixid_label_->show();
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
return;
|
||||
} else {
|
||||
error_matrixid_label_->setText(QLatin1String(""));
|
||||
error_matrixid_label_->hide();
|
||||
}
|
||||
|
||||
try {
|
||||
user = parse<User>(matrixid_input_->text().toStdString());
|
||||
user = parse<User>(userid.toStdString());
|
||||
} catch (const std::exception &) {
|
||||
showError(error_matrixid_label_,
|
||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
||||
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||
emit mxidErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginMethod == LoginMethod::Password) {
|
||||
if (password_input_->text().isEmpty())
|
||||
return showError(error_label_, tr("Empty password"));
|
||||
if (password.isEmpty())
|
||||
return showError(tr("Empty password"));
|
||||
|
||||
http::client()->login(
|
||||
user.localpart(),
|
||||
password_input_->text().toStdString(),
|
||||
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
|
||||
: deviceName_->text().toStdString(),
|
||||
password.toStdString(),
|
||||
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(),
|
||||
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
auto error = err->matrix_error.error;
|
||||
if (error.empty())
|
||||
error = err->parse_error;
|
||||
|
||||
showErrorMessage(error_label_, QString::fromStdString(error));
|
||||
emit errorOccurred();
|
||||
showError(QString::fromStdString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
||||
});
|
||||
} else {
|
||||
auto sso = new SSOHandler();
|
||||
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
|
||||
mtx::requests::Login req{};
|
||||
req.token = token;
|
||||
req.type = mtx::user_interactive::auth_types::token;
|
||||
req.device_id = deviceName_->text().trimmed().isEmpty()
|
||||
? initialDeviceName()
|
||||
: deviceName_->text().toStdString();
|
||||
http::client()->login(
|
||||
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
showErrorMessage(error_label_,
|
||||
QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
connect(
|
||||
sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) {
|
||||
mtx::requests::Login req{};
|
||||
req.token = token;
|
||||
req.type = mtx::user_interactive::auth_types::token;
|
||||
req.device_id =
|
||||
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString();
|
||||
http::client()->login(
|
||||
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
if (res.well_known) {
|
||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||
nhlog::net()->info("Login requested to user server: " +
|
||||
res.well_known->homeserver.base_url);
|
||||
}
|
||||
|
||||
emit loginOk(res);
|
||||
});
|
||||
sso->deleteLater();
|
||||
});
|
||||
emit loginOk(res);
|
||||
});
|
||||
sso->deleteLater();
|
||||
});
|
||||
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
|
||||
showErrorMessage(error_label_, tr("SSO login failed"));
|
||||
showError(tr("SSO login failed"));
|
||||
emit errorOccurred();
|
||||
sso->deleteLater();
|
||||
});
|
||||
@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
||||
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
|
||||
}
|
||||
|
||||
emit loggingIn();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::reset()
|
||||
{
|
||||
matrixid_input_->clear();
|
||||
password_input_->clear();
|
||||
password_input_->show();
|
||||
serverInput_->clear();
|
||||
|
||||
spinner_->stop();
|
||||
errorIcon_->hide();
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
|
||||
inferredServerAddress_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onBackButtonClicked()
|
||||
{
|
||||
emit backButtonClicked();
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
loggingIn_ = true;
|
||||
emit loggingInChanged();
|
||||
}
|
||||
|
166
src/LoginPage.h
166
src/LoginPage.h
@ -6,16 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class FlatButton;
|
||||
class LoadingIndicator;
|
||||
class OverlayModal;
|
||||
class RaisedButton;
|
||||
class TextField;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
#include <QObject>
|
||||
|
||||
namespace mtx {
|
||||
namespace responses {
|
||||
@ -23,62 +14,61 @@ struct Login;
|
||||
}
|
||||
}
|
||||
|
||||
class LoginPage : public QWidget
|
||||
class LoginPage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged)
|
||||
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
|
||||
|
||||
Q_PROPERTY(QString mxidError READ mxidError NOTIFY mxidErrorChanged)
|
||||
Q_PROPERTY(QString error READ error NOTIFY errorOccurred)
|
||||
Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
|
||||
Q_PROPERTY(bool homeserverValid READ homeserverValid NOTIFY lookingUpHsChanged)
|
||||
Q_PROPERTY(bool loggingIn READ loggingIn NOTIFY loggingInChanged)
|
||||
Q_PROPERTY(bool passwordSupported READ passwordSupported NOTIFY versionLookedUp)
|
||||
Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp)
|
||||
Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp)
|
||||
|
||||
public:
|
||||
enum class LoginMethod
|
||||
{
|
||||
Password,
|
||||
SSO,
|
||||
};
|
||||
Q_ENUM(LoginMethod)
|
||||
|
||||
LoginPage(QWidget *parent = nullptr);
|
||||
LoginPage(QObject *parent = nullptr);
|
||||
|
||||
void reset();
|
||||
Q_INVOKABLE QString initialDeviceName() const
|
||||
{
|
||||
return QString::fromStdString(initialDeviceName_());
|
||||
}
|
||||
|
||||
signals:
|
||||
void backButtonClicked();
|
||||
void loggingIn();
|
||||
void errorOccurred();
|
||||
bool lookingUpHs() const { return lookingUpHs_; }
|
||||
bool loggingIn() const { return loggingIn_; }
|
||||
bool passwordSupported() const { return passwordSupported_; }
|
||||
bool ssoSupported() const { return ssoSupported_; }
|
||||
bool homeserverNeeded() const { return homeserverNeeded_; }
|
||||
bool homeserverValid() const { return homeserverValid_; }
|
||||
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void versionErrorCb(const QString &err);
|
||||
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
||||
QString homeserver() { return homeserver_; }
|
||||
QString mxid() { return mxid_; }
|
||||
|
||||
void loginOk(const mtx::responses::Login &res);
|
||||
void showErrorMessage(QLabel *label, const QString &msg);
|
||||
QString error() { return error_; }
|
||||
QString mxidError() { return mxidError_; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void setHomeserver(QString hs);
|
||||
void setMxid(QString id)
|
||||
{
|
||||
if (id != mxid_) {
|
||||
mxid_ = id;
|
||||
emit matrixIdChanged();
|
||||
onMatrixIdEntered();
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
// Displays errors produced during the login.
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
|
||||
private slots:
|
||||
// Callback for the back button.
|
||||
void onBackButtonClicked();
|
||||
|
||||
// Callback for the login button.
|
||||
void onLoginButtonClicked(LoginMethod loginMethod);
|
||||
|
||||
// Callback for probing the server found in the mxid
|
||||
void onMatrixIdEntered();
|
||||
|
||||
// Callback for probing the manually entered server
|
||||
void onServerAddressEntered();
|
||||
|
||||
// Callback for errors produced during server probing
|
||||
void versionError(const QString &error_message);
|
||||
// Callback for successful server probing
|
||||
void versionOk(bool passwordSupported, bool ssoSupported);
|
||||
|
||||
private:
|
||||
void checkHomeserverVersion();
|
||||
std::string initialDeviceName()
|
||||
static std::string initialDeviceName_()
|
||||
{
|
||||
#if defined(Q_OS_MAC)
|
||||
return "Nheko on macOS";
|
||||
@ -93,33 +83,65 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
QVBoxLayout *top_layout_;
|
||||
signals:
|
||||
void loggingInChanged();
|
||||
void errorOccurred();
|
||||
|
||||
QHBoxLayout *top_bar_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void versionErrorCb(const QString &err);
|
||||
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
||||
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_matrixid_label_;
|
||||
void loginOk(const mtx::responses::Login &res);
|
||||
|
||||
void onServerAddressEntered();
|
||||
|
||||
void matrixIdChanged();
|
||||
void homeserverChanged();
|
||||
|
||||
void mxidErrorChanged();
|
||||
void lookingUpHsChanged();
|
||||
void versionLookedUp();
|
||||
void versionLookupFinished();
|
||||
|
||||
public slots:
|
||||
// Displays errors produced during the login.
|
||||
void showError(const QString &msg);
|
||||
|
||||
// Callback for the login button.
|
||||
void onLoginButtonClicked(LoginMethod loginMethod,
|
||||
QString userid,
|
||||
QString password,
|
||||
QString deviceName);
|
||||
|
||||
// Callback for errors produced during server probing
|
||||
void versionError(const QString &error_message);
|
||||
// Callback for successful server probing
|
||||
void versionOk(bool passwordSupported, bool ssoSupported);
|
||||
|
||||
private:
|
||||
void checkHomeserverVersion();
|
||||
void onMatrixIdEntered();
|
||||
void clearErrors()
|
||||
{
|
||||
error_.clear();
|
||||
mxidError_.clear();
|
||||
emit errorOccurred();
|
||||
emit mxidErrorChanged();
|
||||
}
|
||||
|
||||
QHBoxLayout *serverLayout_;
|
||||
QHBoxLayout *matrixidLayout_;
|
||||
LoadingIndicator *spinner_;
|
||||
QLabel *errorIcon_;
|
||||
QString inferredServerAddress_;
|
||||
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *login_button_, *sso_login_button_;
|
||||
QString mxid_;
|
||||
QString homeserver_;
|
||||
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
QString mxidError_;
|
||||
QString error_;
|
||||
|
||||
TextField *matrixid_input_;
|
||||
TextField *password_input_;
|
||||
TextField *deviceName_;
|
||||
TextField *serverInput_;
|
||||
bool passwordSupported = true;
|
||||
bool ssoSupported = false;
|
||||
bool passwordSupported_ = true;
|
||||
bool ssoSupported_ = false;
|
||||
|
||||
bool lookingUpHs_ = false;
|
||||
bool loggingIn_ = false;
|
||||
bool homeserverNeeded_ = false;
|
||||
bool homeserverValid_ = false;
|
||||
};
|
||||
|
@ -5,91 +5,87 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPluginLoader>
|
||||
#include <QShortcut>
|
||||
|
||||
#include <mtx/requests.hpp>
|
||||
#include <mtx/responses/login.hpp>
|
||||
|
||||
#include "BlurhashProvider.h"
|
||||
#include "Cache.h"
|
||||
#include "Cache_p.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Clipboard.h"
|
||||
#include "ColorImageProvider.h"
|
||||
#include "CombinedImagePackModel.h"
|
||||
#include "CompletionProxyModel.h"
|
||||
#include "Config.h"
|
||||
#include "EventAccessors.h"
|
||||
#include "ImagePackListModel.h"
|
||||
#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 "ReadReceiptsModel.h"
|
||||
#include "RegisterPage.h"
|
||||
#include "RoomDirectoryModel.h"
|
||||
#include "RoomsModel.h"
|
||||
#include "SingleImagePackModel.h"
|
||||
#include "TrayIcon.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "UsersModel.h"
|
||||
#include "Utils.h"
|
||||
#include "WelcomePage.h"
|
||||
#include "ui/LoadingIndicator.h"
|
||||
#include "ui/OverlayModal.h"
|
||||
#include "ui/SnackBar.h"
|
||||
#include "emoji/EmojiModel.h"
|
||||
#include "emoji/Provider.h"
|
||||
#include "encryption/DeviceVerificationFlow.h"
|
||||
#include "encryption/SelfVerificationStatus.h"
|
||||
#include "timeline/DelegateChooser.h"
|
||||
#include "timeline/TimelineViewManager.h"
|
||||
#include "ui/MxcAnimatedImage.h"
|
||||
#include "ui/MxcMediaProxy.h"
|
||||
#include "ui/NhekoCursorShape.h"
|
||||
#include "ui/NhekoDropArea.h"
|
||||
#include "ui/NhekoGlobalObject.h"
|
||||
#include "ui/UIA.h"
|
||||
#include "voip/WebRTCSession.h"
|
||||
|
||||
#include "dialogs/CreateRoom.h"
|
||||
|
||||
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
||||
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
|
||||
Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
|
||||
|
||||
MainWindow *MainWindow::instance_ = nullptr;
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
MainWindow::MainWindow(QWindow *parent)
|
||||
: QQuickView(parent)
|
||||
, userSettings_{UserSettings::instance()}
|
||||
{
|
||||
instance_ = this;
|
||||
|
||||
QMainWindow::setWindowTitle(0);
|
||||
MainWindow::setWindowTitle(0);
|
||||
setObjectName(QStringLiteral("MainWindow"));
|
||||
|
||||
modal_ = new OverlayModal(this);
|
||||
|
||||
setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
setMinimumHeight(400);
|
||||
setMinimumWidth(400);
|
||||
restoreWindowSize();
|
||||
|
||||
QFont font;
|
||||
font.setStyleStrategy(QFont::PreferAntialias);
|
||||
setFont(font);
|
||||
chat_page_ = new ChatPage(userSettings_, this);
|
||||
registerQmlTypes();
|
||||
|
||||
setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color());
|
||||
setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
|
||||
|
||||
trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
|
||||
|
||||
welcome_page_ = new WelcomePage(this);
|
||||
login_page_ = new LoginPage(this);
|
||||
register_page_ = new RegisterPage(this);
|
||||
chat_page_ = new ChatPage(userSettings_, this);
|
||||
|
||||
// Initialize sliding widget manager.
|
||||
pageStack_ = new QStackedWidget(this);
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->addWidget(chat_page_);
|
||||
|
||||
setCentralWidget(pageStack_);
|
||||
|
||||
connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
|
||||
connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
|
||||
|
||||
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(
|
||||
register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
|
||||
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
|
||||
connect(
|
||||
chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
|
||||
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
|
||||
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
|
||||
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
|
||||
login_page_->showError(msg);
|
||||
showLoginPage();
|
||||
});
|
||||
connect(chat_page_, &ChatPage::showLoginPage, this, &MainWindow::switchToLoginPage);
|
||||
connect(chat_page_, &ChatPage::showNotification, this, &MainWindow::showNotification);
|
||||
|
||||
connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
|
||||
connect(trayIcon_,
|
||||
@ -97,20 +93,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
this,
|
||||
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
|
||||
|
||||
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
|
||||
|
||||
connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
|
||||
|
||||
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
|
||||
http::client()->set_user(res.user_id);
|
||||
showChatPage();
|
||||
});
|
||||
|
||||
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
|
||||
|
||||
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
|
||||
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
|
||||
|
||||
trayIcon_->setVisible(userSettings_->tray());
|
||||
|
||||
// load cache on event loop
|
||||
@ -133,11 +115,171 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
user_id.toStdString());
|
||||
}
|
||||
|
||||
nhlog::ui()->info("User already signed in, showing chat page");
|
||||
showChatPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::registerQmlTypes()
|
||||
{
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||
qRegisterMetaType<CombinedImagePackModel *>();
|
||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
||||
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||
|
||||
qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
|
||||
|
||||
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<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
|
||||
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
|
||||
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
|
||||
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
|
||||
qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
|
||||
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());
|
||||
|
||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
||||
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||
|
||||
qmlRegisterUncreatableType<FilteredCommunitiesModel>(
|
||||
"im.nheko",
|
||||
1,
|
||||
0,
|
||||
"FilteredCommunitiesModel",
|
||||
QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
|
||||
|
||||
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
|
||||
qmlRegisterUncreatableType<emoji::Emoji>(
|
||||
"im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
|
||||
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());
|
||||
|
||||
imgProvider = new MxcImageProvider();
|
||||
engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
|
||||
engine()->addImageProvider(QStringLiteral("colorimage"), new ColorImageProvider());
|
||||
engine()->addImageProvider(QStringLiteral("blurhash"), new BlurhashProvider());
|
||||
if (JdenticonProvider::isAvailable())
|
||||
engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider());
|
||||
|
||||
QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::setWindowTitle(int notificationCount)
|
||||
{
|
||||
@ -148,20 +290,19 @@ MainWindow::setWindowTitle(int notificationCount)
|
||||
if (notificationCount > 0) {
|
||||
name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount));
|
||||
}
|
||||
QMainWindow::setWindowTitle(name);
|
||||
QQuickView::setTitle(name);
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::event(QEvent *event)
|
||||
{
|
||||
auto type = event->type();
|
||||
if (type == QEvent::WindowActivate) {
|
||||
emit focusChanged(true);
|
||||
} else if (type == QEvent::WindowDeactivate) {
|
||||
emit focusChanged(false);
|
||||
|
||||
if (type == QEvent::Close) {
|
||||
closeEvent(static_cast<QCloseEvent *>(event));
|
||||
}
|
||||
|
||||
return QMainWindow::event(event);
|
||||
return QQuickView::event(event);
|
||||
}
|
||||
|
||||
void
|
||||
@ -188,31 +329,6 @@ MainWindow::saveCurrentWindowSize()
|
||||
settings->setValue(QStringLiteral("window/height"), current.height());
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::removeOverlayProgressBar()
|
||||
{
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
|
||||
connect(timer, &QTimer::timeout, this, [this, timer]() {
|
||||
timer->deleteLater();
|
||||
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
|
||||
if (spinner_)
|
||||
spinner_->stop();
|
||||
});
|
||||
|
||||
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
snackBar_ = new SnackBar(this);
|
||||
connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
|
||||
});
|
||||
|
||||
timer->start(50);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showChatPage()
|
||||
{
|
||||
@ -227,19 +343,13 @@ MainWindow::showChatPage()
|
||||
userSettings_.data()->setDeviceId(device_id);
|
||||
userSettings_.data()->setHomeserver(homeserver);
|
||||
|
||||
showOverlayProgressBar();
|
||||
|
||||
pageStack_->setCurrentWidget(chat_page_);
|
||||
|
||||
pageStack_->removeWidget(welcome_page_);
|
||||
pageStack_->removeWidget(login_page_);
|
||||
pageStack_->removeWidget(register_page_);
|
||||
|
||||
login_page_->reset();
|
||||
chat_page_->bootstrap(userid, homeserver, token);
|
||||
connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
|
||||
connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
|
||||
|
||||
emit reload();
|
||||
nhlog::ui()->info("Switching to chat page");
|
||||
emit switchToChatPage();
|
||||
}
|
||||
|
||||
void
|
||||
@ -247,7 +357,7 @@ MainWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||
if (QMessageBox::question(
|
||||
this, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
|
||||
nullptr, QStringLiteral("nheko"), QStringLiteral("A call is in progress. Quit?")) !=
|
||||
QMessageBox::Yes) {
|
||||
event->ignore();
|
||||
return;
|
||||
@ -289,23 +399,11 @@ MainWindow::hasActiveUser()
|
||||
settings->contains(prefix + "auth/user_id");
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showOverlayProgressBar()
|
||||
{
|
||||
spinner_ = new LoadingIndicator(this);
|
||||
spinner_->setFixedHeight(100);
|
||||
spinner_->setFixedWidth(100);
|
||||
spinner_->setObjectName(QStringLiteral("ChatPageLoadSpinner"));
|
||||
spinner_->start();
|
||||
|
||||
showSolidOverlayModal(spinner_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openCreateRoomDialog(
|
||||
std::function<void(const mtx::requests::CreateRoom &request)> callback)
|
||||
{
|
||||
auto dialog = new dialogs::CreateRoom(this);
|
||||
auto dialog = new dialogs::CreateRoom(nullptr);
|
||||
connect(dialog,
|
||||
&dialogs::CreateRoom::createRoom,
|
||||
this,
|
||||
@ -314,76 +412,19 @@ MainWindow::openCreateRoomDialog(
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
|
||||
{
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30, 150));
|
||||
modal_->setDismissible(true);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
|
||||
{
|
||||
modal_->setWidget(content);
|
||||
modal_->setColor(QColor(30, 30, 30));
|
||||
modal_->setDismissible(false);
|
||||
modal_->setContentAlignment(flags);
|
||||
modal_->raise();
|
||||
modal_->show();
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::hasActiveDialogs() const
|
||||
{
|
||||
return modal_ && modal_->isVisible();
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::pageSupportsTray() const
|
||||
{
|
||||
return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::hideOverlay()
|
||||
{
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
return !http::client()->access_token().empty();
|
||||
}
|
||||
|
||||
inline void
|
||||
MainWindow::showDialog(QWidget *dialog)
|
||||
{
|
||||
utils::centerWidget(dialog, this);
|
||||
dialog->setWindowFlags(Qt::WindowType::Dialog | Qt::WindowType::WindowCloseButtonHint |
|
||||
Qt::WindowType::WindowTitleHint);
|
||||
dialog->raise();
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showWelcomePage()
|
||||
{
|
||||
removeOverlayProgressBar();
|
||||
pageStack_->addWidget(welcome_page_);
|
||||
pageStack_->setCurrentWidget(welcome_page_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showLoginPage()
|
||||
{
|
||||
if (modal_)
|
||||
modal_->hide();
|
||||
|
||||
pageStack_->addWidget(login_page_);
|
||||
pageStack_->setCurrentWidget(login_page_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::showRegisterPage()
|
||||
{
|
||||
pageStack_->addWidget(register_page_);
|
||||
pageStack_->setCurrentWidget(register_page_);
|
||||
utils::centerWidget(dialog, this);
|
||||
dialog->window()->windowHandle()->setTransientParent(this);
|
||||
}
|
||||
|
@ -8,26 +8,21 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QQuickView>
|
||||
#include <QSharedPointer>
|
||||
#include <QStackedWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include "UserSettingsPage.h"
|
||||
#include "ui/OverlayModal.h"
|
||||
|
||||
#include "jdenticoninterface.h"
|
||||
|
||||
class ChatPage;
|
||||
class RegisterPage;
|
||||
class LoginPage;
|
||||
class WelcomePage;
|
||||
|
||||
class LoadingIndicator;
|
||||
class OverlayModal;
|
||||
class SnackBar;
|
||||
class TrayIcon;
|
||||
class UserSettings;
|
||||
class MxcImageProvider;
|
||||
|
||||
namespace mtx {
|
||||
namespace requests {
|
||||
@ -42,17 +37,12 @@ class MemberList;
|
||||
class ReCaptcha;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
class MainWindow : public QQuickView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int x READ x CONSTANT)
|
||||
Q_PROPERTY(int y READ y CONSTANT)
|
||||
Q_PROPERTY(int width READ width CONSTANT)
|
||||
Q_PROPERTY(int height READ height CONSTANT)
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
explicit MainWindow(QWindow *parent = nullptr);
|
||||
|
||||
static MainWindow *instance() { return instance_; }
|
||||
void saveCurrentWindowSize();
|
||||
@ -61,69 +51,51 @@ public:
|
||||
openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
||||
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
||||
|
||||
void hideOverlay();
|
||||
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
|
||||
void
|
||||
showTransparentOverlayModal(QWidget *content,
|
||||
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter);
|
||||
MxcImageProvider *imageProvider() { return imgProvider; }
|
||||
|
||||
//! Show the chat page and start communicating with the given access token.
|
||||
void showChatPage();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event);
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
//! Handle interaction with the tray icon.
|
||||
void iconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
|
||||
//! Show the welcome page in the main window.
|
||||
void showWelcomePage();
|
||||
|
||||
//! Show the login page in the main window.
|
||||
void showLoginPage();
|
||||
|
||||
//! Show the register page in the main window.
|
||||
void showRegisterPage();
|
||||
|
||||
//! Show the chat page and start communicating with the given access token.
|
||||
void showChatPage();
|
||||
|
||||
void showOverlayProgressBar();
|
||||
void removeOverlayProgressBar();
|
||||
|
||||
virtual void setWindowTitle(int notificationCount);
|
||||
|
||||
signals:
|
||||
void focusChanged(const bool focused);
|
||||
void reload();
|
||||
void secretsChanged();
|
||||
|
||||
void showNotification(QString msg);
|
||||
|
||||
void switchToChatPage();
|
||||
void switchToWelcomePage();
|
||||
void switchToLoginPage(QString error);
|
||||
|
||||
private:
|
||||
void showDialog(QWidget *dialog);
|
||||
bool hasActiveUser();
|
||||
void restoreWindowSize();
|
||||
//! Check if there is an open dialog.
|
||||
bool hasActiveDialogs() const;
|
||||
//! Check if the current page supports the "minimize to tray" functionality.
|
||||
bool pageSupportsTray() const;
|
||||
|
||||
void registerQmlTypes();
|
||||
|
||||
static MainWindow *instance_;
|
||||
|
||||
//! The initial welcome screen.
|
||||
WelcomePage *welcome_page_;
|
||||
//! The login screen.
|
||||
LoginPage *login_page_;
|
||||
//! The register page.
|
||||
RegisterPage *register_page_;
|
||||
//! A stacked widget that handles the transitions between widgets.
|
||||
QStackedWidget *pageStack_;
|
||||
//! The main chat area.
|
||||
ChatPage *chat_page_;
|
||||
QSharedPointer<UserSettings> userSettings_;
|
||||
//! Tray icon that shows the unread message count.
|
||||
TrayIcon *trayIcon_;
|
||||
//! Notifications display.
|
||||
SnackBar *snackBar_ = nullptr;
|
||||
//! Overlay modal used to project other widgets.
|
||||
OverlayModal *modal_ = nullptr;
|
||||
LoadingIndicator *spinner_ = nullptr;
|
||||
|
||||
MxcImageProvider *imgProvider = nullptr;
|
||||
};
|
||||
|
@ -4,312 +4,83 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMetaType>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
#include <QtMath>
|
||||
|
||||
#include <mtx/responses/common.hpp>
|
||||
#include <mtx/responses/register.hpp>
|
||||
#include <mtx/responses/well-known.hpp>
|
||||
#include <mtxclient/http/client.hpp>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
#include "LoginPage.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "RegisterPage.h"
|
||||
#include "ui/FlatButton.h"
|
||||
#include "ui/RaisedButton.h"
|
||||
#include "ui/TextField.h"
|
||||
#include "ui/UIA.h"
|
||||
|
||||
#include "dialogs/FallbackAuth.h"
|
||||
#include "dialogs/ReCaptcha.h"
|
||||
|
||||
Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized)
|
||||
Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
|
||||
|
||||
RegisterPage::RegisterPage(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
RegisterPage::RegisterPage(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
|
||||
qRegisterMetaType<mtx::user_interactive::Auth>();
|
||||
top_layout_ = new QVBoxLayout();
|
||||
|
||||
back_layout_ = new QHBoxLayout();
|
||||
back_layout_->setSpacing(0);
|
||||
back_layout_->setContentsMargins(5, 5, -1, -1);
|
||||
|
||||
back_button_ = new FlatButton(this);
|
||||
back_button_->setMinimumSize(QSize(30, 30));
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(QStringLiteral(":/icons/icons/ui/angle-arrow-left.svg"));
|
||||
|
||||
back_button_->setIcon(icon);
|
||||
back_button_->setIconSize(QSize(32, 32));
|
||||
|
||||
back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
back_layout_->addStretch(1);
|
||||
|
||||
QIcon logo;
|
||||
logo.addFile(QStringLiteral(":/logos/register.png"));
|
||||
|
||||
logo_ = new QLabel(this);
|
||||
logo_->setPixmap(logo.pixmap(128));
|
||||
|
||||
logo_layout_ = new QHBoxLayout();
|
||||
logo_layout_->setContentsMargins(0, 0, 0, 0);
|
||||
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
|
||||
|
||||
form_wrapper_ = new QHBoxLayout();
|
||||
form_widget_ = new QWidget();
|
||||
form_widget_->setMinimumSize(QSize(350, 300));
|
||||
|
||||
form_layout_ = new QVBoxLayout();
|
||||
form_layout_->setSpacing(20);
|
||||
form_layout_->setContentsMargins(0, 0, 0, 40);
|
||||
form_widget_->setLayout(form_layout_);
|
||||
|
||||
form_wrapper_->addStretch(1);
|
||||
form_wrapper_->addWidget(form_widget_);
|
||||
form_wrapper_->addStretch(1);
|
||||
|
||||
username_input_ = new TextField();
|
||||
username_input_->setLabel(tr("Username"));
|
||||
username_input_->setRegexp(QRegularExpression(QStringLiteral("[a-z0-9._=/-]+")));
|
||||
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
|
||||
password_input_ = new TextField();
|
||||
password_input_->setLabel(tr("Password"));
|
||||
password_input_->setRegexp(QRegularExpression(QStringLiteral("^.{8,}$")));
|
||||
password_input_->setEchoMode(QLineEdit::Password);
|
||||
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
|
||||
"for password strength may depend on your server."));
|
||||
|
||||
password_confirmation_ = new TextField();
|
||||
password_confirmation_->setLabel(tr("Password confirmation"));
|
||||
password_confirmation_->setEchoMode(QLineEdit::Password);
|
||||
|
||||
server_input_ = new TextField();
|
||||
server_input_->setLabel(tr("Homeserver"));
|
||||
server_input_->setRegexp(QRegularExpression(QStringLiteral(".+")));
|
||||
server_input_->setToolTip(
|
||||
tr("A server that allows registration. Since matrix is decentralized, you need to first "
|
||||
"find a server you can register on or host your own."));
|
||||
|
||||
error_username_label_ = new QLabel(this);
|
||||
error_username_label_->setWordWrap(true);
|
||||
error_username_label_->hide();
|
||||
|
||||
error_password_label_ = new QLabel(this);
|
||||
error_password_label_->setWordWrap(true);
|
||||
error_password_label_->hide();
|
||||
|
||||
error_password_confirmation_label_ = new QLabel(this);
|
||||
error_password_confirmation_label_->setWordWrap(true);
|
||||
error_password_confirmation_label_->hide();
|
||||
|
||||
error_server_label_ = new QLabel(this);
|
||||
error_server_label_->setWordWrap(true);
|
||||
error_server_label_->hide();
|
||||
|
||||
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
|
||||
form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
|
||||
|
||||
button_layout_ = new QHBoxLayout();
|
||||
button_layout_->setSpacing(0);
|
||||
button_layout_->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
error_label_ = new QLabel(this);
|
||||
error_label_->setWordWrap(true);
|
||||
|
||||
register_button_ = new RaisedButton(tr("REGISTER"), this);
|
||||
register_button_->setMinimumSize(350, 65);
|
||||
register_button_->setFontSize(conf::btn::fontSize);
|
||||
register_button_->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
button_layout_->addStretch(1);
|
||||
button_layout_->addWidget(register_button_);
|
||||
button_layout_->addStretch(1);
|
||||
|
||||
top_layout_->addLayout(back_layout_);
|
||||
top_layout_->addLayout(logo_layout_);
|
||||
top_layout_->addLayout(form_wrapper_);
|
||||
top_layout_->addStretch(1);
|
||||
top_layout_->addLayout(button_layout_);
|
||||
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
|
||||
top_layout_->addStretch(1);
|
||||
setLayout(top_layout_);
|
||||
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
|
||||
|
||||
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
|
||||
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
|
||||
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_confirmation_,
|
||||
&TextField::editingFinished,
|
||||
this,
|
||||
&RegisterPage::checkPasswordConfirmation);
|
||||
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
|
||||
|
||||
connect(
|
||||
this,
|
||||
&RegisterPage::serverError,
|
||||
this,
|
||||
[this](const QString &msg) {
|
||||
server_input_->setValid(false);
|
||||
showError(error_server_label_, msg);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
|
||||
connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
|
||||
connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
|
||||
connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); });
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::onBackButtonClicked()
|
||||
RegisterPage::setError(QString err)
|
||||
{
|
||||
emit backButtonClicked();
|
||||
registrationError_ = err;
|
||||
emit errorChanged();
|
||||
registering_ = false;
|
||||
emit registeringChanged();
|
||||
}
|
||||
void
|
||||
RegisterPage::setHsError(QString err)
|
||||
{
|
||||
hsError_ = err;
|
||||
emit hsErrorChanged();
|
||||
lookingUpHs_ = false;
|
||||
emit lookingUpHsChanged();
|
||||
}
|
||||
|
||||
QString
|
||||
RegisterPage::initialDeviceName() const
|
||||
{
|
||||
return QString::fromStdString(LoginPage::initialDeviceName_());
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::showError(const QString &msg)
|
||||
RegisterPage::setServer(QString server)
|
||||
{
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
|
||||
error_label_->setText(msg);
|
||||
}
|
||||
if (server == lastServer)
|
||||
return;
|
||||
|
||||
void
|
||||
RegisterPage::showError(QLabel *label, const QString &msg)
|
||||
{
|
||||
emit errorOccurred();
|
||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
||||
label->setText(msg);
|
||||
label->show();
|
||||
}
|
||||
lastServer = server;
|
||||
|
||||
bool
|
||||
RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
|
||||
{
|
||||
if (t_field->isValid()) {
|
||||
label->hide();
|
||||
return true;
|
||||
} else {
|
||||
showError(label, msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
http::client()->set_server(server.toStdString());
|
||||
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
|
||||
|
||||
bool
|
||||
RegisterPage::checkUsername()
|
||||
{
|
||||
return checkOneField(error_username_label_,
|
||||
username_input_,
|
||||
tr("The username must not be empty, and must contain only the "
|
||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
||||
}
|
||||
hsError_.clear();
|
||||
emit hsErrorChanged();
|
||||
supported_ = false;
|
||||
lookingUpHs_ = true;
|
||||
emit lookingUpHsChanged();
|
||||
|
||||
bool
|
||||
RegisterPage::checkPassword()
|
||||
{
|
||||
return checkOneField(
|
||||
error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkPasswordConfirmation()
|
||||
{
|
||||
if (password_input_->text() == password_confirmation_->text()) {
|
||||
error_password_confirmation_label_->hide();
|
||||
password_confirmation_->setValid(true);
|
||||
return true;
|
||||
} else {
|
||||
showError(error_password_confirmation_label_, tr("Passwords don't match"));
|
||||
password_confirmation_->setValid(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RegisterPage::checkServer()
|
||||
{
|
||||
// This doesn't check that the server is reachable,
|
||||
// just that the input is not obviously wrong.
|
||||
return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::onRegisterButtonClicked()
|
||||
{
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
|
||||
auto server = server_input_->text().toStdString();
|
||||
|
||||
http::client()->set_server(server);
|
||||
http::client()->verify_certificates(
|
||||
!UserSettings::instance()->disableCertificateValidation());
|
||||
|
||||
// This starts a chain of `emit`s which ends up doing the
|
||||
// registration. Signals are used rather than normal function
|
||||
// calls so that the dialogs used in UIA work correctly.
|
||||
//
|
||||
// The sequence of events looks something like this:
|
||||
//
|
||||
// doKnownLookup
|
||||
// v
|
||||
// doVersionsCheck
|
||||
// v
|
||||
// doRegistration -> loops the UIAHandler until complete
|
||||
|
||||
emit wellKnownLookup();
|
||||
|
||||
emit registering();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doWellKnownLookup()
|
||||
{
|
||||
http::client()->well_known(
|
||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||
// Check that the homeserver can be reached
|
||||
emit versionsCheck();
|
||||
versionsCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(tr("Autodiscovery failed. Received malformed response."));
|
||||
setHsError(tr("Autodiscovery failed. Received malformed response."));
|
||||
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
emit serverError(tr("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known."));
|
||||
setHsError(tr("Autodiscovery failed. Unknown error when requesting .well-known."));
|
||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||
"requesting .well-known. {} {}",
|
||||
err->status_code,
|
||||
@ -319,98 +90,140 @@ RegisterPage::doWellKnownLookup()
|
||||
|
||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||
http::client()->set_server(res.homeserver.base_url);
|
||||
emit hsErrorChanged();
|
||||
// Check that the homeserver can be reached
|
||||
emit versionsCheck();
|
||||
versionsCheck();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doVersionsCheck()
|
||||
RegisterPage::versionsCheck()
|
||||
{
|
||||
// Make a request to /_matrix/client/versions to check the address
|
||||
// given is a Matrix homeserver.
|
||||
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
if (err->status_code == 404) {
|
||||
emit serverError(tr("The required endpoints were not found. Possibly "
|
||||
"not a Matrix server."));
|
||||
setHsError(
|
||||
tr("The required endpoints were not found. Possibly not a Matrix server."));
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit serverError(tr("Received malformed response. Make sure the homeserver "
|
||||
"domain is valid."));
|
||||
setHsError(
|
||||
tr("Received malformed response. Make sure the homeserver domain is valid."));
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
emit serverError(tr("An unknown error occured. Make sure the "
|
||||
"homeserver domain is valid."));
|
||||
setHsError(tr("An unknown error occured. Make sure the homeserver domain is valid."));
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt registration without an `auth` dict
|
||||
emit registration();
|
||||
http::client()->registration(
|
||||
[this](const mtx::responses::Register &, mtx::http::RequestErr e) {
|
||||
nhlog::net()->debug("Registration check: {}", e);
|
||||
|
||||
if (!e) {
|
||||
setHsError(tr("Server does not support querying registration flows!"));
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
if (e->status_code != 401) {
|
||||
setHsError(tr("Server does not support registration."));
|
||||
emit hsErrorChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
supported_ = true;
|
||||
lookingUpHs_ = false;
|
||||
emit lookingUpHsChanged();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::doRegistration()
|
||||
RegisterPage::checkUsername(QString name)
|
||||
{
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
connect(UIA::instance(), &UIA::error, this, [this](QString msg) {
|
||||
showError(msg);
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
});
|
||||
http::client()->registration(
|
||||
username,
|
||||
password,
|
||||
::UIA::instance()->genericHandler(QStringLiteral("Registration")),
|
||||
registrationCb());
|
||||
}
|
||||
}
|
||||
usernameAvailable_ = usernameUnavailable_ = false;
|
||||
usernameError_.clear();
|
||||
lookingUpUsername_ = true;
|
||||
emit lookingUpUsernameChanged();
|
||||
|
||||
mtx::http::Callback<mtx::responses::Register>
|
||||
RegisterPage::registrationCb()
|
||||
{
|
||||
// Return a function to be used as the callback when an attempt at
|
||||
// registration is made.
|
||||
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
|
||||
if (!err) {
|
||||
http::client()->set_user(res.user_id);
|
||||
http::client()->set_access_token(res.access_token);
|
||||
emit registerOk();
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
return;
|
||||
}
|
||||
http::client()->register_username_available(
|
||||
name.toStdString(),
|
||||
[this](const mtx::responses::Available &available, mtx::http::RequestErr e) {
|
||||
if (e) {
|
||||
if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_INVALID_USERNAME) {
|
||||
usernameError_ = tr("Invalid username.");
|
||||
} else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_USER_IN_USE) {
|
||||
usernameError_ = tr("Name already in use.");
|
||||
} else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_EXCLUSIVE) {
|
||||
usernameError_ = tr("Part of the reserved namespace.");
|
||||
} else {
|
||||
}
|
||||
|
||||
// The server requires registration flows.
|
||||
if (err->status_code == 401) {
|
||||
if (err->matrix_error.unauthorized.flows.empty()) {
|
||||
nhlog::net()->warn("failed to retrieve registration flows: "
|
||||
"status_code({}), matrix_error({}) ",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
|
||||
showError(QString::fromStdString(err->matrix_error.error));
|
||||
};
|
||||
usernameAvailable_ = false;
|
||||
usernameUnavailable_ = true;
|
||||
} else {
|
||||
usernameAvailable_ = available.available;
|
||||
usernameUnavailable_ = !available.available;
|
||||
}
|
||||
lookingUpUsername_ = false;
|
||||
emit lookingUpUsernameChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
RegisterPage::paintEvent(QPaintEvent *)
|
||||
RegisterPage::startRegistration(QString username, QString password, QString devicename)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
// These inputs should still be alright, but check just in case
|
||||
if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) {
|
||||
registrationError_.clear();
|
||||
emit errorChanged();
|
||||
registering_ = true;
|
||||
emit registeringChanged();
|
||||
|
||||
connect(UIA::instance(), &UIA::error, this, [this](QString msg) {
|
||||
setError(msg);
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
});
|
||||
http::client()->registration(
|
||||
username.toStdString(),
|
||||
password.toStdString(),
|
||||
::UIA::instance()->genericHandler(QStringLiteral("Registration")),
|
||||
[this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
|
||||
registering_ = false;
|
||||
emit registeringChanged();
|
||||
|
||||
if (!err) {
|
||||
http::client()->set_user(res.user_id);
|
||||
http::client()->set_access_token(res.access_token);
|
||||
emit registerOk();
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// The server requires registration flows.
|
||||
if (err->status_code == 401 && err->matrix_error.unauthorized.flows.empty()) {
|
||||
nhlog::net()->warn("failed to retrieve registration flows: "
|
||||
"status_code({}), matrix_error({}) ",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
setError(QString::fromStdString(err->matrix_error.error));
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
|
||||
setError(QString::fromStdString(err->matrix_error.error));
|
||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
||||
},
|
||||
devicename.isEmpty() ? LoginPage::initialDeviceName_() : devicename.toStdString());
|
||||
}
|
||||
}
|
||||
|
@ -6,88 +6,69 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <mtx/user_interactive.hpp>
|
||||
#include <mtxclient/http/client.hpp>
|
||||
|
||||
class FlatButton;
|
||||
class RaisedButton;
|
||||
class TextField;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
|
||||
class RegisterPage : public QWidget
|
||||
class RegisterPage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RegisterPage(QWidget *parent = nullptr);
|
||||
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
|
||||
Q_PROPERTY(QString hsError READ hsError NOTIFY hsErrorChanged)
|
||||
Q_PROPERTY(QString usernameError READ usernameError NOTIFY lookingUpUsernameChanged)
|
||||
Q_PROPERTY(bool registering READ registering NOTIFY registeringChanged)
|
||||
Q_PROPERTY(bool supported READ supported NOTIFY lookingUpHsChanged)
|
||||
Q_PROPERTY(bool lookingUpHs READ lookingUpHs NOTIFY lookingUpHsChanged)
|
||||
Q_PROPERTY(bool lookingUpUsername READ lookingUpUsername NOTIFY lookingUpUsernameChanged)
|
||||
Q_PROPERTY(bool usernameAvailable READ usernameAvailable NOTIFY lookingUpUsernameChanged)
|
||||
Q_PROPERTY(bool usernameUnavailable READ usernameUnavailable NOTIFY lookingUpUsernameChanged)
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
public:
|
||||
RegisterPage(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void setServer(QString server);
|
||||
Q_INVOKABLE void checkUsername(QString name);
|
||||
Q_INVOKABLE void startRegistration(QString username, QString password, QString deviceName);
|
||||
Q_INVOKABLE QString initialDeviceName() const;
|
||||
|
||||
bool registering() const { return registering_; }
|
||||
bool supported() const { return supported_; }
|
||||
bool lookingUpHs() const { return lookingUpHs_; }
|
||||
bool lookingUpUsername() const { return lookingUpUsername_; }
|
||||
bool usernameAvailable() const { return usernameAvailable_; }
|
||||
bool usernameUnavailable() const { return usernameUnavailable_; }
|
||||
|
||||
QString error() const { return registrationError_; }
|
||||
QString usernameError() const { return usernameError_; }
|
||||
QString hsError() const { return hsError_; }
|
||||
|
||||
signals:
|
||||
void backButtonClicked();
|
||||
void errorOccurred();
|
||||
void errorChanged();
|
||||
void hsErrorChanged();
|
||||
|
||||
//! Used to trigger the corresponding slot outside of the main thread.
|
||||
void serverError(const QString &err);
|
||||
void registeringChanged();
|
||||
void lookingUpHsChanged();
|
||||
void lookingUpUsernameChanged();
|
||||
|
||||
void wellKnownLookup();
|
||||
void versionsCheck();
|
||||
void registration();
|
||||
|
||||
void registering();
|
||||
void registerOk();
|
||||
|
||||
private slots:
|
||||
void onBackButtonClicked();
|
||||
void onRegisterButtonClicked();
|
||||
|
||||
// function for showing different errors
|
||||
void showError(const QString &msg);
|
||||
void showError(QLabel *label, const QString &msg);
|
||||
|
||||
bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
|
||||
bool checkUsername();
|
||||
bool checkPassword();
|
||||
bool checkPasswordConfirmation();
|
||||
bool checkServer();
|
||||
|
||||
void doWellKnownLookup();
|
||||
void doVersionsCheck();
|
||||
void doRegistration();
|
||||
mtx::http::Callback<mtx::responses::Register> registrationCb();
|
||||
|
||||
private:
|
||||
QVBoxLayout *top_layout_;
|
||||
void versionsCheck();
|
||||
|
||||
QHBoxLayout *back_layout_;
|
||||
QHBoxLayout *logo_layout_;
|
||||
QHBoxLayout *button_layout_;
|
||||
void setHsError(QString err);
|
||||
void setError(QString err);
|
||||
|
||||
QLabel *logo_;
|
||||
QLabel *error_label_;
|
||||
QLabel *error_username_label_;
|
||||
QLabel *error_password_label_;
|
||||
QLabel *error_password_confirmation_label_;
|
||||
QLabel *error_server_label_;
|
||||
QLabel *error_registration_token_label_;
|
||||
QString registrationError_, hsError_, usernameError_;
|
||||
|
||||
FlatButton *back_button_;
|
||||
RaisedButton *register_button_;
|
||||
bool registering_;
|
||||
bool supported_;
|
||||
bool lookingUpHs_;
|
||||
bool lookingUpUsername_;
|
||||
bool usernameAvailable_;
|
||||
bool usernameUnavailable_;
|
||||
|
||||
QWidget *form_widget_;
|
||||
QHBoxLayout *form_wrapper_;
|
||||
QVBoxLayout *form_layout_;
|
||||
|
||||
TextField *username_input_;
|
||||
TextField *password_input_;
|
||||
TextField *password_confirmation_;
|
||||
TextField *server_input_;
|
||||
TextField *registration_token_input_;
|
||||
QString lastServer;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
#include "TrayIcon.h"
|
||||
|
||||
@ -100,7 +101,7 @@ MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State s
|
||||
return result;
|
||||
}
|
||||
|
||||
TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
|
||||
TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
|
||||
: QSystemTrayIcon(parent)
|
||||
{
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
@ -110,13 +111,13 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
|
||||
setIcon(QIcon(icon_));
|
||||
#endif
|
||||
|
||||
QMenu *menu = new QMenu(parent);
|
||||
QMenu *menu = new QMenu();
|
||||
setContextMenu(menu);
|
||||
|
||||
viewAction_ = new QAction(tr("Show"), this);
|
||||
quitAction_ = new QAction(tr("Quit"), this);
|
||||
|
||||
connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
|
||||
connect(viewAction_, &QAction::triggered, parent, &QWindow::show);
|
||||
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||
|
||||
menu->addAction(viewAction_);
|
||||
|
@ -40,7 +40,7 @@ class TrayIcon : public QSystemTrayIcon
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TrayIcon(const QString &filename, QWidget *parent);
|
||||
TrayIcon(const QString &filename, QWindow *parent);
|
||||
|
||||
public slots:
|
||||
void setUnreadCount(int count);
|
||||
|
@ -5,25 +5,14 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFontComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QFontDatabase>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QScroller>
|
||||
#include <QSpinBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
#include <QtQml>
|
||||
#include <mtx/secret_storage.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
@ -33,8 +22,7 @@
|
||||
#include "UserSettingsPage.h"
|
||||
#include "Utils.h"
|
||||
#include "encryption/Olm.h"
|
||||
#include "ui/FlatButton.h"
|
||||
#include "ui/ToggleButton.h"
|
||||
#include "ui/Theme.h"
|
||||
#include "voip/CallDevices.h"
|
||||
|
||||
#include "config/nheko.h"
|
||||
@ -1518,7 +1506,7 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
|
||||
QString homeFolder =
|
||||
QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
auto filepath = QFileDialog::getOpenFileName(
|
||||
MainWindow::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
|
||||
nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
|
||||
if (!filepath.isEmpty()) {
|
||||
i->setRingtone(filepath);
|
||||
i->setRingtone(filepath);
|
||||
@ -1600,11 +1588,11 @@ UserSettingsModel::importSessionKeys()
|
||||
{
|
||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
const QString fileName = QFileDialog::getOpenFileName(
|
||||
MainWindow::instance(), tr("Open Sessions File"), homeFolder, QLatin1String(""));
|
||||
nullptr, tr("Open Sessions File"), homeFolder, QLatin1String(""));
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
|
||||
QMessageBox::warning(nullptr, tr("Error"), file.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1612,7 +1600,7 @@ UserSettingsModel::importSessionKeys()
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
|
||||
bool ok;
|
||||
auto password = QInputDialog::getText(MainWindow::instance(),
|
||||
auto password = QInputDialog::getText(nullptr,
|
||||
tr("File Password"),
|
||||
tr("Enter the passphrase to decrypt the file:"),
|
||||
QLineEdit::Password,
|
||||
@ -1622,8 +1610,7 @@ UserSettingsModel::importSessionKeys()
|
||||
return;
|
||||
|
||||
if (password.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
|
||||
QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1631,7 +1618,7 @@ UserSettingsModel::importSessionKeys()
|
||||
auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
|
||||
cache::importSessionKeys(std::move(sessions));
|
||||
} catch (const std::exception &e) {
|
||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
|
||||
QMessageBox::warning(nullptr, tr("Error"), e.what());
|
||||
}
|
||||
}
|
||||
void
|
||||
@ -1639,7 +1626,7 @@ UserSettingsModel::exportSessionKeys()
|
||||
{
|
||||
// Open password dialog.
|
||||
bool ok;
|
||||
auto password = QInputDialog::getText(MainWindow::instance(),
|
||||
auto password = QInputDialog::getText(nullptr,
|
||||
tr("File Password"),
|
||||
tr("Enter passphrase to encrypt your session keys:"),
|
||||
QLineEdit::Password,
|
||||
@ -1649,19 +1636,18 @@ UserSettingsModel::exportSessionKeys()
|
||||
return;
|
||||
|
||||
if (password.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
|
||||
QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Open file dialog to save the file.
|
||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
const QString fileName = QFileDialog::getSaveFileName(
|
||||
MainWindow::instance(), tr("File to save the exported session keys"), homeFolder);
|
||||
nullptr, tr("File to save the exported session keys"), homeFolder);
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
|
||||
QMessageBox::warning(nullptr, tr("Error"), file.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1679,7 +1665,7 @@ UserSettingsModel::exportSessionKeys()
|
||||
out << prefix << newline << b64 << newline << suffix << newline;
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
|
||||
QMessageBox::warning(nullptr, tr("Error"), e.what());
|
||||
}
|
||||
}
|
||||
void
|
||||
|
@ -7,12 +7,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QFontDatabase>
|
||||
#include <QFrame>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSettings>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "JdenticonProvider.h"
|
||||
#include <optional>
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <QStringBuilder>
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextDocument>
|
||||
#include <QWindow>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <array>
|
||||
@ -770,20 +771,17 @@ utils::luminance(const QColor &col)
|
||||
}
|
||||
|
||||
void
|
||||
utils::centerWidget(QWidget *widget, QWidget *parent)
|
||||
utils::centerWidget(QWidget *widget, QWindow *parent)
|
||||
{
|
||||
if (parent) {
|
||||
widget->window()->windowHandle()->setTransientParent(parent);
|
||||
return;
|
||||
}
|
||||
|
||||
auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint {
|
||||
return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
|
||||
hostRect.center().y() - (childRect.height() * 0.5));
|
||||
};
|
||||
|
||||
if (parent) {
|
||||
widget->move(parent->window()->frameGeometry().topLeft() +
|
||||
parent->window()->rect().center() - widget->rect().center());
|
||||
return;
|
||||
}
|
||||
|
||||
// Deprecated in 5.13: widget->move(findCenter(QApplication::desktop()->screenGeometry()));
|
||||
widget->move(findCenter(QGuiApplication::primaryScreen()->geometry()));
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,7 @@ luminance(const QColor &col);
|
||||
|
||||
//! Center a widget in relation to another widget.
|
||||
void
|
||||
centerWidget(QWidget *widget, QWidget *parent);
|
||||
centerWidget(QWidget *widget, QWindow *parent);
|
||||
|
||||
void
|
||||
restoreCombobox(QComboBox *combo, const QString &value);
|
||||
|
@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
#include "Config.h"
|
||||
#include "WelcomePage.h"
|
||||
#include "ui/RaisedButton.h"
|
||||
#include "ui/TextLabel.h"
|
||||
|
||||
WelcomePage::WelcomePage(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto topLayout_ = new QVBoxLayout(this);
|
||||
topLayout_->setSpacing(20);
|
||||
topLayout_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QFont headingFont;
|
||||
headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
|
||||
QFont subTitleFont;
|
||||
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
|
||||
|
||||
QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
|
||||
|
||||
auto logo_ = new QLabel(this);
|
||||
logo_->setPixmap(icon.pixmap(256));
|
||||
logo_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
|
||||
QString main(tr("Enjoy your stay!"));
|
||||
|
||||
auto intoTxt_ = new TextLabel(heading, this);
|
||||
intoTxt_->setFont(headingFont);
|
||||
intoTxt_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
auto subTitle = new TextLabel(main, this);
|
||||
subTitle->setFont(subTitleFont);
|
||||
subTitle->setAlignment(Qt::AlignCenter);
|
||||
|
||||
topLayout_->addStretch(1);
|
||||
topLayout_->addWidget(logo_);
|
||||
topLayout_->addWidget(intoTxt_);
|
||||
topLayout_->addWidget(subTitle);
|
||||
|
||||
auto btnLayout_ = new QHBoxLayout();
|
||||
btnLayout_->setSpacing(20);
|
||||
btnLayout_->setContentsMargins(0, 20, 0, 20);
|
||||
|
||||
const int fontHeight = QFontMetrics{subTitleFont}.height();
|
||||
const int buttonHeight = fontHeight * 2.5;
|
||||
const int buttonWidth = fontHeight * 8;
|
||||
|
||||
auto registerBtn = new RaisedButton(tr("REGISTER"), this);
|
||||
registerBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
registerBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
registerBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
auto loginBtn = new RaisedButton(tr("LOGIN"), this);
|
||||
loginBtn->setMinimumSize(buttonWidth, buttonHeight);
|
||||
loginBtn->setFontSize(subTitleFont.pointSizeF());
|
||||
loginBtn->setCornerRadius(conf::btn::cornerRadius);
|
||||
|
||||
btnLayout_->addStretch(1);
|
||||
btnLayout_->addWidget(registerBtn);
|
||||
btnLayout_->addWidget(loginBtn);
|
||||
btnLayout_->addStretch(1);
|
||||
|
||||
topLayout_->addLayout(btnLayout_);
|
||||
topLayout_->addStretch(1);
|
||||
|
||||
connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
|
||||
connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
|
||||
}
|
||||
|
||||
void
|
||||
WelcomePage::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class WelcomePage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WelcomePage(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
signals:
|
||||
// Notify that the user wants to login in.
|
||||
void userLogin();
|
||||
|
||||
// Notify that the user wants to register.
|
||||
void userRegister();
|
||||
};
|
@ -17,6 +17,7 @@
|
||||
#include <QLibraryInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QPoint>
|
||||
#include <QQuickView>
|
||||
#include <QScreen>
|
||||
#include <QStandardPaths>
|
||||
#include <QTranslator>
|
||||
@ -279,6 +280,7 @@ main(int argc, char *argv[])
|
||||
font.setPointSizeF(settings.lock()->fontSize());
|
||||
|
||||
app.setFont(font);
|
||||
settings.lock()->applyTheme();
|
||||
|
||||
if (QLocale().language() == QLocale::C)
|
||||
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom));
|
||||
@ -296,9 +298,10 @@ main(int argc, char *argv[])
|
||||
app.installTranslator(&appTranslator);
|
||||
|
||||
MainWindow w;
|
||||
// QQuickView w;
|
||||
|
||||
// Move the MainWindow to the center
|
||||
w.move(screenCenter(w.width(), w.height()));
|
||||
// w.move(screenCenter(w.width(), w.height()));
|
||||
|
||||
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
|
||||
w.show();
|
||||
@ -314,7 +317,7 @@ main(int argc, char *argv[])
|
||||
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
|
||||
w.show();
|
||||
w.raise();
|
||||
w.activateWindow();
|
||||
w.requestActivate();
|
||||
});
|
||||
|
||||
// It seems like handling the message in a blocking manner is a no-go. I have no idea how to
|
||||
|
@ -266,8 +266,8 @@ void
|
||||
InputBar::openFileSelection()
|
||||
{
|
||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
const auto fileName = QFileDialog::getOpenFileName(
|
||||
ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
|
||||
const auto fileName =
|
||||
QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
|
||||
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
@ -659,7 +659,7 @@ InputBar::command(const QString &command, QString args)
|
||||
void
|
||||
InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats)
|
||||
{
|
||||
auto *previewDialog_ = new dialogs::PreviewUploadOverlay(ChatPage::instance());
|
||||
auto *previewDialog_ = new dialogs::PreviewUploadOverlay(nullptr);
|
||||
previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// Force SVG to _not_ be handled as an image, but as raw data
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Cache_p.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "TimelineModel.h"
|
||||
@ -275,7 +276,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
|
||||
|
||||
connect(newRoom.data(),
|
||||
&TimelineModel::newEncryptedImage,
|
||||
manager->imageProvider(),
|
||||
MainWindow::instance()->imageProvider(),
|
||||
&MxcImageProvider::addEncryptionInfo);
|
||||
connect(newRoom.data(),
|
||||
&TimelineModel::forwardToRoom,
|
||||
@ -509,7 +510,7 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
|
||||
// room_model->addEvents(room.timeline);
|
||||
connect(room_model.data(),
|
||||
&TimelineModel::newCallEvent,
|
||||
manager->callManager(),
|
||||
ChatPage::instance()->callManager(),
|
||||
&CallManager::syncEvent,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
|
@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index)
|
||||
if (index != oldIndex)
|
||||
emit currentIndexChanged(index);
|
||||
|
||||
if (!ChatPage::instance()->isActiveWindow())
|
||||
if (MainWindow::instance() != QGuiApplication::focusWindow())
|
||||
return;
|
||||
|
||||
if (!currentId.startsWith('m')) {
|
||||
@ -1495,7 +1495,7 @@ TimelineModel::saveMedia(const QString &eventId) const
|
||||
const QString openLocation = downloadsFolder + "/" + originalFilename;
|
||||
|
||||
const QString filename =
|
||||
QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString);
|
||||
QFileDialog::getSaveFileName(nullptr, dialogTitle, openLocation, filterString);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return false;
|
||||
|
@ -5,10 +5,10 @@
|
||||
|
||||
#include "TimelineViewManager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDropEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QMetaType>
|
||||
#include <QPalette>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QStandardPaths>
|
||||
@ -45,10 +45,6 @@
|
||||
#include "ui/NhekoGlobalObject.h"
|
||||
#include "ui/UIA.h"
|
||||
|
||||
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
||||
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
|
||||
Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
|
||||
|
||||
namespace msgs = mtx::events::msg;
|
||||
|
||||
namespace {
|
||||
@ -102,19 +98,6 @@ void
|
||||
TimelineViewManager::updateColorPalette()
|
||||
{
|
||||
userColors.clear();
|
||||
|
||||
if (ChatPage::instance()->userSettings()->theme() == QLatin1String("light")) {
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
|
||||
QPalette());
|
||||
} else if (ChatPage::instance()->userSettings()->theme() == QLatin1String("dark")) {
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"),
|
||||
QPalette());
|
||||
} else {
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentActivePalette"), QPalette());
|
||||
view->rootContext()->setContextProperty(QStringLiteral("currentInactivePalette"), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
QColor
|
||||
@ -126,112 +109,15 @@ TimelineViewManager::userColor(QString id, QColor background)
|
||||
return userColors.value(idx);
|
||||
}
|
||||
|
||||
TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
|
||||
TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent)
|
||||
: QObject(parent)
|
||||
, imgProvider(new MxcImageProvider())
|
||||
, colorImgProvider(new ColorImageProvider())
|
||||
, blurhashProvider(new BlurhashProvider())
|
||||
, jdenticonProvider(new JdenticonProvider())
|
||||
, rooms_(new RoomlistModel(this))
|
||||
, communities_(new CommunitiesModel(this))
|
||||
, callManager_(callManager)
|
||||
, verificationManager_(new VerificationManager(this))
|
||||
, presenceEmitter(new PresenceEmitter(this))
|
||||
{
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationDone>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationKey>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationMac>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||
qRegisterMetaType<CombinedImagePackModel *>();
|
||||
|
||||
qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
|
||||
|
||||
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<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
|
||||
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
|
||||
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"));
|
||||
|
||||
static auto self = this;
|
||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance());
|
||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
|
||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
|
||||
qmlRegisterSingletonType<RoomlistModel>(
|
||||
"im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
auto ptr = new FilteredRoomlistModel(self->rooms_);
|
||||
@ -247,79 +133,15 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
||||
return ptr;
|
||||
});
|
||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_);
|
||||
qmlRegisterSingletonInstance(
|
||||
"im.nheko", 1, 0, "Settings", ChatPage::instance()->userSettings().data());
|
||||
qmlRegisterSingletonInstance(
|
||||
"im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
|
||||
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, "VerificationManager", verificationManager_);
|
||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
|
||||
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;
|
||||
});
|
||||
|
||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
||||
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||
|
||||
qmlRegisterUncreatableType<FilteredCommunitiesModel>(
|
||||
"im.nheko",
|
||||
1,
|
||||
0,
|
||||
"FilteredCommunitiesModel",
|
||||
QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
|
||||
|
||||
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
|
||||
qmlRegisterUncreatableType<emoji::Emoji>(
|
||||
"im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
|
||||
qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
|
||||
"im.nheko.EmojiModel",
|
||||
1,
|
||||
0,
|
||||
"EmojiCategory",
|
||||
QStringLiteral("Error: Only enums"));
|
||||
|
||||
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
|
||||
|
||||
#ifdef USE_QUICK_VIEW
|
||||
view = new QQuickView(parent);
|
||||
container = QWidget::createWindowContainer(view, parent);
|
||||
#else
|
||||
view = new QQuickWidget(parent);
|
||||
container = view;
|
||||
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
|
||||
nhlog::ui()->debug("Status changed to {}", status);
|
||||
});
|
||||
#endif
|
||||
container->setMinimumSize(200, 200);
|
||||
updateColorPalette();
|
||||
view->engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
|
||||
view->engine()->addImageProvider(QStringLiteral("colorimage"), colorImgProvider);
|
||||
view->engine()->addImageProvider(QStringLiteral("blurhash"), blurhashProvider);
|
||||
if (JdenticonProvider::isAvailable())
|
||||
view->engine()->addImageProvider(QStringLiteral("jdenticon"), jdenticonProvider);
|
||||
view->setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
|
||||
|
||||
connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
|
||||
connect(UserSettings::instance().get(),
|
||||
&UserSettings::themeChanged,
|
||||
this,
|
||||
&TimelineViewManager::updateColorPalette);
|
||||
connect(parent,
|
||||
&ChatPage::receivedRoomDeviceVerificationRequest,
|
||||
verificationManager_,
|
||||
@ -336,6 +158,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
||||
isInitialSync_ = true;
|
||||
emit initialSyncChanged(true);
|
||||
});
|
||||
connect(qobject_cast<QApplication *>(QApplication::instance()),
|
||||
&QApplication::focusWindowChanged,
|
||||
this,
|
||||
&TimelineViewManager::focusChanged);
|
||||
}
|
||||
|
||||
bool
|
||||
TimelineViewManager::isWindowFocused() const
|
||||
{
|
||||
return MainWindow::instance() == QApplication::focusWindow();
|
||||
}
|
||||
|
||||
void
|
||||
@ -379,7 +211,8 @@ void
|
||||
TimelineViewManager::setVideoCallItem()
|
||||
{
|
||||
WebRTCSession::instance().setVideoItem(
|
||||
view->rootObject()->findChild<QQuickItem *>(QStringLiteral("videoCallItem")));
|
||||
MainWindow::instance()->rootObject()->findChild<QQuickItem *>(
|
||||
QStringLiteral("videoCallItem")));
|
||||
}
|
||||
|
||||
void
|
||||
@ -401,7 +234,7 @@ TimelineViewManager::showEvent(const QString &room_id, const QString &event_id)
|
||||
if (auto room = rooms_->getRoomById(room_id)) {
|
||||
if (rooms_->currentRoom() != room) {
|
||||
rooms_->setCurrentRoom(room_id);
|
||||
container->setFocus();
|
||||
MainWindow::instance()->requestActivate();
|
||||
nhlog::ui()->info("Activated room {}", room_id.toStdString());
|
||||
}
|
||||
|
||||
@ -439,7 +272,7 @@ TimelineViewManager::saveMedia(QString mxcUrl)
|
||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
|
||||
|
||||
const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation);
|
||||
const QString filename = QFileDialog::getSaveFileName(nullptr, {}, openLocation);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
@ -590,12 +423,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::focusTimeline()
|
||||
{
|
||||
getWidget()->setFocus();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
|
||||
QString roomId)
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include <QHash>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QQuickView>
|
||||
#include <QQuickWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx/common.hpp>
|
||||
@ -43,23 +41,19 @@ class TimelineViewManager : public QObject
|
||||
|
||||
Q_PROPERTY(
|
||||
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
|
||||
Q_PROPERTY(
|
||||
bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
|
||||
Q_PROPERTY(bool isWindowFocused READ isWindowFocused NOTIFY focusChanged)
|
||||
|
||||
public:
|
||||
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
|
||||
QWidget *getWidget() const { return container; }
|
||||
|
||||
void sync(const mtx::responses::Sync &sync_);
|
||||
|
||||
MxcImageProvider *imageProvider() { return imgProvider; }
|
||||
CallManager *callManager() { return callManager_; }
|
||||
VerificationManager *verificationManager() { return verificationManager_; }
|
||||
|
||||
void clearAll() { rooms_->clear(); }
|
||||
|
||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||
bool isWindowFocused() const { return isWindowFocused_; }
|
||||
bool isWindowFocused() const;
|
||||
Q_INVOKABLE void openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId);
|
||||
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
||||
Q_INVOKABLE void saveMedia(QString mxcUrl);
|
||||
@ -98,14 +92,8 @@ public slots:
|
||||
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
||||
void initializeRoomlist();
|
||||
void chatFocusChanged(bool focused)
|
||||
{
|
||||
isWindowFocused_ = focused;
|
||||
emit focusChanged();
|
||||
}
|
||||
|
||||
void showEvent(const QString &room_id, const QString &event_id);
|
||||
void focusTimeline();
|
||||
|
||||
void updateColorPalette();
|
||||
void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
|
||||
@ -122,26 +110,12 @@ public slots:
|
||||
RoomlistModel *rooms() { return rooms_; }
|
||||
|
||||
private:
|
||||
#ifdef USE_QUICK_VIEW
|
||||
QQuickView *view;
|
||||
#else
|
||||
QQuickWidget *view;
|
||||
#endif
|
||||
QWidget *container;
|
||||
|
||||
MxcImageProvider *imgProvider;
|
||||
ColorImageProvider *colorImgProvider;
|
||||
BlurhashProvider *blurhashProvider;
|
||||
JdenticonProvider *jdenticonProvider;
|
||||
|
||||
bool isInitialSync_ = true;
|
||||
bool isWindowFocused_ = false;
|
||||
bool isInitialSync_ = true;
|
||||
|
||||
RoomlistModel *rooms_ = nullptr;
|
||||
CommunitiesModel *communities_ = nullptr;
|
||||
|
||||
// don't move this above the rooms_
|
||||
CallManager *callManager_ = nullptr;
|
||||
VerificationManager *verificationManager_ = nullptr;
|
||||
PresenceEmitter *presenceEmitter = nullptr;
|
||||
|
||||
|
@ -1,108 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "DropShadow.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
#include <QPainter>
|
||||
|
||||
void
|
||||
DropShadow::draw(QPainter &painter,
|
||||
qint16 margin,
|
||||
qreal radius,
|
||||
QColor start,
|
||||
QColor end,
|
||||
qreal startPosition,
|
||||
qreal endPosition0,
|
||||
qreal endPosition1,
|
||||
qreal width,
|
||||
qreal height)
|
||||
{
|
||||
painter.setPen(Qt::NoPen);
|
||||
|
||||
QLinearGradient gradient;
|
||||
gradient.setColorAt(startPosition, start);
|
||||
gradient.setColorAt(endPosition0, end);
|
||||
|
||||
// Right
|
||||
QPointF right0(width - margin, height / 2);
|
||||
QPointF right1(width, height / 2);
|
||||
gradient.setStart(right0);
|
||||
gradient.setFinalStop(right1);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
// Deprecated in 5.13: painter.drawRoundRect(
|
||||
// QRectF(QPointF(width - margin * radius, margin), QPointF(width, height -
|
||||
// margin)), 0.0, 0.0);
|
||||
painter.drawRoundedRect(
|
||||
QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), 0.0, 0.0);
|
||||
|
||||
// Left
|
||||
QPointF left0(margin, height / 2);
|
||||
QPointF left1(0, height / 2);
|
||||
gradient.setStart(left0);
|
||||
gradient.setFinalStop(left1);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(
|
||||
QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0);
|
||||
|
||||
// Top
|
||||
QPointF top0(width / 2, margin);
|
||||
QPointF top1(width / 2, 0);
|
||||
gradient.setStart(top0);
|
||||
gradient.setFinalStop(top1);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0);
|
||||
|
||||
// Bottom
|
||||
QPointF bottom0(width / 2, height - margin);
|
||||
QPointF bottom1(width / 2, height);
|
||||
gradient.setStart(bottom0);
|
||||
gradient.setFinalStop(bottom1);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(
|
||||
QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0);
|
||||
|
||||
// BottomRight
|
||||
QPointF bottomright0(width - margin, height - margin);
|
||||
QPointF bottomright1(width, height);
|
||||
gradient.setStart(bottomright0);
|
||||
gradient.setFinalStop(bottomright1);
|
||||
gradient.setColorAt(endPosition1, end);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(QRectF(bottomright0, bottomright1), 0.0, 0.0);
|
||||
|
||||
// BottomLeft
|
||||
QPointF bottomleft0(margin, height - margin);
|
||||
QPointF bottomleft1(0, height);
|
||||
gradient.setStart(bottomleft0);
|
||||
gradient.setFinalStop(bottomleft1);
|
||||
gradient.setColorAt(endPosition1, end);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0);
|
||||
|
||||
// TopLeft
|
||||
QPointF topleft0(margin, margin);
|
||||
QPointF topleft1(0, 0);
|
||||
gradient.setStart(topleft0);
|
||||
gradient.setFinalStop(topleft1);
|
||||
gradient.setColorAt(endPosition1, end);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(QRectF(topleft0, topleft1), 0.0, 0.0);
|
||||
|
||||
// TopRight
|
||||
QPointF topright0(width - margin, margin);
|
||||
QPointF topright1(width, 0);
|
||||
gradient.setStart(topright0);
|
||||
gradient.setFinalStop(topright1);
|
||||
gradient.setColorAt(endPosition1, end);
|
||||
painter.setBrush(QBrush(gradient));
|
||||
painter.drawRoundedRect(QRectF(topright0, topright1), 0.0, 0.0);
|
||||
|
||||
// Widget
|
||||
painter.setBrush(QBrush(QColor(0xff, 0xff, 0xff)));
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.drawRoundedRect(
|
||||
QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), radius, radius);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
|
||||
class QPainter;
|
||||
|
||||
class DropShadow
|
||||
{
|
||||
public:
|
||||
static void draw(QPainter &painter,
|
||||
qint16 margin,
|
||||
qreal radius,
|
||||
QColor start,
|
||||
QColor end,
|
||||
qreal startPosition,
|
||||
qreal endPosition0,
|
||||
qreal endPosition1,
|
||||
qreal width,
|
||||
qreal height);
|
||||
};
|
@ -1,730 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QEventTransition>
|
||||
#include <QFontDatabase>
|
||||
#include <QIcon>
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QResizeEvent>
|
||||
#include <QSignalTransition>
|
||||
|
||||
#include "FlatButton.h"
|
||||
#include "Ripple.h"
|
||||
#include "RippleOverlay.h"
|
||||
#include "ThemeManager.h"
|
||||
|
||||
// The ampersand is automatically set in QPushButton or QCheckbx
|
||||
// by KDEPlatformTheme plugin in Qt5.
|
||||
// [https://bugs.kde.org/show_bug.cgi?id=337491]
|
||||
//
|
||||
// A workaroud is to add
|
||||
//
|
||||
// [Development]
|
||||
// AutoCheckAccelerators=false
|
||||
//
|
||||
// to ~/.config/kdeglobals
|
||||
static QString
|
||||
removeKDEAccelerators(QString text)
|
||||
{
|
||||
return text.remove(QChar('&'));
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::init()
|
||||
{
|
||||
ripple_overlay_ = new RippleOverlay(this);
|
||||
state_machine_ = new FlatButtonStateMachine(this);
|
||||
role_ = ui::Role::Default;
|
||||
ripple_style_ = ui::RippleStyle::PositionedRipple;
|
||||
icon_placement_ = ui::ButtonIconPlacement::LeftIcon;
|
||||
overlay_style_ = ui::OverlayStyle::GrayOverlay;
|
||||
bg_mode_ = Qt::TransparentMode;
|
||||
fixed_ripple_radius_ = 64;
|
||||
corner_radius_ = 3;
|
||||
base_opacity_ = 0.13;
|
||||
font_size_ = 10; // 10.5;
|
||||
use_fixed_ripple_radius_ = false;
|
||||
|
||||
setStyle(&ThemeManager::instance());
|
||||
setAttribute(Qt::WA_Hover);
|
||||
setMouseTracking(true);
|
||||
setCursor(QCursor(Qt::PointingHandCursor));
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(rect(), corner_radius_, corner_radius_);
|
||||
|
||||
ripple_overlay_->setClipPath(path);
|
||||
ripple_overlay_->setClipping(true);
|
||||
|
||||
state_machine_->setupProperties();
|
||||
state_machine_->startAnimations();
|
||||
}
|
||||
|
||||
FlatButton::FlatButton(QWidget *parent, ui::ButtonPreset preset)
|
||||
: QPushButton(parent)
|
||||
{
|
||||
init();
|
||||
applyPreset(preset);
|
||||
}
|
||||
|
||||
FlatButton::FlatButton(const QString &text, QWidget *parent, ui::ButtonPreset preset)
|
||||
: QPushButton(text, parent)
|
||||
{
|
||||
init();
|
||||
applyPreset(preset);
|
||||
}
|
||||
|
||||
FlatButton::FlatButton(const QString &text, ui::Role role, QWidget *parent, ui::ButtonPreset preset)
|
||||
: QPushButton(text, parent)
|
||||
{
|
||||
init();
|
||||
applyPreset(preset);
|
||||
setRole(role);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::applyPreset(ui::ButtonPreset preset)
|
||||
{
|
||||
switch (preset) {
|
||||
case ui::ButtonPreset::FlatPreset:
|
||||
setOverlayStyle(ui::OverlayStyle::NoOverlay);
|
||||
break;
|
||||
case ui::ButtonPreset::CheckablePreset:
|
||||
setOverlayStyle(ui::OverlayStyle::NoOverlay);
|
||||
setCheckable(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setRole(ui::Role role)
|
||||
{
|
||||
role_ = role;
|
||||
state_machine_->setupProperties();
|
||||
}
|
||||
|
||||
ui::Role
|
||||
FlatButton::role() const
|
||||
{
|
||||
return role_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setForegroundColor(const QColor &color)
|
||||
{
|
||||
foreground_color_ = color;
|
||||
emit foregroundColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
FlatButton::foregroundColor() const
|
||||
{
|
||||
if (!foreground_color_.isValid()) {
|
||||
if (bg_mode_ == Qt::OpaqueMode) {
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("BrightWhite"));
|
||||
}
|
||||
|
||||
switch (role_) {
|
||||
case ui::Role::Primary:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Blue"));
|
||||
case ui::Role::Secondary:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Gray"));
|
||||
case ui::Role::Default:
|
||||
default:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Black"));
|
||||
}
|
||||
}
|
||||
|
||||
return foreground_color_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setBackgroundColor(const QColor &color)
|
||||
{
|
||||
background_color_ = color;
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
FlatButton::backgroundColor() const
|
||||
{
|
||||
if (!background_color_.isValid()) {
|
||||
switch (role_) {
|
||||
case ui::Role::Primary:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Blue"));
|
||||
case ui::Role::Secondary:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Gray"));
|
||||
case ui::Role::Default:
|
||||
default:
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("Black"));
|
||||
}
|
||||
}
|
||||
|
||||
return background_color_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setOverlayColor(const QColor &color)
|
||||
{
|
||||
overlay_color_ = color;
|
||||
setOverlayStyle(ui::OverlayStyle::TintedOverlay);
|
||||
emit overlayColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
FlatButton::overlayColor() const
|
||||
{
|
||||
if (!overlay_color_.isValid()) {
|
||||
return foregroundColor();
|
||||
}
|
||||
|
||||
return overlay_color_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setDisabledForegroundColor(const QColor &color)
|
||||
{
|
||||
disabled_color_ = color;
|
||||
emit disabledForegroundColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
FlatButton::disabledForegroundColor() const
|
||||
{
|
||||
if (!disabled_color_.isValid()) {
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("FadedWhite"));
|
||||
}
|
||||
|
||||
return disabled_color_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setDisabledBackgroundColor(const QColor &color)
|
||||
{
|
||||
disabled_background_color_ = color;
|
||||
emit disabledBackgroundColorChanged();
|
||||
}
|
||||
|
||||
QColor
|
||||
FlatButton::disabledBackgroundColor() const
|
||||
{
|
||||
if (!disabled_background_color_.isValid()) {
|
||||
return ThemeManager::instance().themeColor(QStringLiteral("FadedWhite"));
|
||||
}
|
||||
|
||||
return disabled_background_color_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setFontSize(qreal size)
|
||||
{
|
||||
font_size_ = size;
|
||||
|
||||
QFont f(font());
|
||||
f.setPointSizeF(size);
|
||||
setFont(f);
|
||||
|
||||
emit fontSizeChanged();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
qreal
|
||||
FlatButton::fontSize() const
|
||||
{
|
||||
return font_size_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setOverlayStyle(ui::OverlayStyle style)
|
||||
{
|
||||
overlay_style_ = style;
|
||||
update();
|
||||
}
|
||||
|
||||
ui::OverlayStyle
|
||||
FlatButton::overlayStyle() const
|
||||
{
|
||||
return overlay_style_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setRippleStyle(ui::RippleStyle style)
|
||||
{
|
||||
ripple_style_ = style;
|
||||
}
|
||||
|
||||
ui::RippleStyle
|
||||
FlatButton::rippleStyle() const
|
||||
{
|
||||
return ripple_style_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setIconPlacement(ui::ButtonIconPlacement placement)
|
||||
{
|
||||
icon_placement_ = placement;
|
||||
update();
|
||||
}
|
||||
|
||||
ui::ButtonIconPlacement
|
||||
FlatButton::iconPlacement() const
|
||||
{
|
||||
return icon_placement_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setCornerRadius(qreal radius)
|
||||
{
|
||||
corner_radius_ = radius;
|
||||
updateClipPath();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal
|
||||
FlatButton::cornerRadius() const
|
||||
{
|
||||
return corner_radius_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setBackgroundMode(Qt::BGMode mode)
|
||||
{
|
||||
bg_mode_ = mode;
|
||||
state_machine_->setupProperties();
|
||||
}
|
||||
|
||||
Qt::BGMode
|
||||
FlatButton::backgroundMode() const
|
||||
{
|
||||
return bg_mode_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setBaseOpacity(qreal opacity)
|
||||
{
|
||||
base_opacity_ = opacity;
|
||||
state_machine_->setupProperties();
|
||||
}
|
||||
|
||||
qreal
|
||||
FlatButton::baseOpacity() const
|
||||
{
|
||||
return base_opacity_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setCheckable(bool value)
|
||||
{
|
||||
state_machine_->updateCheckedStatus();
|
||||
state_machine_->setCheckedOverlayProgress(0);
|
||||
|
||||
QPushButton::setCheckable(value);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setHasFixedRippleRadius(bool value)
|
||||
{
|
||||
use_fixed_ripple_radius_ = value;
|
||||
}
|
||||
|
||||
bool
|
||||
FlatButton::hasFixedRippleRadius() const
|
||||
{
|
||||
return use_fixed_ripple_radius_;
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::setFixedRippleRadius(qreal radius)
|
||||
{
|
||||
fixed_ripple_radius_ = radius;
|
||||
setHasFixedRippleRadius(true);
|
||||
}
|
||||
|
||||
QSize
|
||||
FlatButton::sizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
|
||||
QSize label(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
|
||||
|
||||
int w = 20 + label.width();
|
||||
int h = label.height();
|
||||
|
||||
if (!icon().isNull()) {
|
||||
w += iconSize().width() + FlatButton::IconPadding;
|
||||
h = qMax(h, iconSize().height());
|
||||
}
|
||||
|
||||
return QSize(w, 20 + h);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::checkStateSet()
|
||||
{
|
||||
state_machine_->updateCheckedStatus();
|
||||
QPushButton::checkStateSet();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (ui::RippleStyle::NoRipple != ripple_style_) {
|
||||
QPoint pos;
|
||||
qreal radiusEndValue;
|
||||
|
||||
if (ui::RippleStyle::CenteredRipple == ripple_style_) {
|
||||
pos = rect().center();
|
||||
} else {
|
||||
pos = event->pos();
|
||||
}
|
||||
|
||||
if (use_fixed_ripple_radius_) {
|
||||
radiusEndValue = fixed_ripple_radius_;
|
||||
} else {
|
||||
radiusEndValue = static_cast<qreal>(width()) / 2;
|
||||
}
|
||||
|
||||
Ripple *ripple = new Ripple(pos);
|
||||
|
||||
ripple->setRadiusEndValue(radiusEndValue);
|
||||
ripple->setOpacityStartValue(0.35);
|
||||
ripple->setColor(foregroundColor());
|
||||
ripple->radiusAnimation()->setDuration(250);
|
||||
ripple->opacityAnimation()->setDuration(250);
|
||||
|
||||
ripple_overlay_->addRipple(ripple);
|
||||
}
|
||||
|
||||
QPushButton::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QPushButton::mouseReleaseEvent(event);
|
||||
state_machine_->updateCheckedStatus();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QPushButton::resizeEvent(event);
|
||||
updateClipPath();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
const qreal cr = corner_radius_;
|
||||
|
||||
if (cr > 0) {
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(rect(), cr, cr);
|
||||
|
||||
painter.setClipPath(path);
|
||||
painter.setClipping(true);
|
||||
}
|
||||
|
||||
paintBackground(&painter);
|
||||
|
||||
painter.setOpacity(1);
|
||||
painter.setClipping(false);
|
||||
|
||||
paintForeground(&painter);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::paintBackground(QPainter *painter)
|
||||
{
|
||||
const qreal overlayOpacity = state_machine_->overlayOpacity();
|
||||
const qreal checkedProgress = state_machine_->checkedOverlayProgress();
|
||||
|
||||
if (Qt::OpaqueMode == bg_mode_) {
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
|
||||
if (isEnabled()) {
|
||||
brush.setColor(backgroundColor());
|
||||
} else {
|
||||
brush.setColor(disabledBackgroundColor());
|
||||
}
|
||||
|
||||
painter->setOpacity(1);
|
||||
painter->setBrush(brush);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->drawRect(rect());
|
||||
}
|
||||
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
painter->setPen(Qt::NoPen);
|
||||
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ui::OverlayStyle::NoOverlay != overlay_style_) && (overlayOpacity > 0)) {
|
||||
if (ui::OverlayStyle::TintedOverlay == overlay_style_) {
|
||||
brush.setColor(overlayColor());
|
||||
} else {
|
||||
brush.setColor(Qt::gray);
|
||||
}
|
||||
|
||||
painter->setOpacity(overlayOpacity);
|
||||
painter->setBrush(brush);
|
||||
painter->drawRect(rect());
|
||||
}
|
||||
|
||||
if (isCheckable() && checkedProgress > 0) {
|
||||
const qreal q = Qt::TransparentMode == bg_mode_ ? 0.45 : 0.7;
|
||||
brush.setColor(foregroundColor());
|
||||
painter->setOpacity(q * checkedProgress);
|
||||
painter->setBrush(brush);
|
||||
QRect r(rect());
|
||||
r.setHeight(static_cast<qreal>(r.height()) * checkedProgress);
|
||||
painter->drawRect(r);
|
||||
}
|
||||
}
|
||||
|
||||
#define COLOR_INTERPOLATE(CH) (1 - progress) * source.CH() + progress *dest.CH()
|
||||
|
||||
void
|
||||
FlatButton::paintForeground(QPainter *painter)
|
||||
{
|
||||
if (isEnabled()) {
|
||||
painter->setPen(foregroundColor());
|
||||
const qreal progress = state_machine_->checkedOverlayProgress();
|
||||
|
||||
if (isCheckable() && progress > 0) {
|
||||
QColor source = foregroundColor();
|
||||
QColor dest = Qt::TransparentMode == bg_mode_ ? Qt::white : backgroundColor();
|
||||
if (qFuzzyCompare(1, progress)) {
|
||||
painter->setPen(dest);
|
||||
} else {
|
||||
painter->setPen(QColor(COLOR_INTERPOLATE(red),
|
||||
COLOR_INTERPOLATE(green),
|
||||
COLOR_INTERPOLATE(blue),
|
||||
COLOR_INTERPOLATE(alpha)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
painter->setPen(disabledForegroundColor());
|
||||
}
|
||||
|
||||
if (icon().isNull()) {
|
||||
painter->drawText(rect(), Qt::AlignCenter, removeKDEAccelerators(text()));
|
||||
return;
|
||||
}
|
||||
|
||||
QSize textSize(fontMetrics().size(Qt::TextSingleLine, removeKDEAccelerators(text())));
|
||||
QSize base(size() - textSize);
|
||||
|
||||
const int iw = iconSize().width() + IconPadding;
|
||||
QPoint pos((base.width() - iw) / 2, 0);
|
||||
|
||||
QRect textGeometry(pos + QPoint(0, base.height() / 2), textSize);
|
||||
QRect iconGeometry(pos + QPoint(0, (height() - iconSize().height()) / 2), iconSize());
|
||||
|
||||
/* if (ui::LeftIcon == icon_placement_) { */
|
||||
/* textGeometry.translate(iw, 0); */
|
||||
/* } else { */
|
||||
/* iconGeometry.translate(textSize.width() + IconPadding, 0); */
|
||||
/* } */
|
||||
|
||||
painter->drawText(textGeometry, Qt::AlignCenter, removeKDEAccelerators(text()));
|
||||
|
||||
QPixmap pixmap = icon().pixmap(iconSize());
|
||||
QPainter icon(&pixmap);
|
||||
icon.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
icon.fillRect(pixmap.rect(), painter->pen().color());
|
||||
painter->drawPixmap(iconGeometry, pixmap);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButton::updateClipPath()
|
||||
{
|
||||
const qreal radius = corner_radius_;
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(rect(), radius, radius);
|
||||
ripple_overlay_->setClipPath(path);
|
||||
}
|
||||
|
||||
FlatButtonStateMachine::FlatButtonStateMachine(FlatButton *parent)
|
||||
: QStateMachine(parent)
|
||||
, button_(parent)
|
||||
, top_level_state_(new QState(QState::ParallelStates))
|
||||
, config_state_(new QState(top_level_state_))
|
||||
, checkable_state_(new QState(top_level_state_))
|
||||
, checked_state_(new QState(checkable_state_))
|
||||
, unchecked_state_(new QState(checkable_state_))
|
||||
, neutral_state_(new QState(config_state_))
|
||||
, neutral_focused_state_(new QState(config_state_))
|
||||
, hovered_state_(new QState(config_state_))
|
||||
, hovered_focused_state_(new QState(config_state_))
|
||||
, pressed_state_(new QState(config_state_))
|
||||
, overlay_opacity_(0)
|
||||
, checked_overlay_progress_(parent->isChecked() ? 1 : 0)
|
||||
, was_checked_(false)
|
||||
{
|
||||
Q_ASSERT(parent);
|
||||
|
||||
parent->installEventFilter(this);
|
||||
|
||||
config_state_->setInitialState(neutral_state_);
|
||||
addState(top_level_state_);
|
||||
setInitialState(top_level_state_);
|
||||
|
||||
checkable_state_->setInitialState(parent->isChecked() ? checked_state_ : unchecked_state_);
|
||||
QSignalTransition *transition;
|
||||
QPropertyAnimation *animation;
|
||||
|
||||
transition = new QSignalTransition(this, SIGNAL(buttonChecked()));
|
||||
transition->setTargetState(checked_state_);
|
||||
unchecked_state_->addTransition(transition);
|
||||
|
||||
animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
|
||||
animation->setDuration(200);
|
||||
transition->addAnimation(animation);
|
||||
|
||||
transition = new QSignalTransition(this, SIGNAL(buttonUnchecked()));
|
||||
transition->setTargetState(unchecked_state_);
|
||||
checked_state_->addTransition(transition);
|
||||
|
||||
animation = new QPropertyAnimation(this, "checkedOverlayProgress", this);
|
||||
animation->setDuration(200);
|
||||
transition->addAnimation(animation);
|
||||
|
||||
addTransition(button_, QEvent::FocusIn, neutral_state_, neutral_focused_state_);
|
||||
addTransition(button_, QEvent::FocusOut, neutral_focused_state_, neutral_state_);
|
||||
addTransition(button_, QEvent::Enter, neutral_state_, hovered_state_);
|
||||
addTransition(button_, QEvent::Leave, hovered_state_, neutral_state_);
|
||||
addTransition(button_, QEvent::Enter, neutral_focused_state_, hovered_focused_state_);
|
||||
addTransition(button_, QEvent::Leave, hovered_focused_state_, neutral_focused_state_);
|
||||
addTransition(button_, QEvent::FocusIn, hovered_state_, hovered_focused_state_);
|
||||
addTransition(button_, QEvent::FocusOut, hovered_focused_state_, hovered_state_);
|
||||
addTransition(this, SIGNAL(buttonPressed()), hovered_state_, pressed_state_);
|
||||
addTransition(button_, QEvent::Leave, pressed_state_, neutral_focused_state_);
|
||||
addTransition(button_, QEvent::FocusOut, pressed_state_, hovered_state_);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::setOverlayOpacity(qreal opacity)
|
||||
{
|
||||
overlay_opacity_ = opacity;
|
||||
emit overlayOpacityChanged();
|
||||
button_->update();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::setCheckedOverlayProgress(qreal opacity)
|
||||
{
|
||||
checked_overlay_progress_ = opacity;
|
||||
emit checkedOverlayProgressChanged();
|
||||
button_->update();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::startAnimations()
|
||||
{
|
||||
start();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::setupProperties()
|
||||
{
|
||||
QColor overlayColor;
|
||||
|
||||
if (Qt::TransparentMode == button_->backgroundMode()) {
|
||||
overlayColor = button_->backgroundColor();
|
||||
} else {
|
||||
overlayColor = button_->foregroundColor();
|
||||
}
|
||||
|
||||
const qreal baseOpacity = button_->baseOpacity();
|
||||
|
||||
neutral_state_->assignProperty(this, "overlayOpacity", 0);
|
||||
neutral_focused_state_->assignProperty(this, "overlayOpacity", 0);
|
||||
hovered_state_->assignProperty(this, "overlayOpacity", baseOpacity);
|
||||
hovered_focused_state_->assignProperty(this, "overlayOpacity", baseOpacity);
|
||||
pressed_state_->assignProperty(this, "overlayOpacity", baseOpacity);
|
||||
checked_state_->assignProperty(this, "checkedOverlayProgress", 1);
|
||||
unchecked_state_->assignProperty(this, "checkedOverlayProgress", 0);
|
||||
|
||||
button_->update();
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::updateCheckedStatus()
|
||||
{
|
||||
const bool checked = button_->isChecked();
|
||||
if (was_checked_ != checked) {
|
||||
was_checked_ = checked;
|
||||
if (checked) {
|
||||
emit buttonChecked();
|
||||
} else {
|
||||
emit buttonUnchecked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
FlatButtonStateMachine::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (QEvent::FocusIn == event->type()) {
|
||||
QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
|
||||
if (focusEvent && Qt::MouseFocusReason == focusEvent->reason()) {
|
||||
emit buttonPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QStateMachine::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::addTransition(QObject *object,
|
||||
const char *signal,
|
||||
QState *fromState,
|
||||
QState *toState)
|
||||
{
|
||||
addTransition(new QSignalTransition(object, signal), fromState, toState);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::addTransition(QObject *object,
|
||||
QEvent::Type eventType,
|
||||
QState *fromState,
|
||||
QState *toState)
|
||||
{
|
||||
addTransition(new QEventTransition(object, eventType), fromState, toState);
|
||||
}
|
||||
|
||||
void
|
||||
FlatButtonStateMachine::addTransition(QAbstractTransition *transition,
|
||||
QState *fromState,
|
||||
QState *toState)
|
||||
{
|
||||
transition->setTargetState(toState);
|
||||
|
||||
QPropertyAnimation *animation;
|
||||
|
||||
animation = new QPropertyAnimation(this, "overlayOpacity", this);
|
||||
animation->setDuration(150);
|
||||
transition->addAnimation(animation);
|
||||
|
||||
fromState->addTransition(transition);
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QStateMachine>
|
||||
|
||||
#include "Theme.h"
|
||||
|
||||
class RippleOverlay;
|
||||
class FlatButton;
|
||||
|
||||
class FlatButtonStateMachine : public QStateMachine
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(
|
||||
qreal overlayOpacity WRITE setOverlayOpacity READ overlayOpacity NOTIFY overlayOpacityChanged)
|
||||
Q_PROPERTY(qreal checkedOverlayProgress WRITE setCheckedOverlayProgress READ
|
||||
checkedOverlayProgress NOTIFY checkedOverlayProgressChanged)
|
||||
|
||||
public:
|
||||
explicit FlatButtonStateMachine(FlatButton *parent);
|
||||
|
||||
void setOverlayOpacity(qreal opacity);
|
||||
void setCheckedOverlayProgress(qreal opacity);
|
||||
|
||||
inline qreal overlayOpacity() const;
|
||||
inline qreal checkedOverlayProgress() const;
|
||||
|
||||
void startAnimations();
|
||||
void setupProperties();
|
||||
void updateCheckedStatus();
|
||||
|
||||
signals:
|
||||
void buttonPressed();
|
||||
void buttonChecked();
|
||||
void buttonUnchecked();
|
||||
|
||||
void overlayOpacityChanged();
|
||||
void checkedOverlayProgressChanged();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
void addTransition(QObject *object, const char *signal, QState *fromState, QState *toState);
|
||||
void addTransition(QObject *object, QEvent::Type eventType, QState *fromState, QState *toState);
|
||||
void addTransition(QAbstractTransition *transition, QState *fromState, QState *toState);
|
||||
|
||||
FlatButton *const button_;
|
||||
|
||||
QState *const top_level_state_;
|
||||
QState *const config_state_;
|
||||
QState *const checkable_state_;
|
||||
QState *const checked_state_;
|
||||
QState *const unchecked_state_;
|
||||
QState *const neutral_state_;
|
||||
QState *const neutral_focused_state_;
|
||||
QState *const hovered_state_;
|
||||
QState *const hovered_focused_state_;
|
||||
QState *const pressed_state_;
|
||||
|
||||
qreal overlay_opacity_;
|
||||
qreal checked_overlay_progress_;
|
||||
|
||||
bool was_checked_;
|
||||
};
|
||||
|
||||
inline qreal
|
||||
FlatButtonStateMachine::overlayOpacity() const
|
||||
{
|
||||
return overlay_opacity_;
|
||||
}
|
||||
|
||||
inline qreal
|
||||
FlatButtonStateMachine::checkedOverlayProgress() const
|
||||
{
|
||||
return checked_overlay_progress_;
|
||||
}
|
||||
|
||||
class FlatButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QColor foregroundColor WRITE setForegroundColor READ foregroundColor NOTIFY
|
||||
foregroundColorChanged)
|
||||
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor NOTIFY
|
||||
backgroundColorChanged)
|
||||
Q_PROPERTY(
|
||||
QColor overlayColor WRITE setOverlayColor READ overlayColor NOTIFY overlayColorChanged)
|
||||
Q_PROPERTY(QColor disabledForegroundColor WRITE setDisabledForegroundColor READ
|
||||
disabledForegroundColor NOTIFY disabledForegroundColorChanged)
|
||||
Q_PROPERTY(QColor disabledBackgroundColor WRITE setDisabledBackgroundColor READ
|
||||
disabledBackgroundColor NOTIFY disabledBackgroundColorChanged)
|
||||
Q_PROPERTY(qreal fontSize WRITE setFontSize READ fontSize NOTIFY fontSizeChanged)
|
||||
|
||||
public:
|
||||
explicit FlatButton(QWidget *parent = nullptr,
|
||||
ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
|
||||
explicit FlatButton(const QString &text,
|
||||
QWidget *parent = nullptr,
|
||||
ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
|
||||
FlatButton(const QString &text,
|
||||
ui::Role role,
|
||||
QWidget *parent = nullptr,
|
||||
ui::ButtonPreset preset = ui::ButtonPreset::FlatPreset);
|
||||
|
||||
void applyPreset(ui::ButtonPreset preset);
|
||||
|
||||
void setBackgroundColor(const QColor &color);
|
||||
void setBackgroundMode(Qt::BGMode mode);
|
||||
void setBaseOpacity(qreal opacity);
|
||||
void setCheckable(bool value);
|
||||
void setCornerRadius(qreal radius);
|
||||
void setDisabledBackgroundColor(const QColor &color);
|
||||
void setDisabledForegroundColor(const QColor &color);
|
||||
void setFixedRippleRadius(qreal radius);
|
||||
void setFontSize(qreal size);
|
||||
void setForegroundColor(const QColor &color);
|
||||
void setHasFixedRippleRadius(bool value);
|
||||
void setIconPlacement(ui::ButtonIconPlacement placement);
|
||||
void setOverlayColor(const QColor &color);
|
||||
void setOverlayStyle(ui::OverlayStyle style);
|
||||
void setRippleStyle(ui::RippleStyle style);
|
||||
void setRole(ui::Role role);
|
||||
|
||||
QColor foregroundColor() const;
|
||||
QColor backgroundColor() const;
|
||||
QColor overlayColor() const;
|
||||
QColor disabledForegroundColor() const;
|
||||
QColor disabledBackgroundColor() const;
|
||||
|
||||
qreal fontSize() const;
|
||||
qreal cornerRadius() const;
|
||||
qreal baseOpacity() const;
|
||||
|
||||
bool hasFixedRippleRadius() const;
|
||||
|
||||
ui::Role role() const;
|
||||
ui::OverlayStyle overlayStyle() const;
|
||||
ui::RippleStyle rippleStyle() const;
|
||||
ui::ButtonIconPlacement iconPlacement() const;
|
||||
|
||||
Qt::BGMode backgroundMode() const;
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
int IconPadding = 0;
|
||||
|
||||
void checkStateSet() override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void paintBackground(QPainter *painter);
|
||||
virtual void paintForeground(QPainter *painter);
|
||||
virtual void updateClipPath();
|
||||
|
||||
void init();
|
||||
|
||||
signals:
|
||||
void foregroundColorChanged();
|
||||
void backgroundColorChanged();
|
||||
void overlayColorChanged();
|
||||
void disabledForegroundColorChanged();
|
||||
void disabledBackgroundColorChanged();
|
||||
void fontSizeChanged();
|
||||
|
||||
private:
|
||||
RippleOverlay *ripple_overlay_;
|
||||
FlatButtonStateMachine *state_machine_;
|
||||
|
||||
ui::Role role_;
|
||||
ui::RippleStyle ripple_style_;
|
||||
ui::ButtonIconPlacement icon_placement_;
|
||||
ui::OverlayStyle overlay_style_;
|
||||
|
||||
Qt::BGMode bg_mode_;
|
||||
|
||||
QColor background_color_;
|
||||
QColor foreground_color_;
|
||||
QColor overlay_color_;
|
||||
QColor disabled_color_;
|
||||
QColor disabled_background_color_;
|
||||
|
||||
qreal fixed_ripple_radius_;
|
||||
qreal corner_radius_;
|
||||
qreal base_opacity_;
|
||||
qreal font_size_;
|
||||
|
||||
bool use_fixed_ripple_radius_;
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "Label.h"
|
||||
#include <QMouseEvent>
|
||||
|
||||
Label::Label(QWidget *parent, Qt::WindowFlags f)
|
||||
: QLabel(parent, f)
|
||||
{}
|
||||
|
||||
Label::Label(const QString &text, QWidget *parent, Qt::WindowFlags f)
|
||||
: QLabel(text, parent, f)
|
||||
{}
|
||||
|
||||
void
|
||||
Label::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
pressPosition_ = e->pos();
|
||||
emit pressed(e);
|
||||
QLabel::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void
|
||||
Label::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
emit released(e);
|
||||
if (pressPosition_ == e->pos())
|
||||
emit clicked(e);
|
||||
QLabel::mouseReleaseEvent(e);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class Label : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Label(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
explicit Label(const QString &text,
|
||||
QWidget *parent = Q_NULLPTR,
|
||||
Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
signals:
|
||||
void clicked(QMouseEvent *e);
|
||||
void pressed(QMouseEvent *e);
|
||||
void released(QMouseEvent *e);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
QPoint pressPosition_;
|
||||
};
|
@ -1,84 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "LoadingIndicator.h"
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
|
||||
LoadingIndicator::LoadingIndicator(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, interval_(70)
|
||||
, angle_(0)
|
||||
, color_(Qt::black)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
timer_ = new QTimer(this);
|
||||
connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout()));
|
||||
}
|
||||
|
||||
void
|
||||
LoadingIndicator::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
Q_UNUSED(e)
|
||||
|
||||
if (!timer_->isActive())
|
||||
return;
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int width = qMin(this->width(), this->height());
|
||||
|
||||
int outerRadius = (width - 4) * 0.5f;
|
||||
int innerRadius = outerRadius * 0.78f;
|
||||
|
||||
int capsuleRadius = (outerRadius - innerRadius) / 2;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
QColor color = color_;
|
||||
|
||||
color.setAlphaF(1.0f - (i / 8.0f));
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(color);
|
||||
|
||||
qreal radius = capsuleRadius * (1.0f - (i / 16.0f));
|
||||
|
||||
painter.save();
|
||||
|
||||
painter.translate(rect().center());
|
||||
painter.rotate(angle_ - i * 45.0f);
|
||||
|
||||
QPointF center = QPointF(-capsuleRadius, -innerRadius);
|
||||
painter.drawEllipse(center, radius * 2, radius * 2);
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoadingIndicator::start()
|
||||
{
|
||||
timer_->start(interval_);
|
||||
show();
|
||||
}
|
||||
|
||||
void
|
||||
LoadingIndicator::stop()
|
||||
{
|
||||
timer_->stop();
|
||||
hide();
|
||||
}
|
||||
|
||||
void
|
||||
LoadingIndicator::onTimeout()
|
||||
{
|
||||
angle_ = (angle_ + 45) % 360;
|
||||
repaint();
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
#include <QWidget>
|
||||
|
||||
class QPainter;
|
||||
class QTimer;
|
||||
class QPaintEvent;
|
||||
class LoadingIndicator : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
|
||||
public:
|
||||
LoadingIndicator(QWidget *parent = nullptr);
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
QColor color() { return color_; }
|
||||
void setColor(QColor color)
|
||||
{
|
||||
color_ = color;
|
||||
emit colorChanged();
|
||||
}
|
||||
|
||||
int interval() { return interval_; }
|
||||
void setInterval(int interval) { interval_ = interval; }
|
||||
|
||||
private slots:
|
||||
void onTimeout();
|
||||
|
||||
signals:
|
||||
void colorChanged();
|
||||
|
||||
private:
|
||||
int interval_;
|
||||
int angle_;
|
||||
|
||||
QColor color_;
|
||||
QTimer *timer_;
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
class Menu : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Menu(QWidget *parent = nullptr)
|
||||
: QMenu(parent){};
|
||||
|
||||
protected:
|
||||
void leaveEvent(QEvent *e) override
|
||||
{
|
||||
hide();
|
||||
|
||||
QMenu::leaveEvent(e);
|
||||
}
|
||||
};
|
@ -139,5 +139,5 @@ Nheko::openCreateRoomDialog() const
|
||||
void
|
||||
Nheko::reparent(QWindow *win) const
|
||||
{
|
||||
win->setTransientParent(MainWindow::instance()->windowHandle());
|
||||
win->setTransientParent(MainWindow::instance());
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPainter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "OverlayModal.h"
|
||||
|
||||
OverlayModal::OverlayModal(QWidget *parent)
|
||||
: OverlayWidget(parent)
|
||||
, color_{QColor(30, 30, 30, 170)}
|
||||
{
|
||||
layout_ = new QVBoxLayout(this);
|
||||
layout_->setSpacing(0);
|
||||
layout_->setContentsMargins(10, 40, 10, 20);
|
||||
setContentAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayModal::setWidget(QWidget *widget)
|
||||
{
|
||||
// Delete the previous widget
|
||||
if (layout_->count() > 0) {
|
||||
QLayoutItem *item;
|
||||
while ((item = layout_->takeAt(0)) != nullptr) {
|
||||
delete item->widget();
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
layout_->addWidget(widget);
|
||||
content_ = widget;
|
||||
content_->setFocus();
|
||||
}
|
||||
|
||||
void
|
||||
OverlayModal::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), color_);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayModal::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (isDismissible_ && content_ && !content_->geometry().contains(e->pos()))
|
||||
hide();
|
||||
}
|
||||
|
||||
void
|
||||
OverlayModal::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
event->accept();
|
||||
hide();
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "OverlayWidget.h"
|
||||
|
||||
class OverlayModal : public OverlayWidget
|
||||
{
|
||||
public:
|
||||
OverlayModal(QWidget *parent);
|
||||
|
||||
void setColor(QColor color) { color_ = color; }
|
||||
void setDismissible(bool state) { isDismissible_ = state; }
|
||||
|
||||
void setContentAlignment(QFlags<Qt::AlignmentFlag> flag) { layout_->setAlignment(flag); }
|
||||
void setWidget(QWidget *widget);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QWidget *content_;
|
||||
QVBoxLayout *layout_;
|
||||
|
||||
QColor color_;
|
||||
|
||||
//! Decides whether or not the modal can be removed by clicking into it.
|
||||
bool isDismissible_ = true;
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "OverlayWidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
OverlayWidget::OverlayWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
if (parent) {
|
||||
parent->installEventFilter(this);
|
||||
setGeometry(overlayGeometry());
|
||||
raise();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
OverlayWidget::event(QEvent *event)
|
||||
{
|
||||
if (!parent())
|
||||
return QWidget::event(event);
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::ParentChange: {
|
||||
parent()->installEventFilter(this);
|
||||
setGeometry(overlayGeometry());
|
||||
break;
|
||||
}
|
||||
case QEvent::ParentAboutToChange: {
|
||||
parent()->removeEventFilter(this);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
bool
|
||||
OverlayWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::Move:
|
||||
case QEvent::Resize:
|
||||
setGeometry(overlayGeometry());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
QRect
|
||||
OverlayWidget::overlayGeometry() const
|
||||
{
|
||||
QWidget *widget = parentWidget();
|
||||
|
||||
if (!widget)
|
||||
return QRect();
|
||||
|
||||
return widget->rect();
|
||||
}
|
||||
|
||||
void
|
||||
OverlayWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QWidget>
|
||||
|
||||
class QPainter;
|
||||
|
||||
class OverlayWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OverlayWidget(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
QRect overlayGeometry() const;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
154
src/ui/Painter.h
154
src/ui/Painter.h
@ -1,154 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QPaintDevice>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QtGlobal>
|
||||
|
||||
class Painter : public QPainter
|
||||
{
|
||||
public:
|
||||
explicit Painter(QPaintDevice *device)
|
||||
: QPainter(device)
|
||||
{}
|
||||
|
||||
void drawTextLeft(int x, int y, const QString &text)
|
||||
{
|
||||
QFontMetrics m(fontMetrics());
|
||||
drawText(x, y + m.ascent(), text);
|
||||
}
|
||||
|
||||
void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1)
|
||||
{
|
||||
QFontMetrics m(fontMetrics());
|
||||
if (textWidth < 0) {
|
||||
textWidth = m.horizontalAdvance(text);
|
||||
}
|
||||
drawText((outerw - x - textWidth), y + m.ascent(), text);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(int x, int y, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
drawPixmap(QPoint(x, y), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(const QPoint &p, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
return drawPixmapLeft(p.x(), p.y(), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(int x, int y, int w, int h, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
drawPixmap(QRect(x, y, w, h), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(const QRect &r, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix)
|
||||
{
|
||||
Q_UNUSED(outerw);
|
||||
drawPixmap(QPoint(x, y), pix);
|
||||
}
|
||||
|
||||
void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix)
|
||||
{
|
||||
return drawPixmapLeft(p.x(), p.y(), outerw, pix);
|
||||
}
|
||||
|
||||
void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
drawPixmap(QPoint((outerw - x - (from.width() / pix.devicePixelRatio())), y), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
|
||||
}
|
||||
void
|
||||
drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
drawPixmap(QRect((outerw - x - w), y, w, h), pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from)
|
||||
{
|
||||
return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
|
||||
}
|
||||
|
||||
void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix)
|
||||
{
|
||||
drawPixmap(QPoint((outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
|
||||
}
|
||||
|
||||
void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix)
|
||||
{
|
||||
return drawPixmapRight(p.x(), p.y(), outerw, pix);
|
||||
}
|
||||
|
||||
void drawAvatar(const QPixmap &pix, int w, int h, int d)
|
||||
{
|
||||
QPainterPath pp;
|
||||
pp.addEllipse((w - d) / 2, (h - d) / 2, d, d);
|
||||
|
||||
QRect region((w - d) / 2, (h - d) / 2, d, d);
|
||||
|
||||
setClipPath(pp);
|
||||
drawPixmap(region, pix);
|
||||
}
|
||||
|
||||
void drawLetterAvatar(const QString &c,
|
||||
const QColor &penColor,
|
||||
const QColor &brushColor,
|
||||
int w,
|
||||
int h,
|
||||
int d)
|
||||
{
|
||||
QRect region((w - d) / 2, (h - d) / 2, d, d);
|
||||
|
||||
setPen(Qt::NoPen);
|
||||
setBrush(brushColor);
|
||||
|
||||
drawEllipse(region.center(), d / 2, d / 2);
|
||||
|
||||
setBrush(Qt::NoBrush);
|
||||
drawEllipse(region.center(), d / 2, d / 2);
|
||||
|
||||
setPen(penColor);
|
||||
drawText(region.translated(0, -1), Qt::AlignCenter, c);
|
||||
}
|
||||
};
|
||||
|
||||
class PainterHighQualityEnabler
|
||||
{
|
||||
public:
|
||||
PainterHighQualityEnabler(Painter &p)
|
||||
: _painter(p)
|
||||
{
|
||||
hints_ =
|
||||
QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing;
|
||||
|
||||
_painter.setRenderHints(hints_);
|
||||
}
|
||||
|
||||
~PainterHighQualityEnabler()
|
||||
{
|
||||
if (hints_)
|
||||
_painter.setRenderHints(hints_, false);
|
||||
}
|
||||
|
||||
PainterHighQualityEnabler(const PainterHighQualityEnabler &other) = delete;
|
||||
PainterHighQualityEnabler &operator=(const PainterHighQualityEnabler &other) = delete;
|
||||
|
||||
private:
|
||||
Painter &_painter;
|
||||
QPainter::RenderHints hints_ = {};
|
||||
};
|
@ -1,92 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QEventTransition>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
#include "RaisedButton.h"
|
||||
|
||||
void
|
||||
RaisedButton::init()
|
||||
{
|
||||
shadow_state_machine_ = new QStateMachine(this);
|
||||
normal_state_ = new QState;
|
||||
pressed_state_ = new QState;
|
||||
effect_ = new QGraphicsDropShadowEffect;
|
||||
|
||||
effect_->setBlurRadius(7);
|
||||
effect_->setOffset(QPointF(0, 2));
|
||||
effect_->setColor(QColor(0, 0, 0, 75));
|
||||
|
||||
setBackgroundMode(Qt::OpaqueMode);
|
||||
setMinimumHeight(42);
|
||||
setGraphicsEffect(effect_);
|
||||
setBaseOpacity(0.3);
|
||||
|
||||
shadow_state_machine_->addState(normal_state_);
|
||||
shadow_state_machine_->addState(pressed_state_);
|
||||
|
||||
normal_state_->assignProperty(effect_, "offset", QPointF(0, 2));
|
||||
normal_state_->assignProperty(effect_, "blurRadius", 7);
|
||||
|
||||
pressed_state_->assignProperty(effect_, "offset", QPointF(0, 5));
|
||||
pressed_state_->assignProperty(effect_, "blurRadius", 29);
|
||||
|
||||
QAbstractTransition *transition;
|
||||
|
||||
transition = new QEventTransition(this, QEvent::MouseButtonPress);
|
||||
transition->setTargetState(pressed_state_);
|
||||
normal_state_->addTransition(transition);
|
||||
|
||||
transition = new QEventTransition(this, QEvent::MouseButtonDblClick);
|
||||
transition->setTargetState(pressed_state_);
|
||||
normal_state_->addTransition(transition);
|
||||
|
||||
transition = new QEventTransition(this, QEvent::MouseButtonRelease);
|
||||
transition->setTargetState(normal_state_);
|
||||
pressed_state_->addTransition(transition);
|
||||
|
||||
QPropertyAnimation *animation;
|
||||
|
||||
animation = new QPropertyAnimation(effect_, "offset", this);
|
||||
animation->setDuration(100);
|
||||
shadow_state_machine_->addDefaultAnimation(animation);
|
||||
|
||||
animation = new QPropertyAnimation(effect_, "blurRadius", this);
|
||||
animation->setDuration(100);
|
||||
shadow_state_machine_->addDefaultAnimation(animation);
|
||||
|
||||
shadow_state_machine_->setInitialState(normal_state_);
|
||||
shadow_state_machine_->start();
|
||||
}
|
||||
|
||||
RaisedButton::RaisedButton(QWidget *parent)
|
||||
: FlatButton(parent)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
RaisedButton::RaisedButton(const QString &text, QWidget *parent)
|
||||
: FlatButton(parent)
|
||||
{
|
||||
init();
|
||||
setText(text);
|
||||
}
|
||||
|
||||
bool
|
||||
RaisedButton::event(QEvent *event)
|
||||
{
|
||||
if (QEvent::EnabledChange == event->type()) {
|
||||
if (isEnabled()) {
|
||||
shadow_state_machine_->start();
|
||||
effect_->setEnabled(true);
|
||||
} else {
|
||||
shadow_state_machine_->stop();
|
||||
effect_->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
return FlatButton::event(event);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include <QState>
|
||||
#include <QStateMachine>
|
||||
|
||||
#include "FlatButton.h"
|
||||
|
||||
class RaisedButton : public FlatButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RaisedButton(QWidget *parent = nullptr);
|
||||
explicit RaisedButton(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
QStateMachine *shadow_state_machine_;
|
||||
QState *normal_state_;
|
||||
QState *pressed_state_;
|
||||
QGraphicsDropShadowEffect *effect_;
|
||||
};
|
@ -1,116 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "Ripple.h"
|
||||
#include "RippleOverlay.h"
|
||||
|
||||
Ripple::Ripple(const QPoint ¢er, QObject *parent)
|
||||
: QParallelAnimationGroup(parent)
|
||||
, overlay_(nullptr)
|
||||
, radius_anim_(animate("radius"))
|
||||
, opacity_anim_(animate("opacity"))
|
||||
, radius_(0)
|
||||
, opacity_(0)
|
||||
, center_(center)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
Ripple::Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent)
|
||||
: QParallelAnimationGroup(parent)
|
||||
, overlay_(overlay)
|
||||
, radius_anim_(animate("radius"))
|
||||
, opacity_anim_(animate("opacity"))
|
||||
, radius_(0)
|
||||
, opacity_(0)
|
||||
, center_(center)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::setRadius(qreal radius)
|
||||
{
|
||||
Q_ASSERT(overlay_);
|
||||
|
||||
if (radius_ == radius)
|
||||
return;
|
||||
|
||||
radius_ = radius;
|
||||
overlay_->update();
|
||||
|
||||
emit radiusChanged();
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::setOpacity(qreal opacity)
|
||||
{
|
||||
Q_ASSERT(overlay_);
|
||||
|
||||
if (opacity_ == opacity)
|
||||
return;
|
||||
|
||||
opacity_ = opacity;
|
||||
overlay_->update();
|
||||
|
||||
emit opacityChanged();
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::setColor(const QColor &color)
|
||||
{
|
||||
if (brush_.color() == color)
|
||||
return;
|
||||
|
||||
brush_.setColor(color);
|
||||
|
||||
if (overlay_)
|
||||
overlay_->update();
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::setBrush(const QBrush &brush)
|
||||
{
|
||||
brush_ = brush;
|
||||
|
||||
if (overlay_)
|
||||
overlay_->update();
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::destroy()
|
||||
{
|
||||
Q_ASSERT(overlay_);
|
||||
|
||||
overlay_->removeRipple(this);
|
||||
}
|
||||
|
||||
QPropertyAnimation *
|
||||
Ripple::animate(const QByteArray &property, const QEasingCurve &easing, int duration)
|
||||
{
|
||||
QPropertyAnimation *animation = new QPropertyAnimation;
|
||||
animation->setTargetObject(this);
|
||||
animation->setPropertyName(property);
|
||||
animation->setEasingCurve(easing);
|
||||
animation->setDuration(duration);
|
||||
|
||||
addAnimation(animation);
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
void
|
||||
Ripple::init()
|
||||
{
|
||||
setOpacityStartValue(0.5);
|
||||
setOpacityEndValue(0);
|
||||
setRadiusStartValue(0);
|
||||
setRadiusEndValue(300);
|
||||
|
||||
brush_.setColor(Qt::black);
|
||||
brush_.setStyle(Qt::SolidPattern);
|
||||
|
||||
connect(this, SIGNAL(finished()), this, SLOT(destroy()));
|
||||
}
|
154
src/ui/Ripple.h
154
src/ui/Ripple.h
@ -1,154 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QBrush>
|
||||
#include <QEasingCurve>
|
||||
#include <QParallelAnimationGroup>
|
||||
#include <QPoint>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
class RippleOverlay;
|
||||
|
||||
class Ripple : public QParallelAnimationGroup
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(qreal radius WRITE setRadius READ radius NOTIFY radiusChanged)
|
||||
Q_PROPERTY(qreal opacity WRITE setOpacity READ opacity NOTIFY opacityChanged)
|
||||
|
||||
public:
|
||||
explicit Ripple(const QPoint ¢er, QObject *parent = nullptr);
|
||||
Ripple(const QPoint ¢er, RippleOverlay *overlay, QObject *parent = nullptr);
|
||||
|
||||
inline void setOverlay(RippleOverlay *overlay);
|
||||
|
||||
void setRadius(qreal radius);
|
||||
void setOpacity(qreal opacity);
|
||||
void setColor(const QColor &color);
|
||||
void setBrush(const QBrush &brush);
|
||||
|
||||
inline qreal radius() const;
|
||||
inline qreal opacity() const;
|
||||
inline QColor color() const;
|
||||
inline QBrush brush() const;
|
||||
inline QPoint center() const;
|
||||
|
||||
inline QPropertyAnimation *radiusAnimation() const;
|
||||
inline QPropertyAnimation *opacityAnimation() const;
|
||||
|
||||
inline void setOpacityStartValue(qreal value);
|
||||
inline void setOpacityEndValue(qreal value);
|
||||
inline void setRadiusStartValue(qreal value);
|
||||
inline void setRadiusEndValue(qreal value);
|
||||
inline void setDuration(int msecs);
|
||||
|
||||
protected slots:
|
||||
void destroy();
|
||||
|
||||
signals:
|
||||
void radiusChanged();
|
||||
void opacityChanged();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(Ripple)
|
||||
|
||||
QPropertyAnimation *animate(const QByteArray &property,
|
||||
const QEasingCurve &easing = QEasingCurve::OutQuad,
|
||||
int duration = 800);
|
||||
|
||||
void init();
|
||||
|
||||
RippleOverlay *overlay_;
|
||||
|
||||
QPropertyAnimation *const radius_anim_;
|
||||
QPropertyAnimation *const opacity_anim_;
|
||||
|
||||
qreal radius_;
|
||||
qreal opacity_;
|
||||
|
||||
QPoint center_;
|
||||
QBrush brush_;
|
||||
};
|
||||
|
||||
inline void
|
||||
Ripple::setOverlay(RippleOverlay *overlay)
|
||||
{
|
||||
overlay_ = overlay;
|
||||
}
|
||||
|
||||
inline qreal
|
||||
Ripple::radius() const
|
||||
{
|
||||
return radius_;
|
||||
}
|
||||
|
||||
inline qreal
|
||||
Ripple::opacity() const
|
||||
{
|
||||
return opacity_;
|
||||
}
|
||||
|
||||
inline QColor
|
||||
Ripple::color() const
|
||||
{
|
||||
return brush_.color();
|
||||
}
|
||||
|
||||
inline QBrush
|
||||
Ripple::brush() const
|
||||
{
|
||||
return brush_;
|
||||
}
|
||||
|
||||
inline QPoint
|
||||
Ripple::center() const
|
||||
{
|
||||
return center_;
|
||||
}
|
||||
|
||||
inline QPropertyAnimation *
|
||||
Ripple::radiusAnimation() const
|
||||
{
|
||||
return radius_anim_;
|
||||
}
|
||||
|
||||
inline QPropertyAnimation *
|
||||
Ripple::opacityAnimation() const
|
||||
{
|
||||
return opacity_anim_;
|
||||
}
|
||||
|
||||
inline void
|
||||
Ripple::setOpacityStartValue(qreal value)
|
||||
{
|
||||
opacity_anim_->setStartValue(value);
|
||||
}
|
||||
|
||||
inline void
|
||||
Ripple::setOpacityEndValue(qreal value)
|
||||
{
|
||||
opacity_anim_->setEndValue(value);
|
||||
}
|
||||
|
||||
inline void
|
||||
Ripple::setRadiusStartValue(qreal value)
|
||||
{
|
||||
radius_anim_->setStartValue(value);
|
||||
}
|
||||
|
||||
inline void
|
||||
Ripple::setRadiusEndValue(qreal value)
|
||||
{
|
||||
radius_anim_->setEndValue(value);
|
||||
}
|
||||
|
||||
inline void
|
||||
Ripple::setDuration(int msecs)
|
||||
{
|
||||
radius_anim_->setDuration(msecs);
|
||||
opacity_anim_->setDuration(msecs);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "Ripple.h"
|
||||
#include "RippleOverlay.h"
|
||||
|
||||
RippleOverlay::RippleOverlay(QWidget *parent)
|
||||
: OverlayWidget(parent)
|
||||
, use_clip_(false)
|
||||
{
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
}
|
||||
|
||||
void
|
||||
RippleOverlay::addRipple(Ripple *ripple)
|
||||
{
|
||||
ripple->setOverlay(this);
|
||||
ripples_.push_back(ripple);
|
||||
ripple->start();
|
||||
}
|
||||
|
||||
void
|
||||
RippleOverlay::addRipple(const QPoint &position, qreal radius)
|
||||
{
|
||||
Ripple *ripple = new Ripple(position);
|
||||
ripple->setRadiusEndValue(radius);
|
||||
addRipple(ripple);
|
||||
}
|
||||
|
||||
void
|
||||
RippleOverlay::removeRipple(Ripple *ripple)
|
||||
{
|
||||
if (ripples_.removeOne(ripple))
|
||||
delete ripple;
|
||||
}
|
||||
|
||||
void
|
||||
RippleOverlay::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(Qt::NoPen);
|
||||
|
||||
if (use_clip_)
|
||||
painter.setClipPath(clip_path_);
|
||||
|
||||
for (auto it = ripples_.constBegin(); it != ripples_.constEnd(); ++it)
|
||||
paintRipple(&painter, *it);
|
||||
}
|
||||
|
||||
void
|
||||
RippleOverlay::paintRipple(QPainter *painter, Ripple *ripple)
|
||||
{
|
||||
const qreal radius = ripple->radius();
|
||||
const QPointF center = ripple->center();
|
||||
|
||||
painter->setOpacity(ripple->opacity());
|
||||
painter->setBrush(ripple->brush());
|
||||
painter->drawEllipse(center, radius, radius);
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPainterPath>
|
||||
|
||||
#include "OverlayWidget.h"
|
||||
|
||||
class Ripple;
|
||||
|
||||
class RippleOverlay : public OverlayWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RippleOverlay(QWidget *parent = nullptr);
|
||||
|
||||
void addRipple(Ripple *ripple);
|
||||
void addRipple(const QPoint &position, qreal radius = 300);
|
||||
|
||||
void removeRipple(Ripple *ripple);
|
||||
|
||||
inline void setClipping(bool enable);
|
||||
inline bool hasClipping() const;
|
||||
|
||||
inline void setClipPath(const QPainterPath &path);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(RippleOverlay)
|
||||
|
||||
void paintRipple(QPainter *painter, Ripple *ripple);
|
||||
|
||||
QList<Ripple *> ripples_;
|
||||
QPainterPath clip_path_;
|
||||
bool use_clip_;
|
||||
};
|
||||
|
||||
inline void
|
||||
RippleOverlay::setClipping(bool enable)
|
||||
{
|
||||
use_clip_ = enable;
|
||||
update();
|
||||
}
|
||||
|
||||
inline bool
|
||||
RippleOverlay::hasClipping() const
|
||||
{
|
||||
return use_clip_;
|
||||
}
|
||||
|
||||
inline void
|
||||
RippleOverlay::setClipPath(const QPainterPath &path)
|
||||
{
|
||||
clip_path_ = path;
|
||||
update();
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "SnackBar.h"
|
||||
|
||||
constexpr int STARTING_OFFSET = 1;
|
||||
constexpr int BOX_PADDING = 10;
|
||||
constexpr double MIN_WIDTH = 400.0;
|
||||
constexpr double MIN_WIDTH_PERCENTAGE = 0.3;
|
||||
|
||||
SnackBar::SnackBar(QWidget *parent)
|
||||
: OverlayWidget(parent)
|
||||
, offset_anim(this, "offset", this)
|
||||
{
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * 1.2);
|
||||
font.setWeight(QFont::Weight::Thin);
|
||||
setFont(font);
|
||||
|
||||
boxHeight_ = QFontMetrics(font).height() * 2;
|
||||
offset_ = STARTING_OFFSET;
|
||||
position_ = SnackBarPosition::Top;
|
||||
|
||||
hideTimer_.setSingleShot(true);
|
||||
|
||||
offset_anim.setStartValue(1.0);
|
||||
offset_anim.setEndValue(0.0);
|
||||
offset_anim.setDuration(100);
|
||||
offset_anim.setEasingCurve(QEasingCurve::OutCubic);
|
||||
|
||||
connect(this, &SnackBar::offsetChanged, this, [this]() mutable { repaint(); });
|
||||
connect(
|
||||
&offset_anim, &QPropertyAnimation::finished, this, [this]() { hideTimer_.start(10000); });
|
||||
|
||||
connect(&hideTimer_, SIGNAL(timeout()), this, SLOT(hideMessage()));
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::start()
|
||||
{
|
||||
if (messages_.empty())
|
||||
return;
|
||||
|
||||
show();
|
||||
raise();
|
||||
|
||||
offset_anim.start();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::hideMessage()
|
||||
{
|
||||
stopTimers();
|
||||
hide();
|
||||
|
||||
if (!messages_.empty())
|
||||
// Moving on to the next message.
|
||||
messages_.pop_front();
|
||||
|
||||
// Resetting the starting position of the widget.
|
||||
offset_ = STARTING_OFFSET;
|
||||
|
||||
if (!messages_.empty())
|
||||
start();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::stopTimers()
|
||||
{
|
||||
hideTimer_.stop();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::showMessage(const QString &msg)
|
||||
{
|
||||
messages_.push_back(msg);
|
||||
|
||||
// There is already an active message.
|
||||
if (isVisible())
|
||||
return;
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::mousePressEvent(QMouseEvent *)
|
||||
{
|
||||
hideMessage();
|
||||
}
|
||||
|
||||
void
|
||||
SnackBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
if (messages_.empty())
|
||||
return;
|
||||
|
||||
auto message_ = messages_.front();
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
brush.setColor(bgColor_);
|
||||
p.setBrush(brush);
|
||||
|
||||
QRect r(0, 0, std::max(MIN_WIDTH, width() * MIN_WIDTH_PERCENTAGE), boxHeight_);
|
||||
|
||||
p.setPen(Qt::white);
|
||||
QRect br = p.boundingRect(r, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
r = br.united(r).adjusted(-BOX_PADDING, -BOX_PADDING, BOX_PADDING, BOX_PADDING);
|
||||
|
||||
const qreal s = 1 - offset_;
|
||||
|
||||
if (position_ == SnackBarPosition::Bottom)
|
||||
p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
|
||||
height() - BOX_PADDING - s * (r.height()));
|
||||
else
|
||||
p.translate((width() - (r.width() - 2 * BOX_PADDING)) / 2,
|
||||
s * (r.height()) - 2 * BOX_PADDING);
|
||||
|
||||
br.moveCenter(r.center());
|
||||
p.drawRoundedRect(r.adjusted(0, 0, 0, 4), 4, 4);
|
||||
p.setPen(textColor_);
|
||||
p.drawText(br, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, message_);
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QPaintEvent>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QTimer>
|
||||
#include <deque>
|
||||
|
||||
#include "OverlayWidget.h"
|
||||
|
||||
enum class SnackBarPosition
|
||||
{
|
||||
Bottom,
|
||||
Top,
|
||||
};
|
||||
|
||||
class SnackBar : public OverlayWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(
|
||||
QColor bgColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
|
||||
Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged)
|
||||
Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged)
|
||||
|
||||
public:
|
||||
explicit SnackBar(QWidget *parent);
|
||||
|
||||
QColor backgroundColor() const { return bgColor_; }
|
||||
void setBackgroundColor(const QColor &color)
|
||||
{
|
||||
bgColor_ = color;
|
||||
update();
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
QColor textColor() const { return textColor_; }
|
||||
void setTextColor(const QColor &color)
|
||||
{
|
||||
textColor_ = color;
|
||||
update();
|
||||
emit textColorChanged();
|
||||
}
|
||||
void setPosition(SnackBarPosition pos)
|
||||
{
|
||||
position_ = pos;
|
||||
update();
|
||||
}
|
||||
|
||||
double offset() { return offset_; }
|
||||
void setOffset(double offset)
|
||||
{
|
||||
if (offset != offset_) {
|
||||
offset_ = offset;
|
||||
emit offsetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
void showMessage(const QString &msg);
|
||||
|
||||
signals:
|
||||
void offsetChanged();
|
||||
void backgroundColorChanged();
|
||||
void textColorChanged();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void hideMessage();
|
||||
|
||||
private:
|
||||
void stopTimers();
|
||||
void start();
|
||||
|
||||
QColor bgColor_;
|
||||
QColor textColor_;
|
||||
|
||||
qreal bgOpacity_;
|
||||
qreal offset_;
|
||||
|
||||
std::deque<QString> messages_;
|
||||
|
||||
QTimer hideTimer_;
|
||||
|
||||
double boxHeight_;
|
||||
|
||||
QPropertyAnimation offset_anim;
|
||||
|
||||
SnackBarPosition position_;
|
||||
};
|
@ -1,123 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "ui/TextLabel.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QDesktopServices>
|
||||
#include <QEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
bool
|
||||
ContextMenuFilter::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
emit contextMenuIsOpening();
|
||||
return true;
|
||||
}
|
||||
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
TextLabel::TextLabel(QWidget *parent)
|
||||
: TextLabel(QString(), parent)
|
||||
{}
|
||||
|
||||
TextLabel::TextLabel(const QString &text, QWidget *parent)
|
||||
: QTextBrowser(parent)
|
||||
{
|
||||
document()->setDefaultStyleSheet(QStringLiteral("a {color: %1; }").arg(utils::linkColor()));
|
||||
|
||||
setText(text);
|
||||
setOpenExternalLinks(true);
|
||||
|
||||
// Make it look and feel like an ordinary label.
|
||||
setReadOnly(true);
|
||||
setFrameStyle(QFrame::NoFrame);
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::Base, Qt::transparent);
|
||||
setPalette(pal);
|
||||
|
||||
// Wrap anywhere but prefer words, adjust minimum height on the fly.
|
||||
setLineWrapMode(QTextEdit::WidgetWidth);
|
||||
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
connect(document()->documentLayout(),
|
||||
&QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
this,
|
||||
&TextLabel::adjustHeight);
|
||||
document()->setDocumentMargin(0);
|
||||
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
setFixedHeight(0);
|
||||
|
||||
connect(this, &TextLabel::linkActivated, this, &TextLabel::handleLinkActivation);
|
||||
|
||||
auto filter = new ContextMenuFilter(this);
|
||||
installEventFilter(filter);
|
||||
connect(filter, &ContextMenuFilter::contextMenuIsOpening, this, [this]() {
|
||||
contextMenuRequested_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TextLabel::focusOutEvent(QFocusEvent *e)
|
||||
{
|
||||
QTextBrowser::focusOutEvent(e);
|
||||
|
||||
// We keep the selection available for the context menu.
|
||||
if (contextMenuRequested_) {
|
||||
contextMenuRequested_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.clearSelection();
|
||||
setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void
|
||||
TextLabel::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
link_ = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : QString();
|
||||
QTextBrowser::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void
|
||||
TextLabel::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() & Qt::LeftButton && !link_.isEmpty() && anchorAt(e->pos()) == link_) {
|
||||
emit linkActivated(link_);
|
||||
return;
|
||||
}
|
||||
|
||||
QTextBrowser::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void
|
||||
TextLabel::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void
|
||||
TextLabel::handleLinkActivation(const QUrl &url)
|
||||
{
|
||||
auto parts = url.toString().split('/');
|
||||
auto defaultHandler = [](const QUrl &url) { QDesktopServices::openUrl(url); };
|
||||
|
||||
if (url.host() != QLatin1String("matrix.to") || parts.isEmpty())
|
||||
return defaultHandler(url);
|
||||
|
||||
try {
|
||||
using namespace mtx::identifiers;
|
||||
parse<User>(parts.last().toStdString());
|
||||
} catch (const std::exception &) {
|
||||
return defaultHandler(url);
|
||||
}
|
||||
|
||||
emit userProfileTriggered(parts.last());
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QTextBrowser>
|
||||
#include <QUrl>
|
||||
|
||||
class QMouseEvent;
|
||||
class QFocusEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
class ContextMenuFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ContextMenuFilter(QWidget *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
signals:
|
||||
void contextMenuIsOpening();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
};
|
||||
|
||||
class TextLabel : public QTextBrowser
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextLabel(const QString &text, QWidget *parent = nullptr);
|
||||
TextLabel(QWidget *parent = nullptr);
|
||||
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void clearLinks() { link_.clear(); }
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void focusOutEvent(QFocusEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void adjustHeight(const QSizeF &size) { setFixedHeight(size.height()); }
|
||||
void handleLinkActivation(const QUrl &link);
|
||||
|
||||
signals:
|
||||
void userProfileTriggered(const QString &user_id);
|
||||
void linkActivated(const QUrl &link);
|
||||
|
||||
private:
|
||||
QString link_;
|
||||
bool contextMenuRequested_ = false;
|
||||
};
|
@ -13,7 +13,6 @@
|
||||
#include <mtx/responses/common.hpp>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "dialogs/FallbackAuth.h"
|
||||
#include "dialogs/ReCaptcha.h"
|
||||
|
||||
@ -71,7 +70,7 @@ UIA::genericHandler(QString context)
|
||||
emit phoneNumber();
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
|
||||
auto captchaDialog =
|
||||
new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance());
|
||||
new dialogs::ReCaptcha(QString::fromStdString(u.session), nullptr);
|
||||
captchaDialog->setWindowTitle(context);
|
||||
|
||||
connect(
|
||||
@ -95,7 +94,7 @@ UIA::genericHandler(QString context)
|
||||
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
||||
bool ok;
|
||||
QString token =
|
||||
QInputDialog::getText(MainWindow::instance(),
|
||||
QInputDialog::getText(nullptr,
|
||||
context,
|
||||
tr("Please enter a valid registration token."),
|
||||
QLineEdit::Normal,
|
||||
@ -113,7 +112,7 @@ UIA::genericHandler(QString context)
|
||||
// use fallback
|
||||
auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage),
|
||||
QString::fromStdString(u.session),
|
||||
MainWindow::instance());
|
||||
nullptr);
|
||||
dialog->setWindowTitle(context);
|
||||
|
||||
connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() {
|
||||
|
Loading…
Reference in New Issue
Block a user