From ce6ba79401533fc49d71f7fd53fa27b8c66fab30 Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Wed, 18 Aug 2021 10:45:15 +0200 Subject: [PATCH 1/6] Minimal version of ChatRoomWidget with test messages(stubs). --- demo/chatroomwidget.py | 60 + demo/mainwindow.py | 6 + demo/models/messageeventmodel.py | 87 ++ demo/qml/AnimatedTransition.qml | 7 + demo/qml/AnimationBehavior.qml | 7 + demo/qml/Attachment.qml | 49 + demo/qml/AuthorInteractionArea.qml | 15 + demo/qml/FastNumberAnimation.qml | 7 + demo/qml/ImageContent.qml | 47 + demo/qml/MyToolTip.qml | 32 + demo/qml/NormalNumberAnimation.qml | 7 + demo/qml/ScrollToButton.qml | 23 + demo/qml/Timeline.qml | 741 ++++++++++++ demo/qml/TimelineItem.qml | 698 ++++++++++++ demo/qml/TimelineItemToolButton.qml | 22 + demo/qml/TimelineMouseArea.qml | 6 + demo/qml/TimelineSettings.qml | 34 + demo/qml/TimelineTextEditSelector.qml | 54 + demo/qml/ToolTipArea.qml | 12 + demo/resources.py | 1487 +++++++++++++++++++++++++ demo/resources.qrc | 20 + 21 files changed, 3421 insertions(+) create mode 100644 demo/chatroomwidget.py create mode 100644 demo/models/messageeventmodel.py create mode 100644 demo/qml/AnimatedTransition.qml create mode 100644 demo/qml/AnimationBehavior.qml create mode 100644 demo/qml/Attachment.qml create mode 100644 demo/qml/AuthorInteractionArea.qml create mode 100644 demo/qml/FastNumberAnimation.qml create mode 100644 demo/qml/ImageContent.qml create mode 100644 demo/qml/MyToolTip.qml create mode 100644 demo/qml/NormalNumberAnimation.qml create mode 100644 demo/qml/ScrollToButton.qml create mode 100644 demo/qml/Timeline.qml create mode 100644 demo/qml/TimelineItem.qml create mode 100644 demo/qml/TimelineItemToolButton.qml create mode 100644 demo/qml/TimelineMouseArea.qml create mode 100644 demo/qml/TimelineSettings.qml create mode 100644 demo/qml/TimelineTextEditSelector.qml create mode 100644 demo/qml/ToolTipArea.qml create mode 100644 demo/resources.py create mode 100644 demo/resources.qrc diff --git a/demo/chatroomwidget.py b/demo/chatroomwidget.py new file mode 100644 index 0000000..eea4932 --- /dev/null +++ b/demo/chatroomwidget.py @@ -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): + ... diff --git a/demo/mainwindow.py b/demo/mainwindow.py index 10ac8bf..160d578 100644 --- a/demo/mainwindow.py +++ b/demo/mainwindow.py @@ -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 @@ -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() diff --git a/demo/models/messageeventmodel.py b/demo/models/messageeventmodel.py new file mode 100644 index 0000000..0d3a8af --- /dev/null +++ b/demo/models/messageeventmodel.py @@ -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') + + 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 diff --git a/demo/qml/AnimatedTransition.qml b/demo/qml/AnimatedTransition.qml new file mode 100644 index 0000000..4168012 --- /dev/null +++ b/demo/qml/AnimatedTransition.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Quotient 1.0 + +Transition { + property var settings: TimelineSettings { } + enabled: settings.enable_animations +} diff --git a/demo/qml/AnimationBehavior.qml b/demo/qml/AnimationBehavior.qml new file mode 100644 index 0000000..7ed7926 --- /dev/null +++ b/demo/qml/AnimationBehavior.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Quotient 1.0 + +Behavior { + property var settings: TimelineSettings { } + enabled: settings.enable_animations +} diff --git a/demo/qml/Attachment.qml b/demo/qml/Attachment.qml new file mode 100644 index 0000000..e59d401 --- /dev/null +++ b/demo/qml/Attachment.qml @@ -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() + } +} diff --git a/demo/qml/AuthorInteractionArea.qml b/demo/qml/AuthorInteractionArea.qml new file mode 100644 index 0000000..d26a463 --- /dev/null +++ b/demo/qml/AuthorInteractionArea.qml @@ -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") +} diff --git a/demo/qml/FastNumberAnimation.qml b/demo/qml/FastNumberAnimation.qml new file mode 100644 index 0000000..a10368d --- /dev/null +++ b/demo/qml/FastNumberAnimation.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Quotient 1.0 + +NumberAnimation { + property var settings: TimelineSettings { } + duration: settings.fast_animations_duration_ms +} diff --git a/demo/qml/ImageContent.qml b/demo/qml/ImageContent.qml new file mode 100644 index 0000000..1f4f499 --- /dev/null +++ b/demo/qml/ImageContent.qml @@ -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) + // } + +} diff --git a/demo/qml/MyToolTip.qml b/demo/qml/MyToolTip.qml new file mode 100644 index 0000000..2beeead --- /dev/null +++ b/demo/qml/MyToolTip.qml @@ -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 + } } +} diff --git a/demo/qml/NormalNumberAnimation.qml b/demo/qml/NormalNumberAnimation.qml new file mode 100644 index 0000000..de203c9 --- /dev/null +++ b/demo/qml/NormalNumberAnimation.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Quotient 1.0 + +NumberAnimation { + property var settings: TimelineSettings { } + duration: settings.animations_duration_ms +} diff --git a/demo/qml/ScrollToButton.qml b/demo/qml/ScrollToButton.qml new file mode 100644 index 0000000..f438f2c --- /dev/null +++ b/demo/qml/ScrollToButton.qml @@ -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 + } + } +} diff --git a/demo/qml/Timeline.qml b/demo/qml/Timeline.qml new file mode 100644 index 0000000..4f0ca83 --- /dev/null +++ b/demo/qml/Timeline.qml @@ -0,0 +1,741 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.1 +// import QtGraphicalEffects 1.0 // For fancy highlighting +import Quotient 1.0 + +Rectangle { + id: root + + TimelineSettings { + id: settings + readonly property bool use_shuttle_dial: value("UI/use_shuttle_dial", true) + + Component.onCompleted: console.log("Using timeline font: " + font) + } + SystemPalette { id: defaultPalette; colorGroup: SystemPalette.Active } + SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } + + color: defaultPalette.base + radius: 2 + + function humanSize(bytes) + { + if (!bytes) + return qsTr("Unknown", "Unknown attachment size") + if (bytes < 4000) + return qsTr("%Ln byte(s)", "", bytes) + bytes = Math.round(bytes / 100) / 10 + if (bytes < 2000) + return qsTr("%L1 kB").arg(bytes) + bytes = Math.round(bytes / 100) / 10 + if (bytes < 2000) + return qsTr("%L1 MB").arg(bytes) + return qsTr("%L1 GB").arg(Math.round(bytes / 100) / 10) + } + + function mixColors(baseColor, mixedColor, mixRatio) + { + return Qt.tint(baseColor, + Qt.rgba(mixedColor.r, mixedColor.g, mixedColor.b, mixRatio)) + } + + Rectangle { + id: roomHeader + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: headerText.height + 5 + + color: defaultPalette.window + border.color: disabledPalette.windowText + radius: 2 + visible: room + + Image { + id: roomAvatar + anchors.verticalCenter: headerText.verticalCenter + anchors.left: parent.left + anchors.margins: 2 + height: headerText.height + // implicitWidth on its own doesn't respect the scale down of + // the received image (that almost always happens) + width: Math.min(headerText.height / implicitHeight * implicitWidth, + parent.width / 2.618) // Golden ratio - just for fun + + source: room && room.avatarMediaId + ? "image://mtx/" + room.avatarMediaId : "" + // Safe upper limit (see also topicField) + sourceSize: Qt.size(-1, settings.lineSpacing * 9) + + fillMode: Image.PreserveAspectFit + + AnimationBehavior on width { + NormalNumberAnimation { easing.type: Easing.OutQuad } + } + } + + Column { + id: headerText + anchors.left: roomAvatar.right + anchors.right: versionActionButton.left + anchors.top: parent.top + anchors.margins: 2 + + spacing: 2 + + TextEdit { + id: roomName + width: roomNameMetrics.advanceWidth + height: roomNameMetrics.height + clip: true + + readonly property bool hasName: room && room.displayName !== "" + TextMetrics { + id: roomNameMetrics + font: roomName.font + elide: Text.ElideRight + elideWidth: headerText.width + text: roomName.hasName ? room.displayName : qsTr("(no name)") + } + + text: roomNameMetrics.elidedText + color: (hasName ? defaultPalette : disabledPalette).windowText + + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + renderType: settings.render_type + readOnly: true + selectByKeyboard: true + selectByMouse: true + + ToolTipArea { + enabled: roomName.hasName && + (roomNameMetrics.text != roomNameMetrics.elidedText + || roomName.lineCount > 1) + text: room ? room.htmlSafeDisplayName : "" + } + } + + Label { + id: versionNotice + visible: room && (room.isUnstable || room.successorId !== "") + width: parent.width + + text: !room ? "" : + room.successorId !== "" + ? qsTr("This room has been upgraded.") : + room.isUnstable ? qsTr("Unstable room version!") : "" + elide: Text.ElideRight + font.italic: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + renderType: settings.render_type + ToolTipArea { + enabled: parent.truncated + text: parent.text + } + } + ScrollView { + id: topicField + width: parent.width + // Allow 6 lines of the topic (or 20% of the vertical space); + // if there are more than 6 lines, show half-line as a hint + height: Math.min(topicText.contentHeight, root.height / 5, + settings.lineSpacing * 6.5) + clip: true + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + AnimationBehavior on height { + NormalNumberAnimation { easing.type: Easing.OutQuad } + } + + // FIXME: The below TextEdit+MouseArea is a massive copy-paste + // from TimelineItem.qml. We need to make a separate component + // for these (RichTextField?). + TextEdit { + id: topicText + width: topicField.width + + readonly property bool hasTopic: room && room.topic !== "" + text: hasTopic + ? room.prettyPrint(room.topic) : qsTr("(no topic)") + color: + (hasTopic ? defaultPalette : disabledPalette).windowText + textFormat: TextEdit.RichText + font: settings.font + renderType: settings.render_type + readOnly: true + selectByKeyboard: true; + selectByMouse: true; + wrapMode: TextEdit.Wrap + + onHoveredLinkChanged: + controller.showStatusMessage(hoveredLink) + + onLinkActivated: controller.resourceRequested(link) + } + } + } + MouseArea { + anchors.fill: headerText + acceptedButtons: Qt.MiddleButton + cursorShape: topicText.hoveredLink + ? Qt.PointingHandCursor : Qt.IBeamCursor + + onClicked: { + if (topicText.hoveredLink) + controller.resourceRequested(topicText.hoveredLink, + "_interactive") + } + } + Button { + id: versionActionButton + visible: room && ((room.isUnstable && room.canSwitchVersions()) + || room.successorId !== "") + anchors.verticalCenter: headerText.verticalCenter + anchors.right: parent.right + width: visible * implicitWidth + text: !room ? "" : room.successorId !== "" + ? qsTr("Go to\nnew room") : qsTr("Room\nsettings") + + onClicked: + if (room.successorId !== "") + controller.resourceRequested(room.successorId, "join") + else + controller.roomSettingsRequested() + } + } + + DropArea { + anchors.fill: parent + onEntered: if (!room) drag.accepted = false + onDropped: { + if (drop.hasUrls) { + controller.fileDrop(drop.urls) + drop.acceptProposedAction() + } else if (drop.hasHtml) { + controller.htmlDrop(drop.html) + drop.acceptProposedAction() + } else if (drop.hasText) { + controller.textDrop(drop.text) + drop.acceptProposedAction() + } + } + } + ScrollView { + id: chatScrollView + anchors.top: roomHeader.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: + settings.use_shuttle_dial ? ScrollBar.AlwaysOff + : ScrollBar.AsNeeded + ScrollBar.vertical.interactive: true + ScrollBar.vertical.active: true +// ScrollBar.vertical.background: Item { /* TODO: timeline map */ } + + ListView { + id: chatView + + model: messageModel + delegate: TimelineItem { + width: chatView.width - scrollerArea.width + view: chatView + moving: chatView.moving || shuttleDial.value + // #737; the solution found in + // https://bugreports.qt.io/browse/QT3DS-784 + ListView.delayRemove: true + } + verticalLayoutDirection: ListView.BottomToTop + flickableDirection: Flickable.VerticalFlick + flickDeceleration: 8000 + boundsBehavior: Flickable.StopAtBounds +// pixelAligned: true // Causes false-negatives in atYEnd + cacheBuffer: 200 + + section.property: "section" + + readonly property int bottommostVisibleIndex: count > 0 ? + atYEnd ? 0 : indexAt(contentX, contentY + height - 1) : -1 + readonly property bool noNeedMoreContent: + !room || room.eventsHistoryJob || room.allHistoryLoaded + + /// The number of events per height unit - always positive + readonly property real eventDensity: + contentHeight > 0 && count > 0 ? count / contentHeight : 0.03 + // 0.03 is just an arbitrary reasonable number + + property int lastRequestedEvents: 0 + readonly property int currentRequestedEvents: + room && room.eventsHistoryJob ? lastRequestedEvents : 0 + + property var textEditWithSelection + property real readMarkerContentPos: originY + readonly property real readMarkerViewportPos: + readMarkerContentPos < contentY ? 0 : + readMarkerContentPos > contentY + height ? height + readMarkerLine.height : + readMarkerContentPos - contentY + + function parkReadMarker() { + readMarkerContentPos = Qt.binding(function() { + return !messageModel || messageModel.readMarkerVisualIndex + > indexAt(contentX, contentY) + ? originY : contentY + contentHeight + }) + console.log("Read marker parked at index", + messageModel.readMarkerVisualIndex + + ", content pos", chatView.readMarkerContentPos, + "(full range is", chatView.originY, "-", + chatView.originY + chatView.contentHeight, + "as of now)") + } + + function ensurePreviousContent() { + if (noNeedMoreContent) + return + + // Take the current speed, or assume we can scroll 8 screens/s + var velocity = moving ? -verticalVelocity : + cruisingAnimation.running ? + cruisingAnimation.velocity : + chatScrollView.height * 8 + // Check if we're about to bump into the ceiling in + // 2 seconds and if yes, request the amount of messages + // enough to scroll at this rate for 3 more seconds + if (velocity > 0 && contentY - velocity*2 < originY) { + lastRequestedEvents = velocity * eventDensity * 3 + room.getPreviousContent(lastRequestedEvents) + } + } + onContentYChanged: ensurePreviousContent() + onContentHeightChanged: ensurePreviousContent() + + function saveViewport() { + if (room) + room.saveViewport(indexAt(contentX, contentY), + bottommostVisibleIndex) + } + + function onModelReset() { + console.log("Model timeline reset") + if (room) + { + forceLayout() + // Load events if there are not enough of them + ensurePreviousContent() + var lastScrollPosition = room.savedTopVisibleIndex() + console.log("Scrolling to position", lastScrollPosition) + positionViewAtIndex(lastScrollPosition, ListView.Contain) + } + } + + function scrollUp(dy) { + if (contentHeight > height) + contentY = Math.max(originY, contentY - dy) + } + function scrollDown(dy) { + if (contentHeight > height) + contentY = Math.min(originY + contentHeight - height, + contentY + dy) + } + + function onWheel(wheel) { + if (wheel.angleDelta.x === 0) { + var yDelta = wheel.angleDelta.y * 10 / 36 + + if (yDelta > 0) + scrollUp(yDelta) + else + scrollDown(-yDelta) + wheel.accepted = true + } else { + wheel.accepted = false + } + } + Connections { + target: controller + // TODO + // onPageUpPressed: + // chatView.scrollUp(chatView.height + // - sectionBanner.childrenRect.height) + + // onPageDownPressed: + // chatView.scrollDown(chatView.height + // - sectionBanner.childrenRect.height) + // onScrollViewTo: chatView.positionViewAtIndex(currentIndex, ListView.Contain) + } + + Component.onCompleted: { + console.log("QML view loaded") + model.modelAboutToBeReset.connect(parkReadMarker) + // FIXME: This is not on the right place: ListView may or + // may not have updated his structures according to the new + // model by now + model.modelReset.connect(onModelReset) + } + + onMovementEnded: saveViewport() + + populate: AnimatedTransition { + // FastNumberAnimation { property: "opacity"; from: 0; to: 1 } + } + + add: AnimatedTransition { + // FastNumberAnimation { property: "opacity"; from: 0; to: 1 } + } + + move: AnimatedTransition { + // FastNumberAnimation { property: "y"; } + // FastNumberAnimation { property: "opacity"; to: 1 } + } + + displaced: AnimatedTransition { + // FastNumberAnimation { + // property: "y"; + // easing.type: Easing.OutQuad + // } + // FastNumberAnimation { property: "opacity"; to: 1 } + } + + Behavior on contentY { + enabled: settings.enable_animations && !chatView.moving + && !cruisingAnimation.running + // SmoothedAnimation { + // id: scrollAnimation + // duration: settings.fast_animations_duration_ms / 3 + // maximumEasingTime: settings.fast_animations_duration_ms + + // onRunningChanged: { + // if (!running) + // chatView.saveViewport() + // } + // } + } + + AnimationBehavior on readMarkerContentPos { + // NormalNumberAnimation { easing.type: Easing.OutQuad } + } + + // This covers the area above the items if there are not enough + // of them to fill the viewport + MouseArea { + z: -1 + anchors.fill: parent + acceptedButtons: Qt.AllButtons + onReleased: controller.focusInput() + } + + Rectangle { + id: readShade + + visible: chatView.count > 0 + anchors.top: parent.top + anchors.topMargin: chatView.originY > chatView.contentY + ? chatView.originY - chatView.contentY : 0 + /// At the bottom of the read shade is the read marker. If + /// the last read item is on the screen, the read marker is at + /// the item's bottom; otherwise, it's just beyond the edge of + /// chatView in the direction of the read marker index (or the + /// timeline, if the timeline is short enough). + /// @sa readMarkerViewportPos + height: chatView.readMarkerViewportPos - anchors.topMargin + anchors.left: parent.left + width: readMarkerLine.width + z: -1 + opacity: 0.1 + + radius: readMarkerLine.height + color: mixColors(disabledPalette.base, defaultPalette.highlight, 0.5) + } + Rectangle { + id: readMarkerLine + + visible: chatView.count > 0 + width: parent.width - scrollerArea.width + anchors.bottom: readShade.bottom + height: 4 + z: 2.5 // On top of any ListView content, below the banner + + gradient: Gradient { + GradientStop { position: 0; color: "transparent" } + GradientStop { position: 1; color: defaultPalette.highlight } + } + } + + + // itemAt is a function rather than a property, so it doesn't + // produce a QML binding; the piece with contentHeight compensates. + readonly property var underlayingItem: contentHeight >= height && + itemAt(contentX, contentY + sectionBanner.height - 2) + readonly property bool sectionBannerVisible: underlayingItem && + (!underlayingItem.sectionVisible || underlayingItem.y < contentY) + + Rectangle { + id: sectionBanner + z: 3 // On top of ListView sections that have z=2 + anchors.left: parent.left + anchors.top: parent.top + width: childrenRect.width + 2 + height: childrenRect.height + 2 + visible: chatView.sectionBannerVisible + color: defaultPalette.window + opacity: 0.8 + Label { + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + opacity: 0.8 + renderType: settings.render_type + text: chatView.underlayingItem ? + chatView.underlayingItem.ListView.section : "" + } + } + } + } + + // === Timeline map === + // Only used with the shuttle scroller for now + + Rectangle { + id: cachedEventsBar + + // A proxy property for animation + property int requestedHistoryEventsCount: + chatView.currentRequestedEvents + AnimationBehavior on requestedHistoryEventsCount { + // NormalNumberAnimation { } + } + + property real averageEvtHeight: + chatView.count + requestedHistoryEventsCount > 0 + ? chatView.height + / (chatView.count + requestedHistoryEventsCount) + : 0 + AnimationBehavior on averageEvtHeight { + // FastNumberAnimation { } + } + + anchors.horizontalCenter: shuttleDial.horizontalCenter + anchors.bottom: chatScrollView.bottom + anchors.bottomMargin: + averageEvtHeight * chatView.bottommostVisibleIndex + width: shuttleDial.backgroundWidth / 2 + height: chatView.bottommostVisibleIndex < 0 ? 0 : + averageEvtHeight + * (chatView.count - chatView.bottommostVisibleIndex) + visible: shuttleDial.visible + + color: defaultPalette.mid + } + Rectangle { + // Loading history events bar, stacked above + // the cached events bar when more history has been requested + anchors.right: cachedEventsBar.right + anchors.top: chatScrollView.top + anchors.bottom: cachedEventsBar.top + width: cachedEventsBar.width + visible: shuttleDial.visible + + opacity: 0.4 + color: defaultPalette.mid + } + + // === Scrolling extensions === + + Slider { + id: shuttleDial + orientation: Qt.Vertical + height: chatScrollView.height * 0.7 + width: chatScrollView.ScrollBar.vertical.width + padding: 2 + anchors.right: parent.right + anchors.rightMargin: (background.width - width) / 2 + anchors.verticalCenter: chatScrollView.verticalCenter + enabled: settings.use_shuttle_dial + visible: enabled && chatView.count > 0 + + readonly property real backgroundWidth: + handle.width + leftPadding + rightPadding + // Npages/sec = value^2 => maxNpages/sec = 9 + readonly property real maxValue: 3.0 + readonly property real deviation: + value / (maxValue * 2) * availableHeight + + background: Item { + x: shuttleDial.handle.x - shuttleDial.leftPadding + width: shuttleDial.backgroundWidth + Rectangle { + id: springLine + // Rectangles (normally) have (x,y) as their top-left corner. + // To draw the "spring" line up from the middle point, its `y` + // should still be the top edge, not the middle point. + y: shuttleDial.height / 2 - Math.max(shuttleDial.deviation, 0) + height: Math.abs(shuttleDial.deviation) + anchors.horizontalCenter: parent.horizontalCenter + width: 2 + color: defaultPalette.highlight + } + } + opacity: scrollerArea.containsMouse ? 1 : 0.7 + AnimationBehavior on opacity { + // FastNumberAnimation { } + } + + from: -maxValue + to: maxValue + + activeFocusOnTab: false + + onPressedChanged: { + if (!pressed) { + value = 0 + controller.focusInput() + } + } + + // This is not an ordinary animation, it's the engine that makes + // the shuttle dial work; for that reason it's not governed by + // settings.enable_animations and only can be disabled together with + // the shuttle dial. + SmoothedAnimation { + id: cruisingAnimation + target: chatView + property: "contentY" + velocity: shuttleDial.value * shuttleDial.value * chatView.height + maximumEasingTime: settings.animations_duration_ms + to: chatView.originY + (shuttleDial.value > 0 ? 0 : + chatView.contentHeight - chatView.height) + running: shuttleDial.value != 0 + + onStopped: chatView.saveViewport() + } + + // Animations don't update `to` value when they are running; so + // when the shuttle value changes sign without becoming zero (which, + // turns out, is quite usual when dragging the shuttle around) the + // animation has to be restarted. + onValueChanged: cruisingAnimation.restart() + Component.onCompleted: { // same reason as above + chatView.originYChanged.connect(cruisingAnimation.restart) + chatView.contentHeightChanged.connect(cruisingAnimation.restart) + } + } + + MouseArea { + id: scrollerArea + anchors.top: chatScrollView.top + anchors.bottom: chatScrollView.bottom + anchors.right: parent.right + width: settings.use_shuttle_dial + ? shuttleDial.backgroundWidth + : chatScrollView.ScrollBar.vertical.width + acceptedButtons: Qt.NoButton + + hoverEnabled: true + } + + Rectangle { + id: timelineStats + anchors.right: scrollerArea.left + anchors.top: chatScrollView.top + width: childrenRect.width + 3 + height: childrenRect.height + 3 + color: defaultPalette.alternateBase + property bool shown: + (chatView.bottommostVisibleIndex >= 0) + // && (scrollerArea.containsMouse || scrollAnimation.running)) + || chatView.currentRequestedEvents > 0 + + onShownChanged: { + if (shown) { + fadeOutDelay.stop() + opacity = 0.8 + } else + fadeOutDelay.restart() + } + Timer { + id: fadeOutDelay + interval: 2000 + onTriggered: parent.opacity = 0 + } + + AnimationBehavior on opacity { + // FastNumberAnimation { } + } + + Label { + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + opacity: 0.8 + renderType: settings.render_type + text: (chatView.count > 0 + ? (chatView.bottommostVisibleIndex === 0 + ? qsTr("Latest events") + : qsTr("%Ln events back from now","", + chatView.bottommostVisibleIndex)) + + "\n" + qsTr("%Ln events cached", "", chatView.count) + : "") + + (chatView.currentRequestedEvents > 0 + ? (chatView.count > 0 ? "\n" : "") + + qsTr("%Ln events requested from the server", + "", chatView.currentRequestedEvents) + : "") + horizontalAlignment: Label.AlignRight + } + } + + ScrollToButton { + id: scrollToBottomButton + + anchors.right: scrollerArea.left + anchors.rightMargin: 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: visible ? 0.5 * height : -height + + visible: !chatView.atYEnd + + icon { + name: "go-bottom" + source: "qrc:///scrolldown.svg" + } + + onClicked: { + chatView.positionViewAtBeginning() + chatView.saveViewport() + } + } + + ScrollToButton { + id: scrollToReaderMarkerButton + + anchors.right: scrollerArea.left + anchors.rightMargin: 2 + anchors.bottom: scrollToBottomButton.top + anchors.bottomMargin: visible ? 0.5 * height : -3 * height + + visible: chatView.count > 1 && + messageModel.readMarkerVisualIndex > 0 && + messageModel.readMarkerVisualIndex + > chatView.indexAt(chatView.contentX, chatView.contentY) + + icon { + name: "go-top" + source: "qrc:///scrollup.svg" + } + + onClicked: { + chatView.positionViewAtIndex(messageModel.readMarkerVisualIndex, + ListView.Center) + chatView.saveViewport() + } + } +} diff --git a/demo/qml/TimelineItem.qml b/demo/qml/TimelineItem.qml new file mode 100644 index 0000000..6aafc83 --- /dev/null +++ b/demo/qml/TimelineItem.qml @@ -0,0 +1,698 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +// import QtGraphicalEffects 1.0 // For fancy highlighting +import Quotient 1.0 + +Item { + // Supplementary components + + TimelineSettings { + id: settings + readonly property bool autoload_images: value("UI/autoload_images", true) + readonly property string highlight_mode: value("UI/highlight_mode", "background") + readonly property color highlight_color: value("UI/highlight_color", "orange") + readonly property color outgoing_color_base: value("UI/outgoing_color", "#4A8780") + readonly property color outgoing_color: + mixColors(defaultPalette.text, settings.outgoing_color_base, 0.5) + readonly property bool show_author_avatars: + value("UI/show_author_avatars", timeline_style != "xchat") + } + SystemPalette { id: defaultPalette; colorGroup: SystemPalette.Active } + SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } + + // Property interface + + /** Determines whether the view is moving at the moment */ + property var view + property bool moving: view.moving + + // TimelineItem definition + + visible: marks !== EventStatus.Hidden + enabled: visible + height: childrenRect.height * visible + + readonly property bool sectionVisible: section !== aboveSection + readonly property bool authorSectionVisible: + sectionVisible || author !== aboveAuthor + readonly property bool replaced: marks === EventStatus.Replaced + readonly property bool pending: [ + EventStatus.Submitted, + EventStatus.Departed, + EventStatus.ReachedServer, + EventStatus.SendingFailed + ].indexOf(marks) != -1 + readonly property bool failed: marks === EventStatus.SendingFailed + readonly property bool eventWithTextPart: + ["message", "emote", "image", "file"].indexOf(eventType) >= 0 + /* readonly but animated */ property string textColor: + marks === EventStatus.Submitted || failed ? disabledPalette.text : + marks === EventStatus.Departed ? + mixColors(disabledPalette.text, defaultPalette.text, 0.5) : + marks === EventStatus.Redacted ? disabledPalette.text : + (eventWithTextPart && author === room.localUser) ? settings.outgoing_color : + highlight && settings.highlight_mode == "text" ? settings.highlight_color : + (["state", "notice", "other"].indexOf(eventType) >= 0) + ? mixColors(disabledPalette.text, defaultPalette.text, 0.5) + : defaultPalette.text + readonly property string authorName: room && room.safeMemberName(author.id) + // FIXME: boilerplate with models/userlistmodel.cpp:115 + readonly property string authorColor: Qt.hsla(userHue, + (1-defaultPalette.window.hslSaturation), + /* contrast but not too heavy: */ + (-0.7*defaultPalette.window.hslLightness + 0.9), + defaultPalette.buttonText.a) + + readonly property bool xchatStyle: settings.timeline_style === "xchat" + readonly property bool actionEvent: eventType == "state" || eventType == "emote" + + readonly property bool readMarkerHere: messageModel.readMarkerVisualIndex === index + + readonly property bool partiallyShown: y + height - 1 > view.contentY + && y < view.contentY + view.height + + readonly property bool bottomEdgeShown: + y + height - 1 > view.contentY + && y + height - 1 < view.contentY + view.height + + onBottomEdgeShownChanged: { + // A message is considered as "read" if its bottom spent long enough + // within the viewing area of the timeline + if (!pending) + controller.onMessageShownChanged(eventId, bottomEdgeShown) + } + + onPendingChanged: bottomEdgeShownChanged() + + onReadMarkerHereChanged: { + if (readMarkerHere) { + if (partiallyShown) { + chatView.readMarkerContentPos = + Qt.binding(function() { return y + height }) + console.log("Read marker line bound at index", index) + } else + chatView.parkReadMarker() + } + } + + onPartiallyShownChanged: readMarkerHereChanged() + + Component.onCompleted: { + if (bottomEdgeShown) + bottomEdgeShownChanged(true) + readMarkerHereChanged() + } + + AnimationBehavior on textColor { + ColorAnimation { duration: settings.animations_duration_ms } + } + + property bool showingDetails + + Connections { + target: controller + // TODO + // onShowDetails: { + // if (currentIndex === index) { + // showingDetails = !showingDetails + // if (!settings.enable_animations) { + // detailsAreaLoader.visible = showingDetails + // detailsAreaLoader.opacity = showingDetails + // } else + // detailsAnimation.start() + // } + // } + // onAnimateMessage: { + // if (currentIndex === index) + // blinkAnimation.start() + // } + } + + SequentialAnimation { + id: detailsAnimation + PropertyAction { + target: detailsAreaLoader; property: "visible" + value: true + } + FastNumberAnimation { + target: detailsAreaLoader; property: "opacity" + to: showingDetails + easing.type: Easing.OutQuad + } + PropertyAction { + target: detailsAreaLoader; property: "visible" + value: showingDetails + } + } + SequentialAnimation { + id: blinkAnimation + loops: 3 + PropertyAction { + target: messageFlasher; property: "visible" + value: true + } + PauseAnimation { + // `settings.animations_duration_ms` intentionally is not in use here + // because this is not just an eye candy animation - the user will lose + // functionality if this animation stops working. + duration: 200 + } + PropertyAction { + target: messageFlasher; property: "visible" + value: false + } + PauseAnimation { + duration: 200 + } + } + + TimelineMouseArea { + anchors.fill: fullMessage + acceptedButtons: Qt.AllButtons + } + + Column { + id: fullMessage + width: parent.width + + Rectangle { + width: parent.width + height: childrenRect.height + 2 + visible: sectionVisible + color: defaultPalette.window + Label { + font.family: settings.font.family + font.pointSize: settings.font.pointSize + font.bold: true + renderType: settings.render_type + text: section + } + } + Loader { + id: detailsAreaLoader +// asynchronous: true // https://bugreports.qt.io/browse/QTBUG-50992 + active: visible + visible: false // Controlled by showDetailsButton + opacity: 0 + width: parent.width + + sourceComponent: detailsArea + } + + Item { + id: message + width: parent.width + height: childrenRect.height + + // There are several layout styles (av - author avatar, + // al - author label, ts - timestamp, c - content + // default (when "timeline_style" is not "xchat"): + // av al + // c ts + // action events (for state and emote events): + // av (al+c in a single control) ts + // (spanning both rows ) + // xchat (when "timeline_style" is "xchat"): + // ts av al c + // xchat action events + // ts av *(asterisk) al c + // + // For any layout, authorAvatar.top is the vertical anchor + // (can't use parent.top because of using childrenRect.height) + + Label { + id: timelabel + visible: xchatStyle + width: if (!visible) { 0 } + anchors.top: authorAvatar.top + anchors.left: parent.left + + opacity: 0.8 + renderType: settings.render_type + font.family: settings.font.family + font.pointSize: settings.font.pointSize + font.italic: pending + + // TODO + // text: "<" + time.toLocaleTimeString(Qt.locale(), + // Locale.ShortFormat) + ">" + text: 'time' + } + Image { + id: authorAvatar + visible: (authorSectionVisible || xchatStyle) + && settings.show_author_avatars && author.avatarMediaId + anchors.left: timelabel.right + anchors.leftMargin: 3 + height: visible ? settings.lineSpacing * (2 - xchatStyle) + : authorLabel.height + + // 2 text line heights by default; 1 line height for XChat + width: settings.show_author_avatars + * settings.lineSpacing * (2 - xchatStyle) + + fillMode: Image.PreserveAspectFit + horizontalAlignment: Image.AlignRight + + source: author.avatarMediaId + ? "image://mtx/" + author.avatarMediaId : "" + sourceSize: Qt.size(width, -1) + + AuthorInteractionArea { authorId: author.id } + AnimationBehavior on height { FastNumberAnimation { } } + } + Label { + id: authorLabel + visible: xchatStyle || (!actionEvent && authorSectionVisible) + anchors.left: authorAvatar.right + anchors.leftMargin: 2 + anchors.top: authorAvatar.top + width: xchatStyle ? 120 - authorAvatar.width + : Math.min(textField.width, implicitWidth) + horizontalAlignment: + actionEvent ? Text.AlignRight : Text.AlignLeft + elide: Text.ElideRight + + color: authorColor + textFormat: Label.PlainText + font.family: settings.font.family + font.pointSize: settings.font.pointSize + font.bold: !xchatStyle + renderType: settings.render_type + + text: (actionEvent ? "* " : "") + authorName + + AuthorInteractionArea { authorId: author.id } + } + + Item { + id: textField + height: textFieldImpl.height + anchors.top: + !xchatStyle && authorLabel.visible ? authorLabel.bottom : + height >= authorAvatar.height ? authorLabel.top : undefined + anchors.verticalCenter: !xchatStyle && !authorLabel.visible + && height < authorAvatar.height + ? authorAvatar.verticalCenter + : undefined + anchors.left: (xchatStyle ? authorLabel : authorAvatar).right + anchors.leftMargin: 2 + anchors.right: parent.right + anchors.rightMargin: 1 + + // RectangularGlow { + // id: highlighter + // anchors.fill: parent + // anchors.margins: glowRadius / 2 + // visible: highlight && settings.highlight_mode != "text" + // glowRadius: 5 + // cornerRadius: glowRadius + // spread: 1 / glowRadius + // color: settings.highlight_color + // opacity: 0.3 + // cached: true + // } + Rectangle { + id: messageFlasher + visible: false + anchors.fill: parent + opacity: 0.5 + color: settings.highlight_color + radius: 2 + } + TextEdit { + id: textFieldImpl + anchors.top: textField.top + width: parent.width + leftPadding: 2 + rightPadding: 2 + x: -textScrollBar.position * contentWidth + + // Doesn't work for attributes + function toHtmlEscaped(txt) { + // Make sure to replace & first + return txt.replace(/&/g, '&') + .replace(//g, '>') + } + + selectByMouse: true + readOnly: true + textFormat: TextEdit.RichText + // FIXME: The text is clumsy and slows down creation + text: (!xchatStyle + ? ("
" + // TODO + // + toHtmlEscaped(time.toLocaleTimeString(Qt.locale(), + // Locale.ShortFormat)) + + "
" + + (actionEvent + ? ("" + + toHtmlEscaped(authorName) + " ") + : "")) + : "") + + display + + (replaced + ? "" + " (" + qsTr("edited") + ")" + : "") + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + color: textColor + font: settings.font + renderType: settings.render_type + + // TODO: In the code below, links should be resolved + // with Qt.resolvedLink, once we figure out what + // to do with relative URLs (note: www.google.com + // is a relative URL, https://www.google.com is not). + // Instead of Qt.resolvedUrl (and, most likely, + // QQmlAbstractUrlInterceptor to convert URLs) + // we might just prefer to do the whole resolving + // in C++. + onHoveredLinkChanged: + controller.showStatusMessage(hoveredLink) + + onLinkActivated: controller.resourceRequested(link) + + TimelineTextEditSelector {} + } + + TimelineMouseArea { + anchors.fill: parent + cursorShape: textFieldImpl.hoveredLink + ? Qt.PointingHandCursor : Qt.IBeamCursor + acceptedButtons: Qt.MiddleButton | Qt.RightButton + + onClicked: { + if (mouse.button === Qt.MiddleButton) { + if (textFieldImpl.hoveredLink) + controller.resourceRequested( + textFieldImpl.hoveredLink, "_interactive") + } else if (mouse.button === Qt.RightButton) { + controller.showMenu(index, textFieldImpl.hoveredLink, + textFieldImpl.selectedText, showingDetails) + } + } + + onWheel: { + if (wheel.angleDelta.x != 0 && + textFieldImpl.width < textFieldImpl.contentWidth) + { + if (wheel.pixelDelta.x != 0) + textScrollBar.position -= + wheel.pixelDelta.x / width + else + textScrollBar.position -= + wheel.angleDelta.x / 6 / width + textScrollBar.position = + Math.min(1, Math.max(0, + textScrollBar.position)) + } else + wheel.accepted = false + } + } + ScrollBar { + id: textScrollBar + hoverEnabled: true + visible: textFieldImpl.contentWidth > textFieldImpl.width + active: visible + orientation: Qt.Horizontal + size: textFieldImpl.width / textFieldImpl.contentWidth + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } + } + + Loader { + id: imageLoader + active: eventType == "image" + + anchors.top: textField.bottom + anchors.left: textField.left + anchors.right: textField.right + + sourceComponent: ImageContent { + property var info: + !progressInfo.isUpload && !progressInfo.active && + content.info && content.info.thumbnail_info + ? content.info.thumbnail_info + : content.info + sourceSize: if (info) { Qt.size(info.w, info.h) } + source: downloaded || progressInfo.isUpload + ? progressInfo.localPath + : progressInfo.failed + ? "" + : content.info && content.info.thumbnail_info + && !autoload + ? "image://mtx/" + content.thumbnailMediaId + : "" + maxHeight: chatView.height - textField.height - + authorLabel.height * !xchatStyle + autoload: settings.autoload_images + } + } + Loader { + id: fileLoader + active: eventType == "file" + + anchors.top: textField.bottom + anchors.left: textField.left + anchors.right: textField.right + height: childrenRect.height + + // sourceComponent: FileContent { } + } + + Label { + id: annotationLabel + anchors.top: imageLoader.active ? imageLoader.bottom + : fileLoader.bottom + anchors.left: textField.left + anchors.right: textField.right + height: annotation ? implicitHeight : 0 + visible: annotation + + font.family: settings.font.family + font.pointSize: settings.font.pointSize + font.italic: true + leftPadding: 2 + rightPadding: 2 + + text: annotation + } + Flow { + anchors.top: annotationLabel.bottom + anchors.left: textField.left + anchors.right: textField.right + + Repeater { + model: reactions + ToolButton { + id: reactionButton + + topPadding: 2 + bottomPadding: 2 + + contentItem: Text { + text: modelData.key + " \u00d7" /* Math "multiply" */ + + modelData.authorsCount + font.family: settings.font.family + font.pointSize: settings.font.pointSize + color: modelData.includesLocalUser + ? defaultPalette.highlight + : defaultPalette.buttonText + } + + background: Rectangle { + radius: 4 + color: reactionButton.down + ? defaultPalette.button : "transparent" + border.color: modelData.includesLocalUser + ? defaultPalette.highlight + : disabledPalette.buttonText + border.width: 1 + } + + hoverEnabled: true + MyToolTip { + visible: hovered + //: %2 is the list of users + text: qsTr("Reaction '%1' from %2") + .arg(modelData.key).arg(modelData.authors) + } + + onClicked: controller.reactionButtonClicked( + eventId, modelData.key) + } + } + } + Loader { + id: buttonAreaLoader + active: failed || // resendButton + (pending && marks !== EventStatus.ReachedServer && marks !== EventStatus.Departed) || // discardButton + (!pending && eventResolvedType == "m.room.create" && refId) || // goToPredecessorButton + (!pending && eventResolvedType == "m.room.tombstone") // goToSuccessorButton + + anchors.top: textField.top + anchors.right: parent.right + height: textField.height + + sourceComponent: buttonArea + } + } + } + + // Components loaded on demand + + Component { + id: buttonArea + + Item { + TimelineItemToolButton { + id: resendButton + visible: failed + anchors.right: discardButton.left + text: qsTr("Resend") + + onClicked: room.retryMessage(eventId) + } + TimelineItemToolButton { + id: discardButton + visible: pending && marks !== EventStatus.ReachedServer + && marks !== EventStatus.Departed + anchors.right: parent.right + text: qsTr("Discard") + + onClicked: room.discardMessage(eventId) + } + TimelineItemToolButton { + id: goToPredecessorButton + visible: !pending && eventResolvedType == "m.room.create" && refId + anchors.right: parent.right + text: qsTr("Go to\nolder room") + + // TODO: Treat unjoined invite-only rooms specially + onClicked: controller.resourceRequested(refId, "join") + } + TimelineItemToolButton { + id: goToSuccessorButton + visible: !pending && eventResolvedType == "m.room.tombstone" + anchors.right: parent.right + text: qsTr("Go to\nnew room") + + // TODO: Treat unjoined invite-only rooms specially + onClicked: controller.resourceRequested(refId, "join") + } + } + } + + Component { + id: detailsArea + + Rectangle { + height: childrenRect.height + radius: 5 + + color: defaultPalette.button + border.color: defaultPalette.mid + + readonly property url evtLink: + "https://matrix.to/#/" + room.id + "/" + eventId + readonly property string sourceText: toolTip + + Item { + id: detailsHeader + width: parent.width + height: childrenRect.height + + TextEdit { + // TODO + // text: "<" + time.toLocaleString(Qt.locale(), Locale.ShortFormat) + ">" + text: 'date' + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + renderType: settings.render_type + readOnly: true + selectByKeyboard: true; selectByMouse: true + + anchors.top: eventTitle.bottom + anchors.left: parent.left + anchors.leftMargin: 3 + z: 1 + } + TextEdit { + id: eventTitle + text: ""+ eventId + "" + textFormat: Text.RichText + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + renderType: settings.render_type + horizontalAlignment: Text.AlignHCenter + readOnly: true + selectByKeyboard: true; selectByMouse: true + + width: parent.width + + onLinkActivated: Qt.openUrlExternally(link) + + MouseArea { + anchors.fill: parent + cursorShape: parent.hoveredLink ? + Qt.PointingHandCursor : + Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + } + TextEdit { + text: eventResolvedType + textFormat: Text.PlainText + font.bold: true + font.family: settings.font.family + font.pointSize: settings.font.pointSize + renderType: settings.render_type + + anchors.top: eventTitle.bottom + anchors.right: parent.right + anchors.rightMargin: 3 + } + + TextEdit { + id: permalink + text: evtLink + font: settings.font + renderType: settings.render_type + width: 0; height: 0; visible: false + } + } + + ScrollView { + anchors.top: detailsHeader.bottom + width: parent.width + height: Math.min(implicitContentHeight, chatView.height / 2) + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + + TextEdit { + text: sourceText + textFormat: Text.PlainText + readOnly: true; + font.family: "Monospace" + font.pointSize: settings.font.pointSize + renderType: settings.render_type + selectByKeyboard: true; selectByMouse: true + } + } + } + } +} diff --git a/demo/qml/TimelineItemToolButton.qml b/demo/qml/TimelineItemToolButton.qml new file mode 100644 index 0000000..74b9bf3 --- /dev/null +++ b/demo/qml/TimelineItemToolButton.qml @@ -0,0 +1,22 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.1 +// import QtQuick.Controls.Styles 2.1 + +ToolButton { + width: visible * implicitWidth + height: visible * parent.height + anchors.top: parent.top + anchors.rightMargin: 2 + + // style: ButtonStyle { + // label: Text { + // text: control.text + // font: settings.font + // fontSizeMode: Text.VerticalFit + // minimumPointSize: settings.font.pointSize - 3 + // verticalAlignment: Text.AlignVCenter + // horizontalAlignment: Text.AlignHCenter + // renderType: settings.render_type + // } + // } +} diff --git a/demo/qml/TimelineMouseArea.qml b/demo/qml/TimelineMouseArea.qml new file mode 100644 index 0000000..fbbfbaf --- /dev/null +++ b/demo/qml/TimelineMouseArea.qml @@ -0,0 +1,6 @@ +import QtQuick 2.2 + +MouseArea { + onWheel: chatView.onWheel(wheel) + onReleased: controller.focusInput() +} diff --git a/demo/qml/TimelineSettings.qml b/demo/qml/TimelineSettings.qml new file mode 100644 index 0000000..504ad81 --- /dev/null +++ b/demo/qml/TimelineSettings.qml @@ -0,0 +1,34 @@ +import QtQuick 2.4 +import Quotient 1.0 + +Settings { + readonly property int animations_duration_ms_impl: + value("UI/animations_duration_ms", 400) + readonly property bool enable_animations: animations_duration_ms_impl > 0 + readonly property int animations_duration_ms: + animations_duration_ms_impl == 0 ? 10 : animations_duration_ms_impl + readonly property int fast_animations_duration_ms: animations_duration_ms / 2 + + readonly property string timeline_style: value("UI/timeline_style", "") + + readonly property string font_family_impl: + value("UI/Fonts/timeline_family", "") + readonly property real font_pointSize_impl: + parseFloat(value("UI/Fonts/timeline_pointSize", "")) + readonly property var defaultMetrics: FontMetrics { } + readonly property var fontInfo: FontMetrics { + font.family: font_family_impl ? font_family_impl + : defaultMetrics.font.family + font.pointSize: font_pointSize_impl > 0 ? font_pointSize_impl + : defaultMetrics.font.pointSize + } + readonly property var font: fontInfo.font + readonly property real fontHeight: fontInfo.height + readonly property real lineSpacing: fontInfo.lineSpacing + + readonly property var render_type_impl: value("UI/Fonts/render_type", + "NativeRendering") + readonly property int render_type: + ["NativeRendering", "Native", "native"].indexOf(render_type_impl) != -1 + ? Text.NativeRendering : Text.QtRendering +} diff --git a/demo/qml/TimelineTextEditSelector.qml b/demo/qml/TimelineTextEditSelector.qml new file mode 100644 index 0000000..c874360 --- /dev/null +++ b/demo/qml/TimelineTextEditSelector.qml @@ -0,0 +1,54 @@ +import QtQuick 2.2 + +/* + * Unfortunately, TextEdit captures LeftButton events for text selection in a way which + * is not compatible with our focus-cancelling mechanism, so we took over the task here. + */ +MouseArea { + property var textEdit: parent + property var selectionMode: TextEdit.SelectCharacters + + anchors.fill: parent + acceptedButtons: Qt.LeftButton + + onPressed: { + var x = mouse.x + var y = mouse.y + if (textEdit.flickableItem) { + x += textEdit.flickableItem.contentX + y += textEdit.flickableItem.contentY + } + var hasSelection = textEdit.selectionEnd > textEdit.selectionStart + if (hasSelection && controller.getModifierKeys() & Qt.ShiftModifier) { + textEdit.moveCursorSelection(textEdit.positionAt(x, y), selectionMode) + } else { + textEdit.cursorPosition = textEdit.positionAt(x, y) + if (chatView.textEditWithSelection) + chatView.textEditWithSelection.deselect() + } + } + onClicked: { + if (textEdit.hoveredLink) + textEdit.onLinkActivated(textEdit.hoveredLink) + } + onDoubleClicked: { + selectionMode = TextEdit.SelectWords + textEdit.selectWord() + } + onReleased: { + selectionMode = TextEdit.SelectCharacters + controller.setGlobalSelectionBuffer(textEdit.selectedText) + chatView.textEditWithSelection = textEdit + + controller.focusInput() + } + onPositionChanged: { + var x = mouse.x + var y = mouse.y + if (textEdit.flickableItem) { + x += textEdit.flickableItem.contentX + y += textEdit.flickableItem.contentY + } + textEdit.moveCursorSelection(textEdit.positionAt(x, y), selectionMode) + } +} diff --git a/demo/qml/ToolTipArea.qml b/demo/qml/ToolTipArea.qml new file mode 100644 index 0000000..9c0a85f --- /dev/null +++ b/demo/qml/ToolTipArea.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 + +MouseArea { + property alias tooltip: tooltip + property alias text: tooltip.text + + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: true + + MyToolTip { id: tooltip; visible: containsMouse } +} diff --git a/demo/resources.py b/demo/resources.py new file mode 100644 index 0000000..edf6149 --- /dev/null +++ b/demo/resources.py @@ -0,0 +1,1487 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.1.1 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x00\x9a\ +i\ +mport QtQuick 2.\ +0\x0aimport Quotien\ +t 1.0\x0a\x0aNumberAni\ +mation {\x0a pro\ +perty var settin\ +gs: TimelineSett\ +ings { }\x0a dur\ +ation: settings.\ +animations_durat\ +ion_ms\x0a}\x0a\ +\x00\x00\x04\xbf\ +i\ +mport QtQuick 2.\ +0\x0aimport Quotien\ +t 1.0\x0a\x0aItem {\x0a \ + width: parent.\ +width\x0a height\ +: visible ? chil\ +drenRect.height \ +: 0\x0a\x0a propert\ +y bool openOnFin\ +ished: false\x0a \ + readonly proper\ +ty bool download\ +ed: progressInfo\ + &&\x0a \ + !progressInf\ +o.isUpload && pr\ +ogressInfo.compl\ +eted\x0a\x0a onDown\ +loadedChanged: {\ +\x0a if (dow\ +nloaded && openO\ +nFinished)\x0a \ + openLocal\ +File()\x0a }\x0a\x0a \ + function openE\ +xternally()\x0a \ +{\x0a if (pr\ +ogressInfo.local\ +Path.toString() \ +|| downloaded)\x0a \ + openL\ +ocalFile()\x0a \ + else\x0a \ +{\x0a op\ +enOnFinished = t\ +rue\x0a \ +room.downloadFil\ +e(eventId)\x0a \ + }\x0a }\x0a\x0a \ +function openLoc\ +alFile()\x0a {\x0a \ + if (Qt.op\ +enUrlExternally(\ +progressInfo.loc\ +alPath))\x0a \ + return;\x0a\x0a \ + controller\ +.showStatusMessa\ +ge(\x0a \ +\x22Couldn't determ\ +ine how to open \ +the file, \x22 +\x0a \ + \x22openi\ +ng its folder in\ +stead\x22, 5000)\x0a\x0a \ + if (Qt.op\ +enUrlExternally(\ +progressInfo.loc\ +alDir))\x0a \ + return;\x0a\x0a \ + controller.\ +showStatusMessag\ +e(\x0a \x22\ +Couldn't determi\ +ne how to open t\ +he file or its f\ +older.\x22,\x0a \ + 5000)\x0a }\ +\x0a\x0a Connection\ +s {\x0a targ\ +et: controller\x0a \ + onOpenExt\ +ernally: if (cur\ +rentIndex === in\ +dex) openExterna\ +lly()\x0a }\x0a}\x0a\ +\x00\x00\x02\xe3\ +i\ +mport QtQuick 2.\ +0\x0aimport QtQuick\ +.Controls 2.1\x0aim\ +port Quotient 1.\ +0\x0a\x0aToolTip {\x0a \ + id:tooltip\x0a \ +TimelineSettings\ + { id: settings \ +}\x0a\x0a padding: \ +4\x0a font: sett\ +ings.font\x0a\x0a b\ +ackground: Recta\ +ngle {\x0a S\ +ystemPalette { i\ +d: palette; colo\ +rGroup: SystemPa\ +lette.Active }\x0a \ + radius: 3\ +\x0a color: \ +palette.window\x0a \ + border.wi\ +dth: 1\x0a b\ +order.color: pal\ +ette.windowText\x0a\ + }\x0a enter:\ + AnimatedTransit\ +ion { NormalNumb\ +erAnimation {\x0a \ + target: to\ +oltip\x0a pr\ +operty: \x22opacity\ +\x22\x0a easing\ +.type: Easing.Ou\ +tQuad\x0a fr\ +om: 0\x0a to\ +: 0.9\x0a } }\x0a \ + exit: Animated\ +Transition { Fas\ +tNumberAnimation\ + {\x0a targe\ +t: tooltip\x0a \ + property: \x22op\ +acity\x22\x0a e\ +asing.type: Easi\ +ng.InQuad\x0a \ + to: 0\x0a } }\x0a\ +}\x0a\ +\x00\x00\x00o\ +i\ +mport QtQuick 2.\ +2\x0a\x0aMouseArea {\x0a \ + onWheel: chat\ +View.onWheel(whe\ +el)\x0a onReleas\ +ed: controller.f\ +ocusInput()\x0a}\x0a\ +\x00\x00\x00\x8f\ +i\ +mport QtQuick 2.\ +0\x0aimport Quotien\ +t 1.0\x0a\x0aTransitio\ +n {\x0a property\ + var settings: T\ +imelineSettings \ +{ }\x0a enabled:\ + settings.enable\ +_animations\x0a}\x0a\ +\x00\x00\x17N\ +\x00\ +\x00mJx\x9c\xdd.\x17\x17\xa00O\x81l5\x83\xfd\ +0JYQ\x80\xd9Bu\xcdR^\x8a?\x84!\xc3\ +?\xe6\x00zT\xe3-\x00]l\x96lB\x9e?#\ +{JC\xd4\xd3\xceV%\xa1\x19\x8c\x86\x8d\x01\x15\xd0\ +2x\xa8\xb0\x8fl\x05\xefY\x9af\x0edoI\x05\ +r\xd0TqB\xff\x93>P\x9aQ\xc8\x81\xcf\xa88\ +\xa0n\x13\xa7\xa9Ac\xd2;\xe1\x19\x8bhX\x0eA\ +x\xdc\xda\x18\xf2\xe0\x81\x96f\x04\x9bs\x9e\x06\x09\x07\ +g\xe9C\xc1\xf2\x09@\xf4\x18;\x03he\xe0\x11V\ +\xd5\xddv(\x008\x19!2#\x13d\xc350\x11\ +\xfdaT\xc0\xe2\x04Kd\xe0\x88\x85\xe2/\x8e\xa6\xc0\ +\xcf\x1d\xb5\xd9=\xb8;\xb5+\x18M[)\xfax\x98\ +^q\x9b$\xe4\xb74\x05\xd5\x8c\x94D\x82\x08\x8a\x16\ +t\xceNX:c\xe2\xe5X\xf6\x0b\xe2h\xa2\xcd\xce\ +\xeb\xe3\x7f\x9e\xbc\x9a\x82\xf0\x00\xe3\xe5\xa0\x09\xc1\xc8\xae\ +a\x97\x08\xd2.)vW\xb0\x19I\x5c\x94\xe2g\x10\ +.\x97\xd3\xfd\xfd/\x86\xe0\x22y\x1f\x9c\xe0`Q$\ +t\x8cp\xde\xac\xd8p\xfdS\xb7\xf1\xfeN\x83\x1ek\ +\xd8\x06\xbeF\xc0\xe7\xc0\x869E\x1b1\xb9\x0bh\x10\ +\xe8\x10\x9dwZ\x94B\xa0a\xc3I\xc99\xd8Kz\ +\xbd\x99j\xcb~Ktw\xf6\x82/\x1fz1~\x8b\ +\x5c\x07\xfeEA\x1e\xc1\xae\x7f}'\xb4\x1b\xc0\x01\xf3\ +\x92g(W\x01\x9dtZq\xe1\xa9\x9d\xa3\xe3VG\ +\x03A\xc3\xa3CaT.]\xa71\x17\x96Y(\x83\ +)\xa9\xc4AH\x9b\x94\x1fTg\xf6s\xa9s;\x11\ +\xc4\xc7'\xa0j\x80YX\x8e\xee\x8e\xd4\xd8'\x82\xfb\ +\xea\x97\xe0\x10\xachr\x8c\xc2(\xf0\x15b\xd9\x09\x18\ +\x15cL\x93ds\x0e^m6%\x1b \xbfr\x80\ +v\xc8>y.]2d\x06\xc0\xf8\xbf\x87\xef\x09\xc8\ +\xd9\x86<\xb5G\x03h\xf1[\xc2\xefDk\xc6a\xeb\ +\xd2W\xd1%\x93xU\x13\x0f\xc4OLo\xf5\x1c\x80\ +\x0b\xcf^\xd8\xd3\x1e-0*\x02\xe3[\xc7\x8b\xa0\x19\ +\x0e5\xf5\xd1\xf3\x05xE\x1c\xc1\x9eD\x84\x16d\x84\ +\xcb\x19\x91xNb\x88j\xe5\x22H\xb1Do8\xe1\ +\xa0\x02X\xc6W\x97\x0b\x13\x18\xea\x948\xab\x9ci\xa1\ +'\x00\x08\xe1s\xf1L\xf3`\x1d\xaf\xce\xc9\xf8\xber\ +\xbb&\xd6v\x842\xda\x06m\x15\xf0\xecDbh\xae\ +B\xea\xe6\xe3h\xbbI\x5c\x1d\xa0(\x1a\x9cJ\xe0\xd5\ +\xdagN\x9a\x8c'\xba\xff\x99\xc5\x9a\x0e\x92!\xca6\ +\xffN\x8c\xb7\xba\x87\xcd\x88\xcd\x1eb\x81 z\xdf\xe1\ +\x9e\xd5\xc0\x8e\xe4n\x9er0\xc1N\xde\x045;\x8b\ +\xc5z\xc6\xf3U&Ds\x0c\xb0\x81\xed@?f&\ +\x8f|\x9a\xb4'\x84\xbd\xe5\x09\x03\xeb{9\x1e\xe12\ +\x85\xb5\x87\xd0\x07w\x04\xe8\x02\x818\x86\x11\xb0\x15\xcc\xbf\x12X\xf4UM\ +\xbfq=\xf8ScO,\xe2T4\xce]\xa4\xd7;\ +s\xa4\x93 \xc0\x0e\xf87\xe8\xc4\xf6\xbe8YA7\ +\xcf\xce\xb73\x1b.\x1c\x0c\xfc\x0f\x85;\x08\xa4\x7f\xc1\ +\x16\xf4:\xc6P?\xab\xfd@\x03#\xf1\xbb\xea\x0d\xdb\ +\x14)#fhe\xaa_\x17\x1f\xf5\xdb\x8fia\x13\ +\xac\x1d\xda\xc3P\x08a\xc1\x89,4q\xb2L\xc6P\ +f2\x08B\xf9K\x86\xe1`%J\xa6\xa8^\xbc{\ +\xf9\xce\xfc\xcd3$\x8b\x02\xdb\xd0\x11\x9a\xbe\xe1*\xcf\ +Q\xeal\x8d\xa8\x88#\x83\xdc\x8f5\x8d<3a\x8b\ +$\xd0C\xd8\xc0\xb7\x9c\x82\x0a\x0bT\xbc\x0b\x13\xf7\xcf\ +\xeb\x86\xc0\x974\x8c\x81\xeeC 4d\xc3\x03[\xaf\ +$\x00\xe3\x99\x97\x86\x84\xa8\xfe\x9f\xcc\x07\x9f\xec\xed\x91\ +\x83\x99\xd2\x85\xb7\xdb!\x17^3\x90\xfb\xabN\x8c,\ +.\xb1\x0b\xe1\xe1S+.\xb0\xa7\ +.\xf9\xd4\xc7\x14\xd8\x18-\xe0MP\x82O4%\xaf\ +\xe4\x8fw\xab\xf2\xfd\x8aF\x0eL\x7fg\xfax\xf0\xd4\ +\xbb;lsmn\xa9^%\x9c/A[\xfc\xedv\ +kQ\x1e\xc7\xeb\x84\x16\x8b\xdf\xbe\xd1\xa7\x14\xc2\x0e\xdf\ +\x1e\x03\x1b\xff\xabG\xdb\xfeK\xa4\x083\xfc\x85F\x09\ +\xfd \x8c\x10\xc0\x8f\x01\xb8`Ps\xd6\x848c!\ +\xce\x09>\x0d\xf4U\xdd\xff\xbd*0U@\xd8\x86\x91\ +\x90f\xd1\x86T\xb3\x81\xc7\x86\xde\x0f\x06G\xe0!%\ +\x09P\xadh\xc1\xd4\x06\x9d&\xa8r\xe2\xb9\x04^\xc3\ +(J\xa04Y\xf3\xfc\x0a\x99\xc9\x1a]\xdb\x95\xc7{\ +{w\xe5\xaf;\xee\xc9\x9c\x9ajo\xe0\xa6t!\xfc\ +\xc9>\xe48\xe1\x08\x07}\xc8\x1a\x04\xcdB\x08=\x8b\ +`\x0e\xb4\x04\x0cVI\xa2\x94b\xdd#\x0c\xd9\x12\x1c\ +\x84\x17\x22f*D\x80z\x98$\xea\xa79\x0f\x18\xe9\ +U\xdadv\x17\xc8u\x1c\x95\x8b)F\x18\xe8\x84\x88\ +_\xf7\xaa\x97\x98i\x05W\x01\xac\x8d\xbdP\xd7 \xf3\ +}W\xc6\xf6\x11yl\x13\xbc\x91\x94\xfd\xce\xc8\xfc\xea\ +\xa6\x0eG\x9ca\xa9\xd5\xf1-\x9d\xb1\xc4\xe1\x95\xce\xc1\ +W\x08\xe64\x8d\x93\x8d\xe1\xa5\x18O\xdd#\x96\x1cD\ +\xe8<\xfe\x995\x07U/\xdc\xe3f<\x89\x1ar\xad\ +\x1bP\x03\x94\xdc\x85\xd0\xa0\x15L\xf9\xf0#\xea\xd5\xd6\ +\x00t\xc1*\xe2\xd8~\xab\x83?\xa5\x0emz\xee\x91\ +C\xcf\xde\xb3m9-6\xc0~9\x87\xd8\xa7\x90\xa8\ +\xa3\xf4.\xca\x12\xf4\xe0\xee\xeelu\x993<\xdf+\ +\x82\x9f\xca \xe6\xbb\xb3\x9c\xaf\x0b\xb6\xfb\xfe\xe2\xc5\x87\ +ov\xbe\xd8\xfb\xfak{S\xa98\x04\xb1\xd3\xf8\xba\ +U\x1b.d\x0cg9\xd2\x9e\x5cDf\x1b\xa1\xd7\x95\ +R\x97\x8cm\x8dVfkJ\xf6z9\xd2\xeaP\xf0\ +U\x1e\xb2\xca\xdd\xb6\x08b\xd0\xb1\xfa\xd38\xb94\xc9\ +\x986\xc4\xc77\xb7\xf9\xbeC\x1al\x1c\xd1}E\xbd\ +\x8c\xc1%\xec\xf85\xcb)(U\xba\xe1\xab\x92\x88\xf4\ +FA\xc6\xf4\x1a\xd4\xae\xca<\xcas\xad\xed&\x0c\x18\ +TuIP \xb6\x09\xc4\xb9;\x22L\x05w(]\ +n\x93\x10~\xaa \xbb9Z\x09\x19\x19\xaf\x17,#\ +#;\xbf2\xd2fA\x1f\x9bM\x9b\xc3a\xef\xaf\x01\ +\x83\xf6ch!\xe0\xd1BV*o\x11\xf6\xc2\xf2\xe6\ +\x80\xb3\xc8\xc0\x806\x8c\x88\xc8\xb9\xa8w\x9e\xa9\xc64\ +y\x14\xa2Q\xa3\x04\xdd\x11PU*.\x988&\x13\ +m\x5c,i\x96a\x0c\x0f\xa1\xd3\x82 #\xcb\x17\x93\ +fw\xb1\xc6\x0eBt\x10\x01\xd6\x22\xe8@B7P\ +k\xd9\xbe\xe1\x0f\xc7\xe0\xeb\xb1<.\xae&.P\xcd\ +ax0O\xb3\x8d\xe2\x98m\xc5\x02\x87\x82I\x020\ +\xb3\x88\xb2Ha\x80\xfd\xc3s}ep\x9a`\xc6`\ +\xe6\xb7J\xe1$(\x96\xc6\xb1\xda7\xe0sx\x83\xc4\ +sp\xf3\xc4fg\x9f2F9\x12\xd4\xc4\xf7\xad\xb7\ +\x95z\xa8\xd3}\xad>J\xe2DX\xa5\xfac\xbe`\ +\xcfP\x84\xbai\xa3\x0ak\x98\xb6(\xe2\xed\x9d\xb0y\ +YI4\xfe\xb8\xd7\xeaZ+\xa2\xe0\xab\xdf\xae\xe5\xff\ +`\x03\x15\x97\xe0\x8f\x85S}4\xd8^]3\x926\ +\x9eK\x834z:\x02S\x8e\xdb\x08\x84|\x8b'\x1f\ +\x0c}\x9bs\x91E\x1f\x83_\x22NC\xd8\xd8\x91\x22\ +n\x04\x91\xbe&\x81\x06\x10\xbe\xe7%\xb06x\x5c\x13\ +\x98q\xf4|\xd4\x02(1\xdaBd\xb6<\x16\x12\xdb\ +1\x1e\x96y\xf8\xd1\xe4\x0c?K\x8e]\x87\xc0\x98*\ +\xaey\xb5\x9d\xa0\xaa\x9ay\xcc\xe3(O\xa8\x8f\x94\x02\ +\xf9\xe4\x84E1=n\x1fY\xdaLZIR\x90\x0b\ +\x8b\xd2\xd5\xfb\x04\x9c\xe283\xe3\x1a\xdd\xb4\x8d\xd2\xd9\ +\x05\xe3\xb4I\xd4\xe4 \xb3\x83\xd0?$\xe3\xc7`:\ +\x06-W5M[\xa1\x0d\x9cf\x0f\x1b\xf0\xc4c\xb1\ +\x8f2m'\xbb\x15\xe8\x0e({\xf4\x84\xec\x9b\xaf\x08\ +\x1a\x8a\x7f\x1e\x01\x1a>\xe5\xd0Ej\x1f\xd2\x0f\x07\xaf\ +\xba-V\xe0\xb4\x9f\x88\xe2 \xc1f\xc1i\xce\x0a<\ +\xb4>,\x96\xc0-\xaf\xe36\x9e\x80O\xfc3\x08#\ +\x04\xa7I|\x99\xa5\xc2+\x91\x83\xc5\x8337\xa5\xa4\ +#3\x1d\xc6*\xba\x1d\xa8\xa3bp\xe5\xd2\xf2f\x17\ +E\xd75\x1e6k\xd4\x16/9\xa3\xd42 \xd8\x05\ +\xfc1\x16D\xde&;\xfb\x0eR\xc8\xaa\x86c\xacO\ +\x91fN\xc69j\xc6\xe3J\xda\x828r(lg\ +\x1eRm\xfa/\xee\xec\x07\xf9\xd4\x80c\xff\xea\xb2C\ +\x06o\x0e\xb1D(\xeb\xe3\xfb\xc6\xe9Q-\xb4\xb6R\ +h\xcb\x85-\xb5\x96%\x1a.\xb8\x8f\xbd\xbd\x86\xd97\ +%\x1b\xc6\x82\x0e\xc8\xfe\xe3\xbd\xca_TC\xdb>\xac\ +\xbbM\xc9\x09-\x17A\x1agc\x94\xdd\xd71K\xa2\ +@\xb1F\x9c.\xc1\xc4\xc4\xe5\xf7\xf8\xb3M\x0e\x17\xf7\ +;g4\x89}@\xc49a-\x1e\x80A\xfd\xe4-\ +\xda\xe9\xe6p\xf0\xd9P(E\xafW\xf8\xb7G\xacT\ +\x88i\x1c\xff:\xcd\x8c4DS\xc9S\xc1iBc\ +qt\xf9g\xdbs\x19p\xde\xefp\x99z=\x12\x8f\ +U\x1d\xdb\xf4\x1f=$#\xa1%&\x95\x0a\xc1\xa3\xf9\ +\xcf\xa9\x04>\xd9\xc0\x1c\x81\x186\xe1Dj\x9e\xf3\x9a\ +\xb3\xaa\xc71p\xa36>\xcd\xce\xa6\x089Y\xd0\xa0\ +k-\xef\x92\x03j{i>Ug\x8anhJ\x97\ +=\x7ff\xcb\x9czl\x03B\xcf{JV\x99\xa8\xa8\ +sT/i\xd4\xb5K\x7f\xc4\x90\xe0\xd3&\xc6\xf7\x1d\ +(\x0f\x10o\xd9`\xbc\xc2\xed\xa9\x0b\xe3\xc1p\x0e\xec\ +\xd16\xca\x83\xa1\x0c\xa1\x86T\xb1cK\xcb\x19\x14 \ +\xb6\xa2\x9c|\x16\xfd\x9bK\x86S!C7D\xf1V\ +\x83\xdcw:C*\xf3\xb6Jh\xfeM\xc2\xd7\x0e\x01\ +\xd0\xa7# \x07U=\x90\x83\x8e\xaa\x9b\x9d[\x94X\ +\xf6\xf5M\x05\x86\xc5\x94\x5c\x02\x06g4\x8aW\x05\xd9\ +u\x90@\x0d\xab\xcc\xe5\xa0\xc2\xa6\xfb\xba\xb0\xc9\x07\xae\ +\x9etJ\xbe\xf0u\x0ay\x9e\xb1\x5cw\xab\x87\xf8\xfa\ +\x17K<\x09\x05\xa2\xc3B\xfa{+\x9b\xe0\xab\xbb\xf2\ +\x0d3\x22\xc3\xb6\xa3\xadA\x8b\xb2FO\x8e\xd0:$\ +\xd3\xcd\x97\x8b\xd5\xcd\xc8N\xa9d\xb7\xb3\x9b\x9d}s\ +[\xdc!\xbc\xd2Xh{\x87\xc4*oI@l\xb9\ +\xda\xcc6\x9f\xb5i\x82\xa6\xf7U\x14\x97\x1d$\xb1,\ +@\xe7r\x85\x07U\xbb1.\xf7\x09[_\xb6O7\ +T\x1b\xa74\x92\x95\xb7\xed\xc5\x88\xa5\x22)z\xfa\xdc\ +L\xc9\x0e\x22u\x1eb\x86\xf4\x05(\xce%/Dm\ +5y\xa8Sx\xdf\xb7\xd3\x9d\xba\x01+\xbd\xe4\xac\xc0\ +\x84\x0e\x9e\xb1\x88\xb0\x89\x96\x10\xa4\xcfV%s\xc7A\ +\xfa\xc4\x86\x94\xfcM\x99&\xaf\x8a\x90.\xb1\xa0\xe0\xa6\ +t\xd5{\x18\x13\x9d\xd0+F\x8aU\xce`\xa4.\x5c\ +&\x0f 2\xca\x0b\xbf\x95P5\x1e\x00=PC\xc6\ +\xbb\x0fv/\xb7\xc9\xd6\x03\x9a.\x9fl\xf5\x07\x98\x84\ +\xd4#\x9f\xca\x91\x10.nM\xea\xa7\xcf\xe5\xd3\xcb\xd2\ +\x07\xee\x93\x9bx\x05K@\xe8^l\xc4a\x8dGV\ +\xe5\x1ah\xf4.CO\xcf\xdb\xc5\xf4\x1d5\xd7\x06g\ +q\xb8pz\x8f\xd8\xea\xea\xc6\x0b,1\xc2\xd0\x18+\ +\x99\x92UZlDr\xb4H0s\x19\xf1uFB\ +\xc0\xa0u<`N\x0d\xd6\xb0\xcb94\xda\x01\x19\x8f\ +\x9e\x96X\xa0 S\xce\xcf\xb6z7`\x9ep\x5c\x96\ +\xe0\xe6'\xc2\x1f\xdd)\xa4\x0b\x9b\xd2$y\xd2;^\ +j\x89\x1fG\x18\x95\xf6\xd4\x9f\x0ev\x13\xec\xe6\xa9Z\ +\xfd[\x1fs=\x22\xa3\x1fG[\xcf\x9f\x969\xfc\x17\ +92Ov\xf3%\xcdZ\xbd\x1e5e\xeb.i\xb4\ +\x16\xd4\x81\xcd\x91W\x1b@\x87\xa7\xbb@\x00\xf8'\xc7\ +\x7fpcz\xa9\xf1\xc8\x8a\x1d\xfawNp\x1e%\x8b\ +\x9c\xcd\x9fm\x19\x19\x8axX\xd1>\xe0\xb8\xa5Y\x16\ +7x'b\xe0\x1d\xc8\xf3\xd8\x8cg\xecI\xc5f\x03\ +\xc1\x19\xa1\xa0d\x84'\x82\xb5\xd72\xb4\xc0\x88k\xab\ +\x97\x06\x1a\x96\xbd\xddu\xdc4\x91\xa4\xa5\xcf\xc9h\x80\ +\xa2\x13qWg?\xd1\xa3\xa3\xc3#\xact\x07\xad\xd8\ +\x0e?\xcd>\xe3\xdc\xbcU\xe2m\x10\x0d>\x15\x22\xae\ +\xc9>\x98\xc2\xbdr~g\x89\xd5\x22\x8b\xfc3\x02\x86\ +\x82\xff\xfdT\x5c\xe4\xe3\x11\x03\x85\xcb\x22\x11\xb6\x8e&\ +Ow\x05\xde}\xdb\xe7\xa7\xa63w\xd7\x93\x89\xc0\xb6\ +\xce\xe9Rf\x09E\xe7\xef\xe1g\x97\xe3T\x95\xdf\xb9\ +\x0d5\xcf\xcaF\x9e\xc0c\x9en\x1b\xfacS\xcal\ +J\x8ee\xe1k\x88^;DO|\xbd\x8d\x89\xd8\xab\ +\x02\x0fjWI\x04\xcf`\x86\x82'\xd7\x1e~Q\xf5\ +\xb3\x98:\xd4\xfd\xde\xc2\xf8m\xc23\xf0\x0f\xd6\x0c\x1c\ +\x84Kt\x1a\xf0\x94s\xedJ\xe8* \xe0TD\x5c\ +\x82\xcaYB\xc5-\xbb\x0fgo\x0b2\xcex\x09K\ +[\xaf\xd7\xc1%\xe7\xe0!\x07!O}P\xb0\xf8\xc4\ +\x1a\xbf]\x1dr\xdb\x00\xd4I\xe7$\xf0A:\xce\x8a\ +\x12\xcbM\xf9\xdc\x5c\xda\x87<\x01\xbd\x97E\xdb$\xe5\ +\x05f\xb3\xafX\xb2q+o\x80\xf1\xfe}\x9a\x1c\xce\ +\x8a\x12s$0R\xa4K\xb0\xc4\x03T\x0e,\x17\xdc\ +;\x8c\x92\xc5*\xdd|\x88\xb4e L\x18l\x89J\ +\x1d\x88o\xe6,W\xb4\xc2m[/x\xa27\x08O\ +z|d\xc9\xc8\xd1\xa3G\xee\xb5\xf2\xec\x0d\xbf\xc6\xe2\ +i\xdc6]\xd6\xea\x95\x1c\xa3\xc2\x193\xef\xf2\xae\x8d\ +\xaa:\x19/j@\x8e\xc4\xb1\x9c\x0c_\x8aK\x94T\ +\x94\xc2\x1a\xf0p\x15\x98\x8a>\xc3\xb2. ~4N\ +\xfc\x80t\xa5\x8d\xf6\xb6\xce\x853\x87\x95\xac\xed8\xc2\ +\xe1\xffu\x15\xea\x98mp\xb0\x14\xae\xf2\x82\xe7\xe7\x0b\ +\x8ab\xd8\xc8K\xd5T\x19b&\x81\xdbN1\x1d\x08\ +\xdb\xf9\x068\xedH\x00&\x227\x7f\xfc\x82\xd1T>\ +\xf0$S\xdb\x05D'q\x14%L>!\xbf\xe2\x13\ +\x91\x22U\xb5\x17\x9eM:J\xe2\xf0\xca\xaeTn6\ +<\x9dM\x91z\xea\x8a\x87\xa8\xdbl\xcc\xd7\x15Qh\ +\x18^Z\xf5[\xccN\xde\x19d\xba\xbd\x93o\x93\xd1\ +\xc7Xg7\xafY\x87\xd5\x95%\xb4^r\x18\xc4\xee\ +\xa3FC\xb4NX\xb6\x1a\x8bB\xd8\xed\x0e<{\x97\ +i\x0f\x951\x0f\x8b.\xe4\x95m\xab\xc6\xb2c\x8d\xb7\ +\x09\xabx\xf6\xfd\x82\xb1\xa4\x8fw\xd6\xd8)\x10)\x8f\ +\x97,)ip\x83I\xa3=\xf2\xe0\xc1-\x97$b\ +t\xf2\xb4\xf1\xd4\x8c\x9d\xfd\x0b\xebgO\x89\xe62\xbe\ +a\x89\x89f?sz\xa2\xfa\x1d\xf7\x1d\x09_sL\ +\xbfK\xfa\xcfo\x9c7\x1e~O\x0c\xad}\xdc%\xff\ +\x18\x84\xa5\x07\x81a\xf3WGS\xfb\xdb\xeaoz3\ +\xde\x1b\x1eB\xba'\xef\xf0\xbf=\xf7H\xcc\xa6h\xa1\ +\xb40y\xd6\x91\x85sY\xa9\xe6\x93\x0a\xbd\x9e\x04X\ +\xd5\xcf\xe3\xcc\x82\xb6x\xa5o\xde{S\x18U\xe6\xd0\ +/G\xe4\xb9K\xf4<\x96\xc8_!\xa8\x1b8\xd9\xf8\ +\xad\x10YR\x0b\xaa\xf2M\xe5u\xbb\xd35\x22\xe9\xe0\ +\x92\xfd\xdd\x0e\x9c;\xadz\xbb\x0e\xa8\xab\xf7\x903\x00\ +\xb3\xbf<&\xaa\x06\xc8\x9f=\xdb\xde\xd0\xa9\xcezO\ +l\xb8\xf5\xe2\xc8_U{\xb6pP\xf4\xb7/9\xca\ +\xfb\xe4m\xb5\xed\xc9\x91z0n\x14\xa8T\xdd\x9d\x04\ +l\x10\xaf\xee\x9dw\xd5?\x18\x85\x9c\xa2dB\xdd1\ +\xf3\xc8\x81\xf5u\x8b8\x9bs\xbf\xfbz\x1f\xfa^\x82\ +\xb3P\x1cC\xb7 .>,\xf1\x8b1\xe2\x10\xcdz\ +#\xe9\xd7e\x8c\x14\x93\x058\x1f\x8e7\x7f\x07\xe5b\ +\x95\xce20\xaa\x1f\xf1\xa7\x17\xc6\xc1\x9dFM\xadQ\ +nY1j:\xd0\x8aaG,\xa1\xd3\xf5\x1db\xba\ +\xf5\xb6 V\xb0\x98x\x0c\xbc\xaeE\xc1$$\x92I\ +~\x06\xc0I\xc1N\xb5x`\x8f\x119\xafS\xdac\ +\x1c\xa6\xf6\xa0\xf9\x90\xef;\x1c\xb8\xea[\x9a`?\xc7\ +\xc6\xe9\xa6\x0e_y/\x09\x14v\xcd*\x1d=w5\ +m_\xa5O\xbd\x0c\xcfJ\xc1\x0a\xbe\xa9\x0a\x92\xd5\x0d\ +\xc7\xea\x06n-}\xfaQ\xdfD\xed\xe2.\xf2\xb0\xb3\ +\x10A\x0d\x12\x141o\x0c\xda\xdff\xea\xd5\x82\xe6\xaf\ +N%\x88_\xc6\xb8\x95\x0e\x14\x9f\xd2\xf8\xbf\xa4\x02\x9b\ +\xdd\x07\xd7\x93c\x83\xd8\xbe\xa51_\xc3\x0a+\x85\xd9\ +c_\xbaj\xa7\xb2\x8cK\xeb\xec\xae\x9f\xb2(f\xd8\ +\x22\xad;\x0f\xac\x87\x1eB\xf65s\x83\xff\xcc\xcd\xa8\ +\x89!\xd6%k\x9f\xa4\xa0\x91\xe6e\x05l\x957U\ +\x0ft\x14\x15\xfe9U\xc1N\x17\xb0\xe7<\xb3y\x96\ +\xd9\xea \x8f\xa1\x8c\xd5\x9a/m\x1e|\xed\xaex\xb0\ +K\xddl\xe6\xfbS\x5c\x913\xb6d\xb4tj\x1el\ +\xe2\xab#\xe2\xfe\xb7\xbc\xc3\xecNSq\xae\xeeou\ +\x85\xc3Q\x0d\xa6+/\x83\x0d\xa8\xd3s\xa4\x8cM\x92\ +\xabk\xbftS\x16\x08\xcb\xb0d\xd6\xba'&\x96\xfb\ +,\xd6\xfe\x92B\xb4w\xc56\x22\x17\xff\xe3jo/\ +\xfar\x84_-\xc1h\x8c\x8c\xd2UR\xc6\xcbd3\ +\x1a\xf6\xa5\x92G\x06Lit\x8a#\xbe\xea9X\xba\ +\xbd\x00\xb5F\xdfA\x98\xcc\xa62\xf95\xeeq\x16&\ +\xab\x88\x15o\xf5\xd7\x82\x86\xaa\xbc\x83\xe6\xa1HUM\ +1\x14B\xeb\x06]\xfd\xed\x95\x8e\xcc\x8e\x9f\x87\xaa/\ +\x1dN{\xabSt\xd3\xd5\x1d\x7f\x1fB4\x9b\xe5\x03\ +\xf42\x87,\xb5E(\x95p\x03\x97\xa8\xcciV\xc8\ +p\xab\xdb\x0f\x9c\xf1\x1c\x0d\xca\xe7\xdb=\x1fn\xb7\xde\ +D\xd5Z\xdf\x11\x1c\xb2\x99\xc6\xcaT\x19\xcb\xfe]6\ +~`\xc2\x00\xdb\xc9\x06\x15\xdcE\xbc\xec\xe1\x8b\xbax\ +L\xa6.;;\xef\xeeN\xc9_\x1f\xeb\xebL\xf8\x81\ +'y-\x89\xf9+\xfbE\x93\xaaI\x9e\x07\x9e)\xde\ +\x22[\x7f\xdd\xdf\x22\xf3\x9c\xa7\x00r\xc8!,\x09h\ +~9\xb6\xf4\xdb\xa4\xf1H\xa9\xa7\xaet\xa9\xf7\x95\x91\ +Z\xb7\xb2\xd7\xa6$\xa8\x1e\xc32\xd8\xd0\xaao\xca\xd8\ +X\xdf)\xedt\x0b\xcf[2\xa4q\xe3\xb4\xd9I\xfb\ +\xdf\xea\xbbq\x10:\x82\xb7\x8a\xf7&\xb2\xc8q\xf3\xd3\ +lcuq\x09\xe3+\xf7G%\xad\x0f\x07\xfa\xbb\xe9\ +\x8f\xceM\xd4\xec T!\xcd{\xa7\xbfo\xcc/\xa8\ +{\xa6\x8e\xff\xaa(\x22\x0d\xc4\x17\xccDe\x0e\x1b\x89\ +/\x9a\xb1\xf9q5\xcf%\xbf\xe0\xa7\xc0\xe7,\x84\xf8\ +\x95\xe7\x9fm>0\xea\xb3\x02@\xb1\xd1DOs\xbe\ +\x0a\xadI\x86F6]\xf7\xe3\x86d\xbaZ5\xd8\xde\ +\x18\xa5\x15\xa0\xd4\x9c\xe3\xe1=\xebN\xbd\xb8A\xac\xbf\ +%LT\x1e\x02$;b)\xcd\xa2\xc6wv\x9a\xdf\ +\x7f\xa8g\xaa\x9e;*\xce\xcd\xcf\x9bvzl\xd2S\ +\xeb\xe0`\xa3\xfe\xd2\x99\xafhP\xd8\xe2G\xb7\xd7j\ +k5\x9cz\xe48\x125\xf4\x8a`\x94\x9c\x95\xf9F\ +\x1f\xcb*\x051\xe9\x90\xf4[Q\xa0[\x8a*\x12\xdc\ +N\x88\xfd\xda\xaeW\xba\x7f\x13\x1f\x9b\xf4})\x176\ +\x84\xc0\x8a\x06\xbf\x17\x89\x87)\x90\x8a\xd4wVX\x9f\ +\x8dt\xdfp\x08M~\xccx\x82\x06\x03\xa7r\x11\xb1\ +*:\xb9@4\xc8*\xfb7\xc7J\x7f\x12g\xd7q\ +\xc9v\xc47\xe8pl\x81\xdfk\x0b\xc5\xc7\xb6\xba6\ +\xa2\xf3\xfcW,o\x9b\x8cp\x8a\xd1g\xdd\x97\xa6\xc6\ +\xfd\xed\xbbR\xab\xf5\xcf\xbd\x1f\x19[\xff\xe7\xec\x86\xa5\ +\xf6}*\xdd\xfc\xb0C\xf5\xc2\x17\x9ft\xe5\xd2\xcc~\ +yu\x13\xc1z\xec\xfe0\xc9\xac\xbd\xedv<\xd1\xe8\ +\x9f\xc6\x91\x0d\xb6\xfd\xbd\xc5U\x8e_\x07.\xf1D\xbf\ +}v1\xd2\x15L)-\xf3\xf8\x06\xb8e\xf7/\x22\ +{,x'\x8e0\xfe\x16\xbf\x95\x12\xea\x99K}\x09\ +U\xee\xd0\x85`\x99R:\xf1\x83of\xa9=x\xc3\ +\x9c~\xdf\x90\x12\xfa[%9{\xee\x01t\xd5\xe5v\ +]hwT\xe1\xde\xe2V:6u3=\x02\x95\xea\ +\xae\xa3\xee\xfb\x5cL\xd5\xe7\xd6I\x8c\xdf\x92\xbc\xb8\xf5\ +\xe7\x0b\xe4\xa0\xde*x]P\xff_l3\xe3`\x1c\ +e\xd7'\xceB{'\x00\xcbU\x95i\xfb\xb8\x84\xed\ +\xe8H\x1e\xdf\xed(\xb7\xfb\xb6<\xb6\x9f]\xc1\xf3\x9d\ +\xae\xa8\xd4\xeb\xe8\xe0\xa1\xaa4Z\xd6\xc9+] \xcb\ +]\x9f\x8f*\xd1\xd6\xf5\xc4~v4o!t\xdf@\ +\xf8\x7f\xc4\x9b=\xf5\xbao:.!\xfe\x11l\xdd\xfb\ +\xf5\x22\xddZ\xd5\x91\xa0\x9b@kg\x1f\xf2\xe4\xd5\x0d\ +\xac@|\xe9\xad\xab*\xb2\xaf\x9c\x11\xdb\xe0\x92Fl\ +VY\xa3Z\x80Q~\xd6\xf8\x98{g\xf3\xd45\xde\ +\x0a@_\x01\xa4X\x9f\xa3\x08\xf2[\xde\x11\x82\x0f)\ +\xc5\xe9\x91s)\xc4-\x1fo\x98\x94\xfa\xaf\x99c\xfb\ +\x0f\x15\xd3\xcf\xad\xdfo[|c]\xc2m\xabxW\ +Ip\xbf*\x07\x07*\xa5\x89\xaf\x8eW3A\xe9\xad\ +\xf4\xfdl%\xfd\xaeAJ\xcb\xec=\xa9\x9c+\xf8\xb3\ +\xe7>h\xe7\xe1\xb0,+\xc3\x0a\x82\xbe\xd3:\xcb\x1f\ +\xf4\xed\xe6m\xdc\xc2\xaa\xb8O\x1f\xb5\xaa\xf3ly\xe2\ +\xba\xdd*m\xd8%\x8f\x1d\x9f\x8eN\xe2\xa5Gh\xea\ +\xc2\xbf\xdat\x00\xc7\xc3T @\xf5\xcb\xc3dM7\ +\xc5\xbb\xb6\xda\xa8\xbb\xe8{\xee]\xa3\xef\xa6Mj\x07\ +\xfds\xa8\x11\xdb\xce\xb9o\xedYjdt\xc23^\ +,i\xe8\x08J\xab\xce\x7f\xa4\x91\xbf\x8d\x19n\x8e\xf5\ +%\xb9u\xc4\xf9\xe9\xde\xff\x02\xdd\xd4\x16~\ +\x00\x00\x01\xc8\ +\x00\ +\x00\x061x\x9c\x95T\xc1N\xc30\x0c\xbd\xf7+L\ +O\x9b4J\x878U\x1a\xdc&8\x00\x1a\x83\x13B\ +U\xd8\xdc\x11\x91&U\xeaN\x14\xc4\xbf\x93\xb4\xa3\xeb\ +\xba\xa5\x83\x9cR\xdb\xef\xf9\xe5\xc5)O3\xa5\x09f\ +4+\xf8\xe2\x1d\xce\x83\x0b\x8foB\x85\x22\x8e\x92`\ +\x1c\x84\x9e7G\x22.W9|y`\x96F\xb6T\ +R\x94\x90i\x95\xa1\xa6\x12\xb8\xa9d\x92\xa7\x8c\xb8\x92\ +y\xbc,t\xb5\x8b\xd3<6\x84\x22\xaa`v\xad\x99\ +(p\xe0?\xdd\x9c\x1d.\xf7Gp\x11\x86CG\x9b\ +W\xa5\x04\xa0d\xaf\x02\xe3->\xeak\x0d\x97\x10\xfe\ +[\xf4Vo\x1f\xf3d\x02!\x5c\xc18\x84^\x05=\ +\xdd\x13\x96S\xec\x90\xe0\xa0\x8438\xf7\x1c\x949i\ +sK@\xbb\xbd\x1b\x17\xa2C\x8e\xda\ +\xf9\xffU\xb1\x9b\xf9\xa3\x90c\x92\x1a\xd2\x8a\xef\x98\x93\ +Q\xe3g\x85>6\x1e\xd7\xc8Wom\xd0[\x15\xe8\ +\x83\xd9\xc9\x98gla\x86\xb7\x85kE]\x03o%\ +j\x94K\xd41\x95\xd9f\x1e\xf7\xa6\xbcU\xe1\x8f\xfe\ +m!\x80\x7fg^\xf2\x1a\x1f*\x1a\xa3\xc6\xf9D\xec\ +\xff\xa1\xd5l\xfb2\x9e\xf7(F\xbf\xacv'\xeb\xdd\ +K\xc0M\xfa\xe3>\x19t\xcf4\x84\x93\x09\x9c\x8e\x1b\ +\xbe+x\xc4\x0f\x0a:\xa4\xe6\xb6\xab\xf0\x8c\x9a\x90\xf7\ +\xed\xfd\x00\x1fl\x07\xfb\ +\x00\x00\x05\xea\ +i\ +mport QtQuick 2.\ +0\x0a// import QtQu\ +ick.Controls 1.4\ +\x0a// import QtQui\ +ck.Layouts 1.1\x0a\x0a\ +Attachment {\x0a \ + property var so\ +urceSize\x0a pro\ +perty url source\ +\x0a property va\ +r maxHeight\x0a \ +property bool au\ +toload\x0a\x0a // I\ +mage {\x0a // \ + id: imageConte\ +nt\x0a // wi\ +dth: parent.widt\ +h\x0a // hei\ +ght: sourceSize.\ +height *\x0a // \ + Math\ +.min(maxHeight /\ + sourceSize.heig\ +ht * 0.9,\x0a //\ + \ + Math.min(w\ +idth / sourceSiz\ +e.width, 1))\x0a \ + // fillMode\ +: Image.Preserve\ +AspectFit\x0a //\ + horizontalA\ +lignment: Image.\ +AlignLeft\x0a\x0a /\ +/ source: pa\ +rent.source\x0a \ +// sourceSiz\ +e: parent.source\ +Size\x0a\x0a // \ + TimelineMouseAr\ +ea {\x0a // \ + anchors.fill\ +: parent\x0a // \ + accepted\ +Buttons: Qt.Left\ +Button\x0a // \ + hoverEnabl\ +ed: true\x0a\x0a //\ + onConta\ +insMouseChanged:\ +\x0a // \ + controller.s\ +howStatusMessage\ +(containsMouse\x0a \ + // \ + \ + ?\ + room.fileSource\ +(eventId) : \x22\x22)\x0a\ + // o\ +nClicked: openEx\ +ternally()\x0a /\ +/ }\x0a\x0a // \ + TimelineMous\ +eArea {\x0a // \ + anchors.f\ +ill: parent\x0a \ +// accep\ +tedButtons: Qt.R\ +ightButton\x0a /\ +/ cursor\ +Shape: Qt.Pointi\ +ngHandCursor\x0a \ + // onCl\ +icked: controlle\ +r.showMenu(index\ +, textFieldImpl.\ +hoveredLink,\x0a \ + // \ +textFieldImpl.se\ +lectedText, show\ +ingDetails)\x0a \ +// }\x0a\x0a //\ + Component.o\ +nCompleted:\x0a \ +// if (v\ +isible && autolo\ +ad && !downloade\ +d && !(progressI\ +nfo && progressI\ +nfo.isUpload))\x0a \ + // \ + room.downloadF\ +ile(eventId)\x0a \ + // }\x0a\x0a}\x0a\ +\x00\x00\x00\x9f\ +i\ +mport QtQuick 2.\ +0\x0aimport Quotien\ +t 1.0\x0a\x0aNumberAni\ +mation {\x0a pro\ +perty var settin\ +gs: TimelineSett\ +ings { }\x0a dur\ +ation: settings.\ +fast_animations_\ +duration_ms\x0a}\x0a\ +\x00\x00\x02\x06\ +i\ +mport QtQuick 2.\ +9\x0aimport QtQuick\ +.Controls 2.2\x0a\x0aR\ +oundButton {\x0a \ + height: setting\ +s.fontHeight * 2\ +\x0a width: heig\ +ht\x0a hoverEnab\ +led: true\x0a op\ +acity: visible *\ + (0.7 + hovered \ +* 0.2)\x0a\x0a disp\ +lay: Button.Icon\ +Only\x0a icon.co\ +lor: defaultPale\ +tte.buttonText\x0a\x0a\ + AnimationBeh\ +avior on opacity\ + {\x0a Norma\ +lNumberAnimation\ + {\x0a e\ +asing.type: Easi\ +ng.OutQuad\x0a \ + }\x0a }\x0a A\ +nimationBehavior\ + on anchors.bott\ +omMargin {\x0a \ + NormalNumberA\ +nimation {\x0a \ + easing.ty\ +pe: Easing.OutQu\ +ad\x0a }\x0a \ + }\x0a}\x0a\ +\x00\x00\x02\x01\ +T\ +imelineMouseArea\ + {\x0a property \ +var authorId\x0a\x0a \ + enabled: paren\ +t.visible\x0a an\ +chors.fill: pare\ +nt\x0a cursorSha\ +pe: Qt.PointingH\ +andCursor\x0a ac\ +ceptedButtons: Q\ +t.LeftButton|Qt.\ +MiddleButton\x0a \ + hoverEnabled: t\ +rue\x0a onEntere\ +d: controller.sh\ +owStatusMessage(\ +authorId)\x0a on\ +Exited: controll\ +er.showStatusMes\ +sage(\x22\x22)\x0a onC\ +licked:\x0a \ +controller.resou\ +rceRequested(aut\ +horId,\x0a \ + \ + mous\ +e.button === Qt.\ +LeftButton\x0a \ + \ + \ +? \x22mention\x22 : \x22_\ +interactive\x22)\x0a}\x0a\ +\ +\x00\x00\x1a8\ +\x00\ +\x00l\xe4x\x9c\xd5=iw\xdbF\x92\xdf\xfd+\xda\ +\xdc7\x09iK\xd0\xe1\x5cC\x8f\xec\x91,'\xf1\xae\ +e;\x96bO\xde\x9b\xb7N\x13h\x92\x18\x83h\x06\ +\x87df\xc6\xff}\xab\xfa\x02\xfa\x02);;\xfb\x16\ +\xcf\x96H\xa0\xbb\xd0]]wU\xb7\xf2\xd5\x9aW\x0d\ +\xf9\xa9\xf9\xa9\xcd\xd3\xf7\xe48\xf9\xeaNn\xddJ\x9e\ +\xf0\xb2\xa9xQ\xc3\xb3c\xf7\xd9s\xba\xe1mS\x93\ +\xa3\xe4\xe8\xce\xc1\x011O\x7f\xa8\xe8z\x99\xa7\xb4x\ +:\x9f\xb3T48$\xd0\xe0{^\x919-\xd3\x0d\ +Y\xe6\x8be\x01\xff\x9b\xbc\x5c\x18\xa8-orV6\ +\xd8\xfa\xce\x9d\xd7\xd0\x91\x96\x8b\x82\x91\x7f\xde!p\xe5\ +\xd9\x94T\x9c7w\xc4\xb7\xab|\xc5\x8a\xbcd\x97\xac\ +A\x10\xb5j\xa4\x1b\xd6\xea\xb6\xb9Y1\x9a\xf1\xb2\xd8\ +\x90u\xc5\xd7\xacj6d\xc6yA\xda\x9a\xbd\xab\x97\ +m\xd3\x14\xec]\x96\xd3bJ\xaei\xd1\xb2\xf1\xe8\xe7\ +g\x07\xee\xb3\xd1\x1ei\xaa\x96M\xee\x18\xa0O8\x0c\ +\xbc\x84\x11'\xbc\xc4\xcf\x05k\x18\xbc<\xe5e\xcd\x0b\ +\x96\x14|\x01\x80j\x18\x07i\xd4p\xc9\x1c\xb09%\ +#r_|\x9a\x08P\x1f\xc5\xcf\xcbM\xdd\xb0\xd5+\ +\x0a@\x1a\x98\xb3\x98F\xc6\xe6\xb4-\x1au\xf3!@\ +.x\xf5C\xc5\xdb\xf5\xd4n\x9f\x9c\xa6M~\xcd\x86\ +@\xe55\x9d\x15,\xdb\x01\xd6\xb9j\x0a\xd0\x048\xd1\ +\xd2\x1dL2\xa35\x13\x8f+\x9a\xe5m=%\xc7\xb2\ +\xf5\xbc-a,\xbc$\xcbvE\xcb\xcb\xfcw6\x9e\ +m\x1aV\xcb\xb9\xf6\x96iN\xc6w{O\xba\x95j\ +\xda\xaa$\xbf\xd5W\x15`\xaf|_\xf2\x9b\x12P\xaf\ +?\x12\xda44]\xae\x90Nj\x00>\x9aX\x10\x05\ +@\xf2\x17\xf2\xd5\xe1\xe1\xe1\x00\xdc?=/\x096\x1d\ +\xd7\x13\x84\x0d\xff\x9d\x91H8'\xe4\x826\xcb\x04\x90\ +Tf\x0a\xf4\x019\x02\xc8\xe2W\xf0\xc5\xc7\xdb^|\ +D\xde\x9f\x8d&\x09\xad\x16\xe3\x7f\xe3;/\x22\xef\xf4\ +\x1a\xfe\xa0\x1b\x0e\x8dB\xd3\xad\xbd\xe0\xab\xfc\xc3\x13$\ +\x95z\x8c\xb4!>\xee\xe1M\x96u\x9f_Sh\xea\ +\x92\x82\x1a\xc4OM\x02L\xdb\xf4z[s\xc2\x0b\x9a\ +T\x8b\x19\x1dwP\x13\xeb\x1d\xc9\xc2\xfa6\xeb\xbd\xd3\ +\x1a\xb3+\x5c\x04F\xa5\x80Y\xfd\x08\xa2\x82U\x1d\x97\ +\x83\xbcZ\xc2\xa4\x92\x82\xcd\x81u\xd7\xb4B\x86\xc7/\ +^\x8b\x0a\x05\x9ai\x22\xbeym\x1a\xbe6-\xe0\xb3\ +y\xbed\xb2\xf3R\xbc\xfd\x8a}h\x12y\x0bD\xc5\ +\xd7\xdd`\xc2\xbcx\x93\x97\x19\xbf\xe9\x08\x89W\x00#\ +\xd1mm\xceW\x8d\xf1\x0d\xdd\x02\x18\x16\xd6w\xae\xf3\ +:\x87N\x12#\xdd\xeb\x9f\xad\xe8\xa2\x8f\xb3>\xdeN\ +\xafiC+\xeb\x91\x9e\xf45H\x5cT\x07O`\xd6\ +\xac\xb2&i?\x0a\xf6\x8e#\xbe\xdfj\x05D\x9b\x97\ +\xd6$\x06\xf1j\xb5\x92\xca\xab\xc8\xd3\xbcy\x9bg\xcd\ +\x92\x009\xe7\xa0\xb7P\xe0d\x9c\xd5\xe5\x97\x0dPi\ +\xbd\x06\xb2!\xcd\x92\x91\x1aF\xcc\xe0\x09<\xe6s\x17\ +\x126\xa8X\xca@ g\x00\x1616n\x96\xb4!\ +\xb4X\xf1\x1a\x7f\xdd\xd0MM\x96t\xbdf\xa5#\xfd\ +n\xf0\xedS)\x02Vy9\xf6\xc9\xa1\x1b\xe9\x8f\xf2\ +\xc6={\xe8>\xd3\xf4/\x85E\xf1\x1a\x00u\x9c|\ +s\xf4\xdd\x04\x07\xfd\x03/2V\x02%\x00\xaf\x90}\ +\xf2\x8f\x16\x06:Gu\xdd\x96w,\x885o\xabT\ +\x11\x06\xf9\xe2\x0b\xf1;\xa1b\xed/\x18\xe8\xc9gY\ +p\x00\x8f\xc9H\xa0bzp\xb0j>\x1c\xa0\x06\xf4\ +{\x12P\x8d#\x17\x9d\x97t\xceH\x0b\xb8\xaaH\x91\ +\xaf\xf2\x86\x8ck\xc6\x00\x895'\xc0@y\xfa}\xce\ +\x8al\x12\x18#\xea\x9e)\x8a\x0c\xd4\x13\xe3\xfd\xa3=\ +c\x16$\xc2tX\xd3\x14u\xf3=\xf2\xe7\x89=\xc5\ +y^\x14\x17<\x83\xce\x82\xde\x93W\xb0\xf4\xac\xbaf\ +\xa7\x82\x00\xbe\xcf\x1b\xbb\xf9i\x093C\x09x\xc6\x96\ +\xf4:\x07\xa4\x01\xf5H\x0c\xff\xd3\xc3\xc5\x0b^\xadh\ +\xf1\xa2]\xcdXe:\x82\x8ef\x14\x0d\x85\xa4\xd9\xac\ +\xe1\xb5O\xe5\x97\x97-\xd8Y4SZ]_\xdd\xb7\ +\x8f}S\xa4hWe\x803;\x02\x1a\xe0\xad\x8e}\ +\x1d\xa9\xd5o\xaa\xa4\x1b0l\x0dC>\x152\xff\x0c\ +\xcc#^\xc6\x992&\xed\xfamz\x8ck/\xa2\x5c\ +\x1f\xef>\xce\xe5i\x06t\xe0#W\x8b\xa2\x17t\xc5\ +\xbc\x87\x8a\xb5\xf4\xf3\x0b\xd6TyZ'4\xbb\x86\x91\ +0\xc1;^\x1f-=\xdcN\x01\x11\x82WZ\xe40\ +_\xb4\x12\xefx\xcf\x22\x16\xe8\x92\xd6\x08\xd8\xe1'\x90\ +\xd9\xeb\x82n\xf0\x09\xb9{r\xe2r\x85F\x83\x1aN\ +\x00\x13.6T\xcb`;i\x93\xea\x96\x09~\x0d\xb6\ +\x03\x0b\x16yB\x08\xa3\xa7\xf8\xf9u\x10\x09\xa6\xe9[\ +\x89\xf0\x9e\x08\xbb\x09\x22\x19\xaf\x06\x1e\xf7\xc6\xa0\xb0\x02\ +R\xc3C\xc7T\x19+\xe3\x92\x93\x12nLF\x13\x0f\ +\xe2G\x1f\xfd\xf6\x0b\xf4:\x8aqf\x1ew\xe0\xa5\xb4\ +\xe7\xb8\x1b\x8a\xadt\x89\xa7Y'}\xd5\xea\xc1C\xb4\ +&3\x90\xb2\x8a@\x82\xcf\xe7t\x95\x17\x9b\xce\x7fI\ +zw\xc3=\xd6\x1cL&)\xe9\xecN\xe6A\x80\x12\ +K\x5c\x10!iL\x1fy\xf3\x1d\xca\x9f \xe9\xbe,\ +q\x5c\xc1\x91\xd7\xac\x00\xb1x\xb6\xf9/\xb6\x99qZ\ +\xc5&\xa8\x9b]p\xf0\xacb\x5cr\x05\xbc\xab\xb00\x1a\x91i\x10W\x91\ +\x97\x0e\xaf\x07\x00\x94\xb2\xe1j\x99\xd7rN@\x1bd\ +\xc6\xc0\xb0i\xd7\x0b\xb0rY\x96\x8c&C\xaf\xecM\ +\xfd\xb1\xf1A\xd5\x0d\x01P!\xf4.\x82\x09\x0dhG\ +!)X4o(\xd8m\xff\xff\xe4\xc1-\x98T\xeb\ +\xfe\x0a\x9cD\xda\xb0\xb0m(iB7\x0d\xb1]\xcc\ +\x02\xc2\xeb2\xadxQ\xbc\xc9\xd9M\x84\x07:+q\ +'\x92u\xdb\x80\x09zZ\x14\xfc\x86|C\x90\xc9\xc1\ +)\x98\x0b\x13_\x80%c0\xf7\x8e\x0f\xff\xa4oj\ +wFX/l\xf20\x04-\x17M+\xb0a\xe1\xff\ +\x8a\xc3\x0f\xf0\x0eJ\x0d\x1e\xac\xd4%\xbclI\x8b\xf9\ +\xbe\x08\x19\x01\x05S\xb2\xcc\x03JY['\xc6[\x10\ +C\x12\xa4\x97\xc2\x92\xc3\x94\xa4\x8b\xb0'\x02g\x9d\x03\ +\xf1\xf5\xb0\x8f \xae\x88\xa9\xfcM\xf2\xb5\xcf\xf8Cf\ +\x8f\x5c\x9d3\xb0,\xc1\xd8\xcb\x7f\x87a\xd1\x02h\x11\ +\xe8\x1e\xc8\xba{x*|\xa2\x97\xf3\xf9\x00\x00\x8d\xdb\ +P\xf7\xfa\x05c\xc0\xdd\xfe\xfb\x83\xc6\xb9\xc2D\x98v\ +?\xdfB\xc7+`\x82`\x00\xf4\xd9\xdf.\x9e\x82t\ +\x00J\x01\x99\x0d\xcb\xac\x8d\xd9\xfbB+\x0a\x8e\xcaq\ +\xbdW\xb4\xae1\xa0\x97\xf2\xf5f\x7fM\xeb\xc6\xe7B\ +\x007\xaf@\x22\xe9P\xe8\xb3\x86\xad\x92\xdfVEB\ +\xde2R\x02:\x80B\x01\xce{\xa0 XM q\ +\xe0?\x80\xa7B\x96Ap\x80\x1d\xa0\xcc\x1a\xfc\xd5\xd7\ +y\xba\xc4\xb1\x09\xb6y\xd9\xb9\x89;5\x16.\x8d\x91\xe5\ +\xd6\x22\x80\x14\xda\xc5w\x0a_A\xc7i`d=\xd5\ +\x15\x9d\xbfil\xb5\x03\x9f\x22\xderF\xd3\xf7\x0b\x91\ +`\x9e\x12\xf4]\xc0\xc5:\xb8G\xae^\x9e\xbf\x9cv\ +\xf5\x12+\xba&\xf7\x0e\xfa\xde\xd4\xf3\xbcn\x02\xce\xbe\ +\xa6%AE\xd6\x93\x15\x18\x81 rV\xd2>C\x93\ +\xb0\xb0\x9e\xc3w\xb6\x00CljyR\x01\x86Q\xfa\ +F\xbfE%\xd1\xf6I\x9dJ6B\x89\x17\x89!\x5c\ +C\xfb\xde\xf8\xdc\xc7+~-2\x1d\x06\xb4\xbc\x81\xfa\ +V\xad\xfa9,z\x22jSB\x8e\xdb\x7f|\xfb\xe0\ +\xdb\x8721\xc9\x8bV\xf8\xabsD,\xc9\xcbP\xf3\ +e\xd3\xac\xeb\xe9\xc1\xc1\xac]T\x0cKo\xea\xe4\xb7\ +&\xc9\xf9\xc1\xac\xe275;\xf8\xe9\xea\xc1\xf9\xe5\xfe\ +\xb7\xdf}\xe5u\xd6\xc8O\x00it\xf3\x9a\xc18]\ +\x9a\xc0\xcb\xb6N\xf5\x8a\xcbj\xa1\xf3\xbcbBnL\ +;hg\x82\xd3\xae\xf8\x95\x93\x22\x9a\xa3\xa6D\xa3\xa5\ +\xd7\xe9{}/y\xa3\xe0\x8a;~\xbfs\x96\xc2\xca\ +\x8a\x84&t\xfb\xee\xf0\xf0\xd0j3C\x04\xd5:*\ +\xd0\x87{\x09B\xe7\xb49\x13\xcf{$\x8c\xd7:\xff\ +\xc0\x8a\xd3\x22_\x94Ly)\x88\xcf'\x14\xf8\xb3\x96\ +\xfai\xbfDb\x02\x16\xa8\x01\xf9\x846\xbf<-\xed\ +\xb0SJ\xd3%X\xd1\xf39\x1aD\xc70(\x87\xe3\ +\xc54\x13\xed\xe7N\xc9H\xdd\x1a\xd9\x0d}\x8f\x18\xd8\ +\x94H\x91\x85Y\xe67\xd2*z\x06\xce\xd9\x07\xf40\ +dd\xfa\x90<\xf6\xd6T\x8e\x11$\xca!H\x86\x1c\ +\xdb\x9f6c\x15?\xfa\xdb\x1eQ\x9f~!\xf7u\xd8\ +d\x9f\x1c\xa1\xb1\xb3\x7f\xb4e@\xc2E/9\x0a\x98\ +\x0b^\xb1'\x12\x90o\xf6H\xb3L\xdb\x96\xec\x1a\x1a\ +\xd5?\x02i\xf0j\xf3\x9f|f\x1e\xd0\xa2Pw\x9f\ +s\xea\xc5z\x0e`\x1d0\xacR\x8a\xb8\x0d\x86\xe3$\ + \x82yc5\xf0\xb6\xccq\xf4*\xff\x0e*,\xc7\ +\x85\xda2\x0b\xb8SHX\xe7\xac\x84\x1e\x1b\x7f\x02V\ +\xb0M`\x19l\xec\x1e\xca\xd5\xe7\x03\xa7\xe1\x94\x1c&\ +\x87\x0fB\x0c\x8a\xf71\x0e$\xb2\xf0\x14\xc8\xa8\x9a\xe5\ +ME+1\x9a\x9a\x8b\xe0\xaa\x9a\xa9\x8d\x05\x8b\x18\x0a\ +Z7\xc6T{*\xb0\x01\xaf\xdc\x81\x8a\xc0\xa1C\x05\ +\xe8\xf6\xf5\x03\xc7\xfd\xa0\x8b\xb7n\x8fC\x03\xc0IG\ +\x86|M+a\xa7\xa3\xab\xfe6o\x96\x97\xc2\xd3\xcf\ +\x1d\x07\xc7^\x17\x1c\xff\x05\xad\xde\xb3JQ\xd7+\x0e\ +s\x04]\xbc\xc8\xcb_vY\xd8\x0e\x00\x0a\x22\x94\x85\ +\x08\xc1\x9fi\xe0=\xe4/\x1do\x08\xe6\xd9\xad\xd7\xa3\ +\x00G=&\xa6\xd6\xa7\xeb\x03\xae&\xd31\xdb\x1da\ +\xef\x1b\xd8NA\x83.\xd1\x02\xcb\xe6\xfdk\xd3s\x1c\ +2\x0b\x83\x80O\xd0\x1d\x9f\x81p\x00\xad4\xd6\xd0\x82\ +\xdd%\x08Q\xcfu\xb7\xafr\x91\x8d\xfb\xdf\x93>\xe6\ +\xeb\x96\x16BR\xdd\xce\xb3~4 \xae\x06]\xd9\xc7\ +\x9aD\xc8\xb4\xbf\x1a\x16{\xfa!\x97@\xe0\xbb_\xe9\ +\x89x\x05{\x05g$\xf0\x0c>\x0bm\xe4\x08G\xc3\ +!\x83\xcf\xc4\xcb}22\xf3F\xa9\x86\xdf\xb4!\x11\ +Z\xcd\xe1\xc1\x8c`}\x0b`\x0c\x8c\x81\x81\x0c\xea\x03\ +SH\x03Gr\x7f\xcb\x8c\xdc.\x88\x5c}\xcbNM\ +\x0c\x8f\x85\x8alK\xc9o\xdc\xc0\xea\xc7\x08\x81\x83\x88\ +n+\xf6\xaab\xa0\xd4\xdbZ\xcd9H\xa8\xe8\x22y\ +\xca)L5\x92\x9e\x83\x01\xfd+\x8c\xaf\xa3\xe1\xa5d\ +&\xa9\xd7\x00q\x0f\xe8\x8b\xd0\xbanW\x8c\xdc\xc03\ +\x10\xe1\xd2H$\xdf\xe1\x07\x06\xa3<\xf0k1P\x00\ +^\xb3\x82\xa7\xa0d\x80\xe3\x94\x0d\xf8\x98\xeck\x03\xea\ +\x8d~\x18\x0fM\xaa+\xad\xda\x1c\x13\x14&\x7f\x91T\ +mY\x0ap\xb7b1\x1f\xce\xf5\xeec\xb0<;-\ +\xc9\xee\x91\xefBx|\xb2d\xe9{\x5c\x93\x1b\xf6%\ +f\xc6\xc0:k0\x7f1kWk\xd4I\x5c\x22\x99\ +\xe5\x05N\x22l\xd6\x1e\xa3\xf5\xc4\xc1h\x03\x95\x99!\ +\xac\x0df\xd3*\xa9\x80D\x7f\xba\x12\x8a\x18HJ\xb1\ +\x9c\xbf\x08\x00\x87\x95\xbc],\xf1\xedj\xd1(\xf6\xc6\ +\x8c2fP0I\xf2@\xa6\xed\xd4\xeb\x82\xa4e\xf0\ +d\xcc\x01%f\xf6\xcd\x1a\xdf;\x06\x15\xa2\x18$&\ +JC*\xf4\xa4\xa3\x92{\x96e\x02_}s\x02/\ +\xa1\xa0\x17\xacq\xf9\x22\x00}\xf7P3^X\xf7.\ +\xe7\xa5\x83\xe61\x0e\x0cw\x93B`k\xdf0\xaf\xd7\ +\xf4\x9ai\xad\x1deq\x11J\x8a#\xc5\x821\xa0M\ +v\x8d\xf9\x86\x0d\xf0\xddd\x17/\x85\x06x\xcd\xc0\xed\ +\x0f\xce\xc7R7R\xab\x1aG\x19K\x14\x9b@l/\ +\x8e\x830\xc1\x01y\xa7Lzj\xe30\xde\x80C\xd0\ +\x00\xd7\xf6\xb5\x95\xcd.y\xa3\xd9G&\xc4WA\x10\ +\xbb\xd0\x88\xbeP*\x22\x9dJQ\xf2J\x18\xed\x80\xac\ +\x93n\xfd2p\x1b\xfb\xd8\x8e\x00\xb2\xb0'\xa1\x89\xad\ +\x19\x5cy\x02\x1c\xb7\x19\xf8o\x0a\x03\xd3]\x90vN\ +\x1b\xf9Z\xbf\xef^\xe7\xe2\xe2$i\x1e\x806X\xda\ +\xd3\xd1\xba\x80\xfb\xf3z\x9cmb\x94\xeez!R\xe0\ +\xc6\x03\xc7B\x1a\xa9\xdd\x06+\xfaal\xb4{OT\ +\xc1\xdb\x06\xb8\xdf\x19\xdd9\xbf)\xff\xb7\xc6\x97\x97\xe3\ +\x9e)aA\xdaW\x90vO\xcb\xf4L>\x7f\x821\ +\xde|\xbbd\xac\x18\xdf\xe0\xcf\xd8\x0c\xc5\xc3D\xec+\ +8gEC\x93\x0f\xe4\xe4\xe4\x84\x1c\xc6\x84;\x92\xf6\ +F\xb4\x84yz\x9dQ\x9c\x1f\x1d\x82\xd7\xf8\xe0\x9bp\ +\x1e\x11_\xa9\xba\x83\x92\x89\x1b\xbc\x86vd\xe3p\xc3\ +h\x0e\xa0\x83 \xd6w\x7f\x08\x88\x9aC\x17\xb2\x0f\xa6\ +|U\x8c:\x8c\x13\x0f\x84\x1d\xf570\x06\xc8\x12X\ +\xad\x94\x9ec\xa82\xb6\xa1\x15h\xc2~\xd25h\xd7\ +\xbd<\x7f\x19\xba\xcf\xcbW`6\xfc\xbc\xc6\x92\xf0:\ +\x94\xc3Q\xf1\x22c\xed\x1a\xe4\x9b;\x91\xdaa;\xd0\ +\xd4\xbb\xf6uP\xe8\x8c\xc2\xbc\xaa$]\xe6E\x06\xb6\ +&\xeeaI4\x13\xc5\xc7\x8a\xabv\xcb\xd1\x8a\x85\xfe\ +\xe4\xf1\xee8\xe2\xe0\x80;{\xf1\x8a\xf7\xc2\xa1!i\ +\xab\x0cn\xf1e\x9b\x9cu\xb8:\xb2oo\x8b\xc2\xfd\ +\xe9\xe2\xb9\x88\xe3\x92B\x84\x9f\x02\xbaVD\x9a\x13\xf1\ +\xf3\x14\x0d\xd8+~\xc6\x846G\x9f\x07Irl\xfb\ +\xdfA$\x98\x12!08\xe1\x1fjT\x90?bO\ +\x89\x10w\xeb\x82\xe2\xf6\x0b\x13\x06_\xd1\x0d\x18\x91!\ +H\xf8\x04\xbb/AG\x92v\x9da\xb1\x01A\xb05\ +\xb0e\x0a.\x0d\xab1\xd1\xcf\xabL\xe9A|I\x19\ +\x08O#,am\xcc\x10`(|m\xe6mO\xb7\ +o\xd1\x0c\xae\x086\xbcf\xb8\xa9\xef)\xac'n\xe0\ +\xb4l;'b\xc4\xd7m!\x02\xf6\xd2-\x01\x03\x00\ +\xbcUe\x17\xf8\x8b\x888\x05\xcd\xec\x17t\xf5b\xac\ +\x1c\x0b\xdc\x9a\xcd\xe8\xa1(\xa9\x9a\x92\xc3\x87\x80\x8f)\ +9\x1a\xd6\xce4\xcb\xfe\xcf\xc7 #\xf0\x7f\xd8 \xf0\ +\xf5~1\xdb\xed\x86\xbf\xc3\xa8eA\x7f\xca>\x17\x7f\ +1\xa9d\xcf(\xd6j\xa0\xa2/\xd4\xe5\xdf\x80\x97~\ +\x91\xa2\xb1R\xfcY\x9a\xf2Z\x93 \x94w\xdeQ=\ +\x86\x1a}\xce\xbbN>)n \x89\xc6\xb1\x98\x81\x13\ +g\x17\xbf.W\x9c\x83\xb8\xc8bk\xd1\xd3\x0eb?\ +\xb6\x90\xeb\xa6q\xaci\xd6\xeatMW\x8c\x06\xf8\xed\ +M\xeb\x9dn\xf2n\x85;R\x1f\xc4 \x81A\x9b\xaf\ +\xda\x95\x5cT\xcc\xeb\xed\x06\xd2\xcd)\x98\x8f\xbc|-\ +\x91a|\xd5\xe8|\xc5\x9cE\x15\x83\xec1\x19j\x88\ +W\xa7}m\xa1\x17\xe9\xf6\xd1}0HQ\xc1\xfa\xd7\ +`\x8c7\xc8L\x7f\xc4v5w\xbcB\xb5\xa5XO\ +U\xcb\xa8\x0cV\x8a\xd0\x19\xdc\x10_\xf3\x86\xad\xa2n\ +\xa5\x0bKy\x99\xa8\xbd\xb0\xb2D\xd6`+\x14Zm\ +c\xe5px\xfd\xee%\xb3\xf0\x1a\xacX1\x8d\x02\x85\ +r\xa7E\xa1\xbez\xcd\x81\x8cX\x01\xb8s\xaa\xfe\xe6\ +b\ +\x87\xd4\x88\x1e4\xac\xcc\x1d\x19\xb2O\xc83\xbf~\xe2\ +@\xed\xe6E\xcf^6F\xe2\xc4\xde\xca$\x93\xc1\xdc\ +=\x17\x98\xa8\xe1\x0e\x99\xcc\x07\x86\xc2\xbf\xac\xd5\xe8\x1e\ +\x12\x14\xa4\xd5M^\xb3=x\xf2\xa5\xca\xfa\xcd\xd8\x86\ +\x97\x99h\xce\xb2\x05s\xb7\x19kp\x1a%\x98l\xc6\ +\xb6\x99N\x94[\x93\xd6\xa3B[Y\xecX\x80'\xe1\ +\xd1\xa9p\xd2\x9e\xe2\xbf.\xbe\x84f\xe3\x12O\xe9\x90\ +|\x18\xa8\x08\xc7\xfe\x7f\xadi8\xa5\xe6\xb5\xd6{\x17\ +\x02\x99\x8a^7\xcc\xd6\xbaD\x16%\xc3\xe1}\xe2x\ +\xe9-\xa0v\x8e-\x5c\xb2\x11\x16\x0aJ\x9bc\xea\xf6\ +(\xb0\xc7S\xed\xa3\x0f&\xf1\x02\xde\x85\xd8a\xd8\x1d\ +\x9b\xe0\xee\xd4\xc7\x83\x10\xf6\xdc\xbd\xfe\xe6\xe4\x94=\x18\ +\xc3\xd7C\xe1\x99]\xa4E7\xca\xcf\x13\x19\x81\x8d4\ +\xbbU\xc6\xb8\xb5WF\x84\xb9\xe5W\xfa\xd2\x84\xe3\x17\ +\xa7\xc0\x8a\x1d'_\xa3VxYb\x1d2\xb2\x00-\ +7\x9d\xab\xa4\xc4\xc6\x9e\xda{!$\x84\xf0P\xfd\xb9\ +\xe3f1<\x8bfJ~P\x9f\x22\xf1\x0a\xfd\x18\x8b\ +F\xd0\xf0S\x9e\xaa0\xdf\xd5\x02\x8f\x1a4m%j\ +F\x01+r\x10\xcc\x91\x01\x13\xa3\x83\xe0\xe6\x13\xfb\x9b\ +\xa7\x82Q\x02\x81\x8c\x14[ML\x8c\x0b\xac \x10D\ +r3\x125&\xec\x1e\xa99\xb4\xd7\xc7 \xb8\x90\xa0\ +Y\xd6\xa6\xb8\xc7\x04\xfdc\x95\x22\x96\xb5H\xeb\x9c\xc1\ +\x83\x9b\x1cH\xc1\x8e\xd8\xe1&\x14V\xd6`\xf5\xd7\xb6\ +\x18\xf1\x93\xf4\x18\x22kq\x83AA7\x00\x18+\xb3\ +\xa6n\xbd\xc5\x89\xce\x9e\x076\x80\xca\x89\x86\xcbY\xec\ + \x85)n9v\x8fR\x09\xd6\xb5X\x9d\xdfhN\ +q\x86\x1a\x1a\xd1\xf8\xae\xd3(Q\xa0\x14\x10L\x94\xbb\ +-6\xbdZ\x83\xc9\xedL\x02k\x9c!\x96y`3\ +\x8ca\x96Z\x87\xd0\xc4\xd1\x15\x22\x86\xf0\xfb\xc9\xf1g\ +\x08\xdf]\xad\x0aSf\xd7\x0b\x1aI\x81r\x9f\xf8\xef\ +\xef\xf4\x88\x17c\x0a\xb6\xf7\x85Zh%c\xb2z\xf8\ +\xdc\x15}\xf5\x94\x84\x9f\xec\x8c\xed\xd6\xc5k\xdb\x1eq\ +\xd3\xe6V\xfbBM\xafO\xd8\x1b\xbau:x}\xd2\ +\xbe Y\x92oV\xc1\xe5\x9cmy\xeaX\xc7\xc4D\ +\x02\xd5\xba\xee\xb6\xa1\xda\xfe\xa4\xccn`\x0c\x8c\xdf_\ +\xf5\x8bQ\xe1\x86~\x86\xbb\x9a\xf0\x04\xb1L\xca8a\ +\x0f\xcajM\xa3\xf8D\xb6\x18Cf\xa2O\xec\xd4!\ +Q\x12\xa8\xf2\xafg\xb4\xa7\x8d\xd0\x92E\xb9\xf3\xa1'\ +}\x10\x22\xf5\x1ci\xab~\xab\xd2)]U\x8d%!\ +\x8b\xdd\xe9v\x08\xb8S\xec\xc1\x8a/\xd36\xe2DF\ +\xdf\xe2;\xc71?2x\x98\x89]\xa2\x05\x92\xa7\xa2\ +\x0b\xf6\xf4Z\x09\xfc\xd8\x14\xc4\x8b\xef\x0f\x0e\xcb\xb5\x5c\ +z^F\xd0F; \xe3\xdb\xc0\xb7\x15G\xdf7\x09\ +\x22\xd0\x9d\x98\x8f\xb5pp)\x883-]\xbbBt\ +\xbd\xe9\xa6_A\xec>\xf5\xbak3\xcc)\xde\x88\x94\ +\xdd\xcb\xdb\xca\xf7\xb3C\xa3\xee\xdc\xeeu\xa8\x0e\xe7\xc8\ +Mw%\xff\xfb\xc3\xeeJ\xc5\xdf\xea\x83\x8bLs\xcf\ +\x91\x08\x83\x07\x0dz\x18(\xd3s\xc7i=\xbc\xe7-\ +\xff\xfe\xb6\xd7t$`\x14\x8dU\xc1\xad\xd4\x8bi\x15\ +\xd6+\xab\xa0\xbe\xff%\xf6\ +H\xde\xf0\xea\xfdCu\xa8\x09m\xd4\xd6\x19\x09\x0c_\ +\xb6\xc0\x0cT\x09\xba`\xb6\xe9\x83\x19H\xa9bY\xb1\ +\x90\xa6X\xd0=c\xe6d\x0eX\x90\x05\x13\xb13t\ +\x04\x87\xc6\xd4\xb1\xe5\xb6\x04\xaa\xf0\x0b\xdd\x8c\xac\xd5\xc2\ +\x142\x85v\x15\xf6\x12\xd0:pd;\xc1\xba\x84x\ +\xea\xef0\x04\x81\x1f\xba7\xe4*\x0d\xa5[#\x99V\ +k*<\x909\xbaO\xc6\xfe0\x1e\x05\xadxC\x87\ +\xc1=\x06}\xab=T}\xa4\xf2\xb4!L\xdc=q\ +\xb7+\xf1\x12C\xb4b\xdb\xf9\xb6\xbc\xadM\xe7\xa7\x1d\ +\x1d\x81J\xfe\xb2Q\x159\xe4\xd7\x86\xff\xaaXK\x98\ +\xe1@\x14\x1b\x91\xf4T\xc3zHj\xde\x87\xa3\x1b\x19\ +\xba\x92}S\xc1\xd35\xa9\xf3E)\xc8\x10\x0b\xe8g\ +,\xe5+4@~g\x15\xc7\xfa\xc4<\xed\x9d\x0f\x8b\ +\xf4\xd9V0\x1eh\xba\x87,\xf8[\x9b\xc3\x80Z\xdc\ +u\x22_\x83;\xf0\x17\xa28\xa8\xf7>*\xf4\xef\xc4\ +\xca!\x01(\xb3\xcc\xc2w\xc0\xd2}Q\x94\x0cT\x0a\ +\xceC\xd2\x13BBl\x19\x11\x14\xa8:\x90\x9dz\x88\ +\x8c\xd5k\x09v\xc53\xf3\x14k\xe3IY\x96\xc3c\ +\xd1\x84\xa2+\xf5fS\xa3\x14\x1d\xc0$\x0c\xc6\x22\xad\ +O\x00fE\x98BY\xe9\xae^Bj\xa0\xcf\xf7\xa5\ +v\xf2\xe5\x87l|m\x89m\xb5\xb3\xd5\xf5xg\xa3\ +\x8d`\x9c\xe4\xb6\x9eJ(\xe9\xfe\x82\xab\xd3P:\x97\ +\x09\xe5\xfbS\xed#\x98x\xea\x96\x13\xc0u\xc2\x13\x0f\ +\xf5\xa9cH\xb2\x0c\x84\xe0q\x03\xdb\xd6h(\xc8\xdd\ +\x15\xb5\x0c\x07\xb7\xbbva\xcb\x87\x16`>\x95 c\ +\xceh\xaf\x82\xd6Ic,\xf9\x8d\xe3\x05\x8c\xb7ER\ +\x1e\x9d\x84\x0cBQ\xa1\x82\x07\xce\x0cXO\xb8\x9f\xdd\ +\xae\x04\xd25F\xa1\xa3g\xa0\xf5\x96\x00\xa5\xed\xb4\x81\ +h\xc6\xd9\x0cX7b\xb6!\xd3fN3\xf6\xb2m\ +\xceqW{R\xc3B\x05\xf6\x10h\x9b\xee\xc4\x8b\x83\ +\x7f\x0cWO[@}\xa9\xd6\x19D\xa80\xab\x80\xfe\ +\xef\x03\xb0\x1f\xa2a|\x8d\x7f\xae\xe2\xd8\xdd\xd6\xce\xcb\ ++ \xd2\x85%\ +\xa5\x11Me\xdc*\x85!S\x17nl0\x94\x16'\ +(\x0b\xb72\x94\xd8$\x104h\xcciC\xcf1Y\ +\xda\xa8\x98]\xe44 s\x00\x11\xfeu\x0b\x13\xddK\ +\xdfK\x87\xb3\xe47\xa3\xbd\xd1\xae\xfb6#\xd1\xcd\xe8\ +&\x83\xfbd\xf4\xf7\x12\x0fu\xf7F \xc3t\xea\xaf\ +l\xd8(\x0bB\x9bFN;\xba\xdfGx\x5c\x1aD\ +\xb0\x18X,<\x08\x0a\xc7\x1c{\xa1z\xab7!\x13\ +\x11\xed\x1cyq:|\xb5\x05\xb7\xc4\xc1@p\x0e\xd1\ +\x95u\xc7\xd8\xb9\xc9\xe28\x8a\x95\xa8U\x10\x0c\x97\x88\ +\x1b\xf6\xb1\xb3\x96\xd1!U\xd2\x15\xf7\x0e\x19\xeb,\x0f\ +x(\x08\xc0\xd5\xa9\xb7\xd6\x83VT\xd0\x0f\xfc\x0d\x9f\ +\xb8\x13L3\x98\xf3\xbe\x1ec\xfd\x0b\xf8%z\x8b<\ +\xd9_:\xd1)\x13\xf5\xeb\x0aa\xd5\x81\x1c\xdd\x94S\ +\xcf\xf3*\xc5q\xec\xa3\x05\xdf\x97\xef\xb5\xfd&\xfd\xe7\ +\x0fF\xbfU\xe9\xf4\xe0\xe0@\xa2\x00\xff\x12DR_\ +/F!1\x18=\xad.\xb2\xbf\xe1\x8c\xc1 textEdit.\ +selectionStart\x0a \ + if (hasSe\ +lection && contr\ +oller.getModifie\ +rKeys() & Qt.Shi\ +ftModifier) {\x0a \ + textEd\ +it.moveCursorSel\ +ection(textEdit.\ +positionAt(x, y)\ +, selectionMode)\ +\x0a } else \ +{\x0a te\ +xtEdit.cursorPos\ +ition = textEdit\ +.positionAt(x, y\ +)\x0a if\ + (chatView.textE\ +ditWithSelection\ +)\x0a \ + chatView.textE\ +ditWithSelection\ +.deselect()\x0a \ + }\x0a }\x0a \ +onClicked: {\x0a \ + if (textEdi\ +t.hoveredLink)\x0a \ + textE\ +dit.onLinkActiva\ +ted(textEdit.hov\ +eredLink)\x0a }\x0a\ + onDoubleClic\ +ked: {\x0a s\ +electionMode = T\ +extEdit.SelectWo\ +rds\x0a text\ +Edit.selectWord(\ +)\x0a }\x0a onRe\ +leased: {\x0a \ + selectionMode \ += TextEdit.Selec\ +tCharacters\x0a \ + controller.s\ +etGlobalSelectio\ +nBuffer(textEdit\ +.selectedText)\x0a \ + chatView.\ +textEditWithSele\ +ction = textEdit\ +\x0a\x0a contro\ +ller.focusInput(\ +)\x0a }\x0a onPo\ +sitionChanged: {\ +\x0a var x =\ + mouse.x\x0a \ + var y = mouse.y\ +\x0a if (tex\ +tEdit.flickableI\ +tem) {\x0a \ + x += textEdit\ +.flickableItem.c\ +ontentX\x0a \ + y += textEdi\ +t.flickableItem.\ +contentY\x0a \ + }\x0a textE\ +dit.moveCursorSe\ +lection(textEdit\ +.positionAt(x, y\ +), selectionMode\ +)\x0a }\x0a}\x0a\ +\x00\x00\x02y\ +i\ +mport QtQuick 2.\ +6\x0aimport QtQuick\ +.Controls 2.1\x0a//\ + import QtQuick.\ +Controls.Styles \ +2.1\x0a\x0aToolButton \ +{\x0a width: vis\ +ible * implicitW\ +idth\x0a height:\ + visible * paren\ +t.height\x0a anc\ +hors.top: parent\ +.top\x0a anchors\ +.rightMargin: 2\x0a\ +\x0a // style: B\ +uttonStyle {\x0a \ + // label: T\ +ext {\x0a // \ + text: contr\ +ol.text\x0a // \ + font: set\ +tings.font\x0a /\ +/ fontSi\ +zeMode: Text.Ver\ +ticalFit\x0a // \ + minimumP\ +ointSize: settin\ +gs.font.pointSiz\ +e - 3\x0a // \ + verticalAli\ +gnment: Text.Ali\ +gnVCenter\x0a //\ + horizon\ +talAlignment: Te\ +xt.AlignHCenter\x0a\ + // r\ +enderType: setti\ +ngs.render_type\x0a\ + // }\x0a \ + // }\x0a}\x0a\ +" + +qt_resource_name = b"\ +\x00\x03\ +\x00\x00x<\ +\x00q\ +\x00m\x00l\ +\x00\x19\ +\x0a\xbe\xd5\x1c\ +\x00N\ +\x00o\x00r\x00m\x00a\x00l\x00N\x00u\x00m\x00b\x00e\x00r\x00A\x00n\x00i\x00m\x00a\ +\x00t\x00i\x00o\x00n\x00.\x00q\x00m\x00l\ +\x00\x0e\ +\x0f\x18||\ +\x00A\ +\x00t\x00t\x00a\x00c\x00h\x00m\x00e\x00n\x00t\x00.\x00q\x00m\x00l\ +\x00\x0d\ +\x02\xd5\x9d\x5c\ +\x00M\ +\x00y\x00T\x00o\x00o\x00l\x00T\x00i\x00p\x00.\x00q\x00m\x00l\ +\x00\x15\ +\x07d\xb1\xbc\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00M\x00o\x00u\x00s\x00e\x00A\x00r\x00e\x00a\ +\x00.\x00q\x00m\x00l\ +\x00\x16\ +\x03g[\x9c\ +\x00A\ +\x00n\x00i\x00m\x00a\x00t\x00e\x00d\x00T\x00r\x00a\x00n\x00s\x00i\x00t\x00i\x00o\ +\x00n\x00.\x00q\x00m\x00l\ +\x00\x10\ +\x02\xf2\x04\xdc\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00I\x00t\x00e\x00m\x00.\x00q\x00m\x00l\ +\x00\x14\ +\x01/\xc1\x9c\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00s\x00.\ +\x00q\x00m\x00l\ +\x00\x10\ +\x03\x7f\x92\x9c\ +\x00I\ +\x00m\x00a\x00g\x00e\x00C\x00o\x00n\x00t\x00e\x00n\x00t\x00.\x00q\x00m\x00l\ +\x00\x17\ +\x05\xdbD\x5c\ +\x00F\ +\x00a\x00s\x00t\x00N\x00u\x00m\x00b\x00e\x00r\x00A\x00n\x00i\x00m\x00a\x00t\x00i\ +\x00o\x00n\x00.\x00q\x00m\x00l\ +\x00\x12\ +\x0el9\xbc\ +\x00S\ +\x00c\x00r\x00o\x00l\x00l\x00T\x00o\x00B\x00u\x00t\x00t\x00o\x00n\x00.\x00q\x00m\ +\x00l\ +\x00\x19\ +\x0a\xd8\x0c\xdc\ +\x00A\ +\x00u\x00t\x00h\x00o\x00r\x00I\x00n\x00t\x00e\x00r\x00a\x00c\x00t\x00i\x00o\x00n\ +\x00A\x00r\x00e\x00a\x00.\x00q\x00m\x00l\ +\x00\x0c\ +\x07(0|\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00.\x00q\x00m\x00l\ +\x00\x0f\ +\x04\x874\xbc\ +\x00T\ +\x00o\x00o\x00l\x00T\x00i\x00p\x00A\x00r\x00e\x00a\x00.\x00q\x00m\x00l\ +\x00\x15\ +\x0d\x93\x85\xbc\ +\x00A\ +\x00n\x00i\x00m\x00a\x00t\x00i\x00o\x00n\x00B\x00e\x00h\x00a\x00v\x00i\x00o\x00r\ +\x00.\x00q\x00m\x00l\ +\x00\x1c\ +\x0a\x96\xbd\xfc\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00T\x00e\x00x\x00t\x00E\x00d\x00i\x00t\x00S\ +\x00e\x00l\x00e\x00c\x00t\x00o\x00r\x00.\x00q\x00m\x00l\ +\x00\x1a\ +\x0e\xec^\xdc\ +\x00T\ +\x00i\x00m\x00e\x00l\x00i\x00n\x00e\x00I\x00t\x00e\x00m\x00T\x00o\x00o\x00l\x00B\ +\x00u\x00t\x00t\x00o\x00n\x00.\x00q\x00m\x00l\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x01\x0e\x00\x01\x00\x00\x00\x01\x00\x00 \xa0\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x00f\x00\x00\x00\x00\x00\x01\x00\x00\x05a\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x00\xe8\x00\x01\x00\x00\x00\x01\x00\x00\x09N\ +\x00\x00\x01{,_rC\ +\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x08\xbb\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x01<\x00\x00\x00\x00\x00\x01\x00\x00\x22l\ +\x00\x00\x01{*8,\x94\ +\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00GH\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x01b\x00\x00\x00\x00\x00\x01\x00\x00(Z\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x01\xf8\x00\x01\x00\x00\x00\x01\x00\x00-\x0c\ +\x00\x00\x01{,isv\ +\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x08H\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x02j\x00\x00\x00\x00\x00\x01\x00\x00H\xd2\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00+\x07\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x02:\x00\x00\x00\x00\x00\x01\x00\x00HA\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x01\x96\x00\x00\x00\x00\x00\x01\x00\x00(\xfd\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +\x00\x00\x02\xa8\x00\x00\x00\x00\x00\x01\x00\x00O\xc1\ +\x00\x00\x01{*:Y\xd8\ +\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x9e\ +\x00\x00\x01z\x7f\x7f\xa9\x0b\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/demo/resources.qrc b/demo/resources.qrc new file mode 100644 index 0000000..6e3803e --- /dev/null +++ b/demo/resources.qrc @@ -0,0 +1,20 @@ + + + qml/Timeline.qml + qml/TimelineItem.qml + qml/TimelineMouseArea.qml + qml/TimelineTextEditSelector.qml + qml/TimelineItemToolButton.qml + qml/TimelineSettings.qml + qml/NormalNumberAnimation.qml + qml/FastNumberAnimation.qml + qml/AnimationBehavior.qml + qml/AnimatedTransition.qml + qml/ToolTipArea.qml + qml/MyToolTip.qml + qml/ScrollToButton.qml + qml/AuthorInteractionArea.qml + qml/ImageContent.qml + qml/Attachment.qml + + From 73095a8982a487cd40e5c4be93514bbab92fc796 Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sun, 2 Jan 2022 11:05:59 +0100 Subject: [PATCH 2/6] Update qt to 6.2 and fix build with it --- .github/workflows/ci.yml | 146 ++++++------ .gitignore | 4 +- CMakeLists.txt | 8 +- .../typesystems/jobs/typesystem_basejob.xml | 4 + PyQuotient/typesystems/typesystem_index.xml | 3 + .../typesystems/typesystem_settings.xml | 4 +- README.md | 11 +- demo/mainwindow.py | 5 +- demo/models/messageeventmodel.py | 2 - demo/roomlistdock.py | 5 +- poetry.lock | 210 ++++++++++-------- pyproject.toml | 6 +- 12 files changed, 217 insertions(+), 191 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 298b068..54c0b8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,84 +17,84 @@ jobs: max-parallel: 1 matrix: os: [ubuntu-20.04, macos-10.15] - qt-version: [ '6.1.2' ] - python-version: [ '3.8' ] + qt-version: ["6.2.2"] + python-version: ["3.8"] steps: - - uses: actions/checkout@v2 - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache + - uses: actions/checkout@v2 - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} - cached: ${{ steps.cache-qt.outputs.cache-hit }} - aqtversion: ==1.2.4 # <1.2.0 has problem with osx installation of Qt 6 - - - name: Cache Python dependencies - if: ${{ runner.os == 'Linux' }} - uses: actions/cache@v2 - with: - path: /home/runner/.cache/pypoetry/ - key: ${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} + - name: Cache Qt + id: cache-qt + uses: actions/cache@v2 + with: + path: ${{ runner.workspace }}/Qt + key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - name: Cache Python dependencies - if: ${{ runner.os == 'macOS' }} - uses: actions/cache@v2 - with: - path: /Users/runner/Library/Caches/pypoetry/ - key: ${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} + - name: Install Qt + uses: jurplel/install-qt-action@v2.11.1 + with: + version: ${{ matrix.qt-version }} + cached: ${{ steps.cache-qt.outputs.cache-hit }} + aqtversion: ==1.2.4 # <1.2.0 has problem with osx installation of Qt 6 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - if [ "${{ matrix.os }}" == "ubuntu-20.04" ]; then - sudo apt-get install llvm - fi + - name: Cache Python dependencies + if: ${{ runner.os == 'Linux' }} + uses: actions/cache@v2 + with: + path: /home/runner/.cache/pypoetry/ + key: ${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - python${{ matrix.python-version }} -m pip install --upgrade pip - git clone --depth 1 --single-branch --branch 1.2.0a2 https://github.com/python-poetry/poetry.git - pushd poetry - python${{ matrix.python-version }} install-poetry.py --version 1.2.0a2 - popd - rm -r poetry - echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Cache Python dependencies + if: ${{ runner.os == 'macOS' }} + uses: actions/cache@v2 + with: + path: /Users/runner/Library/Caches/pypoetry/ + key: ${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - if [ "${{ runner.os }}" == "macOS" ]; then - # is needed for macos to make poetry available - export PATH="/Users/runner/.local/bin:$PATH" + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} - # qt on macos is installed in framework mode, includes like 'QtCore/QUrl' cannot be resolved by default - ln -sf $Qt6_DIR/lib/QtCore.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtCore.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtCore/ - ln -sf $Qt6_DIR/lib/QtGui.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtGui.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtGui/ - ln -sf $Qt6_DIR/lib/QtNetwork.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtNetwork.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtNetwork/ - fi - poetry env use ${{ matrix.python-version }} - poetry install --verbose - poetry run pip3 install PySide6 # temporary workaround, poetry can't install PySide6. TODO: investigate - # git clone https://github.com/quotient-im/libQuotient.git PyQuotient/libQuotient - git clone https://github.com/Aksem/libQuotient.git -b qt-6.1 PyQuotient/libQuotient - - - name: Build - run: | - poetry env use ${{ matrix.python-version }} - mkdir -p build - pushd build - cmake -DBUILD_TESTING=OFF -DBUILD_WITH_QT6=ON -DQT_PATH=$Qt6_DIR -DPYTHON_ENV_VERSION=${{ matrix.python-version }} ../ - make - popd - - - name: Run unit tests - run: | - poetry env use ${{ matrix.python-version }} - poetry run python3 -m pytest + - name: Install dependencies + run: | + if [ "${{ matrix.os }}" == "ubuntu-20.04" ]; then + sudo apt-get install llvm + fi + + python${{ matrix.python-version }} -m pip install --upgrade pip + git clone --depth 1 --single-branch --branch 1.2.0a2 https://github.com/python-poetry/poetry.git + pushd poetry + python${{ matrix.python-version }} install-poetry.py --version 1.2.0a2 + popd + rm -r poetry + echo "$HOME/.local/bin" >> $GITHUB_PATH + + if [ "${{ runner.os }}" == "macOS" ]; then + # is needed for macos to make poetry available + export PATH="/Users/runner/.local/bin:$PATH" + + # qt on macos is installed in framework mode, includes like 'QtCore/QUrl' cannot be resolved by default + ln -sf $Qt6_DIR/lib/QtCore.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtCore.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtCore/ + ln -sf $Qt6_DIR/lib/QtGui.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtGui.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtGui/ + ln -sf $Qt6_DIR/lib/QtNetwork.framework/Versions/A/Headers/* $Qt6_DIR/lib/QtNetwork.framework/Versions/A/Headers/${{ matrix.qt-version }}/QtNetwork/ + fi + poetry env use ${{ matrix.python-version }} + poetry install --verbose + poetry run pip3 install PySide6 # temporary workaround, poetry can't install PySide6. TODO: investigate + # git clone https://github.com/quotient-im/libQuotient.git PyQuotient/libQuotient + git clone https://github.com/Aksem/libQuotient.git -b fix-qt-6 PyQuotient/libQuotient + + - name: Build + run: | + poetry env use ${{ matrix.python-version }} + mkdir -p build + pushd build + cmake -DBUILD_TESTING=OFF -DBUILD_WITH_QT6=ON -DQT_PATH=$Qt6_DIR -DPYTHON_ENV_VERSION=${{ matrix.python-version }} ../ + make + popd + + - name: Run unit tests + run: | + poetry env use ${{ matrix.python-version }} + poetry run python3 -m pytest diff --git a/.gitignore b/.gitignore index 4461b1b..2255fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ PyQuotient/libQuotient.a __pycache__ .pytest_cache -.vscode \ No newline at end of file +.vscode + +build.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ce1eed..300326d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ project(LibQuotientBindings) option(USE_POETRY "Use poetry package manager" ON) option(USE_PREBUILD_LIBQUOTIENT "Use prebuilt quotient" OFF) SET(QT_PATH "" CACHE STRING "Path to qt directory") -SET(PYTHON_ENV_VERSION "3.9" CACHE STRING "Python env version(e.g. 3.9)") +SET(PYTHON_ENV_VERSION "3.8" CACHE STRING "Python env version(e.g. 3.8)") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_PATH}/lib/cmake/Qt6 ${QT_PATH}/lib/cmake/) # ================================ General configuration ====================================== @@ -439,6 +439,8 @@ quotient_timelineitem_wrapper.cpp quotient_timelineitem_wrapper.h quotient_pendingeventitem_wrapper.cpp quotient_pendingeventitem_wrapper.h +quotient_eventstatus_wrapper.h +quotient_eventstatus_wrapper.cpp ) # generated wrappers end @@ -705,9 +707,9 @@ target_link_libraries(${bindings_library} PRIVATE Qt6::Core Qt6::Gui Qt6::Networ add_library(pyside STATIC IMPORTED GLOBAL) # TODO: change hardcoded name if(UNIX AND NOT APPLE) - SET_TARGET_PROPERTIES(pyside PROPERTIES IMPORTED_LOCATION ${pyside6_path}/libpyside6.abi3.so.6.1) + SET_TARGET_PROPERTIES(pyside PROPERTIES IMPORTED_LOCATION ${pyside6_path}/libpyside6.abi3.so.6.2) else(APPLE) - SET_TARGET_PROPERTIES(pyside PROPERTIES IMPORTED_LOCATION ${pyside6_path}/libpyside6.abi3.6.1.dylib) + SET_TARGET_PROPERTIES(pyside PROPERTIES IMPORTED_LOCATION ${pyside6_path}/libpyside6.abi3.6.2.dylib) endif() target_link_libraries(${bindings_library} PRIVATE pyside) diff --git a/PyQuotient/typesystems/jobs/typesystem_basejob.xml b/PyQuotient/typesystems/jobs/typesystem_basejob.xml index b5f8fa5..6f17ec4 100644 --- a/PyQuotient/typesystems/jobs/typesystem_basejob.xml +++ b/PyQuotient/typesystems/jobs/typesystem_basejob.xml @@ -21,6 +21,10 @@ + + + + diff --git a/PyQuotient/typesystems/typesystem_index.xml b/PyQuotient/typesystems/typesystem_index.xml index 6910cfd..e1456fc 100644 --- a/PyQuotient/typesystems/typesystem_index.xml +++ b/PyQuotient/typesystems/typesystem_index.xml @@ -4,6 +4,9 @@ + + + diff --git a/PyQuotient/typesystems/typesystem_settings.xml b/PyQuotient/typesystems/typesystem_settings.xml index a0154d7..85baa89 100644 --- a/PyQuotient/typesystems/typesystem_settings.xml +++ b/PyQuotient/typesystems/typesystem_settings.xml @@ -4,10 +4,10 @@ - + - + diff --git a/README.md b/README.md index 7b80c56..edb004c 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ `poetry install` - or use pip with qt repository `http://download.qt.io/official_releases/QtForPython/`(see dependencies and their versions in pyproject.toml) [More](https://doc.qt.io/qtforpython/shiboken6/gettingstarted.html): - + or use pip with qt repository `http://download.qt.io/official_releases/QtForPython/`(see dependencies and their versions in pyproject.toml) [More](https://doc.qt.io/qtforpython/shiboken6/gettingstarted.html): + `pip3 install --index-url=http://download.qt.io/official_releases/QtForPython/ --trusted-host download.qt.io shiboken6 PySide6 shiboken6-generator` 2. Clone libQuotient: @@ -29,7 +29,6 @@ Optional parameters: **Note:**: deprecated API(properties, methods, etc) is not tested! - ### Usage After PyQuotient import use also `__feature__` import to be able to use API with snake case and true properties: @@ -52,6 +51,8 @@ If you use pip, install development requirements: Run tests: -* If you use poetry: `poetry run python -m pytest` +- If you use poetry: `poetry run python -m pytest` + +- otherwise: `python -m pytest` -* otherwise: `python -m pytest` +Update resources in demo client: `poetry run pyside6-rcc demo/resources.qrc -o demo/resources.py` diff --git a/demo/mainwindow.py b/demo/mainwindow.py index 160d578..afde2e8 100644 --- a/demo/mainwindow.py +++ b/demo/mainwindow.py @@ -7,7 +7,6 @@ from demo.chatroomwidget import ChatRoomWidget from demo.logindialog import LoginDialog from demo.roomlistdock import RoomListDock -from demo.pyquaternionroom import PyquaternionRoom from __feature__ import snake_case, true_property @@ -225,8 +224,8 @@ def show_millis_to_recon(self, connection: Quotient.Connection): user=connection.local_user_id, seconds=math.ceil(connection.millis_to_reconnect) )) - @QtCore.Slot(PyquaternionRoom) - def select_room(self, room: PyquaternionRoom) -> None: + @QtCore.Slot(Quotient.Room) + def select_room(self, room: Quotient.Room) -> None: if room is not None: print(f'Opening room {room.object_name()}') elif self.current_room is not None: diff --git a/demo/models/messageeventmodel.py b/demo/models/messageeventmodel.py index 0d3a8af..3bd53bd 100644 --- a/demo/models/messageeventmodel.py +++ b/demo/models/messageeventmodel.py @@ -28,8 +28,6 @@ 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') - def row_count(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()): # TODO: implement return 2 diff --git a/demo/roomlistdock.py b/demo/roomlistdock.py index 5295efe..6610d39 100644 --- a/demo/roomlistdock.py +++ b/demo/roomlistdock.py @@ -1,7 +1,6 @@ from demo.models.abstractroomordering import RoomGroup from PySide6 import QtCore, QtWidgets, QtGui from PyQuotient import Quotient -from demo.pyquaternionroom import PyquaternionRoom from demo.models.roomlistmodel import RoomListModel, Roles from demo.models.orderbytag import OrderByTag from __feature__ import snake_case, true_property @@ -34,7 +33,7 @@ def paint(self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, super().paint(painter, new_option, index) class RoomListDock(QtWidgets.QDockWidget): - roomSelected = QtCore.Signal(PyquaternionRoom) + roomSelected = QtCore.Signal(Quotient.Room) def __init__(self, parent=None) -> None: super().__init__("Rooms", parent) @@ -66,7 +65,7 @@ def add_connection(self, connection: Quotient.Connection): self.model.add_connection(connection) @QtCore.Slot() - def set_selected_room(self, room: PyquaternionRoom): + def set_selected_room(self, room: Quotient.Room): if self.get_selected_room() == room: return diff --git a/poetry.lock b/poetry.lock index 396be57..6c8f41d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,17 +8,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "colorama" @@ -38,7 +38,7 @@ python-versions = "*" [[package]] name = "lxml" -version = "4.6.3" +version = "4.7.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." category = "main" optional = false @@ -52,49 +52,53 @@ source = ["Cython (>=0.29.7)"] [[package]] name = "packaging" -version = "21.0" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyside6" -version = "6.1.2" +version = "6.2.2.1" description = "Python / C++ bindings helper module" category = "main" optional = false -python-versions = ">=3.6, <3.10" +python-versions = ">=3.6, <3.11" [package.source] type = "legacy" @@ -103,7 +107,7 @@ reference = "qt" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -115,7 +119,7 @@ attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -138,11 +142,11 @@ dev = ["pre-commit", "tox", "pytest-asyncio"] [[package]] name = "shiboken6" -version = "6.1.2" +version = "6.2.2.1" description = "Python / C++ bindings helper module" category = "main" optional = false -python-versions = ">=3.6, <3.10" +python-versions = ">=3.6, <3.11" [package.source] type = "legacy" @@ -151,14 +155,14 @@ reference = "qt" [[package]] name = "shiboken6-generator" -version = "6.1.2" +version = "6.2.2.1" description = "Python / C++ bindings generator" category = "main" optional = false -python-versions = ">=3.6, <3.10" +python-versions = ">=3.6, <3.11" [package.dependencies] -shiboken6 = "6.1.2" +shiboken6 = "6.2.2.1" [package.source] type = "legacy" @@ -176,7 +180,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [metadata] lock-version = "1.1" python-versions = ">= 3.8, < 3.10" -content-hash = "fab23b01f64dac94e1708045d18e89ae42b3f8227fcdf7f3c27b242ba00d6003" +content-hash = "48c21fee334eeef76aa33f51a14f80988ccaf1bf30d62270f3504f37f01c0a00" [metadata.files] atomicwrites = [ @@ -184,8 +188,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -196,97 +200,111 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] lxml = [ - {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, - {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, - {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, - {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, - {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, - {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, - {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, - {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, - {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, - {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, - {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"}, - {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, - {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, - {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, - {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, - {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, - {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"}, - {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, - {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"}, - {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, - {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, - {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, - {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, - {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, - {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"}, - {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, - {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"}, - {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, - {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, - {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, - {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, - {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, - {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"}, - {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, - {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"}, - {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, - {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, - {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, - {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, - {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, - {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"}, - {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, - {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"}, - {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, - {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, - {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, + {file = "lxml-4.7.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f"}, + {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e"}, + {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17"}, + {file = "lxml-4.7.1-cp27-cp27m-win32.whl", hash = "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419"}, + {file = "lxml-4.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2"}, + {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5"}, + {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d"}, + {file = "lxml-4.7.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3"}, + {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175"}, + {file = "lxml-4.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6"}, + {file = "lxml-4.7.1-cp310-cp310-win32.whl", hash = "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6"}, + {file = "lxml-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e"}, + {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675"}, + {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7"}, + {file = "lxml-4.7.1-cp35-cp35m-win32.whl", hash = "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5"}, + {file = "lxml-4.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03"}, + {file = "lxml-4.7.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d"}, + {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6"}, + {file = "lxml-4.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d"}, + {file = "lxml-4.7.1-cp36-cp36m-win32.whl", hash = "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d"}, + {file = "lxml-4.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a"}, + {file = "lxml-4.7.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3"}, + {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944"}, + {file = "lxml-4.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b"}, + {file = "lxml-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4"}, + {file = "lxml-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1"}, + {file = "lxml-4.7.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e"}, + {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e"}, + {file = "lxml-4.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9"}, + {file = "lxml-4.7.1-cp38-cp38-win32.whl", hash = "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd"}, + {file = "lxml-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d"}, + {file = "lxml-4.7.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db"}, + {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851"}, + {file = "lxml-4.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4"}, + {file = "lxml-4.7.1-cp39-cp39-win32.whl", hash = "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0"}, + {file = "lxml-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f"}, + {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267"}, + {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"}, + {file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"}, ] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyside6 = [ - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-macosx_10_14_x86_64.whl", hash = "sha256:d95286a6d59f29f5cad18c4346166f94bfedff9a61056d9b30d641e7a6ee0b28"}, - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:dab04edbde131d09a885fc2c945d9ea3f8e90e6d73546e66de5c2265c4731cf9"}, - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:2dda3467d56ec76031b36ce2571e931cf5f3b6682497edc8a004e0360436da5d"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-macosx_10_14_x86_64.whl", hash = "sha256:acdbf0f085d8d07125ca5acc2ee6b1c56c97c91bd924ebfbcee7ac7d166e7bbe"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:745428c03831f5d60c3ad08b2cbcf25342aef5abfba9d7e79b3a25b38d4bff31"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:6b60c5f7ca1a272526c49a9e5f4cc9a4f5657e89c2c36eaa81f24b4c027847d9"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_14_universal2.whl", hash = "sha256:f9fa755b30bd8098ba3734a8a7e7664b9bf6bf53d7c0462c7e9657f8a8d3392f"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl", hash = "sha256:5d35206897edc00bfab3c67dcf180e45406a93c8694e68c12364c3f9e370dfc6"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl", hash = "sha256:227597e427f3c516a237e714de8dc30e0a4747607ac2283dd350bd4314605803"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_14_universal2.whl", hash = "sha256:cbffaefe676f5cce4204d16c2d8f794521cb8c66abbe6470d6e5cb2b889c4b88"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl", hash = "sha256:325eb45d8ffc59e0cb94d5fb9cdd0a8c28ed1705f3e7fd37c239a1dca50b1c22"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl", hash = "sha256:a4f267789c18caa60afd23818743a5abe164ed4f89f4eafaf4577d4fa646c293"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-mock = [ {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, ] shiboken6 = [ - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-macosx_10_14_x86_64.whl", hash = "sha256:d95286a6d59f29f5cad18c4346166f94bfedff9a61056d9b30d641e7a6ee0b28"}, - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:dab04edbde131d09a885fc2c945d9ea3f8e90e6d73546e66de5c2265c4731cf9"}, - {file = "PySide6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:2dda3467d56ec76031b36ce2571e931cf5f3b6682497edc8a004e0360436da5d"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-macosx_10_14_x86_64.whl", hash = "sha256:acdbf0f085d8d07125ca5acc2ee6b1c56c97c91bd924ebfbcee7ac7d166e7bbe"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:745428c03831f5d60c3ad08b2cbcf25342aef5abfba9d7e79b3a25b38d4bff31"}, - {file = "shiboken6-6.1.2-6.1.2-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:6b60c5f7ca1a272526c49a9e5f4cc9a4f5657e89c2c36eaa81f24b4c027847d9"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_14_universal2.whl", hash = "sha256:f9fa755b30bd8098ba3734a8a7e7664b9bf6bf53d7c0462c7e9657f8a8d3392f"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl", hash = "sha256:5d35206897edc00bfab3c67dcf180e45406a93c8694e68c12364c3f9e370dfc6"}, + {file = "PySide6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl", hash = "sha256:227597e427f3c516a237e714de8dc30e0a4747607ac2283dd350bd4314605803"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_14_universal2.whl", hash = "sha256:cbffaefe676f5cce4204d16c2d8f794521cb8c66abbe6470d6e5cb2b889c4b88"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl", hash = "sha256:325eb45d8ffc59e0cb94d5fb9cdd0a8c28ed1705f3e7fd37c239a1dca50b1c22"}, + {file = "shiboken6-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl", hash = "sha256:a4f267789c18caa60afd23818743a5abe164ed4f89f4eafaf4577d4fa646c293"}, ] shiboken6-generator = [ - {file = "shiboken6_generator-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-macosx_10_14_x86_64.whl", hash = "sha256:e8cf068e21aad66571f67e58129655d296796ee9107f172c3b4958a8efefcdbe"}, - {file = "shiboken6_generator-6.1.2-6.1.2-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl", hash = "sha256:0fc0a39e82522a2cc71ce11dc1d324deb7100e86aaf387c021c04457d8394531"}, - {file = "shiboken6_generator-6.1.2-6.1.2-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:43bae2454784ca58058f8311903791c01adaec48349d4e1aa23c8aafdff24f52"}, + {file = "shiboken6_generator-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-macosx_10_14_universal2.whl", hash = "sha256:7be6dcffcf56ea21f6f4dc47f0fa1c222201138b4063521e6af03f0f81ecb9f4"}, + {file = "shiboken6_generator-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-abi3-manylinux1_x86_64.whl", hash = "sha256:6c5d393f7668e1e4ff0f4237f4e54ba67d60f4e331a1ef72fa208a8738213f6a"}, + {file = "shiboken6_generator-6.2.2.1-6.2.2-cp36.cp37.cp38.cp39.cp310-none-win_amd64.whl", hash = "sha256:48f1cc17adc40de95674b18b670bee9d0ec6248cfd72e1d9b72ccbd43c0dc174"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/pyproject.toml b/pyproject.toml index f68dcbe..033e1d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,9 +11,9 @@ packages = [ [tool.poetry.dependencies] python = ">= 3.8, < 3.10" -shiboken6 = "^6.1.2" -PySide6 = "^6.1.2" -shiboken6-generator = "^6.1.2" +shiboken6 = "^6.2.2.1" +PySide6 = "^6.2.2.1" +shiboken6-generator = "^6.2.2.1" lxml = "^4.6.3" [tool.poetry.dev-dependencies] From 09358b13e7f9c41b1899c9a683cefdbfddecc827 Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sun, 2 Jan 2022 11:24:59 +0100 Subject: [PATCH 3/6] Replace EventStatus with python enum, because binding of Q_NAMESPACE has not meta data and cannot be registered as qml type --- PyQuotient/__init__.py | 22 ++++++++++++++++++++++ demo/models/messageeventmodel.py | 2 ++ 2 files changed, 24 insertions(+) diff --git a/PyQuotient/__init__.py b/PyQuotient/__init__.py index 288d78b..cbb12c8 100644 --- a/PyQuotient/__init__.py +++ b/PyQuotient/__init__.py @@ -1 +1,23 @@ +from enum import Flag +from PySide6 import QtCore from .PyQuotient import Quotient +from __feature__ import snake_case, true_property + + +class EventStatus(QtCore.QObject): + + @QtCore.QFlag + class Code(Flag): + Normal = 0x00 #< No special designation + Submitted = 0x01 #< The event has just been submitted for sending + FileUploaded = 0x02 #< The file attached to the event has been + # uploaded to the server + Departed = 0x03 #< The event has left the client + ReachedServer = 0x04 #< The server has received the event + SendingFailed = 0x05 #< The server could not receive the event + Redacted = 0x08 #< The event has been redacted + Replaced = 0x10 #< The event has been replaced + Hidden = 0x100 + + +Quotient.EventStatus = EventStatus diff --git a/demo/models/messageeventmodel.py b/demo/models/messageeventmodel.py index 3bd53bd..0d3a8af 100644 --- a/demo/models/messageeventmodel.py +++ b/demo/models/messageeventmodel.py @@ -28,6 +28,8 @@ 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') + def row_count(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()): # TODO: implement return 2 From d741b8dcf1a135578e522a3aef74a19d7579f821 Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sun, 2 Jan 2022 11:26:10 +0100 Subject: [PATCH 4/6] Fix CI config --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54c0b8f..44548e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: poetry install --verbose poetry run pip3 install PySide6 # temporary workaround, poetry can't install PySide6. TODO: investigate # git clone https://github.com/quotient-im/libQuotient.git PyQuotient/libQuotient - git clone https://github.com/Aksem/libQuotient.git -b fix-qt-6 PyQuotient/libQuotient + git clone https://github.com/Aksem/libQuotient.git -b fix-qt-6 PyQuotient/libQuotient - name: Build run: | From 77fdc4bda3bba8c4ae59c066f9afa23b7b53ca81 Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sun, 2 Jan 2022 17:10:11 +0100 Subject: [PATCH 5/6] Use original libQuotient in CI --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44548e1..ecc977f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,8 +82,7 @@ jobs: poetry env use ${{ matrix.python-version }} poetry install --verbose poetry run pip3 install PySide6 # temporary workaround, poetry can't install PySide6. TODO: investigate - # git clone https://github.com/quotient-im/libQuotient.git PyQuotient/libQuotient - git clone https://github.com/Aksem/libQuotient.git -b fix-qt-6 PyQuotient/libQuotient + git clone https://github.com/quotient-im/libQuotient.git PyQuotient/libQuotient - name: Build run: | From 0724c9b1c7e6978f8c2a4cb5dba721e05674741f Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sun, 2 Jan 2022 17:42:56 +0100 Subject: [PATCH 6/6] Use gcc 10 on linux --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecc977f..a01651f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,14 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Set up build environment(Linux) + if: ${{ runner.os == 'Linux' }} + run: | + # use gcc 10 on linux + CXX_VERSION_POSTFIX='-10' + echo "CC=gcc$CXX_VERSION_POSTFIX" >>$GITHUB_ENV + echo "CXX=g++$CXX_VERSION_POSTFIX" >>$GITHUB_ENV + - name: Install dependencies run: | if [ "${{ matrix.os }}" == "ubuntu-20.04" ]; then