Chatbar Refactor
This is mostly a stylistic rework of the chatbar but there are some buxfixes / improvements in here as well. The aim was to make the chatbar so it's size was managed in the same manner as the timeline in both bubble and compactmode in the same manner as network/neochat!476. The other features are: - Replies, attachments and edits now look like they are inside the chatbar and use a similar styling to edits in message bubbles - Replies and edits now part of the message so they arte part of the ScrollView and will scroll away when the text is long - ~~The emoji picker is now a popup so it doesn't mess with the timeline layout when activated~~ (done in network/neochat!697) - ~~Emoji dialog is now no longer required as the picker itself is a popup now~~ (no longer the case see above) - The scrollbar now sits on the right of the chatbar actions rather than weirdly to the left - The action icons will always stay in the same place even as the chatbar gets taller Updated\  Scrollbar behaviour before  Scrollbar behaviour after 
This commit is contained in:
@@ -10,188 +10,115 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: attachmentPaneLoader
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
readonly property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPaneLoader.attachmentPath)
|
||||
signal attachmentCancelled()
|
||||
|
||||
property string attachmentPath
|
||||
|
||||
readonly property var attachmentMimetype: FileType.mimeTypeForUrl(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 {
|
||||
QQC2.Pane {
|
||||
id: attachmentPane
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
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
|
||||
)
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: i18n("Attachment:")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: editImageButton
|
||||
visible: hasImage
|
||||
icon.name: "document-edit"
|
||||
text: i18n("Edit")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.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
|
||||
}
|
||||
|
||||
QQC2.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
|
||||
QQC2.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
|
||||
QQC2.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
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: editImageButton
|
||||
visible: hasImage
|
||||
icon.name: "document-edit"
|
||||
text: i18n("Edit")
|
||||
display: QQC2.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;
|
||||
});
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: cancelAttachmentButton
|
||||
icon.name: "dialog-close"
|
||||
text: i18n("Cancel sending Image")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
onClicked: currentRoom.chatBoxAttachmentPath = "";
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
background: null
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: root.attachmentPath
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
root.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: cancelAttachmentButton
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Cancel sending attachment")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: attachmentCancelled();
|
||||
shortcut: "Escape"
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
asynchronous: true
|
||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||
source: hasImage ? root.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 {
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.BusyIndicator {
|
||||
id: imageBusyIndicator
|
||||
|
||||
visible: running
|
||||
running: image.visible && image.progress < 1
|
||||
}
|
||||
RowLayout {
|
||||
id: fileInfoLayout
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: parent.spacing
|
||||
|
||||
Kirigami.Icon {
|
||||
id: mimetypeIcon
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
source: attachmentMimetype.iconName
|
||||
}
|
||||
QQC2.Label {
|
||||
id: fileLabel
|
||||
text: baseFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,205 +5,62 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
QQC2.ToolBar {
|
||||
id: chatBar
|
||||
property alias inputFieldText: inputField.text
|
||||
property alias textField: inputField
|
||||
property alias cursorPosition: inputField.cursorPosition
|
||||
QQC2.Control {
|
||||
id: root
|
||||
|
||||
property alias textField: textField
|
||||
property bool isReplying: currentRoom.chatBoxReplyId.length > 0
|
||||
property bool isEditing: currentRoom.chatBoxEditId.length > 0
|
||||
property bool replyPaneVisible: isReplying || isEditing
|
||||
property NeoChatUser replyUser: currentRoom.chatBoxReplyUser ?? currentRoom.chatBoxEditUser
|
||||
property bool attachmentPaneVisible: currentRoom.chatBoxAttachmentPath.length > 0
|
||||
|
||||
signal inputFieldForceActiveFocusTriggered()
|
||||
signal messageSent()
|
||||
|
||||
onInputFieldForceActiveFocusTriggered: {
|
||||
inputField.forceActiveFocus();
|
||||
// set the cursor to the end of the text
|
||||
inputField.cursorPosition = inputField.length;
|
||||
}
|
||||
property list<Kirigami.Action> actions : [
|
||||
Kirigami.Action {
|
||||
id: attachmentAction
|
||||
|
||||
position: QQC2.ToolBar.Footer
|
||||
property bool isBusy: currentRoom && currentRoom.hasFileUploading
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
// Matrix does not allow sending attachments in replies
|
||||
visible: currentRoom.chatBoxReplyId.length === 0 && currentRoom.chatBoxAttachmentPath.length === 0
|
||||
icon.name: "mail-attachment"
|
||||
text: i18n("Attach an image or file")
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
|
||||
// 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
|
||||
|
||||
QQC2.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)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: inputField.font
|
||||
}
|
||||
|
||||
QQC2.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. Build libQuotient with encryption enabled to send encrypted messages.") : currentRoom.chatBoxEditId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : 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"]
|
||||
onTriggered: {
|
||||
if (Clipboard.hasImage) {
|
||||
attachDialog.open()
|
||||
} else {
|
||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect((path) => {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
} 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()
|
||||
} else if (event.key === Qt.Key_Backspace && inputField.text.length <= 1) {
|
||||
currentRoom.sendTypingNotification(false)
|
||||
repeatTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
interval: 5000
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (!repeatTimer.running && Config.typingNotifications) {
|
||||
var textExists = text.length > 0
|
||||
currentRoom.sendTypingNotification(textExists)
|
||||
textExists ? repeatTimer.start() : repeatTimer.stop()
|
||||
}
|
||||
currentRoom.chatBoxText = text
|
||||
currentRoom.chatBoxAttachmentPath = path;
|
||||
})
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: currentRoom.chatBoxReplyId.length === 0 && (currentRoom.chatBoxAttachmentPath.length === 0 || uploadingBusySpinner.running)
|
||||
implicitWidth: uploadButton.implicitWidth
|
||||
implicitHeight: uploadButton.implicitHeight
|
||||
QQC2.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: QQC2.AbstractButton.IconOnly
|
||||
tooltip: text
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: emojiAction
|
||||
|
||||
onClicked: {
|
||||
if (Clipboard.hasImage) {
|
||||
attachDialog.open()
|
||||
} else {
|
||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect((path) => {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
currentRoom.chatBoxAttachmentPath = path;
|
||||
})
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
property bool isBusy: false
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
QQC2.BusyIndicator {
|
||||
id: uploadingBusySpinner
|
||||
anchors.fill: parent
|
||||
visible: running
|
||||
running: currentRoom && currentRoom.hasFileUploading
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: emojiButton
|
||||
icon.name: "smiley"
|
||||
text: i18n("Add an Emoji")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
checkable: true
|
||||
|
||||
onClicked: {
|
||||
onTriggered: {
|
||||
if (emojiDialog.visible) {
|
||||
emojiDialog.close()
|
||||
} else {
|
||||
@@ -211,29 +68,266 @@ QQC2.ToolBar {
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
tooltip: text
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: sendAction
|
||||
|
||||
property bool isBusy: false
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: sendButton
|
||||
icon.name: "document-send"
|
||||
text: i18n("Send message")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
checkable: true
|
||||
|
||||
onClicked: {
|
||||
chatBar.postMessage()
|
||||
onTriggered: {
|
||||
root.postMessage()
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
tooltip: text
|
||||
}
|
||||
]
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
contentItem: QQC2.ScrollView {
|
||||
id: chatBarScrollView
|
||||
|
||||
property var textFieldHeight: textField.height
|
||||
|
||||
property var visualLeftPadding: (root.width - chatBoxMaxWidth) / 2 - (root.width > chatBoxMaxWidth ? Kirigami.Units.largeSpacing : 0)
|
||||
property var visualRightPadding: (root.width - chatBoxMaxWidth) / 2 + (root.width > chatBoxMaxWidth ? Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0)
|
||||
leftPadding: LayoutMirroring.enabled ? visualRightPadding : visualLeftPadding
|
||||
rightPadding: LayoutMirroring.enabled ? visualLeftPadding : visualRightPadding
|
||||
|
||||
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
|
||||
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
id: chatBarHeightAnimation
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.TextArea{
|
||||
id: textField
|
||||
|
||||
topPadding: Kirigami.Units.largeSpacing + (paneLoader.visible ? paneLoader.height : 0)
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: LayoutMirroring.enabled ? actionsRow.width : (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing)
|
||||
rightPadding: LayoutMirroring.enabled ? (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing) : actionsRow.width
|
||||
|
||||
placeholderText: readOnly ? i18n("This room is encrypted. Build libQuotient with encryption enabled to send encrypted messages.") : currentRoom.chatBoxEditId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: (currentRoom.usesEncryption && !Controller.encryptionSupported)
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
interval: 5000
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (!repeatTimer.running && Config.typingNotifications) {
|
||||
var textExists = text.length > 0
|
||||
currentRoom.sendTypingNotification(textExists)
|
||||
textExists ? repeatTimer.start() : repeatTimer.stop()
|
||||
}
|
||||
currentRoom.chatBoxText = text
|
||||
}
|
||||
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete()
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
textField.insert(cursorPosition, "\n")
|
||||
} else {
|
||||
chatBar.postMessage();
|
||||
}
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete()
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
textField.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 && textField.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()
|
||||
} else if (event.key === Qt.Key_Backspace && textField.text.length <= 1) {
|
||||
currentRoom.sendTypingNotification(false)
|
||||
repeatTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: paneLoader
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.width > chatBoxMaxWidth ? 0 : (chatBarScrollView.QQC2.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 3.5 : Kirigami.Units.largeSpacing)
|
||||
|
||||
active: visible
|
||||
visible: root.replyPaneVisible || root.attachmentPaneVisible
|
||||
sourceComponent: root.replyPaneVisible ? replyPane : attachmentPane
|
||||
}
|
||||
Component {
|
||||
id: replyPane
|
||||
ReplyPane {
|
||||
userName: root.replyUser ? root.replyUser.displayName : ""
|
||||
userColor: root.replyUser ? root.replyUser.color : ""
|
||||
userAvatar: root.replyUser ? "image://mxc/" + currentRoom.getUser(root.replyUser.id).avatarMediaId : ""
|
||||
isReply: root.isReplying
|
||||
text: isEditing ? currentRoom.chatBoxEditMessage : currentRoom.chatBoxReplyMessage
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: attachmentPane
|
||||
AttachmentPane {
|
||||
attachmentPath: currentRoom.chatBoxAttachmentPath
|
||||
|
||||
onAttachmentCancelled: {
|
||||
currentRoom.chatBoxAttachmentPath = "";
|
||||
root.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Because of the paneLoader we have to manage the scroll
|
||||
* position manually or it doesn't keep the cursor visible properly all the time.
|
||||
*/
|
||||
function ensureVisible(r) {
|
||||
// Find the child that is the Flickable created by ScrollView.
|
||||
let flickable = undefined;
|
||||
for (var index in children) {
|
||||
if (children[index] instanceof Flickable) {
|
||||
flickable = children[index];
|
||||
}
|
||||
}
|
||||
|
||||
if (flickable) {
|
||||
if (flickable.contentX >= r.x) {
|
||||
flickable.contentX = r.x;
|
||||
} else if (flickable.contentX + width <= r.x + r.width) {
|
||||
flickable.contentX = r.x + r.width - width;
|
||||
} if (flickable.contentY >= r.y) {
|
||||
flickable.contentY = r.y;
|
||||
} else if (flickable.contentY + height <= r.y + r.height) {
|
||||
flickable.contentY = r.y + r.height - height + textField.bottomPadding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: cancelButton
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: (root.width - chatBoxMaxWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBoxMaxWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
|
||||
|
||||
visible: root.replyPaneVisible
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
action: Kirigami.Action {
|
||||
text: root.isReplying ? i18nc("@action:button", "Cancel reply") : i18nc("@action:button", "Cancel edit")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: {
|
||||
currentRoom.chatBoxReplyId = "";
|
||||
currentRoom.chatBoxEditId = "";
|
||||
currentRoom.chatBoxAttachmentPath = "";
|
||||
root.forceActiveFocus()
|
||||
}
|
||||
shortcut: "Escape"
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
Row {
|
||||
id: actionsRow
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
anchors.right: parent.right
|
||||
property var requiredMargin: (root.width - chatBoxMaxWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBoxMaxWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
|
||||
anchors.leftMargin: layoutDirection === Qt.RightToLeft ? requiredMargin : 0
|
||||
anchors.rightMargin: layoutDirection === Qt.RightToLeft ? 0 : requiredMargin
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Kirigami.Units.largeSpacing - 2
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
Kirigami.Icon {
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
|
||||
source: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
||||
active: actionArea.containsPress
|
||||
visible: modelData.visible
|
||||
enabled: modelData.enabled
|
||||
MouseArea {
|
||||
id: actionArea
|
||||
anchors.fill: parent
|
||||
onClicked: modelData.trigger()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
QQC2.ToolTip.visible: modelData.tooltip !== "" && hoverHandler.hovered
|
||||
QQC2.ToolTip.text: modelData.tooltip
|
||||
HoverHandler { id: hoverHandler }
|
||||
|
||||
QQC2.BusyIndicator {
|
||||
anchors.fill: parent
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
visible: running
|
||||
running: modelData.isBusy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmojiDialog {
|
||||
id: emojiDialog
|
||||
x: parent.width - implicitWidth
|
||||
y: -implicitHeight - Kirigami.Units.smallSpacing
|
||||
y: -implicitHeight // - Kirigami.Units.smallSpacing
|
||||
|
||||
modal: false
|
||||
includeCustom: true
|
||||
@@ -243,6 +337,10 @@ QQC2.ToolBar {
|
||||
onClosed: if (emojiButton.checked) emojiButton.checked = false
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
CompletionMenu {
|
||||
id: completionMenu
|
||||
height: implicitHeight
|
||||
@@ -262,22 +360,34 @@ QQC2.ToolBar {
|
||||
target: currentRoom
|
||||
function onChatBoxEditIdChanged() {
|
||||
if (currentRoom.chatBoxEditMessage.length > 0) {
|
||||
chatBar.inputFieldText = currentRoom.chatBoxEditMessage
|
||||
textField.text = currentRoom.chatBoxEditMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
document: inputField.textDocument
|
||||
cursorPosition: inputField.cursorPosition
|
||||
selectionStart: inputField.selectionStart
|
||||
selectionEnd: inputField.selectionEnd
|
||||
document: textField.textDocument
|
||||
cursorPosition: textField.cursorPosition
|
||||
selectionStart: textField.selectionStart
|
||||
selectionEnd: textField.selectionEnd
|
||||
Component.onCompleted: {
|
||||
RoomManager.chatDocumentHandler = documentHandler;
|
||||
}
|
||||
}
|
||||
|
||||
function forceActiveFocus() {
|
||||
textField.forceActiveFocus();
|
||||
// set the cursor to the end of the text
|
||||
textField.cursorPosition = textField.length;
|
||||
}
|
||||
|
||||
function insertText(text) {
|
||||
let initialCursorPosition = textField.cursorPosition;
|
||||
|
||||
textField.text = textField.text.substr(0, initialCursorPosition) + text + textField.text.substr(initialCursorPosition)
|
||||
textField.cursorPosition = initialCursorPosition + text.length
|
||||
}
|
||||
|
||||
function pasteImage() {
|
||||
let localPath = Clipboard.saveImage();
|
||||
@@ -291,7 +401,7 @@ QQC2.ToolBar {
|
||||
actionsHandler.handleMessage();
|
||||
repeatTimer.stop()
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
inputField.clear();
|
||||
textField.clear();
|
||||
currentRoom.chatBoxReplyId = "";
|
||||
currentRoom.chatBoxEditId = "";
|
||||
messageSent()
|
||||
|
||||
@@ -5,108 +5,52 @@
|
||||
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.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: chatBox
|
||||
|
||||
property alias inputFieldText: chatBar.inputFieldText
|
||||
|
||||
signal messageSent()
|
||||
|
||||
property alias chatBar: chatBar
|
||||
|
||||
readonly property int extraWidth: width >= Kirigami.Units.gridUnit * 47 ? Math.min((width - Kirigami.Units.gridUnit * 47), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int chatBoxMaxWidth: Config.compactLayout ? width : Math.min(width, Kirigami.Units.gridUnit * 39 + extraWidth)
|
||||
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Separator {
|
||||
id: connectionPaneSeparator
|
||||
visible: connectionPane.visible
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Layout.leftMargin: 1 // So we can see the border
|
||||
Layout.rightMargin: 1 // So we can see the border
|
||||
|
||||
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
|
||||
}
|
||||
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||
visible: !Controller.isOnline
|
||||
Layout.fillWidth: true
|
||||
QQC2.Label {
|
||||
id: networkLabel
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Layout.minimumHeight: implicitHeight + Kirigami.Units.largeSpacing
|
||||
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
|
||||
Layout.maximumHeight: chatBarFontMetrics.lineSpacing * 8 - chatBarFontMetrics.leading + textField.topPadding + textField.bottomPadding
|
||||
|
||||
FontMetrics {
|
||||
id: chatBarFontMetrics
|
||||
font: chatBar.textField.font
|
||||
}
|
||||
|
||||
onMessageSent: {
|
||||
chatBox.messageSent();
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
property: "implicitHeight"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertText(str) {
|
||||
let index = chatBar.cursorPosition;
|
||||
chatBox.inputFieldText = inputFieldText.substr(0, chatBar.cursorPosition) + str + inputFieldText.substr(chatBar.cursorPosition);
|
||||
chatBar.cursorPosition = index + str.length;
|
||||
}
|
||||
|
||||
function focusInputField() {
|
||||
chatBar.inputFieldForceActiveFocusTriggered()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,108 +10,83 @@ import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: replyPane
|
||||
property NeoChatUser user: currentRoom.chatBoxReplyUser ?? currentRoom.chatBoxEditUser
|
||||
GridLayout {
|
||||
id: root
|
||||
property string userName
|
||||
property color userColor: Kirigami.Theme.highlightColor
|
||||
property var userAvatar: ""
|
||||
property bool isReply
|
||||
property var text
|
||||
|
||||
signal replyCancelled()
|
||||
rows: 3
|
||||
columns: 3
|
||||
rowSpacing: Kirigami.Units.smallSpacing
|
||||
columnSpacing: Kirigami.Units.largeSpacing
|
||||
|
||||
active: visible
|
||||
sourceComponent: QQC2.Pane {
|
||||
id: replyPane
|
||||
QQC2.Label {
|
||||
id: replyLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.columnSpan: 3
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
text: isReply ? i18n("Replying to:") : i18n("Editing message:")
|
||||
}
|
||||
Rectangle {
|
||||
id: verticalBorder
|
||||
|
||||
spacing: leftPadding
|
||||
Layout.fillHeight: true
|
||||
Layout.rowSpan: 2
|
||||
|
||||
contentItem: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: replyPane.spacing
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
color: userColor
|
||||
}
|
||||
Kirigami.Avatar {
|
||||
id: replyAvatar
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: textArea.font
|
||||
}
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
|
||||
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)
|
||||
}
|
||||
source: userAvatar
|
||||
name: userName
|
||||
color: userColor
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
ColumnLayout {
|
||||
id: textContentLayout
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: fontMetrics.leading
|
||||
QQC2.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))
|
||||
}
|
||||
color: userColor
|
||||
text: userName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QQC2.TextArea {
|
||||
id: textArea
|
||||
|
||||
return heading
|
||||
}
|
||||
}
|
||||
//TODO edit user mentions
|
||||
QQC2.ScrollView {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
QQC2.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: QQC2.Label.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
background: Item {}
|
||||
HoverHandler {
|
||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
action: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: {
|
||||
currentRoom.chatBoxReplyId = "";
|
||||
currentRoom.chatBoxEditId = "";
|
||||
}
|
||||
shortcut: "Escape"
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + replyTextMetrics.elidedText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
readOnly: true
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
background: Item {}
|
||||
HoverHandler {
|
||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
TextMetrics {
|
||||
id: replyTextMetrics
|
||||
|
||||
text: root.text
|
||||
font: textArea.font
|
||||
elide: Qt.ElideRight
|
||||
elideWidth: textArea.width * 2 - Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ Kirigami.ScrollablePage {
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
messageListView.positionViewAtBeginning();
|
||||
hasScrolledUpBefore = false;
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -137,8 +138,8 @@ Kirigami.ScrollablePage {
|
||||
switchRoomUp();
|
||||
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
chatBox.addText(event.text);
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.insertText(event.text);
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -349,7 +350,7 @@ Kirigami.ScrollablePage {
|
||||
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
|
||||
action: Kirigami.Action {
|
||||
onTriggered: {
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
messageListView.goToEvent(currentRoom.readMarkerEventId)
|
||||
}
|
||||
icon.name: "go-up"
|
||||
@@ -373,7 +374,7 @@ Kirigami.ScrollablePage {
|
||||
visible: !messageListView.atYEnd
|
||||
action: Kirigami.Action {
|
||||
onTriggered: {
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
goToLastMessage();
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
}
|
||||
@@ -519,13 +520,14 @@ Kirigami.ScrollablePage {
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
|
||||
onClicked: emojiDialog.open();
|
||||
EmojiDialog {
|
||||
id: emojiDialog
|
||||
showQuickReaction: true
|
||||
onChosen: {
|
||||
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,7 +540,7 @@ Kirigami.ScrollablePage {
|
||||
onClicked: {
|
||||
currentRoom.chatBoxEditId = hoverActions.event.eventId;
|
||||
currentRoom.chatBoxReplyId = "";
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
@@ -549,7 +551,7 @@ Kirigami.ScrollablePage {
|
||||
onClicked: {
|
||||
currentRoom.chatBoxReplyId = hoverActions.event.eventId;
|
||||
currentRoom.chatBoxEditId = "";
|
||||
chatBox.focusInputField();
|
||||
chatBox.chatBar.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user