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:
@@ -361,7 +361,6 @@ Kirigami.Page {
|
||||
id: chatBar
|
||||
width: parent.width
|
||||
currentRoom: root.currentRoom
|
||||
connection: root.currentRoom.connection as NeoChatConnection
|
||||
|
||||
// Creating a reply (or doing anything in the chat bar) can change the height, but this isn't picked up on the root's onHeightChanged.
|
||||
onHeightChanged: root.resetViewSettling()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
131
src/chatbar/SendBar.qml
Normal 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
|
||||
}
|
||||
}
|
||||
76
src/chatbar/StyleButton.qml
Normal file
76
src/chatbar/StyleButton.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/chatbar/StyleDelegate.qml
Normal file
73
src/chatbar/StyleDelegate.qml
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,5 +32,6 @@ private:
|
||||
QPointer<QQuickItem> m_textItem;
|
||||
QTextDocument *document() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void formatDocument();
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "chattextitemhelper.h"
|
||||
#include "clipboard.h"
|
||||
#include "neochatroom.h"
|
||||
#include <qnamespace.h>
|
||||
|
||||
ChatKeyHelper::ChatKeyHelper(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -29,7 +30,10 @@ bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
|
||||
return backspace();
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
return insertReturn();
|
||||
return insertReturn(modifiers);
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Cancel:
|
||||
return cancel();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -155,16 +159,28 @@ bool ChatKeyHelper::backspace()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatKeyHelper::insertReturn()
|
||||
bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
if (!textItem) {
|
||||
return false;
|
||||
}
|
||||
if (textItem->isCompleting) {
|
||||
|
||||
bool shiftPressed = modifiers.testFlag(Qt::ShiftModifier);
|
||||
if (shiftPressed && !sendMessageWithEnter) {
|
||||
Q_EMIT unhandledReturn(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!shiftPressed && textItem->isCompleting) {
|
||||
Q_EMIT unhandledReturn(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!shiftPressed && sendMessageWithEnter) {
|
||||
Q_EMIT unhandledReturn(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
QTextCursor cursor = textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
@@ -173,6 +189,18 @@ bool ChatKeyHelper::insertReturn()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatKeyHelper::cancel()
|
||||
{
|
||||
if (!textItem) {
|
||||
return false;
|
||||
}
|
||||
if (textItem->isCompleting) {
|
||||
Q_EMIT closeCompletion();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatKeyHelper::pasteImage()
|
||||
{
|
||||
if (!textItem) {
|
||||
|
||||
@@ -45,6 +45,15 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE bool handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers);
|
||||
|
||||
/**
|
||||
* @brief Whether the enter/return should send message.
|
||||
*
|
||||
* If false, return/enter adds a new line.
|
||||
*
|
||||
* shift + return/enter does the opposite to return/enter.
|
||||
*/
|
||||
bool sendMessageWithEnter = true;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* @brief There is an unhandled up key press.
|
||||
@@ -98,6 +107,14 @@ Q_SIGNALS:
|
||||
*/
|
||||
void unhandledReturn(bool isCompleting);
|
||||
|
||||
/**
|
||||
* @brief The completion dialog should be closed if open.
|
||||
*
|
||||
* Current trigger conditions:
|
||||
* - Return clicked when a completion has been started.
|
||||
*/
|
||||
void closeCompletion();
|
||||
|
||||
/**
|
||||
* @brief An image has been pasted.
|
||||
*/
|
||||
@@ -116,7 +133,9 @@ private:
|
||||
|
||||
bool backspace();
|
||||
|
||||
bool insertReturn();
|
||||
bool insertReturn(Qt::KeyboardModifiers modifiers);
|
||||
|
||||
bool cancel();
|
||||
|
||||
bool pasteImage();
|
||||
};
|
||||
|
||||
@@ -342,6 +342,14 @@ std::optional<int> ChatTextItemHelper::cursorPosition() const
|
||||
return m_textItem->property("cursorPosition").toInt();
|
||||
}
|
||||
|
||||
QRect ChatTextItemHelper::cursorRectangle() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return {};
|
||||
}
|
||||
return m_textItem->property("cursorRectangle").toRect();
|
||||
}
|
||||
|
||||
int ChatTextItemHelper::selectionStart() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
|
||||
@@ -172,6 +172,11 @@ public:
|
||||
*/
|
||||
std::optional<int> cursorPosition() const;
|
||||
|
||||
/**
|
||||
* @brief Return the rectangle where the cursor of the underlying text item is rendered.
|
||||
*/
|
||||
QRect cursorRectangle() const;
|
||||
|
||||
/**
|
||||
* @brief Set the cursor position of the underlying text item to the given value.
|
||||
*/
|
||||
|
||||
@@ -7,27 +7,29 @@
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
QString RichFormat::styleString(Format format)
|
||||
{
|
||||
switch (format) {
|
||||
case Paragraph:
|
||||
return u"Paragraph"_s;
|
||||
return i18nc("As in the default paragraph text style in the chat bar", "Paragraph Style");
|
||||
case Heading1:
|
||||
return u"Heading 1"_s;
|
||||
return i18nc("As in heading level 1 text style in the chat bar", "Heading 1");
|
||||
case Heading2:
|
||||
return u"Heading 2"_s;
|
||||
return i18nc("As in heading level 2 text style in the chat bar", "Heading 2");
|
||||
case Heading3:
|
||||
return u"Heading 3"_s;
|
||||
return i18nc("As in heading level 3 text style in the chat bar", "Heading 3");
|
||||
case Heading4:
|
||||
return u"Heading 4"_s;
|
||||
return i18nc("As in heading level 4 text style in the chat bar", "Heading 4");
|
||||
case Heading5:
|
||||
return u"Heading 5"_s;
|
||||
return i18nc("As in heading level 5 text style in the chat bar", "Heading 5");
|
||||
case Heading6:
|
||||
return u"Heading 6"_s;
|
||||
return i18nc("As in heading level 6 text style in the chat bar", "Heading 6");
|
||||
case Code:
|
||||
return u"Code"_s;
|
||||
return i18nc("As in code text style in the chat bar", "Code");
|
||||
case Quote:
|
||||
return u"\"Quote\""_s;
|
||||
return i18nc("As in quote text style in the chat bar", "\"Quote\"");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -77,6 +77,21 @@ void CompletionModel::setTextItem(ChatTextItemHelper *textItem)
|
||||
Q_EMIT textItemChanged();
|
||||
}
|
||||
|
||||
bool CompletionModel::isCompleting() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return m_textItem->isCompleting;
|
||||
}
|
||||
|
||||
void CompletionModel::ignoreCurrentCompletion()
|
||||
{
|
||||
m_ignoreCurrentCompletion = true;
|
||||
m_textItem->isCompleting = false;
|
||||
Q_EMIT isCompletingChanged();
|
||||
}
|
||||
|
||||
void CompletionModel::updateTextStart()
|
||||
{
|
||||
auto cursor = m_textItem->textCursor();
|
||||
@@ -193,6 +208,15 @@ void CompletionModel::updateCompletion()
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ignoreCurrentCompletion) {
|
||||
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
|
||||
if (cursor.selectedText() == u' ') {
|
||||
m_ignoreCurrentCompletion = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cursor.setPosition(m_textStart);
|
||||
while (!cursor.selectedText().endsWith(u' ') && !cursor.atBlockEnd()) {
|
||||
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
|
||||
@@ -242,6 +266,7 @@ void CompletionModel::updateCompletion()
|
||||
endResetModel();
|
||||
|
||||
m_textItem->isCompleting = rowCount() > 0;
|
||||
Q_EMIT isCompletingChanged();
|
||||
}
|
||||
|
||||
CompletionModel::AutoCompletionType CompletionModel::autoCompletionType() const
|
||||
|
||||
@@ -62,6 +62,11 @@ class CompletionModel : public QAbstractListModel
|
||||
*/
|
||||
Q_PROPERTY(UserListModel *userListModel READ userListModel WRITE setUserListModel NOTIFY userListModelChanged)
|
||||
|
||||
/**
|
||||
* @brief The UserListModel to be used for room completions.
|
||||
*/
|
||||
Q_PROPERTY(bool isCompleting READ isCompleting NOTIFY isCompletingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the different types of completion available.
|
||||
@@ -98,6 +103,10 @@ public:
|
||||
ChatTextItemHelper *textItem() const;
|
||||
void setTextItem(ChatTextItemHelper *textItem);
|
||||
|
||||
bool isCompleting() const;
|
||||
|
||||
Q_INVOKABLE void ignoreCurrentCompletion();
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
@@ -137,12 +146,14 @@ Q_SIGNALS:
|
||||
void autoCompletionTypeChanged();
|
||||
void roomListModelChanged();
|
||||
void userListModelChanged();
|
||||
void isCompletingChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
ChatBarType::Type m_type = ChatBarType::None;
|
||||
QPointer<ChatTextItemHelper> m_textItem;
|
||||
|
||||
bool m_ignoreCurrentCompletion = false;
|
||||
int m_textStart = 0;
|
||||
void updateTextStart();
|
||||
|
||||
|
||||
@@ -110,14 +110,10 @@ QQC2.Control {
|
||||
height: implicitHeight
|
||||
y: -height - 5
|
||||
z: 10
|
||||
<<<<<<< HEAD
|
||||
|
||||
chatDocumentHandler: documentHandler
|
||||
=======
|
||||
room: root.Message.room
|
||||
type: root.chatBarCache.isEditing ? ChatBarType.Edit : ChatBarType.Thread
|
||||
// textItem: textArea
|
||||
>>>>>>> c7858a151 (Move the remaining functionality of ChatDocumentHandler to ChatTextItemHelper or split into own objects)
|
||||
margins: 0
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
|
||||
@@ -60,21 +60,6 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: cancelButton
|
||||
|
||||
anchors.top: root.top
|
||||
anchors.right: root.right
|
||||
|
||||
visible: root.editable
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: root.Message.room.mainCache.replyId = ""
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
@@ -86,5 +71,22 @@ RowLayout {
|
||||
id: _private
|
||||
// The space available for the component after taking away the border
|
||||
readonly property real availableContentWidth: root.Message.maxContentWidth - verticalBorder.implicitWidth - root.spacing
|
||||
|
||||
readonly property QQC2.Button cancelButton: QQC2.Button {
|
||||
id: cancelButton
|
||||
|
||||
parent: root
|
||||
anchors.top: root.top
|
||||
anchors.right: root.right
|
||||
|
||||
visible: root.editable
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: root.Message.room.mainCache.replyId = ""
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,11 @@ void ChatBarMessageContentModel::connectKeyHelper()
|
||||
insertComponentAtCursor(MessageComponentType::Text);
|
||||
}
|
||||
});
|
||||
connect(m_keyHelper, &ChatKeyHelper::unhandledReturn, this, [this](bool isCompleting) {
|
||||
if (!isCompleting) {
|
||||
postMessage();
|
||||
}
|
||||
});
|
||||
connect(m_keyHelper, &ChatKeyHelper::imagePasted, this, [this](const QString &filePath) {
|
||||
m_room->cacheForType(m_type)->setAttachmentPath(filePath);
|
||||
});
|
||||
@@ -448,6 +453,21 @@ void ChatBarMessageContentModel::removeAttachment()
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatBarMessageContentModel::sendMessageWithEnter() const
|
||||
{
|
||||
return m_sendMessageWithEnter;
|
||||
}
|
||||
|
||||
void ChatBarMessageContentModel::setSendMessageWithEnter(bool sendMessageWithEnter)
|
||||
{
|
||||
if (sendMessageWithEnter == m_sendMessageWithEnter) {
|
||||
return;
|
||||
}
|
||||
m_sendMessageWithEnter = sendMessageWithEnter;
|
||||
m_keyHelper->sendMessageWithEnter = sendMessageWithEnter;
|
||||
Q_EMIT sendMessageWithEnterChanged();
|
||||
}
|
||||
|
||||
ChatBarMessageContentModel::ComponentIt ChatBarMessageContentModel::removeComponent(ComponentIt it)
|
||||
{
|
||||
if (it == m_components.end()) {
|
||||
|
||||
@@ -66,6 +66,11 @@ class ChatBarMessageContentModel : public MessageContentModel
|
||||
*/
|
||||
Q_PROPERTY(bool hasRichFormatting READ hasRichFormatting NOTIFY hasRichFormattingChanged)
|
||||
|
||||
/**
|
||||
* @brief The UserListModel to be used for room completions.
|
||||
*/
|
||||
Q_PROPERTY(bool sendMessageWithEnter READ sendMessageWithEnter WRITE setSendMessageWithEnter NOTIFY sendMessageWithEnterChanged)
|
||||
|
||||
public:
|
||||
explicit ChatBarMessageContentModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -89,12 +94,16 @@ public:
|
||||
|
||||
Q_INVOKABLE void removeAttachment();
|
||||
|
||||
bool sendMessageWithEnter() const;
|
||||
void setSendMessageWithEnter(bool sendMessageWithEnter);
|
||||
|
||||
Q_INVOKABLE void postMessage();
|
||||
|
||||
Q_SIGNALS:
|
||||
void typeChanged();
|
||||
void focusRowChanged();
|
||||
void hasRichFormattingChanged();
|
||||
void sendMessageWithEnterChanged();
|
||||
|
||||
private:
|
||||
ChatBarType::Type m_type = ChatBarType::None;
|
||||
@@ -125,5 +134,7 @@ private:
|
||||
void updateCache() const;
|
||||
QString messageText() const;
|
||||
|
||||
bool m_sendMessageWithEnter = true;
|
||||
|
||||
void clearModel();
|
||||
};
|
||||
|
||||
@@ -264,32 +264,6 @@ FormCard.FormCardPage {
|
||||
title: i18nc("Chat Editor", "Editor")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18nc("@option:radio", "Send messages with Enter")
|
||||
checked: NeoChatConfig.sendMessageWith === 0
|
||||
visible: !Kirigami.Settings.isMobile
|
||||
enabled: !NeoChatConfig.isSendMessageWithImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.sendMessageWith = 0
|
||||
NeoChatConfig.save()
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
id: sendWithEnterRadio
|
||||
text: i18nc("@option:radio", "Send messages with Ctrl+Enter")
|
||||
checked: NeoChatConfig.sendMessageWith === 1
|
||||
visible: !Kirigami.Settings.isMobile
|
||||
enabled: !NeoChatConfig.isSendMessageWithImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.sendMessageWith = 1
|
||||
NeoChatConfig.save()
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
visible: !Kirigami.Settings.isMobile
|
||||
above: sendWithEnterRadio
|
||||
below: quickEditCheckbox
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: quickEditCheckbox
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
|
||||
@@ -335,9 +335,13 @@ QQC2.ScrollView {
|
||||
RowLayout {
|
||||
id: typingPaneContainer
|
||||
visible: _private.room && _private.room.otherMembersTyping.length > 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
height: visible ? typingPane.implicitHeight : 0
|
||||
z: 2
|
||||
Behavior on height {
|
||||
|
||||
@@ -16,28 +16,19 @@ Loader {
|
||||
property string labelText: ""
|
||||
|
||||
active: visible
|
||||
sourceComponent: QQC2.Pane {
|
||||
sourceComponent: QQC2.Control {
|
||||
id: typingPane
|
||||
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
}
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: typingPane.spacing
|
||||
Row {
|
||||
id: dotRow
|
||||
property int duration: 400
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Repeater {
|
||||
model: 3
|
||||
delegate: Rectangle {
|
||||
@@ -113,14 +104,16 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
leftInset: !mirrored ? 0 : -(background as Rectangle).radius
|
||||
rightInset: mirrored ? 0 : -(background as Rectangle).radius
|
||||
bottomInset: -(background as Rectangle).radius
|
||||
background: Rectangle {
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
||||
border.width: 1
|
||||
border {
|
||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
||||
width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user