Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate qml in Python #33

Open
wants to merge 9 commits into
base: roomlist
Choose a base branch
from
60 changes: 60 additions & 0 deletions demo/chatroomwidget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from PySide6 import QtCore, QtWidgets, QtQuickWidgets, QtQml
from PyQuotient import Quotient

from demo.models.messageeventmodel import MessageEventModel

from __feature__ import snake_case, true_property


class ChatRoomWidget(QtWidgets.QWidget):
resourceRequested = QtCore.Signal()
roomSettingsRequested = QtCore.Signal()
showStatusMessage = QtCore.Signal()

def __init__(self, parent=None) -> None:
super().__init__(parent)

self.message_model = MessageEventModel(self)

QtQml.qmlRegisterUncreatableType(Quotient.Room, 'Quotient', 1, 0, 'Room', 'Room objects can only be created by libQuotient')
QtQml.qmlRegisterUncreatableType(Quotient.User, 'Quotient', 1, 0, 'User', 'User objects can only be created by libQuotient')
QtQml.qmlRegisterType(Quotient.GetRoomEventsJob, 'Quotient', 1, 0, 'GetRoomEventsJob')
QtQml.qmlRegisterType(MessageEventModel, 'Quotient', 1, 0, 'MessageEventModel')

QtQml.qmlRegisterType(Quotient.Settings, 'Quotient', 1, 0, 'Settings')

self.timeline_widget = QtQuickWidgets.QQuickWidget(self)
self.timeline_widget.size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.timeline_widget.resize_mode = QtQuickWidgets.QQuickWidget.SizeRootObjectToView

self.timeline_widget.root_context().set_context_property("messageModel", self.message_model)
self.timeline_widget.root_context().set_context_property("controller", self)
self.timeline_widget.root_context().set_context_property("room", None)

self.timeline_widget.source = "qrc:///qml/Timeline.qml"

self.message_text_edit = QtWidgets.QTextEdit(self)
self.message_text_edit.maximum_height = self.maximum_chat_edit_height()
self.send_button = QtWidgets.QPushButton('Send', self)
self.send_button.clicked.connect(self.send_message)

self.h_layout = QtWidgets.QHBoxLayout()
self.h_layout.add_widget(self.message_text_edit)
self.h_layout.add_widget(self.send_button)

self.v_layout = QtWidgets.QVBoxLayout(self)
self.v_layout.add_widget(self.timeline_widget)
self.v_layout.add_layout(self.h_layout)
self.set_layout(self.v_layout)


@QtCore.Slot(str, bool)
def onMessageShownChanged(self, eventId: str, shown: bool): # name should be in camelCase, because it is used so in QML
...

def maximum_chat_edit_height(self):
return self.maximum_height / 3

@QtCore.Slot()
def send_message(self):
...
6 changes: 6 additions & 0 deletions demo/mainwindow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import math

import demo.resources
from PySide6 import QtCore, QtWidgets, QtGui
from PyQuotient import Quotient
from demo.accountregistry import AccountRegistry
from demo.chatroomwidget import ChatRoomWidget
from demo.logindialog import LoginDialog
from demo.roomlistdock import RoomListDock
from demo.pyquaternionroom import PyquaternionRoom
Expand All @@ -25,6 +27,10 @@ def __init__(self):
self.room_list_dock.roomSelected.connect(self.select_room)
self.add_dock_widget(QtCore.Qt.LeftDockWidgetArea, self.room_list_dock)

self.chat_room_widget = ChatRoomWidget(self)
self.chat_room_widget.showStatusMessage.connect(lambda x = '': self.status_bar().show_message(x))
self.set_central_widget(self.chat_room_widget)

self.create_menu()
# Only GUI, account settings will be loaded in invoke_login
self.load_settings()
Expand Down
87 changes: 87 additions & 0 deletions demo/models/messageeventmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from enum import Enum, auto
from PySide6 import QtCore, QtQml
from PyQuotient import Quotient
from __feature__ import snake_case, true_property


class EventRoles(Enum):
EventTypeRole = QtCore.Qt.UserRole + 1
EventIdRole = auto()
TimeRole = auto()
SectionRole = auto()
AboveSectionRole = auto()
AuthorRole = auto()
AboveAuthorRole = auto()
ContentRole = auto()
ContentTypeRole = auto()
HighlightRole = auto()
SpecialMarksRole = auto()
LongOperationRole = auto()
AnnotationRole = auto()
UserHueRole = auto()
RefRole = auto()
ReactionsRole = auto()
EventResolvedTypeRole = auto()


class MessageEventModel(QtCore.QAbstractListModel):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

QtQml.qmlRegisterUncreatableType(Quotient.EventStatus, 'Quotient', 1, 0, 'EventStatus', 'EventStatus is not a creatable type')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this doesn't work now that EventStatus is a namespace (since yesterday in master - sorry for taking time to resolve this)?


def row_count(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()):
# TODO: implement
return 2

def data(self, index: QtCore.QModelIndex, role: int):
row = index.row()

if not index.is_valid() or row >= self.row_count():
return {}

if role == QtCore.Qt.DisplayRole:
return 'it\'s a test message!'

if role == QtCore.Qt.ToolTipRole:
# return evt.originalJson()
return {}

if role == EventRoles.AuthorRole.value:
return {
'avatarMediaId': 1 # TODO: user
}

if role == EventRoles.AnnotationRole.value:
return 'annotation'

if role == EventRoles.ReactionsRole.value:
return []

if role == EventRoles.SectionRole.value:
return 'date' # TODO: render_date()
return {}

@QtCore.Slot(result='QVariant')
def role_names(self):
roles = super().role_names()

roles[EventRoles.EventTypeRole.value] = b'eventType'
roles[EventRoles.EventIdRole.value] = b'eventId'
roles[EventRoles.TimeRole.value] = b'time'
roles[EventRoles.SectionRole.value] = b'section'
roles[EventRoles.AboveSectionRole.value] = b'aboveSection'
roles[EventRoles.AuthorRole.value] = b'author'
roles[EventRoles.AboveAuthorRole.value] = b'aboveAuthor'
roles[EventRoles.ContentRole.value] = b'content'
roles[EventRoles.ContentTypeRole.value] = b'contentType'
roles[EventRoles.HighlightRole.value] = b'highlight'
roles[EventRoles.SpecialMarksRole.value] = b'marks'
roles[EventRoles.LongOperationRole.value] = b'progressInfo'
roles[EventRoles.AnnotationRole.value] = b'annotation'
roles[EventRoles.UserHueRole.value] = b'userHue'
roles[EventRoles.EventResolvedTypeRole.value] = b'eventResolvedType'
roles[EventRoles.RefRole.value] = b'refId'
roles[EventRoles.ReactionsRole.value] = b'reactions'

return roles
7 changes: 7 additions & 0 deletions demo/qml/AnimatedTransition.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import QtQuick 2.0
import Quotient 1.0

Transition {
property var settings: TimelineSettings { }
enabled: settings.enable_animations
}
7 changes: 7 additions & 0 deletions demo/qml/AnimationBehavior.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import QtQuick 2.0
import Quotient 1.0

Behavior {
property var settings: TimelineSettings { }
enabled: settings.enable_animations
}
49 changes: 49 additions & 0 deletions demo/qml/Attachment.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import QtQuick 2.0
import Quotient 1.0

Item {
width: parent.width
height: visible ? childrenRect.height : 0

property bool openOnFinished: false
readonly property bool downloaded: progressInfo &&
!progressInfo.isUpload && progressInfo.completed

onDownloadedChanged: {
if (downloaded && openOnFinished)
openLocalFile()
}

function openExternally()
{
if (progressInfo.localPath.toString() || downloaded)
openLocalFile()
else
{
openOnFinished = true
room.downloadFile(eventId)
}
}

function openLocalFile()
{
if (Qt.openUrlExternally(progressInfo.localPath))
return;

controller.showStatusMessage(
"Couldn't determine how to open the file, " +
"opening its folder instead", 5000)

if (Qt.openUrlExternally(progressInfo.localDir))
return;

controller.showStatusMessage(
"Couldn't determine how to open the file or its folder.",
5000)
}

Connections {
target: controller
onOpenExternally: if (currentIndex === index) openExternally()
}
}
15 changes: 15 additions & 0 deletions demo/qml/AuthorInteractionArea.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
TimelineMouseArea {
property var authorId

enabled: parent.visible
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton|Qt.MiddleButton
hoverEnabled: true
onEntered: controller.showStatusMessage(authorId)
onExited: controller.showStatusMessage("")
onClicked:
controller.resourceRequested(authorId,
mouse.button === Qt.LeftButton
? "mention" : "_interactive")
}
7 changes: 7 additions & 0 deletions demo/qml/FastNumberAnimation.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import QtQuick 2.0
import Quotient 1.0

NumberAnimation {
property var settings: TimelineSettings { }
duration: settings.fast_animations_duration_ms
}
47 changes: 47 additions & 0 deletions demo/qml/ImageContent.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import QtQuick 2.0
// import QtQuick.Controls 1.4
// import QtQuick.Layouts 1.1

Attachment {
property var sourceSize
property url source
property var maxHeight
property bool autoload

// Image {
// id: imageContent
// width: parent.width
// height: sourceSize.height *
// Math.min(maxHeight / sourceSize.height * 0.9,
// Math.min(width / sourceSize.width, 1))
// fillMode: Image.PreserveAspectFit
// horizontalAlignment: Image.AlignLeft

// source: parent.source
// sourceSize: parent.sourceSize

// TimelineMouseArea {
// anchors.fill: parent
// acceptedButtons: Qt.LeftButton
// hoverEnabled: true

// onContainsMouseChanged:
// controller.showStatusMessage(containsMouse
// ? room.fileSource(eventId) : "")
// onClicked: openExternally()
// }

// TimelineMouseArea {
// anchors.fill: parent
// acceptedButtons: Qt.RightButton
// cursorShape: Qt.PointingHandCursor
// onClicked: controller.showMenu(index, textFieldImpl.hoveredLink,
// textFieldImpl.selectedText, showingDetails)
// }

// Component.onCompleted:
// if (visible && autoload && !downloaded && !(progressInfo && progressInfo.isUpload))
// room.downloadFile(eventId)
// }

}
32 changes: 32 additions & 0 deletions demo/qml/MyToolTip.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import QtQuick 2.0
import QtQuick.Controls 2.1
import Quotient 1.0

ToolTip {
id:tooltip
TimelineSettings { id: settings }

padding: 4
font: settings.font

background: Rectangle {
SystemPalette { id: palette; colorGroup: SystemPalette.Active }
radius: 3
color: palette.window
border.width: 1
border.color: palette.windowText
}
enter: AnimatedTransition { NormalNumberAnimation {
target: tooltip
property: "opacity"
easing.type: Easing.OutQuad
from: 0
to: 0.9
} }
exit: AnimatedTransition { FastNumberAnimation {
target: tooltip
property: "opacity"
easing.type: Easing.InQuad
to: 0
} }
}
7 changes: 7 additions & 0 deletions demo/qml/NormalNumberAnimation.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import QtQuick 2.0
import Quotient 1.0

NumberAnimation {
property var settings: TimelineSettings { }
duration: settings.animations_duration_ms
}
23 changes: 23 additions & 0 deletions demo/qml/ScrollToButton.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import QtQuick 2.9
import QtQuick.Controls 2.2

RoundButton {
height: settings.fontHeight * 2
width: height
hoverEnabled: true
opacity: visible * (0.7 + hovered * 0.2)

display: Button.IconOnly
icon.color: defaultPalette.buttonText

AnimationBehavior on opacity {
NormalNumberAnimation {
easing.type: Easing.OutQuad
}
}
AnimationBehavior on anchors.bottomMargin {
NormalNumberAnimation {
easing.type: Easing.OutQuad
}
}
}
Loading