Capitalization, list model and room header work

- Standardized capitalization for variables and file names everywhere in
  QML and JS, get rid of mixed camelCase/snakeCase,
  use camelCase like everywhere in Qt

- ListModel items are now stored and returned as real QObjects with
  PyQt properties and signals.
  This makes dynamic property binding a lot easier and eliminates the need
  for many hacks.

- New update(), updateOrAppendWhere() methods and roles property
  for ListModel

- RoomHeader now properly updates when the room title or topic changes

- Add Backend.pdb(), to make it easier to start the debugger from QML
This commit is contained in:
miruka
2019-04-20 17:36:21 -04:00
parent b33f5f1d34
commit 8f35e60801
34 changed files with 304 additions and 250 deletions

View File

@@ -1,7 +1,8 @@
import QtQuick 2.7
import QtQuick.Controls 1.4 as Controls1
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
import "side_pane" as SidePane
import "sidePane" as SidePane
import "chat" as Chat
//https://doc.qt.io/qt-5/qml-qtquick-controls-splitview.html
@@ -14,12 +15,9 @@ Controls1.SplitView {
}
StackView {
function show_page(componentName) {
pageStack.replace(componentName + ".qml")
}
function show_room(user_id, room_obj) {
function showRoom(userId, roomId) {
pageStack.replace(
"chat/Root.qml", { user_id: user_id, room: room_obj }
"chat/Root.qml", { userId: userId, roomId: roomId }
)
console.log("replaced")
}
@@ -28,6 +26,12 @@ Controls1.SplitView {
onCurrentItemChanged: currentItem.forceActiveFocus()
initialItem: MouseArea { // TODO: (test, remove)
onClicked: pageStack.showRoom(
"@test_mary:matrix.org", "!VDSsFIzQnXARSCVNxS:matrix.org"
)
}
// Buggy
replaceExit: null
popExit: null

View File

@@ -8,7 +8,7 @@ Item {
property var imageSource: null
property int dimmension: 48
readonly property string resolved_name:
readonly property string resolvedName:
! name ? "?" :
typeof(name) == "string" ? name :
(name.value ? name.value : "?")
@@ -21,13 +21,13 @@ Item {
id: "letterRectangle"
anchors.fill: parent
visible: ! invisible && imageSource === null
color: resolved_name === "?" ?
color: resolvedName === "?" ?
Qt.hsla(0, 0, 0.22, 1) :
Qt.hsla(Backend.hueFromString(resolved_name), 0.22, 0.5, 1)
Qt.hsla(Backend.hueFromString(resolvedName), 0.22, 0.5, 1)
HLabel {
anchors.centerIn: parent
text: resolved_name.charAt(0)
text: resolvedName.charAt(0)
color: "white"
font.pixelSize: letterRectangle.height / 1.4
}

View File

@@ -14,7 +14,7 @@ ToolButton {
onClicked: toolTip.hide()
ToolTip {
id: "toolTip"
id: toolTip
text: tooltip
delay: Qt.styleHints.mousePressAndHoldInterval
visible: text ? toolTipZone.containsMouse : false

View File

@@ -2,11 +2,14 @@ import QtQuick 2.7
import QtQuick.Controls 2.0
HLabel {
property string toolTipText: ""
id: text
ToolTip {
delay: Qt.styleHints.mousePressAndHoldInterval
visible: text ? toolTipZone.containsMouse : false
text: user_id
text: toolTipText
}
MouseArea {
id: toolTipZone

View File

@@ -2,12 +2,13 @@ import QtQuick 2.7
import "../base" as Base
Base.HLabel {
text: date_time.toLocaleDateString()
width: messageDelegate.width
horizontalAlignment: Text.AlignHCenter
topPadding: messageDelegate.isFirstMessage ?
0 : messageDelegate.standardSpacing
bottomPadding: messageDelegate.standardSpacing
text: dateTime.toLocaleDateString()
horizontalAlignment: Text.AlignHCenter
font.pixelSize: normalSize * 1.1
color: "darkolivegreen"
}

View File

@@ -11,7 +11,7 @@ RowLayout {
anchors.right: isOwn ? parent.right : undefined
readonly property string contentText:
isMessage ? "" : ChatJS.get_event_text(type, dict)
isMessage ? "" : ChatJS.getEventText(type, dict)
Base.Avatar {
id: avatar
@@ -26,7 +26,7 @@ RowLayout {
(isUndecryptableEvent ? "darkred" : "gray") + "'>" +
(displayName.value || dict.sender) + " " + contentText +
"&nbsp;&nbsp;<font size=" + smallSize + "px color='gray'>" +
Qt.formatDateTime(date_time, "hh:mm:ss") +
Qt.formatDateTime(dateTime, "hh:mm:ss") +
"</font></font>"
textFormat: Text.RichText
background: Rectangle {color: "#DDD"}

View File

@@ -32,19 +32,13 @@ Row {
Base.RichLabel {
id: contentLabel
//text: (isOwn ? "" : content + "&nbsp;&nbsp;") +
//"<font size=" + smallSize + "px color=gray>" +
//Qt.formatDateTime(date_time, "hh:mm:ss") +
//"</font>" +
// (isOwn ? "&nbsp;&nbsp;" + content : "")
//
text: (dict.formatted_body ?
Backend.htmlFilter.filter(dict.formatted_body) :
dict.body) +
"&nbsp;&nbsp;<font size=" + smallSize + "px color=gray>" +
Qt.formatDateTime(date_time, "hh:mm:ss") +
Qt.formatDateTime(dateTime, "hh:mm:ss") +
"</font>" +
(is_local_echo ?
(isLocalEcho ?
"&nbsp;<font size=" + smallSize + "px>⏳</font>" : "")
textFormat: Text.RichText
background: Rectangle {color: "#DDD"}

View File

@@ -7,22 +7,22 @@ import "utils.js" as ChatJS
Column {
id: "messageDelegate"
function mins_between(date1, date2) {
function minsBetween(date1, date2) {
return Math.round((((date2 - date1) % 86400000) % 3600000) / 60000)
}
function is_message(type_) { return type_.startsWith("RoomMessage") }
function getIsMessage(type_) { return type_.startsWith("RoomMessage") }
function get_previous_item() {
function getPreviousItem() {
return index < messageListView.model.count - 1 ?
messageListView.model.get(index + 1) : null
}
property var previousItem: get_previous_item()
property var previousItem: getPreviousItem()
signal reloadPreviousItem()
onReloadPreviousItem: previousItem = get_previous_item()
onReloadPreviousItem: previousItem = getPreviousItem()
readonly property bool isMessage: is_message(type)
readonly property bool isMessage: getIsMessage(type)
readonly property bool isUndecryptableEvent:
type === "OlmEvent" || type === "MegolmEvent"
@@ -31,7 +31,7 @@ Column {
Backend.getUserDisplayName(dict.sender)
readonly property bool isOwn:
chatPage.user_id === dict.sender
chatPage.userId === dict.sender
readonly property bool isFirstEvent: type == "RoomCreateEvent"
@@ -39,19 +39,19 @@ Column {
previousItem &&
! talkBreak &&
! dayBreak &&
is_message(previousItem.type) === isMessage &&
getIsMessage(previousItem.type) === isMessage &&
previousItem.dict.sender === dict.sender &&
mins_between(previousItem.date_time, date_time) <= 5
minsBetween(previousItem.dateTime, dateTime) <= 5
readonly property bool dayBreak:
isFirstEvent ||
previousItem &&
date_time.getDay() != previousItem.date_time.getDay()
dateTime.getDay() != previousItem.dateTime.getDay()
readonly property bool talkBreak:
previousItem &&
! dayBreak &&
mins_between(previousItem.date_time, date_time) >= 20
minsBetween(previousItem.dateTime, dateTime) >= 20
property int standardSpacing: 16
@@ -59,8 +59,8 @@ Column {
property int verticalPadding: 5
ListView.onAdd: {
var next_delegate = messageListView.contentItem.children[index]
if (next_delegate) { next_delegate.reloadPreviousItem() }
var nextDelegate = messageListView.contentItem.children[index]
if (nextDelegate) { nextDelegate.reloadPreviousItem() }
}
width: parent.width

View File

@@ -14,8 +14,7 @@ Rectangle {
id: messageListView
anchors.fill: parent
delegate: MessageDelegate {}
model: Backend.models.roomEvents.get(chatPage.room.room_id)
//highlight: Rectangle {color: "lightsteelblue"; radius: 5}
model: Backend.models.roomEvents.get(chatPage.roomId)
clip: true
topMargin: space
@@ -31,7 +30,7 @@ Rectangle {
onYPosChanged: {
if (yPos <= 0.1) {
Backend.loadPastEvents(chatPage.room.room_id)
Backend.loadPastEvents(chatPage.roomId)
}
}
}

View File

@@ -4,7 +4,10 @@ import QtQuick.Layouts 1.4
import "../base" as Base
Rectangle {
id: root
property string displayName: ""
property string topic: ""
id: "root"
Layout.fillWidth: true
Layout.minimumHeight: 36
Layout.maximumHeight: Layout.minimumHeight
@@ -19,21 +22,23 @@ Rectangle {
id: "avatar"
Layout.alignment: Qt.AlignTop
dimmension: root.Layout.minimumHeight
name: chatPage.room.display_name
name: displayName
}
Base.HLabel {
id: "roomName"
text: chatPage.room.display_name
text: displayName
font.pixelSize: bigSize
elide: Text.ElideRight
maximumLineCount: 1
Layout.maximumWidth: row.width - row.spacing * (row.children.length - 1) - avatar.width
Layout.maximumWidth:
row.width - row.spacing * (row.children.length - 1) -
avatar.width
}
Base.HLabel {
id: "roomDescription"
text: chatPage.room.description || ""
id: "roomTopic"
text: topic
font.pixelSize: smallSize
elide: Text.ElideRight
maximumLineCount: 1

View File

@@ -3,16 +3,23 @@ import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
ColumnLayout {
property var user_id: null
property var room: null
property var userId: null
property var roomId: null
property var roomInfo:
Backend.models.rooms.get(userId).getWhere("roomId", roomId)
id: chatPage
id: "chatPage"
spacing: 0
onFocusChanged: sendBox.setFocus()
RoomHeader {}
RoomHeader {
id: "roomHeader"
displayName: roomInfo.displayName
topic: roomInfo.topic
}
MessageList {}
TypingUsersBar {}
SendBox { id: sendBox }
SendBox { id: "sendBox" }
}

View File

@@ -20,7 +20,7 @@ Rectangle {
Base.Avatar {
id: "avatar"
name: Backend.getUserDisplayName(chatPage.user_id)
name: Backend.getUserDisplayName(chatPage.userId)
dimmension: root.Layout.minimumHeight
//visible: textArea.text === ""
visible: textArea.height <= root.Layout.minimumHeight
@@ -43,13 +43,13 @@ Rectangle {
font.pixelSize: 16
focus: true
function set_typing(typing) {
Backend.clientManager.clients[chatPage.user_id]
.setTypingState(chatPage.room.room_id, typing)
function setTyping(typing) {
Backend.clientManager.clients[chatPage.userId]
.setTypingState(chatPage.roomId, typing)
}
onTypedTextChanged: set_typing(Boolean(text))
onEditingFinished: set_typing(false) // when lost focus
onTypedTextChanged: setTyping(Boolean(text))
onEditingFinished: setTyping(false) // when lost focus
Keys.onReturnPressed: {
event.accepted = true
@@ -62,8 +62,8 @@ Rectangle {
}
if (textArea.text === "") { return }
Backend.clientManager.clients[chatPage.user_id]
.sendMarkdown(chatPage.room.room_id, textArea.text)
Backend.clientManager.clients[chatPage.userId]
.sendMarkdown(chatPage.roomId, textArea.text)
textArea.clear()
}

View File

@@ -11,19 +11,12 @@ Rectangle {
Layout.maximumHeight: Layout.minimumHeight
color: "#BBB"
property var typingUsers: chatPage.roomInfo.typingUsers
Base.HLabel {
id: "usersLabel"
anchors.fill: parent
Timer {
interval: 500
repeat: true
running: true
triggeredOnStart: true
onTriggered: usersLabel.text = ChatJS.get_typing_users_text(
chatPage.user_id, chatPage.room.room_id
)
}
text: ChatJS.getTypingUsersText(typingUsers, chatPage.userId)
elide: Text.ElideMiddle
maximumLineCount: 1

View File

@@ -1,4 +1,4 @@
function get_event_text(type, dict) {
function getEventText(type, dict) {
switch (type) {
case "RoomCreateEvent":
return (dict.federate ? "allowed" : "blocked") +
@@ -18,14 +18,14 @@ function get_event_text(type, dict) {
break
case "RoomHistoryVisibilityEvent":
return get_history_visibility_event_text(dict)
return getHistoryVisibilityEventText(dict)
break
case "PowerLevelsEvent":
return "changed the room's permissions."
case "RoomMemberEvent":
return get_member_event_text(dict)
return getMemberEventText(dict)
break
case "RoomAliasEvent":
@@ -58,7 +58,7 @@ function get_event_text(type, dict) {
}
function get_history_visibility_event_text(dict) {
function getHistoryVisibilityEventText(dict) {
switch (dict.history_visibility) {
case "shared":
var end = "all room members."
@@ -81,7 +81,7 @@ function get_history_visibility_event_text(dict) {
}
function get_member_event_text(dict) {
function getMemberEventText(dict) {
var info = dict.content, prev = dict.prev_content
if (! prev || (info.membership != prev.membership)) {
@@ -127,15 +127,12 @@ function get_member_event_text(dict) {
}
function get_typing_users_text(account_id, room_id) {
function getTypingUsersText(users, ourAccountId) {
var names = []
var room = Backend.models.rooms.get(account_id)
.getWhere("room_id", room_id)
for (var i = 0; i < room.typing_users.length; i++) {
if (room.typing_users[i] !== account_id) {
names.push(Backend.getUserDisplayName(room.typing_users[i], false)
.result())
for (var i = 0; i < users.length; i++) {
if (users[i] !== ourAccountId) {
names.push(Backend.getUserDisplayName(users[i], false).result())
}
}

View File

@@ -1,9 +0,0 @@
import QtQuick 2.7
import "../base" as Base
Rectangle {
Base.HLabel {
anchors.centerIn: parent
text: "Home page"
}
}

View File

@@ -12,7 +12,7 @@ ColumnLayout {
id: "row"
spacing: 0
Base.Avatar { id: "avatar"; name: display_name; dimmension: 36 }
Base.Avatar { id: "avatar"; name: displayName; dimmension: 36 }
ColumnLayout {
Layout.fillWidth: true
@@ -21,7 +21,7 @@ ColumnLayout {
Base.HLabel {
id: "accountLabel"
text: display_name.value || user_id
text: displayName.value || userId
elide: Text.ElideRight
maximumLineCount: 1
Layout.fillWidth: true
@@ -31,7 +31,7 @@ ColumnLayout {
TextField {
id: "statusEdit"
text: status_message || ""
text: statusMessage || ""
placeholderText: qsTr("Set status message")
background: null
color: "black"
@@ -44,7 +44,7 @@ ColumnLayout {
rightPadding: leftPadding
onEditingFinished: {
Backend.setStatusMessage(user_id, text)
Backend.setStatusMessage(userId, text)
pageStack.forceActiveFocus()
}
}
@@ -67,7 +67,7 @@ ColumnLayout {
id: "roomList"
visible: true
interactive: false // no scrolling
for_user_id: user_id
forUserId: userId
Layout.minimumHeight:
roomList.visible ?

View File

@@ -9,24 +9,21 @@ MouseArea {
width: roomList.width
height: roomList.childrenHeight
onClicked: pageStack.show_room(
roomList.for_user_id,
roomList.model.get(index)
)
onClicked: pageStack.showRoom(roomList.forUserId, roomId)
RowLayout {
anchors.fill: parent
id: row
spacing: 1
Base.Avatar { id: avatar; name: display_name; dimmension: root.height }
Base.Avatar { id: avatar; name: displayName; dimmension: root.height }
ColumnLayout {
spacing: 0
Base.HLabel {
id: roomLabel
text: display_name ? display_name : "<i>Empty room</i>"
text: displayName ? displayName : "<i>Empty room</i>"
textFormat: Text.StyledText
elide: Text.ElideRight
maximumLineCount: 1
@@ -39,18 +36,18 @@ MouseArea {
rightPadding: leftPadding
}
Base.HLabel {
function get_text() {
return SidePaneJS.get_last_room_event_text(room_id)
function getText() {
return SidePaneJS.getLastRoomEventText(roomId)
}
Connections {
target: Backend.models.roomEvents.get(room_id)
onChanged: subtitleLabel.text = subtitleLabel.get_text()
target: Backend.models.roomEvents.get(roomId)
onChanged: subtitleLabel.text = subtitleLabel.getText()
}
id: subtitleLabel
visible: text !== ""
text: get_text()
text: getText()
textFormat: Text.StyledText
font.pixelSize: smallSize

View File

@@ -4,7 +4,7 @@ import QtQuick.Layouts 1.4
import "../base" as Base
ListView {
property var for_user_id: null
property var forUserId: null
property int childrenHeight: 36
property int contentHeight: 0
@@ -16,6 +16,6 @@ ListView {
id: "roomList"
spacing: 8
model: Backend.models.rooms.get(for_user_id)
model: Backend.models.rooms.get(forUserId)
delegate: RoomDelegate {}
}

View File

@@ -1,8 +1,8 @@
.import "../chat/utils.js" as ChatJS
function get_last_room_event_text(room_id) {
var eventsModel = Backend.models.roomEvents.get(room_id)
function getLastRoomEventText(roomId) {
var eventsModel = Backend.models.roomEvents.get(roomId)
for (var i = 0; i < eventsModel.count; i++) {
var ev = eventsModel.get(i)
@@ -19,7 +19,7 @@ function get_last_room_event_text(room_id) {
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"
if (undecryptable || ev.type.startsWith("RoomMessage")) {
var color = ev.dict.sender === roomList.for_user_id ?
var color = ev.dict.sender === roomList.forUserId ?
"darkblue" : "purple"
return "<font color='" +
@@ -36,7 +36,7 @@ function get_last_room_event_text(room_id) {
"'>" +
name +
" " +
ChatJS.get_event_text(ev.type, ev.dict) +
ChatJS.getEventText(ev.type, ev.dict) +
"</font>"
}
}