commit
b706e272e5
@ -331,24 +331,13 @@ set(SRC_FILES
|
|||||||
src/timeline/RoomlistModel.cpp
|
src/timeline/RoomlistModel.cpp
|
||||||
|
|
||||||
# UI components
|
# UI components
|
||||||
src/ui/DropShadow.cpp
|
|
||||||
src/ui/FlatButton.cpp
|
|
||||||
src/ui/Label.cpp
|
|
||||||
src/ui/LoadingIndicator.cpp
|
|
||||||
src/ui/MxcAnimatedImage.cpp
|
src/ui/MxcAnimatedImage.cpp
|
||||||
src/ui/MxcMediaProxy.cpp
|
src/ui/MxcMediaProxy.cpp
|
||||||
src/ui/NhekoCursorShape.cpp
|
src/ui/NhekoCursorShape.cpp
|
||||||
src/ui/NhekoDropArea.cpp
|
src/ui/NhekoDropArea.cpp
|
||||||
src/ui/NhekoGlobalObject.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/RoomSettings.cpp
|
||||||
src/ui/SnackBar.cpp
|
|
||||||
src/ui/TextField.cpp
|
src/ui/TextField.cpp
|
||||||
src/ui/TextLabel.cpp
|
|
||||||
src/ui/Theme.cpp
|
src/ui/Theme.cpp
|
||||||
src/ui/ThemeManager.cpp
|
src/ui/ThemeManager.cpp
|
||||||
src/ui/ToggleButton.cpp
|
src/ui/ToggleButton.cpp
|
||||||
@ -395,7 +384,6 @@ set(SRC_FILES
|
|||||||
src/RoomDirectoryModel.cpp
|
src/RoomDirectoryModel.cpp
|
||||||
src/RoomsModel.cpp
|
src/RoomsModel.cpp
|
||||||
src/Utils.cpp
|
src/Utils.cpp
|
||||||
src/WelcomePage.cpp
|
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
|
||||||
third_party/blurhash/blurhash.cpp
|
third_party/blurhash/blurhash.cpp
|
||||||
@ -414,7 +402,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
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_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
@ -538,23 +526,13 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||||||
src/timeline/RoomlistModel.h
|
src/timeline/RoomlistModel.h
|
||||||
|
|
||||||
# UI components
|
# UI components
|
||||||
src/ui/FlatButton.h
|
|
||||||
src/ui/Label.h
|
|
||||||
src/ui/LoadingIndicator.h
|
|
||||||
src/ui/MxcAnimatedImage.h
|
src/ui/MxcAnimatedImage.h
|
||||||
src/ui/MxcMediaProxy.h
|
src/ui/MxcMediaProxy.h
|
||||||
src/ui/Menu.h
|
|
||||||
src/ui/NhekoCursorShape.h
|
src/ui/NhekoCursorShape.h
|
||||||
src/ui/NhekoDropArea.h
|
src/ui/NhekoDropArea.h
|
||||||
src/ui/NhekoGlobalObject.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/RoomSettings.h
|
||||||
src/ui/SnackBar.h
|
|
||||||
src/ui/TextField.h
|
src/ui/TextField.h
|
||||||
src/ui/TextLabel.h
|
|
||||||
src/ui/Theme.h
|
src/ui/Theme.h
|
||||||
src/ui/ThemeManager.h
|
src/ui/ThemeManager.h
|
||||||
src/ui/ToggleButton.h
|
src/ui/ToggleButton.h
|
||||||
@ -596,7 +574,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||||||
src/UsersModel.h
|
src/UsersModel.h
|
||||||
src/RoomDirectoryModel.h
|
src/RoomDirectoryModel.h
|
||||||
src/RoomsModel.h
|
src/RoomsModel.h
|
||||||
src/WelcomePage.h
|
|
||||||
src/ReadReceiptsModel.h
|
src/ReadReceiptsModel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ modules:
|
|||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: 6a7eaa5006b1a18e132be7655e490d9819158dca
|
- commit: 9781553b0186f2db9036d2abbde83c28828eb2b9
|
||||||
#tag: v0.6.1
|
#tag: v0.6.1
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
|
@ -319,7 +319,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location line="+66"/>
|
<location line="+66"/>
|
||||||
<source>Failed to kick %1 from %2: %3</source>
|
<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>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
@ -97,6 +97,7 @@ Rectangle {
|
|||||||
|
|
||||||
implicitHeight: chatPage.height
|
implicitHeight: chatPage.height
|
||||||
collapsed: parent.collapsed
|
collapsed: parent.collapsed
|
||||||
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
|
@ -8,29 +8,139 @@ import QtQuick.Controls 2.12
|
|||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import im.nheko 1.0
|
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
|
Timer {
|
||||||
color: Nheko.colors.text
|
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 {
|
Rectangle {
|
||||||
id: blueBar
|
id: blueBar
|
||||||
|
|
||||||
anchors.top: parent.bottom
|
Layout.fillWidth: true
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: Nheko.colors.highlight
|
color: Nheko.colors.highlight
|
||||||
height: 1
|
height: 1
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: blackBar
|
id: blackBar
|
||||||
|
|
||||||
anchors.verticalCenter: blueBar.verticalCenter
|
anchors.top: parent.top
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
height: parent.height + 1
|
height: parent.height*2
|
||||||
width: 0
|
width: 0
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
|
|
||||||
@ -50,11 +160,12 @@ TextField {
|
|||||||
to: "focused"
|
to: "focused"
|
||||||
reversible: true
|
reversible: true
|
||||||
|
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: blackBar
|
target: blackBar
|
||||||
properties: "width"
|
properties: "width"
|
||||||
duration: 500
|
duration: 310
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InCubic
|
||||||
alwaysRunToEnd: true
|
alwaysRunToEnd: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +175,8 @@ TextField {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
HoverHandler {
|
||||||
id: backgroundRect
|
id: hover
|
||||||
|
enabled: c.ToolTip.text
|
||||||
color: Nheko.colors.base
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Window 2.2
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@ -15,7 +16,7 @@ Item {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onFocusChanged() {
|
function onFocusChanged() {
|
||||||
if (TimelineManager.isWindowFocused) {
|
if (MainWindow.active) {
|
||||||
screenSaverTimer.stop();
|
screenSaverTimer.stop();
|
||||||
screenSaver.state = "Invisible";
|
screenSaver.state = "Invisible";
|
||||||
} else {
|
} else {
|
||||||
@ -32,7 +33,7 @@ Item {
|
|||||||
id: screenSaverTimer
|
id: screenSaverTimer
|
||||||
|
|
||||||
interval: screenTimeout * 1000
|
interval: screenTimeout * 1000
|
||||||
running: true
|
running: !MainWindow.active
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
screenSaver.state = "Visible";
|
screenSaver.state = "Visible";
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ Popup {
|
|||||||
id: quickSwitcher
|
id: quickSwitcher
|
||||||
|
|
||||||
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
||||||
property int textMargin: Math.round(textHeight / 8)
|
|
||||||
|
|
||||||
background: null
|
background: null
|
||||||
width: Math.round(parent.width / 2)
|
width: Math.round(parent.width / 2)
|
||||||
@ -34,7 +33,6 @@ Popup {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||||
padding: textMargin
|
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
onTextEdited: {
|
onTextEdited: {
|
||||||
completerPopup.completer.searchString = text;
|
completerPopup.completer.searchString = text;
|
||||||
@ -60,7 +58,7 @@ Popup {
|
|||||||
id: completerPopup
|
id: completerPopup
|
||||||
|
|
||||||
x: roomTextInput.x
|
x: roomTextInput.x
|
||||||
y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
|
y: roomTextInput.y + quickSwitcher.textHeight
|
||||||
visible: roomTextInput.length > 0
|
visible: roomTextInput.length > 0
|
||||||
width: parent.width
|
width: parent.width
|
||||||
completerName: "room"
|
completerName: "room"
|
||||||
|
@ -385,7 +385,7 @@ Page {
|
|||||||
header: ColumnLayout {
|
header: ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Rectangle {
|
Pane {
|
||||||
id: userInfoPanel
|
id: userInfoPanel
|
||||||
|
|
||||||
function openUserProfile() {
|
function openUserProfile() {
|
||||||
@ -396,12 +396,15 @@ Page {
|
|||||||
userProfile.show();
|
userProfile.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
color: Nheko.colors.window
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
|
//Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium
|
||||||
|
padding: Nheko.paddingMedium
|
||||||
Layout.minimumHeight: 40
|
Layout.minimumHeight: 40
|
||||||
|
|
||||||
|
background: Rectangle {color: Nheko.colors.window}
|
||||||
|
|
||||||
InputDialog {
|
InputDialog {
|
||||||
id: statusDialog
|
id: statusDialog
|
||||||
|
|
||||||
@ -442,14 +445,12 @@ Page {
|
|||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
contentItem: RowLayout {
|
||||||
id: userInfoGrid
|
id: userInfoGrid
|
||||||
|
|
||||||
property var profile: Nheko.currentUser
|
property var profile: Nheko.currentUser
|
||||||
|
|
||||||
spacing: Nheko.paddingMedium
|
spacing: Nheko.paddingMedium
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Nheko.paddingMedium
|
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
@ -614,19 +615,17 @@ Page {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Pane {
|
||||||
color: Nheko.colors.window
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
Layout.preferredHeight: buttonRow.implicitHeight
|
|
||||||
Layout.minimumHeight: 40
|
Layout.minimumHeight: 40
|
||||||
|
|
||||||
RowLayout {
|
horizontalPadding: Nheko.paddingMedium
|
||||||
id: buttonRow
|
verticalPadding: 0
|
||||||
|
|
||||||
anchors.left: parent.left
|
background: Rectangle {color: Nheko.colors.window}
|
||||||
anchors.right: parent.right
|
contentItem: RowLayout {
|
||||||
anchors.margins: Nheko.paddingMedium
|
id: buttonRow
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
@ -9,6 +9,7 @@ import "./dialogs"
|
|||||||
import "./emoji"
|
import "./emoji"
|
||||||
import "./pages"
|
import "./pages"
|
||||||
import "./voip"
|
import "./voip"
|
||||||
|
import "./ui"
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
@ -17,10 +18,12 @@ import QtQuick.Window 2.15
|
|||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
import im.nheko.EmojiModel 1.0
|
import im.nheko.EmojiModel 1.0
|
||||||
|
|
||||||
Page {
|
Pane {
|
||||||
id: timelineRoot
|
id: timelineRoot
|
||||||
|
|
||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
|
background: null
|
||||||
|
padding: 0
|
||||||
|
|
||||||
FontMetrics {
|
FontMetrics {
|
||||||
id: fontMetrics
|
id: fontMetrics
|
||||||
@ -153,11 +156,15 @@ Page {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: StandardKey.Quit
|
||||||
|
onActivated: Qt.quit()
|
||||||
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+K"
|
sequence: "Ctrl+K"
|
||||||
onActivated: {
|
onActivated: {
|
||||||
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
|
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
|
||||||
TimelineManager.focusTimeline();
|
|
||||||
quickSwitch.open();
|
quickSwitch.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,7 +172,6 @@ Page {
|
|||||||
Shortcut {
|
Shortcut {
|
||||||
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
||||||
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
||||||
context: Qt.ApplicationShortcut
|
|
||||||
onActivated: Rooms.nextRoomWithActivity()
|
onActivated: Rooms.nextRoomWithActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,9 +372,51 @@ Page {
|
|||||||
id: mainWindow
|
id: mainWindow
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
initialItem: ChatPage {
|
initialItem: welcomePage
|
||||||
//anchors.fill: parent
|
}
|
||||||
|
|
||||||
|
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"
|
import "./delegates"
|
||||||
|
|
||||||
Rectangle {
|
Pane {
|
||||||
id: topBar
|
id: topBar
|
||||||
|
|
||||||
property bool showBackButton: false
|
property bool showBackButton: false
|
||||||
@ -28,7 +28,11 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||||
z: 3
|
z: 3
|
||||||
color: Nheko.colors.window
|
|
||||||
|
padding: 0
|
||||||
|
background: Rectangle {
|
||||||
|
color: Nheko.colors.window
|
||||||
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onSingleTapped: {
|
onSingleTapped: {
|
||||||
@ -65,248 +69,250 @@ Rectangle {
|
|||||||
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
|
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
|
||||||
}
|
}
|
||||||
|
|
||||||
GridLayout {
|
contentItem: Item {
|
||||||
id: topLayout
|
GridLayout {
|
||||||
|
id: topLayout
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.margins: Nheko.paddingMedium
|
anchors.margins: Nheko.paddingMedium
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
columnSpacing: Nheko.paddingSmall
|
columnSpacing: Nheko.paddingSmall
|
||||||
rowSpacing: Nheko.paddingSmall
|
rowSpacing: Nheko.paddingSmall
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: backToRoomsButton
|
id: backToRoomsButton
|
||||||
|
|
||||||
Layout.column: 0
|
Layout.column: 0
|
||||||
Layout.row: 0
|
Layout.row: 0
|
||||||
Layout.rowSpan: 2
|
Layout.rowSpan: 2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
visible: showBackButton
|
visible: showBackButton
|
||||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Back to room list")
|
ToolTip.text: qsTr("Back to room list")
|
||||||
onClicked: Rooms.resetCurrentRoom()
|
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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ImageButton {
|
Avatar {
|
||||||
id: pinButton
|
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
|
MatrixText {
|
||||||
Layout.column: 4
|
id: roomTopicC
|
||||||
Layout.row: 0
|
Layout.fillWidth: true
|
||||||
Layout.rowSpan: 2
|
Layout.column: 2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.row: 1
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
selectByMouse: false
|
||||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
enabled: false
|
||||||
ToolTip.visible: hovered
|
clip: true
|
||||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
text: roomTopic
|
||||||
onClicked: {
|
}
|
||||||
var ps = Settings.hiddenPins;
|
|
||||||
if (pinsShown) {
|
EncryptionIndicator {
|
||||||
ps.push(roomId);
|
Layout.column: 3
|
||||||
} else {
|
Layout.row: 0
|
||||||
const index = ps.indexOf(roomId);
|
Layout.rowSpan: 2
|
||||||
if (index > -1) {
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
ps.splice(index, 1);
|
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 {
|
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||||
id: roomOptionsButton
|
|
||||||
|
|
||||||
visible: !!room
|
visible: !!room && room.pinnedMessages.length > 0
|
||||||
Layout.column: 5
|
Layout.column: 4
|
||||||
Layout.row: 0
|
Layout.row: 0
|
||||||
Layout.rowSpan: 2
|
Layout.rowSpan: 2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
image: ":/icons/icons/ui/options.svg"
|
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Room options")
|
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
onClicked: {
|
||||||
|
var ps = Settings.hiddenPins;
|
||||||
Platform.Menu {
|
if (pinsShown) {
|
||||||
id: roomOptionsMenu
|
ps.push(roomId);
|
||||||
|
} else {
|
||||||
Platform.MenuItem {
|
const index = ps.indexOf(roomId);
|
||||||
visible: room ? room.permissions.canInvite() : false
|
if (index > -1) {
|
||||||
text: qsTr("Invite users")
|
ps.splice(index, 1);
|
||||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
}
|
||||||
}
|
}
|
||||||
|
Settings.hiddenPins = ps;
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
ImageButton {
|
||||||
|
id: roomOptionsButton
|
||||||
|
|
||||||
ScrollView {
|
visible: !!room
|
||||||
id: pinnedMessages
|
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
|
Platform.Menu {
|
||||||
Layout.column: 2
|
id: roomOptionsMenu
|
||||||
Layout.columnSpan: 3
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Platform.MenuItem {
|
||||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
visible: room ? room.permissions.canInvite() : false
|
||||||
|
text: qsTr("Invite users")
|
||||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
onTriggered: TimelineManager.openInviteUsers(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 {
|
Platform.MenuItem {
|
||||||
id: deletePinButton
|
text: qsTr("Members")
|
||||||
|
onTriggered: TimelineManager.openRoomMembers(room)
|
||||||
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("Leave room")
|
||||||
|
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.MenuItem {
|
||||||
|
text: qsTr("Settings")
|
||||||
|
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ScrollHelper {
|
ScrollView {
|
||||||
flickable: parent
|
id: pinnedMessages
|
||||||
anchors.fill: parent
|
|
||||||
enabled: !Settings.mobileMode
|
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 {
|
CursorShape {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
|
anchors.bottomMargin: pinnedMessages.visible ? pinnedMessages.height : 0
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import im.nheko 1.0
|
|||||||
Button {
|
Button {
|
||||||
id: control
|
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)
|
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ Button {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
//height: control.contentItem.implicitHeight * 2
|
//height: control.contentItem.implicitHeight * 2
|
||||||
//width: control.contentItem.implicitWidth * 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))
|
color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ ApplicationWindow {
|
|||||||
minimumHeight: stack.implicitHeight
|
minimumHeight: stack.implicitHeight
|
||||||
width: stack.implicitWidth
|
width: stack.implicitWidth
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(dialog)
|
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: stack
|
id: stack
|
||||||
|
@ -12,8 +12,6 @@ import QtQuick.Layouts 1.12
|
|||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
//Component.onCompleted: Nheko.reparent(win)
|
|
||||||
|
|
||||||
id: win
|
id: win
|
||||||
|
|
||||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
|
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
|
||||||
@ -175,39 +173,37 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
|
||||||
visible: imagePack.roomid
|
|
||||||
text: qsTr("State key")
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
|
id: statekeyField
|
||||||
|
|
||||||
visible: imagePack.roomid
|
visible: imagePack.roomid
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
label: qsTr("State key")
|
||||||
text: imagePack.statekey
|
text: imagePack.statekey
|
||||||
onTextEdited: imagePack.statekey = text
|
onTextEdited: imagePack.statekey = text
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
|
||||||
text: qsTr("Packname")
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
label: qsTr("Packname")
|
||||||
text: imagePack.packname
|
text: imagePack.packname
|
||||||
onTextEdited: imagePack.packname = text
|
onTextEdited: imagePack.packname = text
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
|
||||||
text: qsTr("Attribution")
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
label: qsTr("Attribution")
|
||||||
text: imagePack.attribution
|
text: imagePack.attribution
|
||||||
onTextEdited: imagePack.attribution = text
|
onTextEdited: imagePack.attribution = text
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
|
Layout.margins: statekeyField.textPadding
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: font.pixelSize * 0.02
|
||||||
text: qsTr("Use as Emoji")
|
text: qsTr("Use as Emoji")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +214,9 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
|
Layout.margins: statekeyField.textPadding
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: font.pixelSize * 0.02
|
||||||
text: qsTr("Use as Sticker")
|
text: qsTr("Use as Sticker")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,27 +252,28 @@ ApplicationWindow {
|
|||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
|
||||||
text: qsTr("Shortcode")
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
label: qsTr("Shortcode")
|
||||||
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
|
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
|
||||||
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
|
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
|
||||||
text: qsTr("Body")
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
|
id: bodyField
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
label: qsTr("Body")
|
||||||
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
|
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
|
||||||
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
|
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
|
Layout.margins: bodyField.textPadding
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: font.pixelSize * 0.02
|
||||||
text: qsTr("Use as Emoji")
|
text: qsTr("Use as Emoji")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +284,9 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
|
Layout.margins: bodyField.textPadding
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: font.pixelSize * 0.02
|
||||||
text: qsTr("Use as Sticker")
|
text: qsTr("Use as Sticker")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +297,9 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
|
Layout.margins: bodyField.textPadding
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.letterSpacing: font.pixelSize * 0.02
|
||||||
text: qsTr("Remove from pack")
|
text: qsTr("Remove from pack")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ ApplicationWindow {
|
|||||||
color: Nheko.colors.base
|
color: Nheko.colors.base
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(win)
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: packEditor
|
id: packEditor
|
||||||
|
@ -18,7 +18,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog
|
flags: Qt.Dialog
|
||||||
Component.onCompleted: Nheko.reparent(inputDialog)
|
|
||||||
width: 350
|
width: 350
|
||||||
height: fontMetrics.lineSpacing * 7
|
height: fontMetrics.lineSpacing * 7
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ ApplicationWindow {
|
|||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(inviteDialogRoot)
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+Enter"
|
sequence: "Ctrl+Enter"
|
||||||
|
@ -17,7 +17,6 @@ ApplicationWindow {
|
|||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
Component.onCompleted: Nheko.reparent(joinRoomRoot)
|
|
||||||
width: 350
|
width: 350
|
||||||
height: fontMetrics.lineSpacing * 7
|
height: fontMetrics.lineSpacing * 7
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog
|
flags: Qt.Dialog
|
||||||
Component.onCompleted: Nheko.reparent(inputDialog)
|
|
||||||
width: 350
|
width: 350
|
||||||
height: fontMetrics.lineSpacing * 7
|
height: fontMetrics.lineSpacing * 7
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ ApplicationWindow {
|
|||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(rawMessageRoot)
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Cancel
|
sequence: StandardKey.Cancel
|
||||||
|
@ -22,7 +22,6 @@ ApplicationWindow {
|
|||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(readReceiptsRoot)
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Cancel
|
sequence: StandardKey.Cancel
|
||||||
|
@ -22,7 +22,6 @@ ApplicationWindow {
|
|||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
modality: Qt.WindowModal
|
modality: Qt.WindowModal
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(roomDirectoryWindow)
|
|
||||||
title: qsTr("Explore Public Rooms")
|
title: qsTr("Explore Public Rooms")
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
@ -189,7 +188,6 @@ ApplicationWindow {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
font.pixelSize: fontMetrics.font.pixelSize
|
font.pixelSize: fontMetrics.font.pixelSize
|
||||||
padding: Nheko.paddingMedium
|
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
placeholderText: qsTr("Search for public rooms")
|
placeholderText: qsTr("Search for public rooms")
|
||||||
onTextChanged: searchTimer.restart()
|
onTextChanged: searchTimer.restart()
|
||||||
@ -200,7 +198,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Layout.minimumWidth: 0.3 * header.width
|
Layout.minimumWidth: 0.3 * header.width
|
||||||
Layout.maximumWidth: 0.3 * header.width
|
Layout.maximumWidth: 0.3 * header.width
|
||||||
padding: Nheko.paddingMedium
|
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
placeholderText: qsTr("Choose custom homeserver")
|
placeholderText: qsTr("Choose custom homeserver")
|
||||||
onTextChanged: publicRooms.setMatrixServer(text)
|
onTextChanged: publicRooms.setMatrixServer(text)
|
||||||
|
@ -24,7 +24,6 @@ ApplicationWindow {
|
|||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(roomMembersRoot)
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Cancel
|
sequence: StandardKey.Cancel
|
||||||
|
@ -23,7 +23,6 @@ ApplicationWindow {
|
|||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(roomSettingsDialog)
|
|
||||||
title: qsTr("Room Settings")
|
title: qsTr("Room Settings")
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
|
@ -13,9 +13,6 @@ import QtQuick.Window 2.13
|
|||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
// this does not work in ApplicationWindow, just in Window
|
|
||||||
//transientParent: Nheko.mainwindow()
|
|
||||||
|
|
||||||
id: userProfileDialog
|
id: userProfileDialog
|
||||||
|
|
||||||
property var profile
|
property var profile
|
||||||
@ -29,7 +26,6 @@ ApplicationWindow {
|
|||||||
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
|
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
Component.onCompleted: Nheko.reparent(userProfileDialog)
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Cancel
|
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/TypingIndicator.qml</file>
|
||||||
<file>qml/NotificationWarning.qml</file>
|
<file>qml/NotificationWarning.qml</file>
|
||||||
<file>qml/pages/UserSettingsPage.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/AdaptiveLayout.qml</file>
|
||||||
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
||||||
<file>qml/components/AvatarListTile.qml</file>
|
<file>qml/components/AvatarListTile.qml</file>
|
||||||
@ -154,6 +157,7 @@
|
|||||||
<file>qml/ui/NhekoSlider.qml</file>
|
<file>qml/ui/NhekoSlider.qml</file>
|
||||||
<file>qml/ui/Ripple.qml</file>
|
<file>qml/ui/Ripple.qml</file>
|
||||||
<file>qml/ui/Spinner.qml</file>
|
<file>qml/ui/Spinner.qml</file>
|
||||||
|
<file>qml/ui/Snackbar.qml</file>
|
||||||
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
||||||
<file>qml/ui/media/MediaControls.qml</file>
|
<file>qml/ui/media/MediaControls.qml</file>
|
||||||
<file>qml/voip/ActiveCallBar.qml</file>
|
<file>qml/voip/ActiveCallBar.qml</file>
|
||||||
|
@ -325,7 +325,7 @@ static void
|
|||||||
fatalSecretError()
|
fatalSecretError()
|
||||||
{
|
{
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
ChatPage::instance(),
|
nullptr,
|
||||||
QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
|
QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"),
|
||||||
QCoreApplication::translate(
|
QCoreApplication::translate(
|
||||||
"SecretStorage",
|
"SecretStorage",
|
||||||
@ -391,6 +391,7 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
|
|||||||
&QKeychain::ReadPasswordJob::finished,
|
&QKeychain::ReadPasswordJob::finished,
|
||||||
this,
|
this,
|
||||||
[this, name, toLoad, job](QKeychain::Job *) mutable {
|
[this, name, toLoad, job](QKeychain::Job *) mutable {
|
||||||
|
nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first);
|
||||||
const QString secret = job->textData();
|
const QString secret = job->textData();
|
||||||
if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
|
if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
|
||||||
nhlog::db()->error("Restoring secret '{}' failed ({}): {}",
|
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.
|
// You can't start a job from the finish signal of a job.
|
||||||
QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); });
|
QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); });
|
||||||
});
|
});
|
||||||
|
nhlog::db()->debug("Reading '{}'", name_);
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "encryption/DeviceVerificationFlow.h"
|
#include "encryption/DeviceVerificationFlow.h"
|
||||||
#include "encryption/Olm.h"
|
#include "encryption/Olm.h"
|
||||||
#include "ui/OverlayModal.h"
|
|
||||||
#include "ui/Theme.h"
|
#include "ui/Theme.h"
|
||||||
#include "ui/UserProfile.h"
|
#include "ui/UserProfile.h"
|
||||||
#include "voip/CallManager.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(mtx::secret_storage::AesHmacSha2KeyDescription)
|
||||||
Q_DECLARE_METATYPE(SecretsToDecrypt)
|
Q_DECLARE_METATYPE(SecretsToDecrypt)
|
||||||
|
|
||||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
|
||||||
: QWidget(parent)
|
: QObject(parent)
|
||||||
, isConnected_(true)
|
, isConnected_(true)
|
||||||
, userSettings_{userSettings}
|
, userSettings_{userSettings}
|
||||||
, notificationsManager(this)
|
, notificationsManager(this)
|
||||||
@ -61,14 +60,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||||||
qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
|
qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
|
||||||
qRegisterMetaType<SecretsToDecrypt>();
|
qRegisterMetaType<SecretsToDecrypt>();
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout(this);
|
|
||||||
topLayout_->setSpacing(0);
|
|
||||||
topLayout_->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
view_manager_ = new TimelineViewManager(callManager_, this);
|
view_manager_ = new TimelineViewManager(callManager_, this);
|
||||||
|
|
||||||
topLayout_->addWidget(view_manager_->getWidget());
|
|
||||||
|
|
||||||
connect(this,
|
connect(this,
|
||||||
&ChatPage::downloadedSecrets,
|
&ChatPage::downloadedSecrets,
|
||||||
this,
|
this,
|
||||||
@ -154,7 +147,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||||||
[this](const QString &roomid, const QString &eventid) {
|
[this](const QString &roomid, const QString &eventid) {
|
||||||
Q_UNUSED(eventid)
|
Q_UNUSED(eventid)
|
||||||
view_manager_->rooms()->setCurrentRoom(roomid);
|
view_manager_->rooms()->setCurrentRoom(roomid);
|
||||||
activateWindow();
|
MainWindow::instance()->requestActivate();
|
||||||
});
|
});
|
||||||
connect(¬ificationsManager,
|
connect(¬ificationsManager,
|
||||||
&NotificationsManager::sendNotificationReply,
|
&NotificationsManager::sendNotificationReply,
|
||||||
@ -162,17 +155,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||||||
[this](const QString &roomid, const QString &eventid, const QString &body) {
|
[this](const QString &roomid, const QString &eventid, const QString &body) {
|
||||||
view_manager_->rooms()->setCurrentRoom(roomid);
|
view_manager_->rooms()->setCurrentRoom(roomid);
|
||||||
view_manager_->queueReply(roomid, eventid, body);
|
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(
|
connect(
|
||||||
this,
|
this,
|
||||||
&ChatPage::initializeViews,
|
&ChatPage::initializeViews,
|
||||||
@ -183,8 +168,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||||||
&ChatPage::initializeEmptyViews,
|
&ChatPage::initializeEmptyViews,
|
||||||
view_manager_,
|
view_manager_,
|
||||||
&TimelineViewManager::initializeRoomlist);
|
&TimelineViewManager::initializeRoomlist);
|
||||||
connect(
|
|
||||||
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
|
|
||||||
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
|
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
|
||||||
view_manager_->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
|
// TODO: Replace this once we have proper pushrules support. This is a horrible hack
|
||||||
if (prevNotificationCount < notificationCount) {
|
if (prevNotificationCount < notificationCount) {
|
||||||
if (userSettings_->hasAlertOnNotification())
|
if (userSettings_->hasAlertOnNotification())
|
||||||
QApplication::alert(this);
|
MainWindow::instance()->alert(0);
|
||||||
}
|
}
|
||||||
prevNotificationCount = notificationCount;
|
prevNotificationCount = notificationCount;
|
||||||
|
|
||||||
@ -331,7 +314,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
|||||||
} else if (cacheVersion == cache::CacheVersion::Older) {
|
} else if (cacheVersion == cache::CacheVersion::Older) {
|
||||||
if (!cache::runMigrations()) {
|
if (!cache::runMigrations()) {
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this,
|
nullptr,
|
||||||
tr("Cache migration failed!"),
|
tr("Cache migration failed!"),
|
||||||
tr("Migrating the cache to the current version failed. "
|
tr("Migrating the cache to the current version failed. "
|
||||||
"This can have different reasons. Please open an "
|
"This can have different reasons. Please open an "
|
||||||
@ -344,7 +327,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
|||||||
return;
|
return;
|
||||||
} else if (cacheVersion == cache::CacheVersion::Newer) {
|
} else if (cacheVersion == cache::CacheVersion::Newer) {
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this,
|
nullptr,
|
||||||
tr("Incompatible cache version"),
|
tr("Incompatible cache version"),
|
||||||
tr("The cache on your disk is newer than this version of Nheko "
|
tr("The cache on your disk is newer than this version of Nheko "
|
||||||
"supports. Please update Nheko or clear your cache."));
|
"supports. Please update Nheko or clear your cache."));
|
||||||
@ -690,7 +673,7 @@ ChatPage::joinRoomVia(const std::string &room_id,
|
|||||||
if (promptForConfirmation &&
|
if (promptForConfirmation &&
|
||||||
QMessageBox::Yes !=
|
QMessageBox::Yes !=
|
||||||
QMessageBox::question(
|
QMessageBox::question(
|
||||||
this,
|
nullptr,
|
||||||
tr("Confirm join"),
|
tr("Confirm join"),
|
||||||
tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
|
tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
|
||||||
return;
|
return;
|
||||||
@ -776,7 +759,7 @@ ChatPage::inviteUser(QString userid, QString reason)
|
|||||||
{
|
{
|
||||||
auto room = currentRoom();
|
auto room = currentRoom();
|
||||||
|
|
||||||
if (QMessageBox::question(this,
|
if (QMessageBox::question(nullptr,
|
||||||
tr("Confirm invite"),
|
tr("Confirm invite"),
|
||||||
tr("Do you really want to invite %1 (%2)?")
|
tr("Do you really want to invite %1 (%2)?")
|
||||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||||
@ -787,6 +770,8 @@ ChatPage::inviteUser(QString userid, QString reason)
|
|||||||
userid.toStdString(),
|
userid.toStdString(),
|
||||||
[this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
|
[this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
nhlog::net()->error(
|
||||||
|
"Failed to invite {} to {}: {}", userid.toStdString(), room.toStdString(), *err);
|
||||||
emit showNotification(
|
emit showNotification(
|
||||||
tr("Failed to invite %1 to %2: %3")
|
tr("Failed to invite %1 to %2: %3")
|
||||||
.arg(userid, room, QString::fromStdString(err->matrix_error.error)));
|
.arg(userid, room, QString::fromStdString(err->matrix_error.error)));
|
||||||
@ -800,7 +785,7 @@ ChatPage::kickUser(QString userid, QString reason)
|
|||||||
{
|
{
|
||||||
auto room = currentRoom();
|
auto room = currentRoom();
|
||||||
|
|
||||||
if (QMessageBox::question(this,
|
if (QMessageBox::question(nullptr,
|
||||||
tr("Confirm kick"),
|
tr("Confirm kick"),
|
||||||
tr("Do you really want to kick %1 (%2)?")
|
tr("Do you really want to kick %1 (%2)?")
|
||||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||||
@ -825,7 +810,7 @@ ChatPage::banUser(QString userid, QString reason)
|
|||||||
auto room = currentRoom();
|
auto room = currentRoom();
|
||||||
|
|
||||||
if (QMessageBox::question(
|
if (QMessageBox::question(
|
||||||
this,
|
nullptr,
|
||||||
tr("Confirm ban"),
|
tr("Confirm ban"),
|
||||||
tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) !=
|
tr("Do you really want to ban %1 (%2)?").arg(cache::displayName(room, userid), userid)) !=
|
||||||
QMessageBox::Yes)
|
QMessageBox::Yes)
|
||||||
@ -849,7 +834,7 @@ ChatPage::unbanUser(QString userid, QString reason)
|
|||||||
{
|
{
|
||||||
auto room = currentRoom();
|
auto room = currentRoom();
|
||||||
|
|
||||||
if (QMessageBox::question(this,
|
if (QMessageBox::question(nullptr,
|
||||||
tr("Confirm unban"),
|
tr("Confirm unban"),
|
||||||
tr("Do you really want to unban %1 (%2)?")
|
tr("Do you really want to unban %1 (%2)?")
|
||||||
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
.arg(cache::displayName(room, userid), userid)) != QMessageBox::Yes)
|
||||||
@ -1064,8 +1049,6 @@ ChatPage::initiateLogout()
|
|||||||
|
|
||||||
emit loggedOut();
|
emit loggedOut();
|
||||||
});
|
});
|
||||||
|
|
||||||
emit showOverlayProgressBar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -1083,7 +1066,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
|
|||||||
const SecretsToDecrypt &secrets)
|
const SecretsToDecrypt &secrets)
|
||||||
{
|
{
|
||||||
QString text = QInputDialog::getText(
|
QString text = QInputDialog::getText(
|
||||||
ChatPage::instance(),
|
nullptr,
|
||||||
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
|
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
|
||||||
keyDesc.name.empty()
|
keyDesc.name.empty()
|
||||||
? QCoreApplication::translate(
|
? QCoreApplication::translate(
|
||||||
@ -1115,7 +1098,7 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
|
|||||||
|
|
||||||
if (!decryptionKey) {
|
if (!decryptionKey) {
|
||||||
QMessageBox::information(
|
QMessageBox::information(
|
||||||
ChatPage::instance(),
|
nullptr,
|
||||||
QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
|
QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
|
||||||
QCoreApplication::translate("CrossSigningSecrets",
|
QCoreApplication::translate("CrossSigningSecrets",
|
||||||
"Failed to decrypt secrets with the "
|
"Failed to decrypt secrets with the "
|
||||||
@ -1209,7 +1192,7 @@ ChatPage::startChat(QString userid)
|
|||||||
|
|
||||||
if (QMessageBox::Yes !=
|
if (QMessageBox::Yes !=
|
||||||
QMessageBox::question(
|
QMessageBox::question(
|
||||||
this,
|
nullptr,
|
||||||
tr("Confirm invite"),
|
tr("Confirm invite"),
|
||||||
tr("Do you really want to start a private chat with %1?").arg(userid)))
|
tr("Do you really want to start a private chat with %1?").arg(userid)))
|
||||||
return;
|
return;
|
||||||
@ -1395,7 +1378,7 @@ ChatPage::handleMatrixUri(const QUrl &uri)
|
|||||||
bool
|
bool
|
||||||
ChatPage::isRoomActive(const QString &room_id)
|
ChatPage::isRoomActive(const QString &room_id)
|
||||||
{
|
{
|
||||||
return isActiveWindow() && currentRoom() == room_id;
|
return MainWindow::instance()->isActive() && currentRoom() == room_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stack>
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <mtx/common.hpp>
|
#include <mtx/common.hpp>
|
||||||
@ -18,17 +17,15 @@
|
|||||||
#include <mtx/events/presence.hpp>
|
#include <mtx/events/presence.hpp>
|
||||||
#include <mtx/secret_storage.hpp>
|
#include <mtx/secret_storage.hpp>
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QSharedPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "CacheCryptoStructs.h"
|
#include "CacheCryptoStructs.h"
|
||||||
#include "CacheStructs.h"
|
#include "CacheStructs.h"
|
||||||
#include "notifications/Manager.h"
|
#include "notifications/Manager.h"
|
||||||
|
|
||||||
class OverlayModal;
|
|
||||||
class TimelineViewManager;
|
class TimelineViewManager;
|
||||||
class UserSettings;
|
class UserSettings;
|
||||||
class NotificationsManager;
|
class NotificationsManager;
|
||||||
@ -51,12 +48,12 @@ struct Rooms;
|
|||||||
|
|
||||||
using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
|
using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
|
||||||
|
|
||||||
class ChatPage : public QWidget
|
class ChatPage : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
|
ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent = nullptr);
|
||||||
|
|
||||||
// Initialize all the components of the UI.
|
// Initialize all the components of the UI.
|
||||||
void bootstrap(QString userid, QString homeserver, QString token);
|
void bootstrap(QString userid, QString homeserver, QString token);
|
||||||
@ -112,7 +109,6 @@ signals:
|
|||||||
void showNotification(const QString &msg);
|
void showNotification(const QString &msg);
|
||||||
void showLoginPage(const QString &msg);
|
void showLoginPage(const QString &msg);
|
||||||
void showUserSettingsPage();
|
void showUserSettingsPage();
|
||||||
void showOverlayProgressBar();
|
|
||||||
|
|
||||||
void ownProfileOk();
|
void ownProfileOk();
|
||||||
void setUserDisplayName(const QString &name);
|
void setUserDisplayName(const QString &name);
|
||||||
@ -143,7 +139,6 @@ signals:
|
|||||||
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
||||||
void themeChanged();
|
void themeChanged();
|
||||||
void decryptSidebarChanged();
|
void decryptSidebarChanged();
|
||||||
void chatFocusChanged(const bool focused);
|
|
||||||
|
|
||||||
//! Signals for device verificaiton
|
//! Signals for device verificaiton
|
||||||
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
|
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
|
||||||
@ -201,8 +196,6 @@ private:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
void connectCallMessage();
|
void connectCallMessage();
|
||||||
|
|
||||||
QHBoxLayout *topLayout_;
|
|
||||||
|
|
||||||
TimelineViewManager *view_manager_;
|
TimelineViewManager *view_manager_;
|
||||||
|
|
||||||
QTimer connectivityTimer_;
|
QTimer connectivityTimer_;
|
||||||
|
@ -5,11 +5,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QFontMetrics>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QStyleOption>
|
|
||||||
#include <QtMath>
|
|
||||||
|
|
||||||
#include <mtx/identifiers.hpp>
|
#include <mtx/identifiers.hpp>
|
||||||
#include <mtx/requests.hpp>
|
#include <mtx/requests.hpp>
|
||||||
@ -18,247 +13,94 @@
|
|||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "LoginPage.h"
|
#include "LoginPage.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
#include "SSOHandler.h"
|
#include "SSOHandler.h"
|
||||||
#include "UserSettingsPage.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)
|
Q_DECLARE_METATYPE(LoginPage::LoginMethod)
|
||||||
|
|
||||||
using namespace mtx::identifiers;
|
using namespace mtx::identifiers;
|
||||||
|
|
||||||
LoginPage::LoginPage(QWidget *parent)
|
LoginPage::LoginPage(QObject *parent)
|
||||||
: QWidget(parent)
|
: QObject(parent)
|
||||||
, inferredServerAddress_()
|
, inferredServerAddress_()
|
||||||
{
|
{
|
||||||
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
|
[[maybe_unused]] static auto ignored =
|
||||||
|
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_);
|
|
||||||
|
|
||||||
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
|
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
|
||||||
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, 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()));
|
http::client()->set_user(res.user_id);
|
||||||
connect(login_button_, &RaisedButton::clicked, this, [this]() {
|
MainWindow::instance()->showChatPage();
|
||||||
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
|
},
|
||||||
});
|
Qt::QueuedConnection);
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
LoginPage::showError(const QString &msg)
|
LoginPage::showError(const QString &msg)
|
||||||
{
|
{
|
||||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
loggingIn_ = false;
|
||||||
int width = rect.width();
|
emit loggingInChanged();
|
||||||
int height = rect.height();
|
|
||||||
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
|
error_ = msg;
|
||||||
error_label_->setText(msg);
|
emit errorOccurred();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LoginPage::showError(QLabel *label, const QString &msg)
|
LoginPage::setHomeserver(QString hs)
|
||||||
{
|
{
|
||||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
if (hs != homeserver_) {
|
||||||
int width = rect.width();
|
homeserver_ = hs;
|
||||||
int height = rect.height();
|
homeserverValid_ = false;
|
||||||
label->setFixedHeight((int)qCeil(width / 200.0) * height);
|
emit homeserverChanged();
|
||||||
label->setText(msg);
|
http::client()->set_server(hs.toStdString());
|
||||||
|
checkHomeserverVersion();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LoginPage::onMatrixIdEntered()
|
LoginPage::onMatrixIdEntered()
|
||||||
{
|
{
|
||||||
error_label_->setText(QLatin1String(""));
|
clearErrors();
|
||||||
|
|
||||||
|
homeserverValid_ = false;
|
||||||
|
emit homeserverChanged();
|
||||||
|
|
||||||
User user;
|
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()) {
|
if (user.hostname().empty() || user.localpart().empty()) {
|
||||||
error_matrixid_label_->show();
|
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||||
showError(error_matrixid_label_,
|
emit mxidErrorChanged();
|
||||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
error_matrixid_label_->setText(QLatin1String(""));
|
nhlog::net()->debug("hostname: {}", user.hostname());
|
||||||
error_matrixid_label_->hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (user.hostname() != inferredServerAddress_.toStdString()) {
|
||||||
user = parse<User>(matrixid_input_->text().toStdString());
|
homeserverNeeded_ = false;
|
||||||
} catch (const std::exception &) {
|
lookingUpHs_ = true;
|
||||||
showError(error_matrixid_label_,
|
emit lookingUpHsChanged();
|
||||||
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);
|
|
||||||
|
|
||||||
http::client()->set_server(user.hostname());
|
http::client()->set_server(user.hostname());
|
||||||
http::client()->verify_certificates(
|
http::client()->verify_certificates(
|
||||||
!UserSettings::instance()->disableCertificateValidation());
|
!UserSettings::instance()->disableCertificateValidation());
|
||||||
|
homeserver_ = QString::fromStdString(user.hostname());
|
||||||
|
emit homeserverChanged();
|
||||||
|
|
||||||
http::client()->well_known(
|
http::client()->well_known(
|
||||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
[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 + "'");
|
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||||
http::client()->set_server(res.homeserver.base_url);
|
http::client()->set_server(res.homeserver.base_url);
|
||||||
|
emit homeserverChanged();
|
||||||
checkHomeserverVersion();
|
checkHomeserverVersion();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -294,6 +137,16 @@ LoginPage::onMatrixIdEntered()
|
|||||||
void
|
void
|
||||||
LoginPage::checkHomeserverVersion()
|
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) {
|
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err->status_code == 404) {
|
if (err->status_code == 404) {
|
||||||
@ -318,107 +171,78 @@ LoginPage::checkHomeserverVersion()
|
|||||||
if (err || flows.flows.empty())
|
if (err || flows.flows.empty())
|
||||||
emit versionOkCb(true, false);
|
emit versionOkCb(true, false);
|
||||||
|
|
||||||
bool ssoSupported_ = false;
|
bool ssoSupported = false;
|
||||||
bool passwordSupported_ = false;
|
bool passwordSupported = false;
|
||||||
for (const auto &flow : flows.flows) {
|
for (const auto &flow : flows.flows) {
|
||||||
if (flow.type == mtx::user_interactive::auth_types::sso) {
|
if (flow.type == mtx::user_interactive::auth_types::sso) {
|
||||||
ssoSupported_ = true;
|
ssoSupported = true;
|
||||||
} else if (flow.type == mtx::user_interactive::auth_types::password) {
|
} 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
|
void
|
||||||
LoginPage::versionError(const QString &error)
|
LoginPage::versionError(const QString &error)
|
||||||
{
|
{
|
||||||
showError(error_label_, error);
|
showError(error);
|
||||||
serverInput_->show();
|
|
||||||
|
|
||||||
spinner_->stop();
|
homeserverNeeded_ = true;
|
||||||
serverLayout_->removeWidget(spinner_);
|
lookingUpHs_ = false;
|
||||||
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
|
homeserverValid_ = false;
|
||||||
errorIcon_->show();
|
emit lookingUpHsChanged();
|
||||||
matrixidLayout_->removeWidget(spinner_);
|
emit versionLookedUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
|
LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
|
||||||
{
|
{
|
||||||
passwordSupported = passwordSupported_;
|
passwordSupported_ = passwordSupported;
|
||||||
ssoSupported = ssoSupported_;
|
ssoSupported_ = ssoSupported;
|
||||||
|
|
||||||
serverLayout_->removeWidget(spinner_);
|
lookingUpHs_ = false;
|
||||||
matrixidLayout_->removeWidget(spinner_);
|
homeserverValid_ = true;
|
||||||
spinner_->stop();
|
emit homeserverChanged();
|
||||||
|
emit lookingUpHsChanged();
|
||||||
password_input_->setVisible(passwordSupported);
|
emit versionLookedUp();
|
||||||
password_input_->setEnabled(passwordSupported);
|
|
||||||
sso_login_button_->setVisible(ssoSupported);
|
|
||||||
login_button_->setVisible(passwordSupported);
|
|
||||||
|
|
||||||
if (serverInput_->isVisible())
|
|
||||||
serverInput_->hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
|
||||||
|
QString userid,
|
||||||
|
QString password,
|
||||||
|
QString deviceName)
|
||||||
{
|
{
|
||||||
error_label_->setText(QLatin1String(""));
|
clearErrors();
|
||||||
|
|
||||||
User user;
|
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 {
|
try {
|
||||||
user = parse<User>(matrixid_input_->text().toStdString());
|
user = parse<User>(userid.toStdString());
|
||||||
} catch (const std::exception &) {
|
} catch (const std::exception &) {
|
||||||
showError(error_matrixid_label_,
|
mxidError_ = tr("You have entered an invalid Matrix ID e.g @joe:matrix.org");
|
||||||
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
|
emit mxidErrorChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginMethod == LoginMethod::Password) {
|
if (loginMethod == LoginMethod::Password) {
|
||||||
if (password_input_->text().isEmpty())
|
if (password.isEmpty())
|
||||||
return showError(error_label_, tr("Empty password"));
|
return showError(tr("Empty password"));
|
||||||
|
|
||||||
http::client()->login(
|
http::client()->login(
|
||||||
user.localpart(),
|
user.localpart(),
|
||||||
password_input_->text().toStdString(),
|
password.toStdString(),
|
||||||
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
|
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString(),
|
||||||
: deviceName_->text().toStdString(),
|
|
||||||
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
auto error = err->matrix_error.error;
|
auto error = err->matrix_error.error;
|
||||||
if (error.empty())
|
if (error.empty())
|
||||||
error = err->parse_error;
|
error = err->parse_error;
|
||||||
|
|
||||||
showErrorMessage(error_label_, QString::fromStdString(error));
|
showError(QString::fromStdString(error));
|
||||||
emit errorOccurred();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,34 +256,33 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
auto sso = new SSOHandler();
|
auto sso = new SSOHandler();
|
||||||
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
|
connect(
|
||||||
mtx::requests::Login req{};
|
sso, &SSOHandler::ssoSuccess, this, [this, sso, userid, deviceName](std::string token) {
|
||||||
req.token = token;
|
mtx::requests::Login req{};
|
||||||
req.type = mtx::user_interactive::auth_types::token;
|
req.token = token;
|
||||||
req.device_id = deviceName_->text().trimmed().isEmpty()
|
req.type = mtx::user_interactive::auth_types::token;
|
||||||
? initialDeviceName()
|
req.device_id =
|
||||||
: deviceName_->text().toStdString();
|
deviceName.trimmed().isEmpty() ? initialDeviceName_() : deviceName.toStdString();
|
||||||
http::client()->login(
|
http::client()->login(
|
||||||
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
showErrorMessage(error_label_,
|
showError(QString::fromStdString(err->matrix_error.error));
|
||||||
QString::fromStdString(err->matrix_error.error));
|
emit errorOccurred();
|
||||||
emit errorOccurred();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (res.well_known) {
|
if (res.well_known) {
|
||||||
http::client()->set_server(res.well_known->homeserver.base_url);
|
http::client()->set_server(res.well_known->homeserver.base_url);
|
||||||
nhlog::net()->info("Login requested to user server: " +
|
nhlog::net()->info("Login requested to user server: " +
|
||||||
res.well_known->homeserver.base_url);
|
res.well_known->homeserver.base_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit loginOk(res);
|
emit loginOk(res);
|
||||||
});
|
});
|
||||||
sso->deleteLater();
|
sso->deleteLater();
|
||||||
});
|
});
|
||||||
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
|
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
|
||||||
showErrorMessage(error_label_, tr("SSO login failed"));
|
showError(tr("SSO login failed"));
|
||||||
emit errorOccurred();
|
emit errorOccurred();
|
||||||
sso->deleteLater();
|
sso->deleteLater();
|
||||||
});
|
});
|
||||||
@ -468,37 +291,6 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
|
|||||||
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
|
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit loggingIn();
|
loggingIn_ = true;
|
||||||
}
|
emit loggingInChanged();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
166
src/LoginPage.h
166
src/LoginPage.h
@ -6,16 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QObject>
|
||||||
|
|
||||||
class FlatButton;
|
|
||||||
class LoadingIndicator;
|
|
||||||
class OverlayModal;
|
|
||||||
class RaisedButton;
|
|
||||||
class TextField;
|
|
||||||
class QLabel;
|
|
||||||
class QVBoxLayout;
|
|
||||||
class QHBoxLayout;
|
|
||||||
|
|
||||||
namespace mtx {
|
namespace mtx {
|
||||||
namespace responses {
|
namespace responses {
|
||||||
@ -23,62 +14,61 @@ struct Login;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginPage : public QWidget
|
class LoginPage : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
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:
|
public:
|
||||||
enum class LoginMethod
|
enum class LoginMethod
|
||||||
{
|
{
|
||||||
Password,
|
Password,
|
||||||
SSO,
|
SSO,
|
||||||
};
|
};
|
||||||
|
Q_ENUM(LoginMethod)
|
||||||
|
|
||||||
LoginPage(QWidget *parent = nullptr);
|
LoginPage(QObject *parent = nullptr);
|
||||||
|
|
||||||
void reset();
|
Q_INVOKABLE QString initialDeviceName() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(initialDeviceName_());
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
bool lookingUpHs() const { return lookingUpHs_; }
|
||||||
void backButtonClicked();
|
bool loggingIn() const { return loggingIn_; }
|
||||||
void loggingIn();
|
bool passwordSupported() const { return passwordSupported_; }
|
||||||
void errorOccurred();
|
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.
|
QString homeserver() { return homeserver_; }
|
||||||
void versionErrorCb(const QString &err);
|
QString mxid() { return mxid_; }
|
||||||
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
|
||||||
|
|
||||||
void loginOk(const mtx::responses::Login &res);
|
QString error() { return error_; }
|
||||||
void showErrorMessage(QLabel *label, const QString &msg);
|
QString mxidError() { return mxidError_; }
|
||||||
|
|
||||||
protected:
|
void setHomeserver(QString hs);
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void setMxid(QString id)
|
||||||
|
{
|
||||||
|
if (id != mxid_) {
|
||||||
|
mxid_ = id;
|
||||||
|
emit matrixIdChanged();
|
||||||
|
onMatrixIdEntered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
static std::string initialDeviceName_()
|
||||||
// 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()
|
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
return "Nheko on macOS";
|
return "Nheko on macOS";
|
||||||
@ -93,33 +83,65 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QVBoxLayout *top_layout_;
|
signals:
|
||||||
|
void loggingInChanged();
|
||||||
|
void errorOccurred();
|
||||||
|
|
||||||
QHBoxLayout *top_bar_layout_;
|
//! Used to trigger the corresponding slot outside of the main thread.
|
||||||
QHBoxLayout *logo_layout_;
|
void versionErrorCb(const QString &err);
|
||||||
QHBoxLayout *button_layout_;
|
void versionOkCb(bool passwordSupported, bool ssoSupported);
|
||||||
|
|
||||||
QLabel *logo_;
|
void loginOk(const mtx::responses::Login &res);
|
||||||
QLabel *error_label_;
|
|
||||||
QLabel *error_matrixid_label_;
|
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_;
|
QString inferredServerAddress_;
|
||||||
|
|
||||||
FlatButton *back_button_;
|
QString mxid_;
|
||||||
RaisedButton *login_button_, *sso_login_button_;
|
QString homeserver_;
|
||||||
|
|
||||||
QWidget *form_widget_;
|
QString mxidError_;
|
||||||
QHBoxLayout *form_wrapper_;
|
QString error_;
|
||||||
QVBoxLayout *form_layout_;
|
|
||||||
|
|
||||||
TextField *matrixid_input_;
|
bool passwordSupported_ = true;
|
||||||
TextField *password_input_;
|
bool ssoSupported_ = false;
|
||||||
TextField *deviceName_;
|
|
||||||
TextField *serverInput_;
|
bool lookingUpHs_ = false;
|
||||||
bool passwordSupported = true;
|
bool loggingIn_ = false;
|
||||||
bool ssoSupported = false;
|
bool homeserverNeeded_ = false;
|
||||||
|
bool homeserverValid_ = false;
|
||||||
};
|
};
|
||||||
|
@ -5,91 +5,87 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QLayout>
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPluginLoader>
|
|
||||||
#include <QShortcut>
|
|
||||||
|
|
||||||
#include <mtx/requests.hpp>
|
#include <mtx/requests.hpp>
|
||||||
#include <mtx/responses/login.hpp>
|
#include <mtx/responses/login.hpp>
|
||||||
|
|
||||||
|
#include "BlurhashProvider.h"
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
#include "Cache_p.h"
|
#include "Cache_p.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
|
#include "Clipboard.h"
|
||||||
|
#include "ColorImageProvider.h"
|
||||||
|
#include "CombinedImagePackModel.h"
|
||||||
|
#include "CompletionProxyModel.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "EventAccessors.h"
|
||||||
|
#include "ImagePackListModel.h"
|
||||||
|
#include "InviteesModel.h"
|
||||||
#include "JdenticonProvider.h"
|
#include "JdenticonProvider.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "LoginPage.h"
|
#include "LoginPage.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
#include "MemberList.h"
|
#include "MemberList.h"
|
||||||
|
#include "MxcImageProvider.h"
|
||||||
|
#include "ReadReceiptsModel.h"
|
||||||
#include "RegisterPage.h"
|
#include "RegisterPage.h"
|
||||||
|
#include "RoomDirectoryModel.h"
|
||||||
|
#include "RoomsModel.h"
|
||||||
|
#include "SingleImagePackModel.h"
|
||||||
#include "TrayIcon.h"
|
#include "TrayIcon.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
|
#include "UsersModel.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WelcomePage.h"
|
#include "emoji/EmojiModel.h"
|
||||||
#include "ui/LoadingIndicator.h"
|
#include "emoji/Provider.h"
|
||||||
#include "ui/OverlayModal.h"
|
#include "encryption/DeviceVerificationFlow.h"
|
||||||
#include "ui/SnackBar.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 "voip/WebRTCSession.h"
|
||||||
|
|
||||||
#include "dialogs/CreateRoom.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::instance_ = nullptr;
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWindow *parent)
|
||||||
: QMainWindow(parent)
|
: QQuickView(parent)
|
||||||
, userSettings_{UserSettings::instance()}
|
, userSettings_{UserSettings::instance()}
|
||||||
{
|
{
|
||||||
instance_ = this;
|
instance_ = this;
|
||||||
|
|
||||||
QMainWindow::setWindowTitle(0);
|
MainWindow::setWindowTitle(0);
|
||||||
setObjectName(QStringLiteral("MainWindow"));
|
setObjectName(QStringLiteral("MainWindow"));
|
||||||
|
setResizeMode(QQuickView::SizeRootObjectToView);
|
||||||
modal_ = new OverlayModal(this);
|
setMinimumHeight(400);
|
||||||
|
setMinimumWidth(400);
|
||||||
restoreWindowSize();
|
restoreWindowSize();
|
||||||
|
|
||||||
QFont font;
|
chat_page_ = new ChatPage(userSettings_, this);
|
||||||
font.setStyleStrategy(QFont::PreferAntialias);
|
registerQmlTypes();
|
||||||
setFont(font);
|
|
||||||
|
setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color());
|
||||||
|
setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
|
||||||
|
|
||||||
trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
|
trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
|
||||||
|
|
||||||
welcome_page_ = new WelcomePage(this);
|
connect(chat_page_, &ChatPage::closing, this, [this] { switchToLoginPage(""); });
|
||||||
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::unreadMessages, this, &MainWindow::setWindowTitle);
|
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
|
||||||
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
|
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
|
||||||
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
|
connect(chat_page_, &ChatPage::showLoginPage, this, &MainWindow::switchToLoginPage);
|
||||||
login_page_->showError(msg);
|
connect(chat_page_, &ChatPage::showNotification, this, &MainWindow::showNotification);
|
||||||
showLoginPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
|
connect(userSettings_.get(), &UserSettings::trayChanged, trayIcon_, &TrayIcon::setVisible);
|
||||||
connect(trayIcon_,
|
connect(trayIcon_,
|
||||||
@ -97,20 +93,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
this,
|
this,
|
||||||
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
|
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());
|
trayIcon_->setVisible(userSettings_->tray());
|
||||||
|
|
||||||
// load cache on event loop
|
// load cache on event loop
|
||||||
@ -133,11 +115,171 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
user_id.toStdString());
|
user_id.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nhlog::ui()->info("User already signed in, showing chat page");
|
||||||
showChatPage();
|
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
|
void
|
||||||
MainWindow::setWindowTitle(int notificationCount)
|
MainWindow::setWindowTitle(int notificationCount)
|
||||||
{
|
{
|
||||||
@ -148,20 +290,19 @@ MainWindow::setWindowTitle(int notificationCount)
|
|||||||
if (notificationCount > 0) {
|
if (notificationCount > 0) {
|
||||||
name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount));
|
name.append(QString{QStringLiteral(" (%1)")}.arg(notificationCount));
|
||||||
}
|
}
|
||||||
QMainWindow::setWindowTitle(name);
|
QQuickView::setTitle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MainWindow::event(QEvent *event)
|
MainWindow::event(QEvent *event)
|
||||||
{
|
{
|
||||||
auto type = event->type();
|
auto type = event->type();
|
||||||
if (type == QEvent::WindowActivate) {
|
|
||||||
emit focusChanged(true);
|
if (type == QEvent::Close) {
|
||||||
} else if (type == QEvent::WindowDeactivate) {
|
closeEvent(static_cast<QCloseEvent *>(event));
|
||||||
emit focusChanged(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QMainWindow::event(event);
|
return QQuickView::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -188,31 +329,6 @@ MainWindow::saveCurrentWindowSize()
|
|||||||
settings->setValue(QStringLiteral("window/height"), current.height());
|
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
|
void
|
||||||
MainWindow::showChatPage()
|
MainWindow::showChatPage()
|
||||||
{
|
{
|
||||||
@ -227,19 +343,13 @@ MainWindow::showChatPage()
|
|||||||
userSettings_.data()->setDeviceId(device_id);
|
userSettings_.data()->setDeviceId(device_id);
|
||||||
userSettings_.data()->setHomeserver(homeserver);
|
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);
|
chat_page_->bootstrap(userid, homeserver, token);
|
||||||
connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
|
connect(cache::client(), &Cache::databaseReady, this, &MainWindow::secretsChanged);
|
||||||
connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
|
connect(cache::client(), &Cache::secretChanged, this, &MainWindow::secretsChanged);
|
||||||
|
|
||||||
emit reload();
|
emit reload();
|
||||||
|
nhlog::ui()->info("Switching to chat page");
|
||||||
|
emit switchToChatPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -247,7 +357,7 @@ MainWindow::closeEvent(QCloseEvent *event)
|
|||||||
{
|
{
|
||||||
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
|
||||||
if (QMessageBox::question(
|
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) {
|
QMessageBox::Yes) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
return;
|
return;
|
||||||
@ -289,23 +399,11 @@ MainWindow::hasActiveUser()
|
|||||||
settings->contains(prefix + "auth/user_id");
|
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
|
void
|
||||||
MainWindow::openCreateRoomDialog(
|
MainWindow::openCreateRoomDialog(
|
||||||
std::function<void(const mtx::requests::CreateRoom &request)> callback)
|
std::function<void(const mtx::requests::CreateRoom &request)> callback)
|
||||||
{
|
{
|
||||||
auto dialog = new dialogs::CreateRoom(this);
|
auto dialog = new dialogs::CreateRoom(nullptr);
|
||||||
connect(dialog,
|
connect(dialog,
|
||||||
&dialogs::CreateRoom::createRoom,
|
&dialogs::CreateRoom::createRoom,
|
||||||
this,
|
this,
|
||||||
@ -314,76 +412,19 @@ MainWindow::openCreateRoomDialog(
|
|||||||
showDialog(dialog);
|
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
|
bool
|
||||||
MainWindow::pageSupportsTray() const
|
MainWindow::pageSupportsTray() const
|
||||||
{
|
{
|
||||||
return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
|
return !http::client()->access_token().empty();
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MainWindow::hideOverlay()
|
|
||||||
{
|
|
||||||
if (modal_)
|
|
||||||
modal_->hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
MainWindow::showDialog(QWidget *dialog)
|
MainWindow::showDialog(QWidget *dialog)
|
||||||
{
|
{
|
||||||
utils::centerWidget(dialog, this);
|
dialog->setWindowFlags(Qt::WindowType::Dialog | Qt::WindowType::WindowCloseButtonHint |
|
||||||
|
Qt::WindowType::WindowTitleHint);
|
||||||
dialog->raise();
|
dialog->raise();
|
||||||
dialog->show();
|
dialog->show();
|
||||||
}
|
utils::centerWidget(dialog, this);
|
||||||
|
dialog->window()->windowHandle()->setTransientParent(this);
|
||||||
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_);
|
|
||||||
}
|
}
|
||||||
|
@ -8,26 +8,21 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QQuickView>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QStackedWidget>
|
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "ui/OverlayModal.h"
|
|
||||||
|
|
||||||
#include "jdenticoninterface.h"
|
#include "jdenticoninterface.h"
|
||||||
|
|
||||||
class ChatPage;
|
class ChatPage;
|
||||||
class RegisterPage;
|
class RegisterPage;
|
||||||
class LoginPage;
|
|
||||||
class WelcomePage;
|
class WelcomePage;
|
||||||
|
|
||||||
class LoadingIndicator;
|
|
||||||
class OverlayModal;
|
|
||||||
class SnackBar;
|
|
||||||
class TrayIcon;
|
class TrayIcon;
|
||||||
class UserSettings;
|
class UserSettings;
|
||||||
|
class MxcImageProvider;
|
||||||
|
|
||||||
namespace mtx {
|
namespace mtx {
|
||||||
namespace requests {
|
namespace requests {
|
||||||
@ -42,17 +37,12 @@ class MemberList;
|
|||||||
class ReCaptcha;
|
class ReCaptcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QQuickView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
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:
|
public:
|
||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWindow *parent = nullptr);
|
||||||
|
|
||||||
static MainWindow *instance() { return instance_; }
|
static MainWindow *instance() { return instance_; }
|
||||||
void saveCurrentWindowSize();
|
void saveCurrentWindowSize();
|
||||||
@ -61,69 +51,51 @@ public:
|
|||||||
openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
||||||
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
||||||
|
|
||||||
void hideOverlay();
|
MxcImageProvider *imageProvider() { return imgProvider; }
|
||||||
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
|
|
||||||
void
|
//! Show the chat page and start communicating with the given access token.
|
||||||
showTransparentOverlayModal(QWidget *content,
|
void showChatPage();
|
||||||
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event);
|
||||||
bool event(QEvent *event) override;
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
//! Handle interaction with the tray icon.
|
//! Handle interaction with the tray icon.
|
||||||
void iconActivated(QSystemTrayIcon::ActivationReason reason);
|
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);
|
virtual void setWindowTitle(int notificationCount);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void focusChanged(const bool focused);
|
|
||||||
void reload();
|
void reload();
|
||||||
void secretsChanged();
|
void secretsChanged();
|
||||||
|
|
||||||
|
void showNotification(QString msg);
|
||||||
|
|
||||||
|
void switchToChatPage();
|
||||||
|
void switchToWelcomePage();
|
||||||
|
void switchToLoginPage(QString error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void showDialog(QWidget *dialog);
|
void showDialog(QWidget *dialog);
|
||||||
bool hasActiveUser();
|
bool hasActiveUser();
|
||||||
void restoreWindowSize();
|
void restoreWindowSize();
|
||||||
//! Check if there is an open dialog.
|
|
||||||
bool hasActiveDialogs() const;
|
|
||||||
//! Check if the current page supports the "minimize to tray" functionality.
|
//! Check if the current page supports the "minimize to tray" functionality.
|
||||||
bool pageSupportsTray() const;
|
bool pageSupportsTray() const;
|
||||||
|
|
||||||
|
void registerQmlTypes();
|
||||||
|
|
||||||
static MainWindow *instance_;
|
static MainWindow *instance_;
|
||||||
|
|
||||||
//! The initial welcome screen.
|
//! The initial welcome screen.
|
||||||
WelcomePage *welcome_page_;
|
WelcomePage *welcome_page_;
|
||||||
//! The login screen.
|
|
||||||
LoginPage *login_page_;
|
|
||||||
//! The register page.
|
//! The register page.
|
||||||
RegisterPage *register_page_;
|
RegisterPage *register_page_;
|
||||||
//! A stacked widget that handles the transitions between widgets.
|
|
||||||
QStackedWidget *pageStack_;
|
|
||||||
//! The main chat area.
|
//! The main chat area.
|
||||||
ChatPage *chat_page_;
|
ChatPage *chat_page_;
|
||||||
QSharedPointer<UserSettings> userSettings_;
|
QSharedPointer<UserSettings> userSettings_;
|
||||||
//! Tray icon that shows the unread message count.
|
//! Tray icon that shows the unread message count.
|
||||||
TrayIcon *trayIcon_;
|
TrayIcon *trayIcon_;
|
||||||
//! Notifications display.
|
|
||||||
SnackBar *snackBar_ = nullptr;
|
MxcImageProvider *imgProvider = nullptr;
|
||||||
//! Overlay modal used to project other widgets.
|
|
||||||
OverlayModal *modal_ = nullptr;
|
|
||||||
LoadingIndicator *spinner_ = nullptr;
|
|
||||||
};
|
};
|
||||||
|
@ -4,312 +4,83 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <mtx/responses/common.hpp>
|
||||||
#include <QLabel>
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QStyleOption>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QtMath>
|
|
||||||
|
|
||||||
#include <mtx/responses/register.hpp>
|
#include <mtx/responses/register.hpp>
|
||||||
#include <mtx/responses/well-known.hpp>
|
#include <mtx/responses/well-known.hpp>
|
||||||
#include <mtxclient/http/client.hpp>
|
#include <mtxclient/http/client.hpp>
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
#include "LoginPage.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
#include "RegisterPage.h"
|
#include "RegisterPage.h"
|
||||||
#include "ui/FlatButton.h"
|
|
||||||
#include "ui/RaisedButton.h"
|
|
||||||
#include "ui/TextField.h"
|
|
||||||
#include "ui/UIA.h"
|
#include "ui/UIA.h"
|
||||||
|
|
||||||
#include "dialogs/FallbackAuth.h"
|
RegisterPage::RegisterPage(QObject *parent)
|
||||||
#include "dialogs/ReCaptcha.h"
|
: QObject(parent)
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(mtx::user_interactive::Unauthorized)
|
|
||||||
Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
|
|
||||||
|
|
||||||
RegisterPage::RegisterPage(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
{
|
{
|
||||||
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
|
connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); });
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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
|
void
|
||||||
RegisterPage::showError(const QString &msg)
|
RegisterPage::setServer(QString server)
|
||||||
{
|
{
|
||||||
emit errorOccurred();
|
if (server == lastServer)
|
||||||
auto rect = QFontMetrics(font()).boundingRect(msg);
|
return;
|
||||||
int width = rect.width();
|
|
||||||
int height = rect.height();
|
|
||||||
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
|
|
||||||
error_label_->setText(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
lastServer = server;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
http::client()->set_server(server.toStdString());
|
||||||
RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
|
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
|
||||||
{
|
|
||||||
if (t_field->isValid()) {
|
|
||||||
label->hide();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
showError(label, msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
hsError_.clear();
|
||||||
RegisterPage::checkUsername()
|
emit hsErrorChanged();
|
||||||
{
|
supported_ = false;
|
||||||
return checkOneField(error_username_label_,
|
lookingUpHs_ = true;
|
||||||
username_input_,
|
emit lookingUpHsChanged();
|
||||||
tr("The username must not be empty, and must contain only the "
|
|
||||||
"characters a-z, 0-9, ., _, =, -, and /."));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
http::client()->well_known(
|
||||||
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err->status_code == 404) {
|
if (err->status_code == 404) {
|
||||||
nhlog::net()->info("Autodiscovery: No .well-known.");
|
nhlog::net()->info("Autodiscovery: No .well-known.");
|
||||||
// Check that the homeserver can be reached
|
// Check that the homeserver can be reached
|
||||||
emit versionsCheck();
|
versionsCheck();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!err->parse_error.empty()) {
|
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.");
|
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
|
||||||
|
emit hsErrorChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit serverError(tr("Autodiscovery failed. Unknown error when "
|
setHsError(tr("Autodiscovery failed. Unknown error when requesting .well-known."));
|
||||||
"requesting .well-known."));
|
|
||||||
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
nhlog::net()->error("Autodiscovery failed. Unknown error when "
|
||||||
"requesting .well-known. {} {}",
|
"requesting .well-known. {} {}",
|
||||||
err->status_code,
|
err->status_code,
|
||||||
@ -319,98 +90,140 @@ RegisterPage::doWellKnownLookup()
|
|||||||
|
|
||||||
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
|
||||||
http::client()->set_server(res.homeserver.base_url);
|
http::client()->set_server(res.homeserver.base_url);
|
||||||
|
emit hsErrorChanged();
|
||||||
// Check that the homeserver can be reached
|
// Check that the homeserver can be reached
|
||||||
emit versionsCheck();
|
versionsCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
RegisterPage::doVersionsCheck()
|
RegisterPage::versionsCheck()
|
||||||
{
|
{
|
||||||
// Make a request to /_matrix/client/versions to check the address
|
// Make a request to /_matrix/client/versions to check the address
|
||||||
// given is a Matrix homeserver.
|
// given is a Matrix homeserver.
|
||||||
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err->status_code == 404) {
|
if (err->status_code == 404) {
|
||||||
emit serverError(tr("The required endpoints were not found. Possibly "
|
setHsError(
|
||||||
"not a Matrix server."));
|
tr("The required endpoints were not found. Possibly not a Matrix server."));
|
||||||
|
emit hsErrorChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!err->parse_error.empty()) {
|
if (!err->parse_error.empty()) {
|
||||||
emit serverError(tr("Received malformed response. Make sure the homeserver "
|
setHsError(
|
||||||
"domain is valid."));
|
tr("Received malformed response. Make sure the homeserver domain is valid."));
|
||||||
|
emit hsErrorChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit serverError(tr("An unknown error occured. Make sure the "
|
setHsError(tr("An unknown error occured. Make sure the homeserver domain is valid."));
|
||||||
"homeserver domain is valid."));
|
emit hsErrorChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt registration without an `auth` dict
|
http::client()->registration(
|
||||||
emit 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
|
void
|
||||||
RegisterPage::doRegistration()
|
RegisterPage::checkUsername(QString name)
|
||||||
{
|
{
|
||||||
// These inputs should still be alright, but check just in case
|
usernameAvailable_ = usernameUnavailable_ = false;
|
||||||
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
|
usernameError_.clear();
|
||||||
auto username = username_input_->text().toStdString();
|
lookingUpUsername_ = true;
|
||||||
auto password = password_input_->text().toStdString();
|
emit lookingUpUsernameChanged();
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx::http::Callback<mtx::responses::Register>
|
http::client()->register_username_available(
|
||||||
RegisterPage::registrationCb()
|
name.toStdString(),
|
||||||
{
|
[this](const mtx::responses::Available &available, mtx::http::RequestErr e) {
|
||||||
// Return a function to be used as the callback when an attempt at
|
if (e) {
|
||||||
// registration is made.
|
if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_INVALID_USERNAME) {
|
||||||
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
|
usernameError_ = tr("Invalid username.");
|
||||||
if (!err) {
|
} else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_USER_IN_USE) {
|
||||||
http::client()->set_user(res.user_id);
|
usernameError_ = tr("Name already in use.");
|
||||||
http::client()->set_access_token(res.access_token);
|
} else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_EXCLUSIVE) {
|
||||||
emit registerOk();
|
usernameError_ = tr("Part of the reserved namespace.");
|
||||||
disconnect(UIA::instance(), &UIA::error, this, nullptr);
|
} else {
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// The server requires registration flows.
|
usernameAvailable_ = false;
|
||||||
if (err->status_code == 401) {
|
usernameUnavailable_ = true;
|
||||||
if (err->matrix_error.unauthorized.flows.empty()) {
|
} else {
|
||||||
nhlog::net()->warn("failed to retrieve registration flows: "
|
usernameAvailable_ = available.available;
|
||||||
"status_code({}), matrix_error({}) ",
|
usernameUnavailable_ = !available.available;
|
||||||
static_cast<int>(err->status_code),
|
}
|
||||||
err->matrix_error.error);
|
lookingUpUsername_ = false;
|
||||||
showError(QString::fromStdString(err->matrix_error.error));
|
emit lookingUpUsernameChanged();
|
||||||
}
|
});
|
||||||
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));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
RegisterPage::paintEvent(QPaintEvent *)
|
RegisterPage::startRegistration(QString username, QString password, QString devicename)
|
||||||
{
|
{
|
||||||
QStyleOption opt;
|
// These inputs should still be alright, but check just in case
|
||||||
opt.initFrom(this);
|
if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) {
|
||||||
QPainter p(this);
|
registrationError_.clear();
|
||||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <mtx/user_interactive.hpp>
|
#include <mtx/user_interactive.hpp>
|
||||||
#include <mtxclient/http/client.hpp>
|
#include <mtxclient/http/client.hpp>
|
||||||
|
|
||||||
class FlatButton;
|
class RegisterPage : public QObject
|
||||||
class RaisedButton;
|
|
||||||
class TextField;
|
|
||||||
class QLabel;
|
|
||||||
class QVBoxLayout;
|
|
||||||
class QHBoxLayout;
|
|
||||||
|
|
||||||
class RegisterPage : public QWidget
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
|
||||||
RegisterPage(QWidget *parent = nullptr);
|
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:
|
public:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
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:
|
signals:
|
||||||
void backButtonClicked();
|
void errorChanged();
|
||||||
void errorOccurred();
|
void hsErrorChanged();
|
||||||
|
|
||||||
//! Used to trigger the corresponding slot outside of the main thread.
|
void registeringChanged();
|
||||||
void serverError(const QString &err);
|
void lookingUpHsChanged();
|
||||||
|
void lookingUpUsernameChanged();
|
||||||
|
|
||||||
void wellKnownLookup();
|
|
||||||
void versionsCheck();
|
|
||||||
void registration();
|
|
||||||
|
|
||||||
void registering();
|
|
||||||
void registerOk();
|
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:
|
private:
|
||||||
QVBoxLayout *top_layout_;
|
void versionsCheck();
|
||||||
|
|
||||||
QHBoxLayout *back_layout_;
|
void setHsError(QString err);
|
||||||
QHBoxLayout *logo_layout_;
|
void setError(QString err);
|
||||||
QHBoxLayout *button_layout_;
|
|
||||||
|
|
||||||
QLabel *logo_;
|
QString registrationError_, hsError_, usernameError_;
|
||||||
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_;
|
|
||||||
|
|
||||||
FlatButton *back_button_;
|
bool registering_;
|
||||||
RaisedButton *register_button_;
|
bool supported_;
|
||||||
|
bool lookingUpHs_;
|
||||||
|
bool lookingUpUsername_;
|
||||||
|
bool usernameAvailable_;
|
||||||
|
bool usernameUnavailable_;
|
||||||
|
|
||||||
QWidget *form_widget_;
|
QString lastServer;
|
||||||
QHBoxLayout *form_wrapper_;
|
|
||||||
QVBoxLayout *form_layout_;
|
|
||||||
|
|
||||||
TextField *username_input_;
|
|
||||||
TextField *password_input_;
|
|
||||||
TextField *password_confirmation_;
|
|
||||||
TextField *server_input_;
|
|
||||||
TextField *registration_token_input_;
|
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
#include "TrayIcon.h"
|
#include "TrayIcon.h"
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State s
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
|
TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
|
||||||
: QSystemTrayIcon(parent)
|
: QSystemTrayIcon(parent)
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||||
@ -110,13 +111,13 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
|
|||||||
setIcon(QIcon(icon_));
|
setIcon(QIcon(icon_));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QMenu *menu = new QMenu(parent);
|
QMenu *menu = new QMenu();
|
||||||
setContextMenu(menu);
|
setContextMenu(menu);
|
||||||
|
|
||||||
viewAction_ = new QAction(tr("Show"), this);
|
viewAction_ = new QAction(tr("Show"), this);
|
||||||
quitAction_ = new QAction(tr("Quit"), 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);
|
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||||
|
|
||||||
menu->addAction(viewAction_);
|
menu->addAction(viewAction_);
|
||||||
|
@ -40,7 +40,7 @@ class TrayIcon : public QSystemTrayIcon
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
TrayIcon(const QString &filename, QWidget *parent);
|
TrayIcon(const QString &filename, QWindow *parent);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setUnreadCount(int count);
|
void setUnreadCount(int count);
|
||||||
|
@ -5,25 +5,14 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QComboBox>
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFontComboBox>
|
#include <QFontDatabase>
|
||||||
#include <QFormLayout>
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPainter>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QResizeEvent>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QScroller>
|
|
||||||
#include <QSpinBox>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QtQml>
|
|
||||||
#include <mtx/secret_storage.hpp>
|
#include <mtx/secret_storage.hpp>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
@ -33,8 +22,7 @@
|
|||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "encryption/Olm.h"
|
#include "encryption/Olm.h"
|
||||||
#include "ui/FlatButton.h"
|
#include "ui/Theme.h"
|
||||||
#include "ui/ToggleButton.h"
|
|
||||||
#include "voip/CallDevices.h"
|
#include "voip/CallDevices.h"
|
||||||
|
|
||||||
#include "config/nheko.h"
|
#include "config/nheko.h"
|
||||||
@ -1518,7 +1506,7 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
|
|||||||
QString homeFolder =
|
QString homeFolder =
|
||||||
QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||||
auto filepath = QFileDialog::getOpenFileName(
|
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()) {
|
if (!filepath.isEmpty()) {
|
||||||
i->setRingtone(filepath);
|
i->setRingtone(filepath);
|
||||||
i->setRingtone(filepath);
|
i->setRingtone(filepath);
|
||||||
@ -1600,11 +1588,11 @@ UserSettingsModel::importSessionKeys()
|
|||||||
{
|
{
|
||||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||||
const QString fileName = QFileDialog::getOpenFileName(
|
const QString fileName = QFileDialog::getOpenFileName(
|
||||||
MainWindow::instance(), tr("Open Sessions File"), homeFolder, QLatin1String(""));
|
nullptr, tr("Open Sessions File"), homeFolder, QLatin1String(""));
|
||||||
|
|
||||||
QFile file(fileName);
|
QFile file(fileName);
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
|
QMessageBox::warning(nullptr, tr("Error"), file.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1612,7 +1600,7 @@ UserSettingsModel::importSessionKeys()
|
|||||||
auto payload = std::string(bin.data(), bin.size());
|
auto payload = std::string(bin.data(), bin.size());
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
auto password = QInputDialog::getText(MainWindow::instance(),
|
auto password = QInputDialog::getText(nullptr,
|
||||||
tr("File Password"),
|
tr("File Password"),
|
||||||
tr("Enter the passphrase to decrypt the file:"),
|
tr("Enter the passphrase to decrypt the file:"),
|
||||||
QLineEdit::Password,
|
QLineEdit::Password,
|
||||||
@ -1622,8 +1610,7 @@ UserSettingsModel::importSessionKeys()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
|
||||||
MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1631,7 +1618,7 @@ UserSettingsModel::importSessionKeys()
|
|||||||
auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
|
auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
|
||||||
cache::importSessionKeys(std::move(sessions));
|
cache::importSessionKeys(std::move(sessions));
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
|
QMessageBox::warning(nullptr, tr("Error"), e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
@ -1639,7 +1626,7 @@ UserSettingsModel::exportSessionKeys()
|
|||||||
{
|
{
|
||||||
// Open password dialog.
|
// Open password dialog.
|
||||||
bool ok;
|
bool ok;
|
||||||
auto password = QInputDialog::getText(MainWindow::instance(),
|
auto password = QInputDialog::getText(nullptr,
|
||||||
tr("File Password"),
|
tr("File Password"),
|
||||||
tr("Enter passphrase to encrypt your session keys:"),
|
tr("Enter passphrase to encrypt your session keys:"),
|
||||||
QLineEdit::Password,
|
QLineEdit::Password,
|
||||||
@ -1649,19 +1636,18 @@ UserSettingsModel::exportSessionKeys()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(nullptr, tr("Error"), tr("The password cannot be empty"));
|
||||||
MainWindow::instance(), tr("Error"), tr("The password cannot be empty"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open file dialog to save the file.
|
// Open file dialog to save the file.
|
||||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||||
const QString fileName = QFileDialog::getSaveFileName(
|
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);
|
QFile file(fileName);
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), file.errorString());
|
QMessageBox::warning(nullptr, tr("Error"), file.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1679,7 +1665,7 @@ UserSettingsModel::exportSessionKeys()
|
|||||||
out << prefix << newline << b64 << newline << suffix << newline;
|
out << prefix << newline << b64 << newline << suffix << newline;
|
||||||
file.close();
|
file.close();
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
QMessageBox::warning(MainWindow::instance(), tr("Error"), e.what());
|
QMessageBox::warning(nullptr, tr("Error"), e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
|
@ -7,12 +7,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QFontDatabase>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "JdenticonProvider.h"
|
#include "JdenticonProvider.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
#include <QTextBoundaryFinder>
|
#include <QTextBoundaryFinder>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
#include <QWindow>
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@ -770,20 +771,17 @@ utils::luminance(const QColor &col)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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 {
|
auto findCenter = [childRect = widget->rect()](QRect hostRect) -> QPoint {
|
||||||
return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
|
return QPoint(hostRect.center().x() - (childRect.width() * 0.5),
|
||||||
hostRect.center().y() - (childRect.height() * 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()));
|
widget->move(findCenter(QGuiApplication::primaryScreen()->geometry()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ luminance(const QColor &col);
|
|||||||
|
|
||||||
//! Center a widget in relation to another widget.
|
//! Center a widget in relation to another widget.
|
||||||
void
|
void
|
||||||
centerWidget(QWidget *widget, QWidget *parent);
|
centerWidget(QWidget *widget, QWindow *parent);
|
||||||
|
|
||||||
void
|
void
|
||||||
restoreCombobox(QComboBox *combo, const QString &value);
|
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 <QLibraryInfo>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QQuickView>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
@ -279,6 +280,7 @@ main(int argc, char *argv[])
|
|||||||
font.setPointSizeF(settings.lock()->fontSize());
|
font.setPointSizeF(settings.lock()->fontSize());
|
||||||
|
|
||||||
app.setFont(font);
|
app.setFont(font);
|
||||||
|
settings.lock()->applyTheme();
|
||||||
|
|
||||||
if (QLocale().language() == QLocale::C)
|
if (QLocale().language() == QLocale::C)
|
||||||
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom));
|
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom));
|
||||||
@ -296,9 +298,10 @@ main(int argc, char *argv[])
|
|||||||
app.installTranslator(&appTranslator);
|
app.installTranslator(&appTranslator);
|
||||||
|
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
|
// QQuickView w;
|
||||||
|
|
||||||
// Move the MainWindow to the center
|
// 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()))
|
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
|
||||||
w.show();
|
w.show();
|
||||||
@ -314,7 +317,7 @@ main(int argc, char *argv[])
|
|||||||
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
|
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
|
||||||
w.show();
|
w.show();
|
||||||
w.raise();
|
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
|
// 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()
|
InputBar::openFileSelection()
|
||||||
{
|
{
|
||||||
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||||
const auto fileName = QFileDialog::getOpenFileName(
|
const auto fileName =
|
||||||
ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)"));
|
QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
|
||||||
|
|
||||||
if (fileName.isEmpty())
|
if (fileName.isEmpty())
|
||||||
return;
|
return;
|
||||||
@ -659,7 +659,7 @@ InputBar::command(const QString &command, QString args)
|
|||||||
void
|
void
|
||||||
InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats)
|
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);
|
previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
// Force SVG to _not_ be handled as an image, but as raw data
|
// Force SVG to _not_ be handled as an image, but as raw data
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "Cache_p.h"
|
#include "Cache_p.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
#include "MxcImageProvider.h"
|
#include "MxcImageProvider.h"
|
||||||
#include "TimelineModel.h"
|
#include "TimelineModel.h"
|
||||||
@ -275,7 +276,7 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
|
|||||||
|
|
||||||
connect(newRoom.data(),
|
connect(newRoom.data(),
|
||||||
&TimelineModel::newEncryptedImage,
|
&TimelineModel::newEncryptedImage,
|
||||||
manager->imageProvider(),
|
MainWindow::instance()->imageProvider(),
|
||||||
&MxcImageProvider::addEncryptionInfo);
|
&MxcImageProvider::addEncryptionInfo);
|
||||||
connect(newRoom.data(),
|
connect(newRoom.data(),
|
||||||
&TimelineModel::forwardToRoom,
|
&TimelineModel::forwardToRoom,
|
||||||
@ -509,7 +510,7 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
|
|||||||
// room_model->addEvents(room.timeline);
|
// room_model->addEvents(room.timeline);
|
||||||
connect(room_model.data(),
|
connect(room_model.data(),
|
||||||
&TimelineModel::newCallEvent,
|
&TimelineModel::newCallEvent,
|
||||||
manager->callManager(),
|
ChatPage::instance()->callManager(),
|
||||||
&CallManager::syncEvent,
|
&CallManager::syncEvent,
|
||||||
Qt::UniqueConnection);
|
Qt::UniqueConnection);
|
||||||
|
|
||||||
|
@ -1031,7 +1031,7 @@ TimelineModel::setCurrentIndex(int index)
|
|||||||
if (index != oldIndex)
|
if (index != oldIndex)
|
||||||
emit currentIndexChanged(index);
|
emit currentIndexChanged(index);
|
||||||
|
|
||||||
if (!ChatPage::instance()->isActiveWindow())
|
if (MainWindow::instance() != QGuiApplication::focusWindow())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!currentId.startsWith('m')) {
|
if (!currentId.startsWith('m')) {
|
||||||
@ -1495,7 +1495,7 @@ TimelineModel::saveMedia(const QString &eventId) const
|
|||||||
const QString openLocation = downloadsFolder + "/" + originalFilename;
|
const QString openLocation = downloadsFolder + "/" + originalFilename;
|
||||||
|
|
||||||
const QString filename =
|
const QString filename =
|
||||||
QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString);
|
QFileDialog::getSaveFileName(nullptr, dialogTitle, openLocation, filterString);
|
||||||
|
|
||||||
if (filename.isEmpty())
|
if (filename.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
#include "TimelineViewManager.h"
|
#include "TimelineViewManager.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QDropEvent>
|
#include <QDropEvent>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QPalette>
|
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
@ -45,10 +45,6 @@
|
|||||||
#include "ui/NhekoGlobalObject.h"
|
#include "ui/NhekoGlobalObject.h"
|
||||||
#include "ui/UIA.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 msgs = mtx::events::msg;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -102,19 +98,6 @@ void
|
|||||||
TimelineViewManager::updateColorPalette()
|
TimelineViewManager::updateColorPalette()
|
||||||
{
|
{
|
||||||
userColors.clear();
|
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
|
QColor
|
||||||
@ -126,112 +109,15 @@ TimelineViewManager::userColor(QString id, QColor background)
|
|||||||
return userColors.value(idx);
|
return userColors.value(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
|
TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, imgProvider(new MxcImageProvider())
|
|
||||||
, colorImgProvider(new ColorImageProvider())
|
|
||||||
, blurhashProvider(new BlurhashProvider())
|
|
||||||
, jdenticonProvider(new JdenticonProvider())
|
|
||||||
, rooms_(new RoomlistModel(this))
|
, rooms_(new RoomlistModel(this))
|
||||||
, communities_(new CommunitiesModel(this))
|
, communities_(new CommunitiesModel(this))
|
||||||
, callManager_(callManager)
|
|
||||||
, verificationManager_(new VerificationManager(this))
|
, verificationManager_(new VerificationManager(this))
|
||||||
, presenceEmitter(new PresenceEmitter(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;
|
static auto self = this;
|
||||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", MainWindow::instance());
|
|
||||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
|
qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
|
||||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
|
|
||||||
qmlRegisterSingletonType<RoomlistModel>(
|
qmlRegisterSingletonType<RoomlistModel>(
|
||||||
"im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
"im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||||
auto ptr = new FilteredRoomlistModel(self->rooms_);
|
auto ptr = new FilteredRoomlistModel(self->rooms_);
|
||||||
@ -247,79 +133,15 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||||||
return ptr;
|
return ptr;
|
||||||
});
|
});
|
||||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_);
|
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, "VerificationManager", verificationManager_);
|
||||||
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
|
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();
|
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,
|
connect(parent,
|
||||||
&ChatPage::receivedRoomDeviceVerificationRequest,
|
&ChatPage::receivedRoomDeviceVerificationRequest,
|
||||||
verificationManager_,
|
verificationManager_,
|
||||||
@ -336,6 +158,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||||||
isInitialSync_ = true;
|
isInitialSync_ = true;
|
||||||
emit initialSyncChanged(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
|
void
|
||||||
@ -379,7 +211,8 @@ void
|
|||||||
TimelineViewManager::setVideoCallItem()
|
TimelineViewManager::setVideoCallItem()
|
||||||
{
|
{
|
||||||
WebRTCSession::instance().setVideoItem(
|
WebRTCSession::instance().setVideoItem(
|
||||||
view->rootObject()->findChild<QQuickItem *>(QStringLiteral("videoCallItem")));
|
MainWindow::instance()->rootObject()->findChild<QQuickItem *>(
|
||||||
|
QStringLiteral("videoCallItem")));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -401,7 +234,7 @@ TimelineViewManager::showEvent(const QString &room_id, const QString &event_id)
|
|||||||
if (auto room = rooms_->getRoomById(room_id)) {
|
if (auto room = rooms_->getRoomById(room_id)) {
|
||||||
if (rooms_->currentRoom() != room) {
|
if (rooms_->currentRoom() != room) {
|
||||||
rooms_->setCurrentRoom(room_id);
|
rooms_->setCurrentRoom(room_id);
|
||||||
container->setFocus();
|
MainWindow::instance()->requestActivate();
|
||||||
nhlog::ui()->info("Activated room {}", room_id.toStdString());
|
nhlog::ui()->info("Activated room {}", room_id.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,7 +272,7 @@ TimelineViewManager::saveMedia(QString mxcUrl)
|
|||||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||||
const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
|
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())
|
if (filename.isEmpty())
|
||||||
return;
|
return;
|
||||||
@ -590,12 +423,6 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
TimelineViewManager::focusTimeline()
|
|
||||||
{
|
|
||||||
getWidget()->setFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
|
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
|
||||||
QString roomId)
|
QString roomId)
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
#include <QQuickView>
|
|
||||||
#include <QQuickWidget>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include <mtx/common.hpp>
|
#include <mtx/common.hpp>
|
||||||
@ -43,23 +41,19 @@ class TimelineViewManager : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
|
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(bool isWindowFocused READ isWindowFocused NOTIFY focusChanged)
|
||||||
bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
|
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
|
||||||
QWidget *getWidget() const { return container; }
|
|
||||||
|
|
||||||
void sync(const mtx::responses::Sync &sync_);
|
void sync(const mtx::responses::Sync &sync_);
|
||||||
|
|
||||||
MxcImageProvider *imageProvider() { return imgProvider; }
|
|
||||||
CallManager *callManager() { return callManager_; }
|
|
||||||
VerificationManager *verificationManager() { return verificationManager_; }
|
VerificationManager *verificationManager() { return verificationManager_; }
|
||||||
|
|
||||||
void clearAll() { rooms_->clear(); }
|
void clearAll() { rooms_->clear(); }
|
||||||
|
|
||||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
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 openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId);
|
||||||
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
||||||
Q_INVOKABLE void saveMedia(QString mxcUrl);
|
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 updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||||
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
|
||||||
void initializeRoomlist();
|
void initializeRoomlist();
|
||||||
void chatFocusChanged(bool focused)
|
|
||||||
{
|
|
||||||
isWindowFocused_ = focused;
|
|
||||||
emit focusChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void showEvent(const QString &room_id, const QString &event_id);
|
void showEvent(const QString &room_id, const QString &event_id);
|
||||||
void focusTimeline();
|
|
||||||
|
|
||||||
void updateColorPalette();
|
void updateColorPalette();
|
||||||
void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
|
void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody);
|
||||||
@ -122,26 +110,12 @@ public slots:
|
|||||||
RoomlistModel *rooms() { return rooms_; }
|
RoomlistModel *rooms() { return rooms_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef USE_QUICK_VIEW
|
bool isInitialSync_ = true;
|
||||||
QQuickView *view;
|
|
||||||
#else
|
|
||||||
QQuickWidget *view;
|
|
||||||
#endif
|
|
||||||
QWidget *container;
|
|
||||||
|
|
||||||
MxcImageProvider *imgProvider;
|
|
||||||
ColorImageProvider *colorImgProvider;
|
|
||||||
BlurhashProvider *blurhashProvider;
|
|
||||||
JdenticonProvider *jdenticonProvider;
|
|
||||||
|
|
||||||
bool isInitialSync_ = true;
|
|
||||||
bool isWindowFocused_ = false;
|
|
||||||
|
|
||||||
RoomlistModel *rooms_ = nullptr;
|
RoomlistModel *rooms_ = nullptr;
|
||||||
CommunitiesModel *communities_ = nullptr;
|
CommunitiesModel *communities_ = nullptr;
|
||||||
|
|
||||||
// don't move this above the rooms_
|
// don't move this above the rooms_
|
||||||
CallManager *callManager_ = nullptr;
|
|
||||||
VerificationManager *verificationManager_ = nullptr;
|
VerificationManager *verificationManager_ = nullptr;
|
||||||
PresenceEmitter *presenceEmitter = 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
|
void
|
||||||
Nheko::reparent(QWindow *win) const
|
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 <mtx/responses/common.hpp>
|
||||||
|
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MainWindow.h"
|
|
||||||
#include "dialogs/FallbackAuth.h"
|
#include "dialogs/FallbackAuth.h"
|
||||||
#include "dialogs/ReCaptcha.h"
|
#include "dialogs/ReCaptcha.h"
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ UIA::genericHandler(QString context)
|
|||||||
emit phoneNumber();
|
emit phoneNumber();
|
||||||
} else if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
|
} else if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
|
||||||
auto captchaDialog =
|
auto captchaDialog =
|
||||||
new dialogs::ReCaptcha(QString::fromStdString(u.session), MainWindow::instance());
|
new dialogs::ReCaptcha(QString::fromStdString(u.session), nullptr);
|
||||||
captchaDialog->setWindowTitle(context);
|
captchaDialog->setWindowTitle(context);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
@ -95,7 +94,7 @@ UIA::genericHandler(QString context)
|
|||||||
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
||||||
bool ok;
|
bool ok;
|
||||||
QString token =
|
QString token =
|
||||||
QInputDialog::getText(MainWindow::instance(),
|
QInputDialog::getText(nullptr,
|
||||||
context,
|
context,
|
||||||
tr("Please enter a valid registration token."),
|
tr("Please enter a valid registration token."),
|
||||||
QLineEdit::Normal,
|
QLineEdit::Normal,
|
||||||
@ -113,7 +112,7 @@ UIA::genericHandler(QString context)
|
|||||||
// use fallback
|
// use fallback
|
||||||
auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage),
|
auto dialog = new dialogs::FallbackAuth(QString::fromStdString(current_stage),
|
||||||
QString::fromStdString(u.session),
|
QString::fromStdString(u.session),
|
||||||
MainWindow::instance());
|
nullptr);
|
||||||
dialog->setWindowTitle(context);
|
dialog->setWindowTitle(context);
|
||||||
|
|
||||||
connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() {
|
connect(dialog, &dialogs::FallbackAuth::confirmation, this, [h, u, dialog]() {
|
||||||
|
Loading…
Reference in New Issue
Block a user