Move QML files to src/qml and don't use internal qml modules

This commit is contained in:
Tobias Fella
2022-10-19 14:47:17 +02:00
parent 813a8003c6
commit 2817ce9d16
105 changed files with 144 additions and 315 deletions

View File

@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Loader {
id: attachmentPaneLoader
readonly property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPaneLoader.attachmentPath)
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
readonly property string attachmentPath: currentRoom.chatBoxAttachmentPath
readonly property string baseFileName: attachmentPath.substring(attachmentPath.lastIndexOf('/') + 1, attachmentPath.length)
active: visible
sourceComponent: Component {
Pane {
id: attachmentPane
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: Item {
property real spacing: attachmentPane.spacing > 0 ? attachmentPane.spacing : toolBar.spacing
implicitWidth: Math.max(image.implicitWidth, imageBusyIndicator.implicitWidth, fileInfoLayout.implicitWidth, toolBar.implicitWidth)
implicitHeight: Math.max(
(hasImage ? Math.max(image.preferredHeight, imageBusyIndicator.implicitHeight) + spacing : 0)
+ fileInfoLayout.implicitHeight,
toolBar.implicitHeight
)
Image {
id: image
property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
height: preferredHeight
anchors {
horizontalCenter: parent.horizontalCenter
bottom: fileInfoLayout.top
bottomMargin: parent.spacing
}
width: Math.min(implicitWidth, attachmentPane.availableWidth)
asynchronous: true
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
smooth: height === preferredHeight && parent.height === parent.implicitHeight // Don't smooth until height animation stops
source: hasImage ? attachmentPaneLoader.attachmentPath : ""
visible: hasImage
fillMode: Image.PreserveAspectFit
onSourceChanged: {
// Reset source size height, which affect implicitHeight
sourceSize.height = -1
}
onSourceSizeChanged: {
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
// This can save a lot of RAM when loading large images.
// It also improves visual quality for large images.
sourceSize.height = Kirigami.Units.gridUnit * 8
}
}
Behavior on height {
NumberAnimation {
property: "height"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
}
}
}
BusyIndicator {
id: imageBusyIndicator
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
bottom: fileInfoLayout.top
bottomMargin: parent.spacing
}
visible: running
running: image.visible && image.progress < 1
}
RowLayout {
id: fileInfoLayout
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: undefined
anchors.bottom: parent.bottom
spacing: parent.spacing
Kirigami.Icon {
id: mimetypeIcon
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
implicitWidth: implicitHeight
source: attachmentMimetype.iconName
}
Label {
id: fileLabel
text: baseFileName
}
states: State {
when: !hasImage
AnchorChanges {
target: fileInfoLayout
anchors.bottom: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Using a toolbar to get a button spacing consistent with what the QQC2 style normally has
// Also has some accessibility info
ToolBar {
id: toolBar
width: parent.width
anchors.top: parent.top
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
Kirigami.Theme.inherit: true
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: RowLayout {
spacing: parent.spacing
Label {
Layout.leftMargin: -attachmentPane.leftPadding
Layout.topMargin: -attachmentPane.topPadding
leftPadding: cancelAttachmentButton.leftPadding + 1 + attachmentPane.leftPadding
rightPadding: cancelAttachmentButton.rightPadding + 1
topPadding: cancelAttachmentButton.topPadding + attachmentPane.topPadding
bottomPadding: cancelAttachmentButton.bottomPadding
text: i18n("Attachment:")
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
background: Kirigami.ShadowedRectangle {
property real cornerRadius: cancelAttachmentButton.background.hasOwnProperty("radius") ?
Math.min(cancelAttachmentButton.background.radius, height/2) : 0
corners.bottomLeftRadius: toolBar.mirrored ? cornerRadius : 0
corners.bottomRightRadius: toolBar.mirrored ? 0 : cornerRadius
color: Kirigami.Theme.backgroundColor
opacity: 0.75
}
}
Item {
Layout.fillWidth: true
}
ToolButton {
id: editImageButton
visible: hasImage
icon.name: "document-edit"
text: i18n("Edit")
display: AbstractButton.IconOnly
Component {
id: imageEditorPage
ImageEditorPage {
imagePath: attachmentPaneLoader.attachmentPath
}
}
onClicked: {
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function(newPath) {
applicationWindow().pageStack.layers.pop();
attachmentPaneLoader.attachmentPath = newPath;
});
}
ToolTip.text: text
ToolTip.visible: hovered
}
ToolButton {
id: cancelAttachmentButton
icon.name: "dialog-close"
text: i18n("Cancel sending Image")
display: AbstractButton.IconOnly
onClicked: currentRoom.chatBoxAttachmentPath = "";
ToolTip.text: text
ToolTip.visible: hovered
}
}
background: null
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
}
}

View File

@@ -0,0 +1,274 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import org.kde.kirigami 2.18 as Kirigami
import org.kde.neochat 1.0
ToolBar {
id: chatBar
property alias inputFieldText: inputField.text
property alias textField: inputField
property alias emojiPaneOpened: emojiButton.checked
signal closeAllTriggered()
signal inputFieldForceActiveFocusTriggered()
signal messageSent()
onInputFieldForceActiveFocusTriggered: {
inputField.forceActiveFocus();
// set the cursor to the end of the text
inputField.cursorPosition = inputField.length;
}
position: ToolBar.Footer
Kirigami.Theme.colorSet: Kirigami.Theme.View
// Using a custom background because some styles like Material
// or Fusion might have ugly colors for a TextArea placed inside
// of a toolbar. ToolBar is otherwise the closest QQC2 component
// to what we want because of the padding and spacing values.
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
contentItem: RowLayout {
spacing: chatBar.spacing
ScrollView {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.minimumHeight: inputField.implicitHeight
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
+ inputField.topPadding + inputField.bottomPadding
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
FontMetrics {
id: fontMetrics
font: inputField.font
}
TextArea {
id: inputField
focus: true
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
* Make sure there is no background since we are using the ToolBar background.
*
* This could cause a problem if the QQC2 style was designed around TextArea
* background colors being very different from the QPalette::Base color.
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
*/
background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
topPadding: 0
bottomPadding: 0
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : currentRoom.chatBoxEditId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter
horizontalAlignment: TextEdit.AlignLeft
wrapMode: Text.Wrap
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Kirigami.Theme.textColor
selectionColor: Kirigami.Theme.highlightColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
hoverEnabled: !Kirigami.Settings.tabletMode
selectByMouse: !Kirigami.Settings.tabletMode
Keys.onEnterPressed: {
if (completionMenu.visible) {
completionMenu.complete()
} else if (event.modifiers & Qt.ShiftModifier) {
inputField.insert(cursorPosition, "\n")
} else {
chatBar.postMessage();
}
}
Keys.onReturnPressed: {
if (completionMenu.visible) {
completionMenu.complete()
} else if (event.modifiers & Qt.ShiftModifier) {
inputField.insert(cursorPosition, "\n")
} else {
chatBar.postMessage();
}
}
Keys.onTabPressed: {
if (completionMenu.visible) {
completionMenu.complete()
}
}
Keys.onPressed: {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
chatBar.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
let replyEvent = messageEventModel.getLatestMessageFromIndex(0)
if (replyEvent && replyEvent["event_id"]) {
currentRoom.chatBoxReplyId = replyEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
let editEvent = messageEventModel.getLastLocalUserMessageEventId()
if (editEvent) {
currentRoom.chatBoxEditId = editEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex()
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex()
}
}
Timer {
id: repeatTimer
interval: 5000
}
onTextChanged: {
if (!repeatTimer.running && Config.typingNotifications) {
currentRoom.sendTypingNotification(true)
}
repeatTimer.start()
currentRoom.chatBoxText = text
}
}
}
Item {
visible: currentRoom.chatBoxReplyId.length === 0 && (currentRoom.chatBoxAttachmentPath.length === 0 || uploadingBusySpinner.running)
implicitWidth: uploadButton.implicitWidth
implicitHeight: uploadButton.implicitHeight
ToolButton {
id: uploadButton
anchors.fill: parent
// Matrix does not allow sending attachments in replies
visible: currentRoom.chatBoxReplyId.length === 0 && currentRoom.chatBoxAttachmentPath.length === 0 && !uploadingBusySpinner.running
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: AbstractButton.IconOnly
onClicked: {
if (Clipboard.hasImage) {
attachDialog.open()
} else {
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect((path) => {
if (!path) {
return;
}
currentRoom.chatBoxAttachmentPath = path;
})
fileDialog.open()
}
}
ToolTip.text: text
ToolTip.visible: hovered
}
BusyIndicator {
id: uploadingBusySpinner
anchors.fill: parent
visible: running
running: currentRoom && currentRoom.hasFileUploading
}
}
ToolButton {
id: emojiButton
icon.name: "preferences-desktop-emoticons"
text: i18n("Add an Emoji")
display: AbstractButton.IconOnly
checkable: true
ToolTip.text: text
ToolTip.visible: hovered
}
ToolButton {
id: sendButton
icon.name: "document-send"
text: i18n("Send message")
display: AbstractButton.IconOnly
onClicked: {
chatBar.postMessage()
}
ToolTip.text: text
ToolTip.visible: hovered
}
}
CompletionMenu {
id: completionMenu
height: implicitHeight
y: -height - 5
z: 1
chatDocumentHandler: documentHandler
Behavior on height {
NumberAnimation {
property: "height"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
}
}
}
Connections {
target: currentRoom
function onChatBoxEditIdChanged() {
chatBar.inputFieldText = currentRoom.chatBoxEditMessage
}
}
ChatDocumentHandler {
id: documentHandler
document: inputField.textDocument
cursorPosition: inputField.cursorPosition
selectionStart: inputField.selectionStart
selectionEnd: inputField.selectionEnd
Component.onCompleted: {
RoomManager.chatDocumentHandler = documentHandler;
}
}
function pasteImage() {
let localPath = Clipboard.saveImage();
if (localPath.length === 0) {
return;
}
currentRoom.chatBoxAttachmentPath = localPath
}
function postMessage() {
actionsHandler.handleMessage();
currentRoom.markAllMessagesAsRead();
inputField.clear();
currentRoom.chatBoxReplyId = "";
currentRoom.chatBoxEditId = "";
messageSent()
}
}

View File

@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
ColumnLayout {
id: chatBox
property alias inputFieldText: chatBar.inputFieldText
signal messageSent()
spacing: 0
Kirigami.Separator {
id: connectionPaneSeparator
visible: connectionPane.visible
Layout.fillWidth: true
}
QQC2.Pane {
id: connectionPane
padding: fontMetrics.lineSpacing * 0.25
FontMetrics {
id: fontMetrics
font: networkLabel.font
}
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
visible: !Controller.isOnline
Layout.fillWidth: true
QQC2.Label {
id: networkLabel
text: i18n("NeoChat is offline. Please check your network connection.")
}
}
Kirigami.Separator {
id: emojiPickerLoaderSeparator
visible: emojiPickerLoader.visible
Layout.fillWidth: true
height: visible ? implicitHeight : 0
}
Loader {
id: emojiPickerLoader
active: visible
visible: chatBar.emojiPaneOpened
Layout.fillWidth: true
sourceComponent: QQC2.Pane {
topPadding: 0
bottomPadding: 0
rightPadding: 0
leftPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: EmojiPicker {
textArea: chatBar.textField
onChosen: addText(emoji)
}
}
}
Kirigami.Separator {
id: replySeparator
visible: replyPane.visible
Layout.fillWidth: true
}
ReplyPane {
id: replyPane
visible: currentRoom.chatBoxReplyId.length > 0 || currentRoom.chatBoxEditId.length > 0
Layout.fillWidth: true
onReplyCancelled: {
chatBox.focusInputField()
}
}
Kirigami.Separator {
id: attachmentSeparator
visible: attachmentPane.visible
Layout.fillWidth: true
}
AttachmentPane {
id: attachmentPane
visible: currentRoom.chatBoxAttachmentPath.length > 0
Layout.fillWidth: true
}
Kirigami.Separator {
id: chatBarSeparator
visible: chatBar.visible
Layout.fillWidth: true
}
ChatBar {
id: chatBar
visible: currentRoom.canSendEvent("m.room.message")
Layout.fillWidth: true
onCloseAllTriggered: closeAll()
onMessageSent: {
closeAll()
chatBox.messageSent();
}
Behavior on implicitHeight {
NumberAnimation {
property: "implicitHeight"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
}
}
}
function addText(text) {
chatBox.inputFieldText = inputFieldText + text
}
function insertText(str) {
chatBox.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
}
function focusInputField() {
chatBar.inputFieldForceActiveFocusTriggered()
}
function closeAll() {
// TODO clear();
chatBar.emojiPaneOpened = false;
}
}

View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Popup {
id: completionMenu
width: parent.width
visible: completions.count > 0
RoomListModel {
id: roomListModel
connection: Controller.activeConnection
}
required property var chatDocumentHandler
Component.onCompleted: {
chatDocumentHandler.completionModel.roomListModel = roomListModel;
}
function incrementIndex() {
completions.incrementCurrentIndex()
}
function decrementIndex() {
completions.decrementCurrentIndex()
}
function complete() {
completionMenu.chatDocumentHandler.complete(completions.currentIndex)
}
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
implicitHeight: Math.min(completions.contentHeight, Kirigami.Units.gridUnit * 10)
contentItem: ListView {
id: completions
anchors.fill: parent
model: completionMenu.chatDocumentHandler.completionModel
currentIndex: 0
keyNavigationWraps: true
highlightMoveDuration: 100
delegate: Kirigami.BasicListItem {
text: model.text
subtitle: model.subtitle ?? ""
leading: RowLayout {
Kirigami.Avatar {
visible: model.icon !== "invalid"
Layout.preferredWidth: height
Layout.fillHeight: true
source: model.icon === "invalid" ? "" : ("image://mxc/" + model.icon)
name: model.text
}
}
onClicked: completionMenu.chatDocumentHandler.complete(model.index)
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}

View File

@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0
Loader {
id: replyPane
property NeoChatUser user: currentRoom.chatBoxReplyUser ?? currentRoom.chatBoxEditUser
signal replyCancelled()
active: visible
sourceComponent: Pane {
id: replyPane
Kirigami.Theme.colorSet: Kirigami.Theme.View
spacing: leftPadding
contentItem: RowLayout {
Layout.fillWidth: true
spacing: replyPane.spacing
FontMetrics {
id: fontMetrics
font: textArea.font
}
Kirigami.Avatar {
id: avatar
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
Layout.preferredWidth: Layout.preferredHeight
Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
source: user ? "image://mxc/" + currentRoom.getUser(user.id).avatarMediaId : ""
name: user ? user.displayName : ""
color: user ? user.color : "transparent"
visible: Boolean(user)
}
ColumnLayout {
id: textContentLayout
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
spacing: fontMetrics.leading
Label {
Layout.fillWidth: true
textFormat: Text.StyledText
elide: Text.ElideRight
text: {
let heading = "<b>%1</b>"
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
if (currentRoom.chatBoxEditId.length > 0) {
heading = heading.arg(i18n("Editing message:")) + "<br/>"
} else {
heading = heading.arg(i18n("Replying to %1:", userName))
}
return heading
}
}
//TODO edit user mentions
ScrollView {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: textArea
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + (currentRoom.chatBoxEditId.length > 0 ? currentRoom.chatBoxEditMessage : currentRoom.chatBoxReplyMessage)
selectByMouse: true
selectByKeyboard: true
readOnly: true
wrapMode: Label.Wrap
textFormat: TextEdit.RichText
background: Item {}
HoverHandler {
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
}
}
}
}
ToolButton {
display: AbstractButton.IconOnly
action: Kirigami.Action {
text: i18nc("@action:button", "Cancel reply")
icon.name: "dialog-close"
onTriggered: {
currentRoom.chatBoxReplyId = "";
currentRoom.chatBoxEditId = "";
}
shortcut: "Escape"
}
ToolTip.text: text
ToolTip.visible: hovered
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
}