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
|
id: chatBar
|
||||||
width: parent.width
|
width: parent.width
|
||||||
currentRoom: root.currentRoom
|
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.
|
// 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()
|
onHeightChanged: root.resetViewSettling()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
|
|||||||
AttachDialog.qml
|
AttachDialog.qml
|
||||||
ChatBar.qml
|
ChatBar.qml
|
||||||
RichEditBar.qml
|
RichEditBar.qml
|
||||||
|
SendBar.qml
|
||||||
CompletionMenu.qml
|
CompletionMenu.qml
|
||||||
EmojiDelegate.qml
|
EmojiDelegate.qml
|
||||||
EmojiGrid.qml
|
EmojiGrid.qml
|
||||||
@@ -17,6 +18,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
|
|||||||
EmojiDialog.qml
|
EmojiDialog.qml
|
||||||
EmojiTonesPicker.qml
|
EmojiTonesPicker.qml
|
||||||
StylePicker.qml
|
StylePicker.qml
|
||||||
|
StyleDelegate.qml
|
||||||
ImageEditorPage.qml
|
ImageEditorPage.qml
|
||||||
VoiceMessageDialog.qml
|
VoiceMessageDialog.qml
|
||||||
ImageDialog.qml
|
ImageDialog.qml
|
||||||
@@ -24,6 +26,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
|
|||||||
LocationChooser.qml
|
LocationChooser.qml
|
||||||
NewPollDialog.qml
|
NewPollDialog.qml
|
||||||
TableDialog.qml
|
TableDialog.qml
|
||||||
|
StyleButton.qml
|
||||||
SOURCES
|
SOURCES
|
||||||
chatbuttonhelper.cpp
|
chatbuttonhelper.cpp
|
||||||
styledelegatehelper.cpp
|
styledelegatehelper.cpp
|
||||||
|
|||||||
@@ -25,20 +25,14 @@ import org.kde.neochat.libneochat as LibNeoChat
|
|||||||
*
|
*
|
||||||
* @sa ChatBar
|
* @sa ChatBar
|
||||||
*/
|
*/
|
||||||
QQC2.Control {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The current room that user is viewing.
|
* @brief The current room that user is viewing.
|
||||||
*/
|
*/
|
||||||
required property NeoChatRoom currentRoom
|
required property LibNeoChat.NeoChatRoom currentRoom
|
||||||
|
|
||||||
required property NeoChatConnection connection
|
|
||||||
|
|
||||||
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
|
|
||||||
|
|
||||||
onCurrentRoomChanged: {
|
onCurrentRoomChanged: {
|
||||||
_private.chatBarCache = currentRoom.mainCache
|
|
||||||
if (ShareHandler.text.length > 0 && ShareHandler.room === root.currentRoom.id) {
|
if (ShareHandler.text.length > 0 && ShareHandler.room === root.currentRoom.id) {
|
||||||
contentModel.focusedTextItem.
|
contentModel.focusedTextItem.
|
||||||
textField.text = ShareHandler.text;
|
textField.text = ShareHandler.text;
|
||||||
@@ -47,37 +41,7 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ShareHandler
|
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.room: root.currentRoom
|
||||||
Message.contentModel: contentModel
|
Message.contentModel: contentModel
|
||||||
|
|
||||||
background: Rectangle {
|
implicitHeight: chatBar.implicitHeight + Kirigami.Units.largeSpacing
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
Kirigami.Separator {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
height: Math.max(Math.min(chatScrollView.contentHeight + bottomPadding + topPadding, Kirigami.Units.gridUnit * 10), Kirigami.Units.gridUnit * 5)
|
QQC2.Control {
|
||||||
leftPadding: rightPadding
|
id: chatBar
|
||||||
rightPadding: (root.width - chatBarSizeHelper.availableWidth) / 2 + Kirigami.Units.largeSpacing
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
anchors.top: root.top
|
||||||
QQC2.ScrollView {
|
anchors.horizontalCenter: root.horizontalCenter
|
||||||
id: chatScrollView
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
|
|
||||||
|
|
||||||
clip: true
|
spacing: 0
|
||||||
|
|
||||||
ColumnLayout {
|
width: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
|
||||||
width: chatScrollView.width
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
spacing: Kirigami.Units.smallSpacing
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
Repeater {
|
contentItem: ColumnLayout {
|
||||||
id: chatContentView
|
RichEditBar {
|
||||||
model: ChatBarMessageContentModel {
|
id: richEditBar
|
||||||
id: contentModel
|
visible: NeoChatConfig.sendMessageWith === 1
|
||||||
type: ChatBarType.Room
|
maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
|
||||||
room: root.currentRoom
|
|
||||||
|
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
|
background: Kirigami.ShadowedRectangle {
|
||||||
contentModel: chatContentView.model
|
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 {
|
LibNeoChat.DelegateSizeHelper {
|
||||||
id: chatBarSizeHelper
|
id: chatBarSizeHelper
|
||||||
@@ -171,35 +182,23 @@ QQC2.Control {
|
|||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: _private
|
id: _private
|
||||||
property ChatBarCache chatBarCache
|
|
||||||
onChatBarCacheChanged: {
|
|
||||||
richEditBar.chatBarCache = chatBarCache
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteImage() {
|
property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel {
|
||||||
let localPath = Clipboard.saveImage();
|
room: root.currentRoom
|
||||||
if (localPath.length === 0) {
|
type: LibNeoChat.ChatBarType.Room
|
||||||
return false;
|
textItem: contentModel.focusedTextItem
|
||||||
}
|
roomListModel: RoomManager.roomListModel
|
||||||
_private.chatBarCache.attachmentPath = localPath;
|
userListModel: RoomManager.userListModel
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletionMenu {
|
onIsCompletingChanged: {
|
||||||
id: completionMenu
|
if (!isCompleting) {
|
||||||
room: root.currentRoom
|
return;
|
||||||
type: LibNeoChat.ChatBarType.Room
|
}
|
||||||
textItem: contentModel.focusedTextItem
|
|
||||||
|
|
||||||
x: 1
|
let dialog = Qt.createComponent('org.kde.neochat.chatbar', 'CompletionMenu').createObject(contentModel.focusedTextItem.textItem, {
|
||||||
y: -height
|
model: _private.completionModel,
|
||||||
width: parent.width - 1
|
keyHelper: contentModel.keyHelper
|
||||||
Behavior on height {
|
}).open();
|
||||||
NumberAnimation {
|
|
||||||
property: "height"
|
|
||||||
duration: Kirigami.Units.shortDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,33 +11,55 @@ import org.kde.kirigami as Kirigami
|
|||||||
import org.kde.kirigamiaddons.delegates as Delegates
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||||
|
|
||||||
import org.kde.neochat
|
|
||||||
import org.kde.neochat.libneochat as LibNeoChat
|
import org.kde.neochat.libneochat as LibNeoChat
|
||||||
|
|
||||||
QQC2.Popup {
|
QQC2.Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
property alias model: completions.model
|
||||||
* @brief The current room that user is viewing.
|
|
||||||
*/
|
|
||||||
required property LibNeoChat.NeoChatRoom room
|
|
||||||
|
|
||||||
/**
|
required property LibNeoChat.ChatKeyHelper keyHelper
|
||||||
* @brief The chatbar type
|
|
||||||
*/
|
|
||||||
required property int type
|
|
||||||
|
|
||||||
/**
|
Connections {
|
||||||
* @brief The chatbar type
|
target: keyHelper
|
||||||
*/
|
|
||||||
required property LibNeoChat.ChatTextItemHelper textItem
|
|
||||||
|
|
||||||
visible: completions.count > 0
|
function onUnhandledUp(isCompleting: bool): void {
|
||||||
|
if (!isCompleting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.decrementIndex();
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: if (visible) {
|
function onUnhandledDown(isCompleting: bool): void {
|
||||||
root.open();
|
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() {
|
function incrementIndex() {
|
||||||
completions.incrementCurrentIndex();
|
completions.incrementCurrentIndex();
|
||||||
}
|
}
|
||||||
@@ -47,11 +69,11 @@ QQC2.Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function complete(text: string, hRef: string) {
|
function complete(text: string, hRef: string) {
|
||||||
completionModel.insertCompletion(text, hRef);
|
model.insertCompletion(text, hRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
function completeCurrent() {
|
function completeCurrent() {
|
||||||
completionModel.insertCompletion(completions.currentItem.replacedText, completions.currentItem.hRef);
|
model.insertCompletion(completions.currentItem.replacedText, completions.currentItem.hRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
@@ -61,70 +83,57 @@ QQC2.Popup {
|
|||||||
|
|
||||||
implicitHeight: Math.min(completions.contentHeight, Kirigami.Units.gridUnit * 10)
|
implicitHeight: Math.min(completions.contentHeight, Kirigami.Units.gridUnit * 10)
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: QQC2.ScrollView {
|
||||||
spacing: 0
|
contentWidth: Kirigami.Units.gridUnit * 20
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
QQC2.ScrollView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: contentHeight
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 10
|
|
||||||
|
|
||||||
background: Rectangle {
|
ListView {
|
||||||
color: Kirigami.Theme.backgroundColor
|
id: completions
|
||||||
}
|
currentIndex: 0
|
||||||
|
keyNavigationWraps: true
|
||||||
|
highlightMoveDuration: 100
|
||||||
|
onCountChanged: currentIndex = 0
|
||||||
|
delegate: Delegates.RoundedItemDelegate {
|
||||||
|
id: completionDelegate
|
||||||
|
|
||||||
ListView {
|
required property int index
|
||||||
id: completions
|
required property string displayName
|
||||||
|
required property string subtitle
|
||||||
|
required property string iconName
|
||||||
|
required property string replacedText
|
||||||
|
required property url hRef
|
||||||
|
|
||||||
model: LibNeoChat.CompletionModel {
|
text: displayName
|
||||||
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
|
|
||||||
|
|
||||||
required property int index
|
contentItem: RowLayout {
|
||||||
required property string displayName
|
KirigamiComponents.Avatar {
|
||||||
required property string subtitle
|
visible: completionDelegate.iconName !== "invalid"
|
||||||
required property string iconName
|
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||||
required property string replacedText
|
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||||
required property url hRef
|
source: completionDelegate.iconName === "invalid" ? "" : completionDelegate.iconName
|
||||||
|
name: completionDelegate.text
|
||||||
text: displayName
|
}
|
||||||
|
Delegates.SubtitleContentItem {
|
||||||
contentItem: RowLayout {
|
itemDelegate: completionDelegate
|
||||||
KirigamiComponents.Avatar {
|
labelItem.textFormat: Text.PlainText
|
||||||
visible: completionDelegate.iconName !== "invalid"
|
labelItem.clip: true // Intentional to limit insane Unicode in display names
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
subtitle: completionDelegate.subtitle ?? ""
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
subtitleItem.textFormat: Text.PlainText
|
||||||
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
|
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.libneochat as LibNeoChat
|
||||||
import org.kde.neochat.messagecontent as MessageContent
|
import org.kde.neochat.messagecontent as MessageContent
|
||||||
|
|
||||||
QQC2.ToolBar {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,41 +19,39 @@ QQC2.ToolBar {
|
|||||||
*/
|
*/
|
||||||
required property LibNeoChat.NeoChatRoom room
|
required property LibNeoChat.NeoChatRoom room
|
||||||
|
|
||||||
property LibNeoChat.ChatBarCache chatBarCache
|
|
||||||
|
|
||||||
required property MessageContent.ChatBarMessageContentModel contentModel
|
required property MessageContent.ChatBarMessageContentModel contentModel
|
||||||
|
|
||||||
required property real maxAvailableWidth
|
required property real maxAvailableWidth
|
||||||
|
|
||||||
readonly property real uncompressedImplicitWidth: textFormatRow.implicitWidth +
|
readonly property real uncompressedImplicitWidth: boldButton.implicitWidth +
|
||||||
|
italicButton.implicitWidth +
|
||||||
|
extraTextFormatRow.implicitWidth +
|
||||||
listRow.implicitWidth +
|
listRow.implicitWidth +
|
||||||
styleButton.implicitWidth +
|
styleButton.implicitWidth +
|
||||||
emojiButton.implicitWidth +
|
emojiButton.implicitWidth +
|
||||||
linkButton.implicitWidth +
|
linkButton.implicitWidth +
|
||||||
sendRow.implicitWidth +
|
root.spacing * 7 +
|
||||||
sendButton.implicitWidth +
|
Kirigami.Units.gridUnit
|
||||||
buttonRow.spacing * 9 +
|
|
||||||
3
|
|
||||||
|
|
||||||
readonly property real listCompressedImplicitWidth: textFormatRow.implicitWidth +
|
readonly property real listCompressedImplicitWidth: boldButton.implicitWidth +
|
||||||
|
italicButton.implicitWidth +
|
||||||
|
extraTextFormatRow.implicitWidth +
|
||||||
compressedListButton.implicitWidth +
|
compressedListButton.implicitWidth +
|
||||||
styleButton.implicitWidth +
|
styleButton.uncompressedWidth +
|
||||||
emojiButton.implicitWidth +
|
emojiButton.implicitWidth +
|
||||||
linkButton.implicitWidth +
|
linkButton.implicitWidth +
|
||||||
sendRow.implicitWidth +
|
root.spacing * 7 +
|
||||||
sendButton.implicitWidth +
|
Kirigami.Units.gridUnit
|
||||||
buttonRow.spacing * 9 +
|
|
||||||
3
|
|
||||||
|
|
||||||
readonly property real textFormatCompressedImplicitWidth: compressedTextFormatButton.implicitWidth +
|
readonly property real extraTextCompressedImplicitWidth: boldButton.implicitWidth +
|
||||||
compressedListButton.implicitWidth +
|
italicButton.implicitWidth +
|
||||||
styleButton.implicitWidth +
|
compressedExtraTextFormatButton.implicitWidth +
|
||||||
emojiButton.implicitWidth +
|
compressedListButton.implicitWidth +
|
||||||
linkButton.implicitWidth +
|
styleButton.uncompressedWidth +
|
||||||
sendRow.implicitWidth +
|
emojiButton.implicitWidth +
|
||||||
sendButton.implicitWidth +
|
linkButton.implicitWidth +
|
||||||
buttonRow.spacing * 9 +
|
root.spacing * 7 +
|
||||||
3
|
Kirigami.Units.gridUnit
|
||||||
|
|
||||||
readonly property ChatButtonHelper chatButtonHelper: ChatButtonHelper {
|
readonly property ChatButtonHelper chatButtonHelper: ChatButtonHelper {
|
||||||
textItem: contentModel.focusedTextItem
|
textItem: contentModel.focusedTextItem
|
||||||
@@ -61,128 +59,111 @@ QQC2.ToolBar {
|
|||||||
|
|
||||||
signal clicked
|
signal clicked
|
||||||
|
|
||||||
RowLayout {
|
QQC2.ToolButton {
|
||||||
id: buttonRow
|
id: boldButton
|
||||||
RowLayout {
|
Shortcut {
|
||||||
id: textFormatRow
|
sequence: "Ctrl+B"
|
||||||
visible: root.maxAvailableWidth > root.listCompressedImplicitWidth
|
onActivated: boldButton.clicked()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
QQC2.ToolButton {
|
||||||
id: compressedTextFormatButton
|
id: underlineButton
|
||||||
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
|
Shortcut {
|
||||||
icon.name: "dialog-text-and-font"
|
sequence: "Ctrl+U"
|
||||||
|
onActivated: underlineButton.clicked()
|
||||||
|
}
|
||||||
|
icon.name: "format-text-underline"
|
||||||
enabled: chatButtonHelper.richFormatEnabled
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Format Text")
|
text: i18nc("@action:button", "Underline")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: compressedTextFormatMenu.visible
|
checked: root.chatButtonHelper.underline
|
||||||
onClicked: {
|
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 {
|
QQC2.Menu {
|
||||||
id: compressedTextFormatMenu
|
|
||||||
y: -implicitHeight
|
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 {
|
QQC2.MenuItem {
|
||||||
icon.name: "format-text-underline"
|
icon.name: "format-text-underline"
|
||||||
text: i18nc("@action:button", "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.text: text
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
}
|
||||||
Kirigami.Separator {
|
QQC2.ToolButton {
|
||||||
Layout.fillHeight: true
|
icon.name: "format-list-ordered"
|
||||||
Layout.margins: 0
|
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 {
|
QQC2.ToolButton {
|
||||||
id: listRow
|
id: indentAction
|
||||||
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
|
icon.name: "format-indent-more"
|
||||||
QQC2.ToolButton {
|
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"
|
icon.name: "format-list-unordered"
|
||||||
enabled: chatButtonHelper.richFormatEnabled
|
|
||||||
text: i18nc("@action:button", "Unordered List")
|
text: i18nc("@action:button", "Unordered List")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
onTriggered: {
|
||||||
checkable: true
|
|
||||||
checked: root.chatButtonHelper.unorderedList
|
|
||||||
onClicked: {
|
|
||||||
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.UnorderedList);
|
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.UnorderedList);
|
||||||
root.clicked();
|
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"
|
icon.name: "format-list-ordered"
|
||||||
enabled: chatButtonHelper.richFormatEnabled
|
|
||||||
text: i18nc("@action:button", "Ordered List")
|
text: i18nc("@action:button", "Ordered List")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
onTriggered: {
|
||||||
checkable: true
|
|
||||||
checked: root.chatButtonHelper.orderedlist
|
|
||||||
onClicked: {
|
|
||||||
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.OrderedList);
|
root.chatButtonHelper.setFormat(LibNeoChat.RichFormat.OrderedList);
|
||||||
root.clicked();
|
root.clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolTip.text: text
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.MenuItem {
|
||||||
id: indentAction
|
|
||||||
icon.name: "format-indent-more"
|
icon.name: "format-indent-more"
|
||||||
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListMore
|
|
||||||
text: i18nc("@action:button", "Increase List Level")
|
text: i18nc("@action:button", "Increase List Level")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
enabled: root.chatButtonHelper.canIndentListMore
|
||||||
onClicked: {
|
onTriggered: {
|
||||||
root.chatButtonHelper.indentListMore();
|
root.chatButtonHelper.indentListMore();
|
||||||
root.clicked();
|
root.clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolTip.text: text
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.MenuItem {
|
||||||
id: dedentAction
|
|
||||||
icon.name: "format-indent-less"
|
icon.name: "format-indent-less"
|
||||||
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListLess
|
|
||||||
text: i18nc("@action:button", "Decrease List Level")
|
text: i18nc("@action:button", "Decrease List Level")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
enabled: root.chatButtonHelper.canIndentListLess
|
||||||
onClicked: {
|
onTriggered: {
|
||||||
root.chatButtonHelper.indentListLess();
|
root.chatButtonHelper.indentListLess();
|
||||||
root.clicked();
|
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 {
|
QQC2.ToolTip.text: text
|
||||||
id: compressedListMenu
|
QQC2.ToolTip.visible: hovered
|
||||||
y: -implicitHeight
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
@@ -575,21 +391,6 @@ QQC2.ToolBar {
|
|||||||
LinkDialog {}
|
LinkDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: attachDialog
|
|
||||||
AttachDialog {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFileDialog
|
|
||||||
LibNeoChat.OpenFileDialog {
|
|
||||||
parentWindow: Window.window
|
|
||||||
currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: emojiDialog
|
id: emojiDialog
|
||||||
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
|
required property ChatButtonHelper chatButtonHelper
|
||||||
|
|
||||||
y: -implicitHeight
|
y: -implicitHeight
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
@@ -26,24 +27,21 @@ QQC2.Popup {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: 9
|
model: 9
|
||||||
|
|
||||||
delegate: QQC2.TextArea {
|
delegate: StyleDelegate {
|
||||||
id: styleDelegate
|
id: styleDelegate
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 7
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 8
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
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
|
style: index
|
||||||
readOnly: true
|
highlight: root.chatButtonHelper.currentStyle === index || hovered
|
||||||
selectByMouse: false
|
|
||||||
|
|
||||||
onPressed: (event) => {
|
onPressed: (event) => {
|
||||||
if (styleDelegate.index === LibNeoChat.RichFormat.Paragraph ||
|
if (index === LibNeoChat.RichFormat.Paragraph ||
|
||||||
styleDelegate.index === LibNeoChat.RichFormat.Code ||
|
index === LibNeoChat.RichFormat.Code ||
|
||||||
styleDelegate.index === LibNeoChat.RichFormat.Quote
|
index === LibNeoChat.RichFormat.Quote
|
||||||
) {
|
) {
|
||||||
root.chatContentModel.insertStyleAtCursor(styleDelegate.index);
|
root.chatContentModel.insertStyleAtCursor(styleDelegate.index);
|
||||||
} else {
|
} else {
|
||||||
@@ -51,71 +49,19 @@ QQC2.Popup {
|
|||||||
}
|
}
|
||||||
root.close();
|
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 {
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
border {
|
border {
|
||||||
width: 1
|
width: 1
|
||||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
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;
|
m_textItem = textItem;
|
||||||
|
|
||||||
if (m_textItem) {
|
if (m_textItem) {
|
||||||
|
connect(m_textItem, SIGNAL(styleChanged()), this, SLOT(formatDocument()));
|
||||||
|
connect(m_textItem, SIGNAL(styleChanged()), this, SLOT(formatDocument()));
|
||||||
if (document()) {
|
if (document()) {
|
||||||
formatDocument();
|
formatDocument();
|
||||||
}
|
}
|
||||||
@@ -59,10 +61,11 @@ void StyleDelegateHelper::formatDocument()
|
|||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
cursor.select(QTextCursor::Document);
|
cursor.select(QTextCursor::Document);
|
||||||
cursor.removeSelectedText();
|
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 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
|
// Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
|
||||||
// level=2 look the same
|
// level=2 look the same
|
||||||
const int sizeAdjustment = headingLevel > 0 ? 5 - headingLevel : 0;
|
const int sizeAdjustment = headingLevel > 0 ? 5 - headingLevel : 0;
|
||||||
|
|||||||
@@ -32,5 +32,6 @@ private:
|
|||||||
QPointer<QQuickItem> m_textItem;
|
QPointer<QQuickItem> m_textItem;
|
||||||
QTextDocument *document() const;
|
QTextDocument *document() const;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
void formatDocument();
|
void formatDocument();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "chattextitemhelper.h"
|
#include "chattextitemhelper.h"
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include <qnamespace.h>
|
||||||
|
|
||||||
ChatKeyHelper::ChatKeyHelper(QObject *parent)
|
ChatKeyHelper::ChatKeyHelper(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -29,7 +30,10 @@ bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
|
|||||||
return backspace();
|
return backspace();
|
||||||
case Qt::Key_Enter:
|
case Qt::Key_Enter:
|
||||||
case Qt::Key_Return:
|
case Qt::Key_Return:
|
||||||
return insertReturn();
|
return insertReturn(modifiers);
|
||||||
|
case Qt::Key_Escape:
|
||||||
|
case Qt::Key_Cancel:
|
||||||
|
return cancel();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -155,16 +159,28 @@ bool ChatKeyHelper::backspace()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatKeyHelper::insertReturn()
|
bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
|
||||||
{
|
{
|
||||||
if (!textItem) {
|
if (!textItem) {
|
||||||
return false;
|
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);
|
Q_EMIT unhandledReturn(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!shiftPressed && sendMessageWithEnter) {
|
||||||
|
Q_EMIT unhandledReturn(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QTextCursor cursor = textItem->textCursor();
|
QTextCursor cursor = textItem->textCursor();
|
||||||
if (cursor.isNull()) {
|
if (cursor.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -173,6 +189,18 @@ bool ChatKeyHelper::insertReturn()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatKeyHelper::cancel()
|
||||||
|
{
|
||||||
|
if (!textItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (textItem->isCompleting) {
|
||||||
|
Q_EMIT closeCompletion();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool ChatKeyHelper::pasteImage()
|
bool ChatKeyHelper::pasteImage()
|
||||||
{
|
{
|
||||||
if (!textItem) {
|
if (!textItem) {
|
||||||
|
|||||||
@@ -45,6 +45,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE bool handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers);
|
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:
|
Q_SIGNALS:
|
||||||
/**
|
/**
|
||||||
* @brief There is an unhandled up key press.
|
* @brief There is an unhandled up key press.
|
||||||
@@ -98,6 +107,14 @@ Q_SIGNALS:
|
|||||||
*/
|
*/
|
||||||
void unhandledReturn(bool isCompleting);
|
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.
|
* @brief An image has been pasted.
|
||||||
*/
|
*/
|
||||||
@@ -116,7 +133,9 @@ private:
|
|||||||
|
|
||||||
bool backspace();
|
bool backspace();
|
||||||
|
|
||||||
bool insertReturn();
|
bool insertReturn(Qt::KeyboardModifiers modifiers);
|
||||||
|
|
||||||
|
bool cancel();
|
||||||
|
|
||||||
bool pasteImage();
|
bool pasteImage();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -342,6 +342,14 @@ std::optional<int> ChatTextItemHelper::cursorPosition() const
|
|||||||
return m_textItem->property("cursorPosition").toInt();
|
return m_textItem->property("cursorPosition").toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRect ChatTextItemHelper::cursorRectangle() const
|
||||||
|
{
|
||||||
|
if (!m_textItem) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_textItem->property("cursorRectangle").toRect();
|
||||||
|
}
|
||||||
|
|
||||||
int ChatTextItemHelper::selectionStart() const
|
int ChatTextItemHelper::selectionStart() const
|
||||||
{
|
{
|
||||||
if (!m_textItem) {
|
if (!m_textItem) {
|
||||||
|
|||||||
@@ -172,6 +172,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::optional<int> cursorPosition() const;
|
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.
|
* @brief Set the cursor position of the underlying text item to the given value.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,27 +7,29 @@
|
|||||||
#include <QTextCharFormat>
|
#include <QTextCharFormat>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
QString RichFormat::styleString(Format format)
|
QString RichFormat::styleString(Format format)
|
||||||
{
|
{
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Paragraph:
|
case Paragraph:
|
||||||
return u"Paragraph"_s;
|
return i18nc("As in the default paragraph text style in the chat bar", "Paragraph Style");
|
||||||
case Heading1:
|
case Heading1:
|
||||||
return u"Heading 1"_s;
|
return i18nc("As in heading level 1 text style in the chat bar", "Heading 1");
|
||||||
case Heading2:
|
case Heading2:
|
||||||
return u"Heading 2"_s;
|
return i18nc("As in heading level 2 text style in the chat bar", "Heading 2");
|
||||||
case Heading3:
|
case Heading3:
|
||||||
return u"Heading 3"_s;
|
return i18nc("As in heading level 3 text style in the chat bar", "Heading 3");
|
||||||
case Heading4:
|
case Heading4:
|
||||||
return u"Heading 4"_s;
|
return i18nc("As in heading level 4 text style in the chat bar", "Heading 4");
|
||||||
case Heading5:
|
case Heading5:
|
||||||
return u"Heading 5"_s;
|
return i18nc("As in heading level 5 text style in the chat bar", "Heading 5");
|
||||||
case Heading6:
|
case Heading6:
|
||||||
return u"Heading 6"_s;
|
return i18nc("As in heading level 6 text style in the chat bar", "Heading 6");
|
||||||
case Code:
|
case Code:
|
||||||
return u"Code"_s;
|
return i18nc("As in code text style in the chat bar", "Code");
|
||||||
case Quote:
|
case Quote:
|
||||||
return u"\"Quote\""_s;
|
return i18nc("As in quote text style in the chat bar", "\"Quote\"");
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,21 @@ void CompletionModel::setTextItem(ChatTextItemHelper *textItem)
|
|||||||
Q_EMIT textItemChanged();
|
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()
|
void CompletionModel::updateTextStart()
|
||||||
{
|
{
|
||||||
auto cursor = m_textItem->textCursor();
|
auto cursor = m_textItem->textCursor();
|
||||||
@@ -193,6 +208,15 @@ void CompletionModel::updateCompletion()
|
|||||||
if (cursor.isNull()) {
|
if (cursor.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_ignoreCurrentCompletion) {
|
||||||
|
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
|
||||||
|
if (cursor.selectedText() == u' ') {
|
||||||
|
m_ignoreCurrentCompletion = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cursor.setPosition(m_textStart);
|
cursor.setPosition(m_textStart);
|
||||||
while (!cursor.selectedText().endsWith(u' ') && !cursor.atBlockEnd()) {
|
while (!cursor.selectedText().endsWith(u' ') && !cursor.atBlockEnd()) {
|
||||||
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
|
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
|
||||||
@@ -242,6 +266,7 @@ void CompletionModel::updateCompletion()
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
m_textItem->isCompleting = rowCount() > 0;
|
m_textItem->isCompleting = rowCount() > 0;
|
||||||
|
Q_EMIT isCompletingChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletionModel::AutoCompletionType CompletionModel::autoCompletionType() const
|
CompletionModel::AutoCompletionType CompletionModel::autoCompletionType() const
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ class CompletionModel : public QAbstractListModel
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(UserListModel *userListModel READ userListModel WRITE setUserListModel NOTIFY userListModelChanged)
|
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:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Defines the different types of completion available.
|
* @brief Defines the different types of completion available.
|
||||||
@@ -98,6 +103,10 @@ public:
|
|||||||
ChatTextItemHelper *textItem() const;
|
ChatTextItemHelper *textItem() const;
|
||||||
void setTextItem(ChatTextItemHelper *textItem);
|
void setTextItem(ChatTextItemHelper *textItem);
|
||||||
|
|
||||||
|
bool isCompleting() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void ignoreCurrentCompletion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the given role value at the given index.
|
* @brief Get the given role value at the given index.
|
||||||
*
|
*
|
||||||
@@ -137,12 +146,14 @@ Q_SIGNALS:
|
|||||||
void autoCompletionTypeChanged();
|
void autoCompletionTypeChanged();
|
||||||
void roomListModelChanged();
|
void roomListModelChanged();
|
||||||
void userListModelChanged();
|
void userListModelChanged();
|
||||||
|
void isCompletingChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
ChatBarType::Type m_type = ChatBarType::None;
|
ChatBarType::Type m_type = ChatBarType::None;
|
||||||
QPointer<ChatTextItemHelper> m_textItem;
|
QPointer<ChatTextItemHelper> m_textItem;
|
||||||
|
|
||||||
|
bool m_ignoreCurrentCompletion = false;
|
||||||
int m_textStart = 0;
|
int m_textStart = 0;
|
||||||
void updateTextStart();
|
void updateTextStart();
|
||||||
|
|
||||||
|
|||||||
@@ -110,14 +110,10 @@ QQC2.Control {
|
|||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
y: -height - 5
|
y: -height - 5
|
||||||
z: 10
|
z: 10
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
chatDocumentHandler: documentHandler
|
|
||||||
=======
|
|
||||||
room: root.Message.room
|
room: root.Message.room
|
||||||
type: root.chatBarCache.isEditing ? ChatBarType.Edit : ChatBarType.Thread
|
type: root.chatBarCache.isEditing ? ChatBarType.Edit : ChatBarType.Thread
|
||||||
// textItem: textArea
|
// textItem: textArea
|
||||||
>>>>>>> c7858a151 (Move the remaining functionality of ChatDocumentHandler to ChatTextItemHelper or split into own objects)
|
|
||||||
margins: 0
|
margins: 0
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
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 {
|
HoverHandler {
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
@@ -86,5 +71,22 @@ RowLayout {
|
|||||||
id: _private
|
id: _private
|
||||||
// The space available for the component after taking away the border
|
// The space available for the component after taking away the border
|
||||||
readonly property real availableContentWidth: root.Message.maxContentWidth - verticalBorder.implicitWidth - root.spacing
|
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);
|
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) {
|
connect(m_keyHelper, &ChatKeyHelper::imagePasted, this, [this](const QString &filePath) {
|
||||||
m_room->cacheForType(m_type)->setAttachmentPath(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)
|
ChatBarMessageContentModel::ComponentIt ChatBarMessageContentModel::removeComponent(ComponentIt it)
|
||||||
{
|
{
|
||||||
if (it == m_components.end()) {
|
if (it == m_components.end()) {
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ class ChatBarMessageContentModel : public MessageContentModel
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool hasRichFormatting READ hasRichFormatting NOTIFY hasRichFormattingChanged)
|
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:
|
public:
|
||||||
explicit ChatBarMessageContentModel(QObject *parent = nullptr);
|
explicit ChatBarMessageContentModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
@@ -89,12 +94,16 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void removeAttachment();
|
Q_INVOKABLE void removeAttachment();
|
||||||
|
|
||||||
|
bool sendMessageWithEnter() const;
|
||||||
|
void setSendMessageWithEnter(bool sendMessageWithEnter);
|
||||||
|
|
||||||
Q_INVOKABLE void postMessage();
|
Q_INVOKABLE void postMessage();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void typeChanged();
|
void typeChanged();
|
||||||
void focusRowChanged();
|
void focusRowChanged();
|
||||||
void hasRichFormattingChanged();
|
void hasRichFormattingChanged();
|
||||||
|
void sendMessageWithEnterChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ChatBarType::Type m_type = ChatBarType::None;
|
ChatBarType::Type m_type = ChatBarType::None;
|
||||||
@@ -125,5 +134,7 @@ private:
|
|||||||
void updateCache() const;
|
void updateCache() const;
|
||||||
QString messageText() const;
|
QString messageText() const;
|
||||||
|
|
||||||
|
bool m_sendMessageWithEnter = true;
|
||||||
|
|
||||||
void clearModel();
|
void clearModel();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -264,32 +264,6 @@ FormCard.FormCardPage {
|
|||||||
title: i18nc("Chat Editor", "Editor")
|
title: i18nc("Chat Editor", "Editor")
|
||||||
}
|
}
|
||||||
FormCard.FormCard {
|
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 {
|
FormCard.FormCheckDelegate {
|
||||||
id: quickEditCheckbox
|
id: quickEditCheckbox
|
||||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||||
|
|||||||
@@ -335,9 +335,13 @@ QQC2.ScrollView {
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
id: typingPaneContainer
|
id: typingPaneContainer
|
||||||
visible: _private.room && _private.room.otherMembersTyping.length > 0
|
visible: _private.room && _private.room.otherMembersTyping.length > 0
|
||||||
anchors.left: parent.left
|
anchors {
|
||||||
anchors.right: parent.right
|
left: parent.left
|
||||||
anchors.bottom: parent.bottom
|
leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
height: visible ? typingPane.implicitHeight : 0
|
height: visible ? typingPane.implicitHeight : 0
|
||||||
z: 2
|
z: 2
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
|
|||||||
@@ -16,28 +16,19 @@ Loader {
|
|||||||
property string labelText: ""
|
property string labelText: ""
|
||||||
|
|
||||||
active: visible
|
active: visible
|
||||||
sourceComponent: QQC2.Pane {
|
sourceComponent: QQC2.Control {
|
||||||
id: typingPane
|
id: typingPane
|
||||||
|
|
||||||
leftPadding: Kirigami.Units.largeSpacing
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
FontMetrics {
|
FontMetrics {
|
||||||
id: fontMetrics
|
id: fontMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
Kirigami.Theme.inherit: false
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
spacing: typingPane.spacing
|
spacing: typingPane.spacing
|
||||||
Row {
|
Row {
|
||||||
id: dotRow
|
id: dotRow
|
||||||
property int duration: 400
|
property int duration: 400
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
Repeater {
|
Repeater {
|
||||||
model: 3
|
model: 3
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
@@ -113,14 +104,16 @@ Loader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leftInset: !mirrored ? 0 : -(background as Rectangle).radius
|
background: Kirigami.ShadowedRectangle {
|
||||||
rightInset: mirrored ? 0 : -(background as Rectangle).radius
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
bottomInset: -(background as Rectangle).radius
|
Kirigami.Theme.inherit: false
|
||||||
background: Rectangle {
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
border {
|
||||||
border.width: 1
|
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user