diff --git a/CMakeLists.txt b/CMakeLists.txt index ae9a5e46..a7cddc50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ include(LMDB) # # Discover Qt dependencies. # -find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 REQUIRED) +find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED) find_package(Qt5QuickCompiler) find_package(Qt5DBus) @@ -234,6 +234,7 @@ set(SRC_FILES src/MainWindow.cpp src/MatrixClient.cpp src/MxcImageProvider.cpp + src/ColorImageProvider.cpp src/QuickSwitcher.cpp src/Olm.cpp src/RegisterPage.cpp @@ -414,6 +415,7 @@ set(COMMON_LIBS Qt5::Multimedia Qt5::Qml Qt5::QuickControls2 + Qt5::QuickWidgets nlohmann_json::nlohmann_json) if(APPVEYOR_BUILD) diff --git a/resources/qml/EncryptionIndicator.qml b/resources/qml/EncryptionIndicator.qml index 0d0e86cf..2cd9161b 100644 --- a/resources/qml/EncryptionIndicator.qml +++ b/resources/qml/EncryptionIndicator.qml @@ -1,6 +1,5 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 -import QtGraphicalEffects 1.0 import com.github.nheko 1.0 Rectangle { @@ -19,12 +18,7 @@ Rectangle { Image { id: stateImg anchors.fill: parent - source: "qrc:/icons/icons/ui/lock.png" - } - ColorOverlay { - anchors.fill: stateImg - source: stateImg - color: colors.buttonText + source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText } } diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index dda9865b..dc576e18 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -1,9 +1,8 @@ import QtQuick 2.3 import QtQuick.Controls 2.3 -import QtGraphicalEffects 1.0 Button { - property alias image: buttonImg.source + property string image: undefined id: button @@ -17,11 +16,7 @@ Button { id: buttonImg // Workaround, can't get icon.source working for now... anchors.fill: parent - } - ColorOverlay { - anchors.fill: buttonImg - source: buttonImg - color: button.hovered ? colors.highlight : colors.buttonText + source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText) } MouseArea diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml index 9f8d2cae..2ed59a17 100644 --- a/resources/qml/StatusIndicator.qml +++ b/resources/qml/StatusIndicator.qml @@ -1,6 +1,5 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 -import QtGraphicalEffects 1.0 import com.github.nheko 1.0 Rectangle { @@ -28,18 +27,12 @@ Rectangle { // Workaround, can't get icon.source working for now... anchors.fill: parent source: switch (indicator.state) { - case MtxEvent.Failed: return "qrc:/icons/icons/ui/remove-symbol.png" - case MtxEvent.Sent: return "qrc:/icons/icons/ui/clock.png" - case MtxEvent.Received: return "qrc:/icons/icons/ui/checkmark.png" - case MtxEvent.Read: return "qrc:/icons/icons/ui/double-tick-indicator.png" + case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText + case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText + case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText + case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText default: return "" } } - ColorOverlay { - anchors.fill: stateImg - source: stateImg - color: colors.buttonText - visible: stateImg.source != "" - } } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 8f9090e3..63a84701 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -50,7 +50,7 @@ RowLayout { Layout.preferredHeight: 16 id: replyButton - image: "qrc:/icons/icons/ui/mail-reply.png" + image: ":/icons/icons/ui/mail-reply.png" ToolTip { visible: replyButton.hovered text: qsTr("Reply") @@ -64,7 +64,7 @@ RowLayout { Layout.preferredHeight: 16 id: optionsButton - image: "qrc:/icons/icons/ui/vertical-ellipsis.png" + image: ":/icons/icons/ui/vertical-ellipsis.png" ToolTip { visible: optionsButton.hovered text: qsTr("Options") diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c2f6f9b9..b25b3a7c 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -8,151 +8,151 @@ import com.github.nheko 1.0 import "./delegates" -Rectangle { - anchors.fill: parent - +Item { property var colors: currentActivePalette property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled } property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive property int avatarSize: 40 - color: colors.window - - Text { - visible: !timelineManager.timeline - anchors.centerIn: parent - text: qsTr("No room open") - font.pointSize: 24 - color: colors.windowText - } - - ListView { - id: chat - - cacheBuffer: 2000 - - visible: timelineManager.timeline != null + Rectangle { anchors.fill: parent + color: colors.window - anchors.leftMargin: 4 - anchors.rightMargin: scrollbar.width + Text { + visible: !timelineManager.timeline + anchors.centerIn: parent + text: qsTr("No room open") + font.pointSize: 24 + color: colors.windowText + } - model: timelineManager.timeline + ListView { + id: chat - onModelChanged: { - if (model) { - currentIndex = model.currentIndex - if (model.currentIndex == count - 1) { + cacheBuffer: 2000 + + visible: timelineManager.timeline != null + anchors.fill: parent + + anchors.leftMargin: 4 + anchors.rightMargin: scrollbar.width + + model: timelineManager.timeline + + onModelChanged: { + if (model) { + currentIndex = model.currentIndex + if (model.currentIndex == count - 1) { + positionViewAtEnd() + } else { + positionViewAtIndex(model.currentIndex, ListView.End) + } + + //if (contentHeight < height) { + // model.fetchHistory(); + //} + } + } + + ScrollBar.vertical: ScrollBar { + id: scrollbar + anchors.top: parent.top + anchors.left: parent.right + anchors.bottom: parent.bottom + onPressedChanged: if (!pressed) chat.updatePosition() + } + + property bool atBottom: false + onCountChanged: { + if (atBottom) { + var newIndex = count - 1 // last index positionViewAtEnd() - } else { - positionViewAtIndex(model.currentIndex, ListView.End) + currentIndex = newIndex + model.currentIndex = newIndex } - if (contentHeight < height) { + if (contentHeight < height && model) { model.fetchHistory(); } } - } - ScrollBar.vertical: ScrollBar { - id: scrollbar - anchors.top: parent.top - anchors.left: parent.right - anchors.bottom: parent.bottom - onPressedChanged: if (!pressed) chat.updatePosition() - } + onAtYBeginningChanged: if (atYBeginning) model.fetchHistory() - property bool atBottom: false - onCountChanged: { - if (atBottom && Window.active) { - var newIndex = count - 1 // last index - positionViewAtEnd() - currentIndex = newIndex - model.currentIndex = newIndex - } - - if (contentHeight < height && model) { - model.fetchHistory(); - } - } - - onAtYBeginningChanged: if (atYBeginning) model.fetchHistory() - - function updatePosition() { - for (var y = chat.contentY + chat.height; y > chat.height; y -= 5) { - var i = chat.itemAt(100, y); - if (!i) continue; - if (!i.isFullyVisible()) continue; - chat.model.currentIndex = i.getIndex(); - chat.currentIndex = i.getIndex() - atBottom = i.getIndex() == count - 1; - console.log("bottom:" + atBottom) - break; - } - } - onMovementEnded: updatePosition() - - spacing: 4 - delegate: TimelineRow { - function isFullyVisible() { - return height > 1 && (y - chat.contentY - 1) + height < chat.height - } - function getIndex() { - return index; - } - } - - section { - property: "section" - delegate: Column { - topPadding: 4 - bottomPadding: 4 - spacing: 8 - - width: parent.width - height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 - - Label { - id: dateBubble - anchors.horizontalCenter: parent.horizontalCenter - visible: section.includes(" ") - text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1]))) - color: colors.windowText - - height: contentHeight * 1.2 - width: contentWidth * 1.2 - horizontalAlignment: Text.AlignHCenter - background: Rectangle { - radius: parent.height / 2 - color: colors.dark - } + function updatePosition() { + for (var y = chat.contentY + chat.height; y > chat.height; y -= 9) { + var i = chat.itemAt(100, y); + if (!i) continue; + if (!i.isFullyVisible()) continue; + chat.model.currentIndex = i.getIndex(); + chat.currentIndex = i.getIndex() + atBottom = i.getIndex() == count - 1; + break; } - Row { - height: userName.height - spacing: 4 - Avatar { - width: avatarSize - height: avatarSize - url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/") - displayName: chat.model.displayName(section.split(" ")[0]) + } + onMovementEnded: updatePosition() - MouseArea { - anchors.fill: parent - onClicked: chat.model.openUserProfile(section.split(" ")[0]) - cursorShape: Qt.PointingHandCursor + spacing: 4 + delegate: TimelineRow { + function isFullyVisible() { + return height > 1 && (y - chat.contentY - 1) + height < chat.height + } + function getIndex() { + return index; + } + } + + section { + property: "section" + delegate: Column { + topPadding: 4 + bottomPadding: 4 + spacing: 8 + + width: parent.width + height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 + + Label { + id: dateBubble + anchors.horizontalCenter: parent.horizontalCenter + visible: section.includes(" ") + text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1]))) + color: colors.windowText + + height: contentHeight * 1.2 + width: contentWidth * 1.2 + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + radius: parent.height / 2 + color: colors.dark } } + Row { + height: userName.height + spacing: 4 + Avatar { + width: avatarSize + height: avatarSize + url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/") + displayName: chat.model.displayName(section.split(" ")[0]) - Text { - id: userName - text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0])) - color: chat.model.userColor(section.split(" ")[0], colors.window) - textFormat: Text.RichText + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(section.split(" ")[0]) + cursorShape: Qt.PointingHandCursor + } + } - MouseArea { - anchors.fill: parent - onClicked: chat.model.openUserProfile(section.split(" ")[0]) - cursorShape: Qt.PointingHandCursor + Text { + id: userName + text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0])) + color: chat.model.userColor(section.split(" ")[0], colors.window) + textFormat: Text.RichText + + MouseArea { + anchors.fill: parent + onClicked: chat.model.openUserProfile(section.split(" ")[0]) + cursorShape: Qt.PointingHandCursor + } } } } diff --git a/src/ColorImageProvider.cpp b/src/ColorImageProvider.cpp new file mode 100644 index 00000000..92e4732b --- /dev/null +++ b/src/ColorImageProvider.cpp @@ -0,0 +1,30 @@ +#include "ColorImageProvider.h" + +#include "Logging.h" +#include + +QPixmap +ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &) +{ + auto args = id.split('?'); + + nhlog::ui()->info("Loading {}, source is {}", id.toStdString(), args[0].toStdString()); + + QPixmap source(args[0]); + + if (size) + *size = QSize(source.width(), source.height()); + + if (args.size() < 2) + return source; + + QColor color(args[1]); + + QPixmap colorized = source; + QPainter painter(&colorized); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + painter.fillRect(colorized.rect(), color); + painter.end(); + + return colorized; +} diff --git a/src/ColorImageProvider.h b/src/ColorImageProvider.h new file mode 100644 index 00000000..21f36c12 --- /dev/null +++ b/src/ColorImageProvider.h @@ -0,0 +1,11 @@ +#include + +class ColorImageProvider : public QQuickImageProvider +{ +public: + ColorImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) + {} + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; +}; diff --git a/src/timeline2/DelegateChooser.cpp b/src/timeline2/DelegateChooser.cpp index 6aeea69b..632a2a64 100644 --- a/src/timeline2/DelegateChooser.cpp +++ b/src/timeline2/DelegateChooser.cpp @@ -119,7 +119,6 @@ DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status) chooser.child = dynamic_cast(object()); if (chooser.child == nullptr) { nhlog::ui()->error("Delegate has to be derived of Item!"); - delete chooser.child; return; } diff --git a/src/timeline2/TimelineModel.cpp b/src/timeline2/TimelineModel.cpp index b2b6f803..ab7d3d47 100644 --- a/src/timeline2/TimelineModel.cpp +++ b/src/timeline2/TimelineModel.cpp @@ -844,7 +844,7 @@ TimelineModel::replyAction(QString id) return related_; }, event); - related.type = mtx::events::getMessageType(boost::apply_visitor( + related.type = mtx::events::getMessageType(boost::apply_visitor( [](const auto &e) -> std::string { return eventMsgType(e); }, event)); related.quoted_body = boost::apply_visitor( [](const auto &e) -> QString { return eventFormattedBody(e); }, event); diff --git a/src/timeline2/TimelineViewManager.cpp b/src/timeline2/TimelineViewManager.cpp index a054bc78..d733ad90 100644 --- a/src/timeline2/TimelineViewManager.cpp +++ b/src/timeline2/TimelineViewManager.cpp @@ -8,6 +8,7 @@ #include #include "ChatPage.h" +#include "ColorImageProvider.h" #include "DelegateChooser.h" #include "Logging.h" #include "MxcImageProvider.h" @@ -51,6 +52,7 @@ TimelineViewManager::updateColorPalette() TimelineViewManager::TimelineViewManager(QWidget *parent) : imgProvider(new MxcImageProvider()) + , colorImgProvider(new ColorImageProvider()) { qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "com.github.nheko", @@ -61,12 +63,24 @@ TimelineViewManager::TimelineViewManager(QWidget *parent) qmlRegisterType("com.github.nheko", 1, 0, "DelegateChoice"); qmlRegisterType("com.github.nheko", 1, 0, "DelegateChooser"); +#ifdef USE_QUICK_VIEW view = new QQuickView(); container = QWidget::createWindowContainer(view, parent); +#else + view = new QQuickWidget(parent); + container = view; + view->setResizeMode(QQuickWidget::SizeRootObjectToView); + container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) { + nhlog::ui()->debug("Status changed to {}", status); + }); +#endif container->setMinimumSize(200, 200); view->rootContext()->setContextProperty("timelineManager", this); updateColorPalette(); view->engine()->addImageProvider("MxcImage", imgProvider); + view->engine()->addImageProvider("colorimage", colorImgProvider); view->setSource(QUrl("qrc:///qml/TimelineView.qml")); connect(dynamic_cast(parent), diff --git a/src/timeline2/TimelineViewManager.h b/src/timeline2/TimelineViewManager.h index b14e78ff..691c8ddb 100644 --- a/src/timeline2/TimelineViewManager.h +++ b/src/timeline2/TimelineViewManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -16,6 +17,7 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" class MxcImageProvider; +class ColorImageProvider; class TimelineViewManager : public QObject { @@ -99,10 +101,15 @@ public slots: uint64_t dsize); private: +#ifdef USE_QUICK_VIEW QQuickView *view; +#else + QQuickWidget *view; +#endif QWidget *container; TimelineModel *timeline_ = nullptr; MxcImageProvider *imgProvider; + ColorImageProvider *colorImgProvider; QHash> models; };