Update the look of the chatbar to be floating with the rich text controls on top and send buttons inline

This commit is contained in:
James Graham
2026-01-17 15:46:00 +00:00
parent 79de8a792c
commit 6b318ec754
25 changed files with 945 additions and 806 deletions

View File

@@ -9,6 +9,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
AttachDialog.qml
ChatBar.qml
RichEditBar.qml
SendBar.qml
CompletionMenu.qml
EmojiDelegate.qml
EmojiGrid.qml
@@ -17,6 +18,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
EmojiDialog.qml
EmojiTonesPicker.qml
StylePicker.qml
StyleDelegate.qml
ImageEditorPage.qml
VoiceMessageDialog.qml
ImageDialog.qml
@@ -24,6 +26,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
LocationChooser.qml
NewPollDialog.qml
TableDialog.qml
StyleButton.qml
SOURCES
chatbuttonhelper.cpp
styledelegatehelper.cpp

View File

@@ -25,20 +25,14 @@ import org.kde.neochat.libneochat as LibNeoChat
*
* @sa ChatBar
*/
QQC2.Control {
Item {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
required property NeoChatConnection connection
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
required property LibNeoChat.NeoChatRoom currentRoom
onCurrentRoomChanged: {
_private.chatBarCache = currentRoom.mainCache
if (ShareHandler.text.length > 0 && ShareHandler.room === root.currentRoom.id) {
contentModel.focusedTextItem.
textField.text = ShareHandler.text;
@@ -47,37 +41,7 @@ QQC2.Control {
}
}
Connections {
target: contentModel.keyHelper
function onUnhandledUp(isCompleting: bool): void {
if (!isCompleting) {
return;
}
completionMenu.decrementIndex();
}
function onUnhandledDown(isCompleting: bool): void {
if (!isCompleting) {
return;
}
completionMenu.incrementIndex();
}
function onUnhandledTab(isCompleting: bool): void {
if (!isCompleting) {
return;
}
completionMenu.completeCurrent();
}
function onUnhandledReturn(isCompleting: bool): void {
if (!isCompleting) {
return;
}
completionMenu.completeCurrent();
}
}
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
Connections {
target: ShareHandler
@@ -100,64 +64,111 @@ QQC2.Control {
}
}
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Message.room: root.currentRoom
Message.contentModel: contentModel
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Separator {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
}
}
implicitHeight: chatBar.implicitHeight + Kirigami.Units.largeSpacing
height: Math.max(Math.min(chatScrollView.contentHeight + bottomPadding + topPadding, Kirigami.Units.gridUnit * 10), Kirigami.Units.gridUnit * 5)
leftPadding: rightPadding
rightPadding: (root.width - chatBarSizeHelper.availableWidth) / 2 + Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
QQC2.Control {
id: chatBar
contentItem: ColumnLayout {
QQC2.ScrollView {
id: chatScrollView
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
anchors.top: root.top
anchors.horizontalCenter: root.horizontalCenter
clip: true
spacing: 0
ColumnLayout {
width: chatScrollView.width
spacing: Kirigami.Units.smallSpacing
width: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
Repeater {
id: chatContentView
model: ChatBarMessageContentModel {
id: contentModel
type: ChatBarType.Room
room: root.currentRoom
contentItem: ColumnLayout {
RichEditBar {
id: richEditBar
visible: NeoChatConfig.sendMessageWith === 1
maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
room: root.currentRoom
contentModel: chatContentView.model
onClicked: contentModel.refocusCurrentComponent()
}
Kirigami.Separator {
Layout.fillWidth: true
visible: NeoChatConfig.sendMessageWith === 1
}
RowLayout {
spacing: 0
QQC2.ScrollView {
id: chatScrollView
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
clip: true
ColumnLayout {
width: chatScrollView.width
spacing: Kirigami.Units.smallSpacing
Repeater {
id: chatContentView
model: ChatBarMessageContentModel {
id: contentModel
type: ChatBarType.Room
room: root.currentRoom
sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0
}
delegate: MessageComponentChooser {}
}
}
delegate: MessageComponentChooser {}
}
SendBar {
room: root.currentRoom
contentModel: chatContentView.model
}
}
}
RichEditBar {
id: richEditBar
Layout.alignment: Qt.AlignCenter
maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
room: root.currentRoom
contentModel: chatContentView.model
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
onClicked: contentModel.refocusCurrentComponent()
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border {
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
width: 1
}
}
}
MouseArea {
id: hoverArea
anchors {
top: chatModeButton.top
left: root.left
right: root.right
bottom: chatBar.top
}
propagateComposedEvents: true
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
QQC2.Button {
id: chatModeButton
anchors {
bottom: chatBar.top
bottomMargin: Kirigami.Units.smallSpacing
horizontalCenter: root.horizontalCenter
}
visible: hoverArea.containsMouse || hovered || chatBar.hovered
width: Kirigami.Units.iconSizes.enormous
height: Kirigami.Units.iconSizes.smallMedium
icon.name: NeoChatConfig.sendMessageWith === 0 ? "arrow-up" : "arrow-down"
onClicked: NeoChatConfig.sendMessageWith = NeoChatConfig.sendMessageWith === 0 ? 1 : 0
}
LibNeoChat.DelegateSizeHelper {
id: chatBarSizeHelper
@@ -171,35 +182,23 @@ QQC2.Control {
QtObject {
id: _private
property ChatBarCache chatBarCache
onChatBarCacheChanged: {
richEditBar.chatBarCache = chatBarCache
}
function pasteImage() {
let localPath = Clipboard.saveImage();
if (localPath.length === 0) {
return false;
}
_private.chatBarCache.attachmentPath = localPath;
return true;
}
}
property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel {
room: root.currentRoom
type: LibNeoChat.ChatBarType.Room
textItem: contentModel.focusedTextItem
roomListModel: RoomManager.roomListModel
userListModel: RoomManager.userListModel
CompletionMenu {
id: completionMenu
room: root.currentRoom
type: LibNeoChat.ChatBarType.Room
textItem: contentModel.focusedTextItem
onIsCompletingChanged: {
if (!isCompleting) {
return;
}
x: 1
y: -height
width: parent.width - 1
Behavior on height {
NumberAnimation {
property: "height"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
let dialog = Qt.createComponent('org.kde.neochat.chatbar', 'CompletionMenu').createObject(contentModel.focusedTextItem.textItem, {
model: _private.completionModel,
keyHelper: contentModel.keyHelper
}).open();
}
}
}

View File

@@ -11,33 +11,55 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.libneochat as LibNeoChat
QQC2.Popup {
id: root
/**
* @brief The current room that user is viewing.
*/
required property LibNeoChat.NeoChatRoom room
property alias model: completions.model
/**
* @brief The chatbar type
*/
required property int type
required property LibNeoChat.ChatKeyHelper keyHelper
/**
* @brief The chatbar type
*/
required property LibNeoChat.ChatTextItemHelper textItem
Connections {
target: keyHelper
visible: completions.count > 0
function onUnhandledUp(isCompleting: bool): void {
if (!isCompleting) {
return;
}
root.decrementIndex();
}
onVisibleChanged: if (visible) {
root.open();
function onUnhandledDown(isCompleting: bool): void {
if (!isCompleting) {
return;
}
root.incrementIndex();
}
function onUnhandledTab(isCompleting: bool): void {
if (!isCompleting) {
return;
}
root.completeCurrent();
}
function onUnhandledReturn(isCompleting: bool): void {
if (!isCompleting) {
return;
}
root.completeCurrent();
}
function onCloseCompletion(): void {
root.close();
root.model.ignoreCurrentCompletion();
}
}
x: model.textItem.textItem.cursorRectangle.x - Kirigami.Units.largeSpacing
y: model.textItem.textItem.cursorRectangle.y - implicitHeight - Kirigami.Units.smallSpacing
function incrementIndex() {
completions.incrementCurrentIndex();
}
@@ -47,11 +69,11 @@ QQC2.Popup {
}
function complete(text: string, hRef: string) {
completionModel.insertCompletion(text, hRef);
model.insertCompletion(text, hRef);
}
function completeCurrent() {
completionModel.insertCompletion(completions.currentItem.replacedText, completions.currentItem.hRef);
model.insertCompletion(completions.currentItem.replacedText, completions.currentItem.hRef);
}
leftPadding: 0
@@ -61,70 +83,57 @@ QQC2.Popup {
implicitHeight: Math.min(completions.contentHeight, Kirigami.Units.gridUnit * 10)
contentItem: ColumnLayout {
spacing: 0
Kirigami.Separator {
Layout.fillWidth: true
}
QQC2.ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Layout.maximumHeight: Kirigami.Units.gridUnit * 10
contentItem: QQC2.ScrollView {
contentWidth: Kirigami.Units.gridUnit * 20
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
ListView {
id: completions
currentIndex: 0
keyNavigationWraps: true
highlightMoveDuration: 100
onCountChanged: currentIndex = 0
delegate: Delegates.RoundedItemDelegate {
id: completionDelegate
ListView {
id: completions
required property int index
required property string displayName
required property string subtitle
required property string iconName
required property string replacedText
required property url hRef
model: LibNeoChat.CompletionModel {
id: completionModel
room: root.room
type: root.type
textItem: root.textItem
roomListModel: RoomManager.roomListModel
userListModel: RoomManager.userListModel
}
currentIndex: 0
keyNavigationWraps: true
highlightMoveDuration: 100
onCountChanged: currentIndex = 0
delegate: Delegates.RoundedItemDelegate {
id: completionDelegate
text: displayName
required property int index
required property string displayName
required property string subtitle
required property string iconName
required property string replacedText
required property url hRef
text: displayName
contentItem: RowLayout {
KirigamiComponents.Avatar {
visible: completionDelegate.iconName !== "invalid"
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: completionDelegate.iconName === "invalid" ? "" : completionDelegate.iconName
name: completionDelegate.text
}
Delegates.SubtitleContentItem {
itemDelegate: completionDelegate
labelItem.textFormat: Text.PlainText
labelItem.clip: true // Intentional to limit insane Unicode in display names
subtitle: completionDelegate.subtitle ?? ""
subtitleItem.textFormat: Text.PlainText
}
contentItem: RowLayout {
KirigamiComponents.Avatar {
visible: completionDelegate.iconName !== "invalid"
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: completionDelegate.iconName === "invalid" ? "" : completionDelegate.iconName
name: completionDelegate.text
}
Delegates.SubtitleContentItem {
itemDelegate: completionDelegate
labelItem.textFormat: Text.PlainText
labelItem.clip: true // Intentional to limit insane Unicode in display names
subtitle: completionDelegate.subtitle ?? ""
subtitleItem.textFormat: Text.PlainText
}
onClicked: completionModel.insertCompletion(replacedText, hRef)
}
onClicked: root.model.insertCompletion(replacedText, hRef)
}
}
}
background: Rectangle {
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border {
width: 1
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
}
}
}

View File

@@ -11,7 +11,7 @@ import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
import org.kde.neochat.messagecontent as MessageContent
QQC2.ToolBar {
RowLayout {
id: root
/**
@@ -19,41 +19,39 @@ QQC2.ToolBar {
*/
required property LibNeoChat.NeoChatRoom room
property LibNeoChat.ChatBarCache chatBarCache
required property MessageContent.ChatBarMessageContentModel contentModel
required property real maxAvailableWidth
readonly property real uncompressedImplicitWidth: textFormatRow.implicitWidth +
readonly property real uncompressedImplicitWidth: boldButton.implicitWidth +
italicButton.implicitWidth +
extraTextFormatRow.implicitWidth +
listRow.implicitWidth +
styleButton.implicitWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
sendButton.implicitWidth +
buttonRow.spacing * 9 +
3
root.spacing * 7 +
Kirigami.Units.gridUnit
readonly property real listCompressedImplicitWidth: textFormatRow.implicitWidth +
readonly property real listCompressedImplicitWidth: boldButton.implicitWidth +
italicButton.implicitWidth +
extraTextFormatRow.implicitWidth +
compressedListButton.implicitWidth +
styleButton.implicitWidth +
styleButton.uncompressedWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
sendButton.implicitWidth +
buttonRow.spacing * 9 +
3
root.spacing * 7 +
Kirigami.Units.gridUnit
readonly property real textFormatCompressedImplicitWidth: compressedTextFormatButton.implicitWidth +
compressedListButton.implicitWidth +
styleButton.implicitWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
sendButton.implicitWidth +
buttonRow.spacing * 9 +
3
readonly property real extraTextCompressedImplicitWidth: boldButton.implicitWidth +
italicButton.implicitWidth +
compressedExtraTextFormatButton.implicitWidth +
compressedListButton.implicitWidth +
styleButton.uncompressedWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
root.spacing * 7 +
Kirigami.Units.gridUnit
readonly property ChatButtonHelper chatButtonHelper: ChatButtonHelper {
textItem: contentModel.focusedTextItem
@@ -61,128 +59,111 @@ QQC2.ToolBar {
signal clicked
RowLayout {
id: buttonRow
RowLayout {
id: textFormatRow
visible: root.maxAvailableWidth > root.listCompressedImplicitWidth
QQC2.ToolButton {
id: boldButton
Shortcut {
sequence: "Ctrl+B"
onActivated: boldButton.clicked()
}
icon.name: "format-text-bold"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Bold")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.bold
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Bold);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: italicButton
Shortcut {
sequence: "Ctrl+I"
onActivated: italicButton.clicked()
}
icon.name: "format-text-italic"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Italic")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.italic
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Italic);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: underlineButton
Shortcut {
sequence: "Ctrl+U"
onActivated: underlineButton.clicked()
}
icon.name: "format-text-underline"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Underline")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.underline
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Underline);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
icon.name: "format-text-strikethrough"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Strikethrough")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.strikethrough
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Strikethrough);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: boldButton
Shortcut {
sequence: "Ctrl+B"
onActivated: boldButton.clicked()
}
icon.name: "format-text-bold"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Bold")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.bold
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Bold);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: italicButton
Shortcut {
sequence: "Ctrl+I"
onActivated: italicButton.clicked()
}
icon.name: "format-text-italic"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Italic")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.italic
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Italic);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
RowLayout {
id: extraTextFormatRow
visible: root.maxAvailableWidth > root.listCompressedImplicitWidth
QQC2.ToolButton {
id: compressedTextFormatButton
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
icon.name: "dialog-text-and-font"
id: underlineButton
Shortcut {
sequence: "Ctrl+U"
onActivated: underlineButton.clicked()
}
icon.name: "format-text-underline"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Format Text")
text: i18nc("@action:button", "Underline")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: compressedTextFormatMenu.visible
checked: root.chatButtonHelper.underline
onClicked: {
compressedTextFormatMenu.open()
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Underline);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
icon.name: "format-text-strikethrough"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Strikethrough")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.strikethrough
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Strikethrough);
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
id: compressedExtraTextFormatButton
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
icon.name: "dialog-text-and-font"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Format Text")
display: QQC2.AbstractButton.IconOnly
checkable: true
onClicked: {
let dialog = compressedTextFormatMenu.createObject(compressedExtraTextFormatButton)
dialog.onClosed.connect(() => {
compressedExtraTextFormatButton.checked = false;
});
dialog.open();
compressedExtraTextFormatButton.checked = true;
}
Component {
id: compressedTextFormatMenu
QQC2.Menu {
id: compressedTextFormatMenu
y: -implicitHeight
QQC2.MenuItem {
icon.name: "format-text-bold"
text: i18nc("@action:button", "Bold")
checkable: true
checked: root.chatButtonHelper.bold
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Bold);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-text-italic"
text: i18nc("@action:button", "Italic")
checkable: true
checked: root.chatButtonHelper.italic
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.Italic);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-text-underline"
text: i18nc("@action:button", "Underline")
@@ -204,370 +185,205 @@ QQC2.ToolBar {
}
}
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
StyleButton {
id: styleButton
Layout.minimumWidth: compressed ? -1 : Kirigami.Units.gridUnit * 8 + Kirigami.Units.largeSpacing * 2
icon.name: "typewriter"
text: i18nc("@action:button", "Text Style")
style: root.chatButtonHelper.currentStyle
compressed: root.maxAvailableWidth < root.extraTextCompressedImplicitWidth
enabled: root.chatButtonHelper.styleFormatEnabled
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: styleMenu.visible
onClicked: {
if (styleMenu.visible) {
styleMenu.close();
return;
}
open = true;
styleMenu.open();
}
StylePicker {
id: styleMenu
chatContentModel: root.contentModel
chatButtonHelper: root.chatButtonHelper
onClosed: {
root.clicked()
styleButton.open = false;
}
}
}
QQC2.ToolButton {
id: emojiButton
property bool isBusy: false
visible: !Kirigami.Settings.isMobile
icon.name: "smiley"
text: i18n("Emojis & Stickers")
display: QQC2.AbstractButton.IconOnly
checkable: true
onClicked: {
let dialog = emojiDialog.createObject(root).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: linkButton
icon.name: "insert-link-symbolic"
text: i18nc("@action:button", "Insert link")
display: QQC2.AbstractButton.IconOnly
onClicked: {
let dialog = linkDialog.createObject(QQC2.Overlay.overlay, {
linkText: root.chatButtonHelper.currentLinkText,
linkUrl: root.chatButtonHelper.currentLinkUrl
})
dialog.onAccepted.connect(() => {
root.chatButtonHelper.updateLink(dialog.linkUrl, dialog.linkText)
root.clicked();
});
dialog.open();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
RowLayout {
id: listRow
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
QQC2.ToolButton {
icon.name: "format-list-unordered"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Unordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.unorderedList
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.UnorderedList);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Kirigami.Separator {
Layout.fillHeight: true
Layout.margins: 0
QQC2.ToolButton {
icon.name: "format-list-ordered"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Ordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.orderedlist
onClicked: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.OrderedList);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
RowLayout {
id: listRow
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
QQC2.ToolButton {
QQC2.ToolButton {
id: indentAction
icon.name: "format-indent-more"
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListMore
text: i18nc("@action:button", "Increase List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
root.chatButtonHelper.indentListMore();
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: dedentAction
icon.name: "format-indent-less"
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListLess
text: i18nc("@action:button", "Decrease List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
root.chatButtonHelper.indentListLess();
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
id: compressedListButton
enabled: chatButtonHelper.richFormatEnabled
visible: root.maxAvailableWidth < root.uncompressedImplicitWidth
icon.name: "format-list-unordered"
text: i18nc("@action:button", "List Style")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: compressedListMenu.visible
onClicked: {
compressedListMenu.open()
}
QQC2.Menu {
id: compressedListMenu
y: -implicitHeight
QQC2.MenuItem {
icon.name: "format-list-unordered"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Unordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.unorderedList
onClicked: {
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.UnorderedList);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
QQC2.MenuItem {
icon.name: "format-list-ordered"
enabled: chatButtonHelper.richFormatEnabled
text: i18nc("@action:button", "Ordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.chatButtonHelper.orderedlist
onClicked: {
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.OrderedList);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: indentAction
QQC2.MenuItem {
icon.name: "format-indent-more"
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListMore
text: i18nc("@action:button", "Increase List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
enabled: root.chatButtonHelper.canIndentListMore
onTriggered: {
root.chatButtonHelper.indentListMore();
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: dedentAction
QQC2.MenuItem {
icon.name: "format-indent-less"
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListLess
text: i18nc("@action:button", "Decrease List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
enabled: root.chatButtonHelper.canIndentListLess
onTriggered: {
root.chatButtonHelper.indentListLess();
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
id: compressedListButton
enabled: chatButtonHelper.richFormatEnabled
visible: root.maxAvailableWidth < root.uncompressedImplicitWidth
icon.name: "format-list-unordered"
text: i18nc("@action:button", "List Style")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: compressedListMenu.visible
onClicked: {
compressedListMenu.open()
}
QQC2.Menu {
id: compressedListMenu
y: -implicitHeight
QQC2.MenuItem {
icon.name: "format-list-unordered"
text: i18nc("@action:button", "Unordered List")
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.UnorderedList);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-list-ordered"
text: i18nc("@action:button", "Ordered List")
onTriggered: {
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.OrderedList);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-indent-more"
text: i18nc("@action:button", "Increase List Level")
enabled: root.chatButtonHelper.canIndentListMore
onTriggered: {
root.chatButtonHelper.indentListMore();
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-indent-less"
text: i18nc("@action:button", "Decrease List Level")
enabled: root.chatButtonHelper.canIndentListLess
onTriggered: {
root.chatButtonHelper.indentListLess();
root.clicked();
}
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: styleButton
icon.name: "typewriter"
text: i18nc("@action:button", "Text Style")
enabled: root.chatButtonHelper.styleFormatEnabled
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: styleMenu.visible
onClicked: {
if (styleMenu.visible) {
styleMenu.close();
return;
}
styleMenu.open()
}
StylePicker {
id: styleMenu
chatContentModel: root.contentModel
chatButtonHelper: root.chatButtonHelper
onClosed: root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Kirigami.Separator {
Layout.fillHeight: true
Layout.margins: 0
}
QQC2.ToolButton {
id: emojiButton
property bool isBusy: false
visible: !Kirigami.Settings.isMobile
icon.name: "smiley"
text: i18n("Emojis & Stickers")
display: QQC2.AbstractButton.IconOnly
checkable: true
onClicked: {
let dialog = emojiDialog.createObject(root).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: linkButton
icon.name: "insert-link-symbolic"
text: i18nc("@action:button", "Insert link")
display: QQC2.AbstractButton.IconOnly
onClicked: {
let dialog = linkDialog.createObject(QQC2.Overlay.overlay, {
linkText: root.chatButtonHelper.currentLinkText,
linkUrl: root.chatButtonHelper.currentLinkUrl
})
dialog.onAccepted.connect(() => {
root.chatButtonHelper.updateLink(dialog.linkUrl, dialog.linkText)
root.clicked();
});
dialog.open();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Kirigami.Separator {
Layout.fillHeight: true
Layout.margins: 0
}
RowLayout {
id: sendRow
visible: root.maxAvailableWidth > root.textFormatCompressedImplicitWidth
QQC2.ToolButton {
id: attachmentButton
property bool isBusy: root.room && root.room.hasFileUploading
visible: root.chatBarCache.attachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: QQC2.AbstractButton.IconOnly
onClicked: {
if (!root.contentModel.hasRichFormatting) {
fileDialog();
return;
}
let warningDialog = Qt.createComponent('org.kde.kirigami', 'PromptDialog').createObject(QQC2.Overlay.overlay, {
dialogType: Kirigami.PromptDialog.Warning,
title: i18n("Attach an image or file?"),
subtitle: i18n("Attachments can only have plain text captions, all rich formatting will be removed"),
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
});
warningDialog.onAccepted.connect(() => {
attachmentButton.fileDialog();
});
warningDialog.open();
}
function fileDialog(): void {
let dialog = (LibNeoChat.Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
dialog.open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: mapButton
icon.name: "globe"
property bool isBusy: false
text: i18n("Send a Location")
display: QQC2.AbstractButton.IconOnly
onClicked: {
locationChooser.createObject(QQC2.ApplicationWindow.overlay, {
room: root.room
}).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: pollButton
icon.name: "amarok_playcount"
property bool isBusy: false
text: i18nc("@action:button", "Create a Poll")
display: QQC2.AbstractButton.IconOnly
onClicked: {
newPollDialog.createObject(QQC2.Overlay.overlay, {
room: root.room
}).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
}
QQC2.ToolButton {
id: compressedSendButton
visible: root.maxAvailableWidth < root.textFormatCompressedImplicitWidth
icon.name: "overflow-menu"
text: i18nc("@action:button", "Send Other")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: compressedSendMenu.visible
onClicked: {
compressedSendMenu.open()
}
QQC2.Menu {
id: compressedSendMenu
y: -implicitHeight
QQC2.MenuItem {
visible: root.chatBarCache.attachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
onTriggered: {
let dialog = (LibNeoChat.Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
dialog.chosen.connect(path => root.chatBarCache.attachmentPath = path);
dialog.open();
}
}
QQC2.MenuItem {
icon.name: "globe"
text: i18n("Send a Location")
onTriggered: {
locationChooser.createObject(QQC2.ApplicationWindow.overlay, {
room: root.room
}).open();
}
}
QQC2.MenuItem {
icon.name: "amarok_playcount"
text: i18nc("@action:button", "Create a Poll")
onTriggered: {
newPollDialog.createObject(QQC2.Overlay.overlay, {
room: root.room
}).open();
}
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: sendButton
property bool isBusy: false
icon.name: "document-send"
text: i18n("Send message")
display: QQC2.AbstractButton.IconOnly
onClicked: root.contentModel.postMessage();
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: 5
shadow {
size: 15
yOffset: 3
color: Qt.rgba(0, 0, 0, 0.2)
}
border {
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
width: 1
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Component {
@@ -575,21 +391,6 @@ QQC2.ToolBar {
LinkDialog {}
}
Component {
id: attachDialog
AttachDialog {
anchors.centerIn: parent
}
}
Component {
id: openFileDialog
LibNeoChat.OpenFileDialog {
parentWindow: Window.window
currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
}
}
Component {
id: emojiDialog
EmojiDialog {

131
src/chatbar/SendBar.qml Normal file
View File

@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtCore
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
import org.kde.neochat.messagecontent as MessageContent
RowLayout {
id: root
/**
* @brief The current room that user is viewing.
*/
required property LibNeoChat.NeoChatRoom room
property LibNeoChat.ChatBarCache chatBarCache
required property MessageContent.ChatBarMessageContentModel contentModel
Kirigami.Separator {
Layout.fillHeight: true
}
QQC2.ToolButton {
id: attachmentButton
property bool isBusy: root.room && root.room.hasFileUploading
visible: root.chatBarCache.attachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: QQC2.AbstractButton.IconOnly
onClicked: {
if (!root.contentModel.hasRichFormatting) {
if (LibNeoChat.Clipboard.hasImage) {
attachDialog();
} else {
fileDialog();
}
return;
}
let warningDialog = Qt.createComponent('org.kde.kirigami', 'PromptDialog').createObject(QQC2.Overlay.overlay, {
dialogType: Kirigami.PromptDialog.Warning,
title: i18n("Attach an image or file?"),
subtitle: i18n("Attachments can only have plain text captions, all rich formatting will be removed"),
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
});
warningDialog.onAccepted.connect(() => {
if (LibNeoChat.Clipboard.hasImage) {
attachmentButton.attachDialog();
} else {
attachmentButton.fileDialog();
}
});
warningDialog.open();
}
function attachDialog(): void {
let dialog = Qt.createComponent('org.kde.neochat.chatbar', 'AttachDialog').createObject(QQC2.Overlay.overlay) as AttachDialog;
dialog.anchors.centerIn = QQC2.Overlay.overlay;
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
dialog.open();
}
function fileDialog(): void {
let dialog = Qt.createComponent('org.kde.neochat.libneochat', 'OpenFileDialog').createObject(QQC2.Overlay.overlay, {
parentWindow: Window.window,
currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
});
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
dialog.open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: mapButton
icon.name: "globe"
property bool isBusy: false
text: i18n("Send a Location")
display: QQC2.AbstractButton.IconOnly
onClicked: {
locationChooser.createObject(QQC2.ApplicationWindow.overlay, {
room: root.room
}).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: pollButton
icon.name: "amarok_playcount"
property bool isBusy: false
text: i18nc("@action:button", "Create a Poll")
display: QQC2.AbstractButton.IconOnly
onClicked: {
newPollDialog.createObject(QQC2.Overlay.overlay, {
room: root.room
}).open();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
QQC2.ToolButton {
id: sendButton
property bool isBusy: false
icon.name: "document-send"
text: i18n("Send message")
display: QQC2.AbstractButton.IconOnly
onClicked: root.contentModel.postMessage();
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
}

View File

@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
QQC2.AbstractButton {
id: root
required property int style
property bool open: false
property bool compressed: false
readonly property real uncompressedWidth: styleDelegate.implicitWidth + arrowIcon.implicitWidth + 1 + contentRow.spacing * 2 + padding * 2
padding: Kirigami.Units.smallSpacing
icon {
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
}
contentItem: RowLayout {
id: contentRow
StyleDelegate {
id: styleDelegate
Layout.fillWidth: true
visible: !root.compressed
style: root.style
sizeText: false
onPressed: root.clicked()
}
Kirigami.Icon {
id: styleIcon
visible: root.compressed
source: root.icon.name
implicitWidth: root.icon.width
implicitHeight: root.icon.height
}
Kirigami.Separator {
Layout.fillHeight: true
}
Kirigami.Icon {
id: arrowIcon
source: root.open ? "arrow-down" : "arrow-up"
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: Kirigami.Units.iconSizes.small
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
radius: Kirigami.Units.cornerRadius
border {
width: root.hovered || root.open ? 1 : 0
color: Kirigami.Theme.highlightColor
}
}
}

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
QQC2.TextArea {
id: root
required property int style
property bool highlight: false
property bool sizeText: true
leftPadding: lineRow.visible ? lineRow.width + lineRow.anchors.leftMargin + Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
verticalAlignment: Text.AlignVCenter
readOnly: true
selectByMouse: false
RowLayout {
id: lineRow
anchors {
top: root.top
bottom: root.bottom
left: root.left
leftMargin: Kirigami.Units.smallSpacing
}
visible: root.style === LibNeoChat.RichFormat.Code
QQC2.Label {
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: "1"
color: Kirigami.Theme.disabledTextColor
font.family: "monospace"
}
Kirigami.Separator {
Layout.fillHeight: true
}
}
StyleDelegateHelper {
textItem: root
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: root.style === LibNeoChat.RichFormat.Quote ? Kirigami.Theme.Window : Kirigami.Theme.View
Kirigami.Theme.inherit: false
radius: Kirigami.Units.cornerRadius
border {
width: 1
color: root.highlight ?
Kirigami.Theme.highlightColor :
Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor,
Kirigami.Theme.textColor,
Kirigami.Theme.frameContrast
)
}
}
}

View File

@@ -19,6 +19,7 @@ QQC2.Popup {
required property ChatButtonHelper chatButtonHelper
y: -implicitHeight
padding: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
@@ -26,24 +27,21 @@ QQC2.Popup {
Repeater {
model: 9
delegate: QQC2.TextArea {
delegate: StyleDelegate {
id: styleDelegate
required property int index
Layout.fillWidth: true
Layout.minimumWidth: Kirigami.Units.gridUnit * 7
Layout.minimumWidth: Kirigami.Units.gridUnit * 8
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
leftPadding: lineRow.visible ? lineRow.width + lineRow.anchors.leftMargin + Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
verticalAlignment: Text.AlignVCenter
enabled: root.chatContentModel.focusType !== LibNeoChat.MessageComponentType.Code || styleDelegate.index === LibNeoChat.RichFormat.Paragraph || styleDelegate.index === LibNeoChat.RichFormat.Quote
readOnly: true
selectByMouse: false
style: index
highlight: root.chatButtonHelper.currentStyle === index || hovered
onPressed: (event) => {
if (styleDelegate.index === LibNeoChat.RichFormat.Paragraph ||
styleDelegate.index === LibNeoChat.RichFormat.Code ||
styleDelegate.index === LibNeoChat.RichFormat.Quote
if (index === LibNeoChat.RichFormat.Paragraph ||
index === LibNeoChat.RichFormat.Code ||
index === LibNeoChat.RichFormat.Quote
) {
root.chatContentModel.insertStyleAtCursor(styleDelegate.index);
} else {
@@ -51,71 +49,19 @@ QQC2.Popup {
}
root.close();
}
RowLayout {
id: lineRow
anchors {
top: styleDelegate.top
bottom: styleDelegate.bottom
left: styleDelegate.left
leftMargin: Kirigami.Units.smallSpacing
}
visible: styleDelegate.index === LibNeoChat.RichFormat.Code
QQC2.Label {
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: "1"
color: Kirigami.Theme.disabledTextColor
font.family: "monospace"
}
Kirigami.Separator {
Layout.fillHeight: true
}
}
StyleDelegateHelper {
textItem: styleDelegate
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: styleDelegate.index === LibNeoChat.RichFormat.Quote ? Kirigami.Theme.Window : Kirigami.Theme.View
Kirigami.Theme.inherit: false
radius: Kirigami.Units.cornerRadius
border {
width: 1
color: styleDelegate.hovered || (root.chatButtonHelper.currentStyle === styleDelegate.index) ?
Kirigami.Theme.highlightColor :
Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor,
Kirigami.Theme.textColor,
Kirigami.Theme.frameContrast
)
}
}
}
}
}
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border {
width: 1
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
}
shadow {
size: Kirigami.Units.gridUnit
yOffset: 0
color: Qt.rgba(0, 0, 0, 0.2)
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
}
}

View File

@@ -28,6 +28,8 @@ void StyleDelegateHelper::setTextItem(QQuickItem *textItem)
m_textItem = textItem;
if (m_textItem) {
connect(m_textItem, SIGNAL(styleChanged()), this, SLOT(formatDocument()));
connect(m_textItem, SIGNAL(styleChanged()), this, SLOT(formatDocument()));
if (document()) {
formatDocument();
}
@@ -59,10 +61,11 @@ void StyleDelegateHelper::formatDocument()
cursor.beginEditBlock();
cursor.select(QTextCursor::Document);
cursor.removeSelectedText();
const auto style = static_cast<RichFormat::Format>(m_textItem->property("index").toInt());
const auto style = static_cast<RichFormat::Format>(m_textItem->property("style").toInt());
const auto string = RichFormat::styleString(style);
const int headingLevel = style <= 6 ? style : 0;
const auto sizeText = static_cast<RichFormat::Format>(m_textItem->property("sizeText").toBool());
const int headingLevel = style <= 6 && sizeText ? style : 0;
// Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
// level=2 look the same
const int sizeAdjustment = headingLevel > 0 ? 5 - headingLevel : 0;

View File

@@ -32,5 +32,6 @@ private:
QPointer<QQuickItem> m_textItem;
QTextDocument *document() const;
private Q_SLOTS:
void formatDocument();
};