diff --git a/CMakeLists.txt b/CMakeLists.txt index fda60b71..321234dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,7 +237,9 @@ set(SRC_FILES src/RunGuard.cpp src/SideBarActions.cpp src/Splitter.cpp - src/SuggestionsPopup.cpp + src/popups/SuggestionsPopup.cpp + src/popups/PopupItem.cpp + src/popups/ReplyPopup.cpp src/TextInputWidget.cpp src/TopRoomBar.cpp src/TrayIcon.cpp @@ -375,7 +377,9 @@ qt5_wrap_cpp(MOC_HEADERS src/RoomList.h src/SideBarActions.h src/Splitter.h - src/SuggestionsPopup.h + src/popups/SuggestionsPopup.h + src/popups/ReplyPopup.h + src/popups/PopupItem.h src/TextInputWidget.h src/TopRoomBar.h src/TrayIcon.h diff --git a/src/QuickSwitcher.cpp b/src/QuickSwitcher.cpp index eb79a427..f8f6c001 100644 --- a/src/QuickSwitcher.cpp +++ b/src/QuickSwitcher.cpp @@ -23,7 +23,7 @@ #include #include "QuickSwitcher.h" -#include "SuggestionsPopup.h" +#include "popups/SuggestionsPopup.h" RoomSearchInput::RoomSearchInput(QWidget *parent) : TextField(parent) diff --git a/src/QuickSwitcher.h b/src/QuickSwitcher.h index 24b9adfa..05f7be07 100644 --- a/src/QuickSwitcher.h +++ b/src/QuickSwitcher.h @@ -22,7 +22,7 @@ #include #include -#include "SuggestionsPopup.h" +#include "popups/SuggestionsPopup.h" #include "ui/TextField.h" Q_DECLARE_METATYPE(std::vector) diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 340c6f7c..b4251a0e 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -48,7 +48,8 @@ static constexpr int ButtonHeight = 22; FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit{parent} , history_index_{0} - , popup_{parent} + , suggestionsPopup_{parent} + , replyPopup_{parent} , previewDialog_{parent} { setFrameStyle(QFrame::NoFrame); @@ -75,29 +76,34 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent) &FilteredTextEdit::uploadData); connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults); - connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { - popup_.hide(); - - auto cursor = textCursor(); - const int end = cursor.position(); - - cursor.setPosition(atTriggerPosition_, QTextCursor::MoveAnchor); - cursor.setPosition(end, QTextCursor::KeepAnchor); - cursor.removeSelectedText(); - cursor.insertText(text); + connect(&replyPopup_, &ReplyPopup::userSelected, this, [this](const QString &text) { + // TODO: Show user avatar window. + nhlog::ui()->info("User selected: " + text.toStdString()); }); + connect( + &suggestionsPopup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { + suggestionsPopup_.hide(); + + auto cursor = textCursor(); + const int end = cursor.position(); + + cursor.setPosition(atTriggerPosition_, QTextCursor::MoveAnchor); + cursor.setPosition(end, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + cursor.insertText(text); + }); // For cycling through the suggestions by hitting tab. connect(this, &FilteredTextEdit::selectNextSuggestion, - &popup_, + &suggestionsPopup_, &SuggestionsPopup::selectNextSuggestion); connect(this, &FilteredTextEdit::selectPreviousSuggestion, - &popup_, + &suggestionsPopup_, &SuggestionsPopup::selectPreviousSuggestion); connect(this, &FilteredTextEdit::selectHoveredSuggestion, this, [this]() { - popup_.selectHoveredSuggestion(); + suggestionsPopup_.selectHoveredSuggestion(); }); previewDialog_.hide(); @@ -117,9 +123,9 @@ FilteredTextEdit::showResults(const QVector &results) pos = viewport()->mapToGlobal(rect.topLeft()); } - popup_.addUsers(results); - popup_.move(pos.x(), pos.y() - popup_.height() - 10); - popup_.show(); + suggestionsPopup_.addUsers(results); + suggestionsPopup_.move(pos.x(), pos.y() - suggestionsPopup_.height() - 10); + suggestionsPopup_.show(); } void @@ -146,7 +152,7 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) closeSuggestions(); } - if (popup_.isVisible()) { + if (suggestionsPopup_.isVisible()) { switch (event->key()) { case Qt::Key_Down: case Qt::Key_Tab: @@ -169,6 +175,19 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) } } + if (replyPopup_.isVisible()) { + switch (event->key()) + { + case Qt::Key_Escape: + closeReply(); + return; + + default: + break; + } + } + + switch (event->key()) { case Qt::Key_At: atTriggerPosition_ = textCursor().position(); @@ -419,6 +438,24 @@ FilteredTextEdit::submit() clear(); } +void +FilteredTextEdit::showReplyPopup(const QString &user, const QString &msg, const QString &event_id) +{ + QPoint pos; + + if (isAnchorValid()) { + auto cursor = textCursor(); + cursor.setPosition(atTriggerPosition_); + pos = viewport()->mapToGlobal(cursorRect(cursor).topLeft()); + } else { + auto rect = cursorRect(); + pos = viewport()->mapToGlobal(rect.topLeft()); + } + replyPopup_.setReplyContent(user, msg, event_id); + replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10); + replyPopup_.show(); +} + void FilteredTextEdit::textChanged() { @@ -666,9 +703,10 @@ TextInputWidget::paintEvent(QPaintEvent *) void TextInputWidget::addReply(const QString &username, const QString &msg, const QString &replied_event) { - input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg)); + // input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg)); input_->setFocus(); + input_->showReplyPopup(username, msg, replied_event); auto cursor = input_->textCursor(); cursor.movePosition(QTextCursor::End); input_->setTextCursor(cursor); diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index a12183d8..2936340b 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -28,7 +28,8 @@ #include #include -#include "SuggestionsPopup.h" +#include "popups/SuggestionsPopup.h" +#include "popups/ReplyPopup.h" #include "dialogs/PreviewUploadOverlay.h" #include "emoji/PickButton.h" @@ -55,6 +56,7 @@ public: void submit(); void setRelatedEvent(const QString &event) { related_event_ = event; } + void showReplyPopup(const QString &user, const QString &msg, const QString &event_id); signals: void heightChanged(int height); @@ -85,7 +87,7 @@ protected: void insertFromMimeData(const QMimeData *source) override; void focusOutEvent(QFocusEvent *event) override { - popup_.hide(); + suggestionsPopup_.hide(); QTextEdit::focusOutEvent(event); } @@ -94,7 +96,8 @@ private: size_t history_index_; QTimer *typingTimer_; - SuggestionsPopup popup_; + SuggestionsPopup suggestionsPopup_; + ReplyPopup replyPopup_; // Used for replies QString related_event_; @@ -109,7 +112,8 @@ private: int anchorWidth(AnchorType anchor) { return static_cast(anchor); } - void closeSuggestions() { popup_.hide(); } + void closeSuggestions() { suggestionsPopup_.hide(); } + void closeReply() { replyPopup_.hide(); } void resetAnchor() { atTriggerPosition_ = -1; } bool isAnchorValid() { return atTriggerPosition_ != -1; } bool hasAnchor(int pos, AnchorType anchor) diff --git a/src/SuggestionsPopup.cpp b/src/popups/PopupItem.cpp similarity index 50% rename from src/SuggestionsPopup.cpp rename to src/popups/PopupItem.cpp index 952d2ef3..b10cd32e 100644 --- a/src/SuggestionsPopup.cpp +++ b/src/popups/PopupItem.cpp @@ -2,11 +2,9 @@ #include #include -#include "Config.h" -#include "SuggestionsPopup.h" -#include "Utils.h" -#include "ui/Avatar.h" -#include "ui/DropShadow.h" +#include "PopupItem.h" +#include "../Utils.h" +#include "../ui/Avatar.h" constexpr int PopupHMargin = 4; constexpr int PopupItemMargin = 3; @@ -146,151 +144,4 @@ RoomItem::mousePressEvent(QMouseEvent *event) emit clicked(selectedText()); QWidget::mousePressEvent(event); -} - -SuggestionsPopup::SuggestionsPopup(QWidget *parent) - : QWidget(parent) -{ - setAttribute(Qt::WA_ShowWithoutActivating, true); - setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); - - layout_ = new QVBoxLayout(this); - layout_->setMargin(0); - layout_->setSpacing(0); -} - -void -SuggestionsPopup::addRooms(const std::vector &rooms) -{ - if (rooms.empty()) { - hide(); - return; - } - - const size_t layoutCount = layout_->count(); - const size_t roomCount = rooms.size(); - - // Remove the extra widgets from the layout. - if (roomCount < layoutCount) - removeLayoutItemsAfter(roomCount - 1); - - for (size_t i = 0; i < roomCount; ++i) { - auto item = layout_->itemAt(i); - - // Create a new widget if there isn't already one in that - // layout position. - if (!item) { - auto room = new RoomItem(this, rooms.at(i)); - connect(room, &RoomItem::clicked, this, &SuggestionsPopup::itemSelected); - layout_->addWidget(room); - } else { - // Update the current widget with the new data. - auto room = qobject_cast(item->widget()); - if (room) - room->updateItem(rooms.at(i)); - } - } - - resetSelection(); - adjustSize(); - - resize(geometry().width(), 40 * rooms.size()); - - selectNextSuggestion(); -} - -void -SuggestionsPopup::addUsers(const QVector &users) -{ - if (users.isEmpty()) { - hide(); - return; - } - - const size_t layoutCount = layout_->count(); - const size_t userCount = users.size(); - - // Remove the extra widgets from the layout. - if (userCount < layoutCount) - removeLayoutItemsAfter(userCount - 1); - - for (size_t i = 0; i < userCount; ++i) { - auto item = layout_->itemAt(i); - - // Create a new widget if there isn't already one in that - // layout position. - if (!item) { - auto user = new UserItem(this, users.at(i).user_id); - connect(user, &UserItem::clicked, this, &SuggestionsPopup::itemSelected); - layout_->addWidget(user); - } else { - // Update the current widget with the new data. - auto userWidget = qobject_cast(item->widget()); - if (userWidget) - userWidget->updateItem(users.at(i).user_id); - } - } - - resetSelection(); - adjustSize(); - - selectNextSuggestion(); -} - -void -SuggestionsPopup::hoverSelection() -{ - resetHovering(); - setHovering(selectedItem_); - update(); -} - -void -SuggestionsPopup::selectNextSuggestion() -{ - selectedItem_++; - if (selectedItem_ >= layout_->count()) - selectFirstItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::selectPreviousSuggestion() -{ - selectedItem_--; - if (selectedItem_ < 0) - selectLastItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::resetHovering() -{ - for (int i = 0; i < layout_->count(); ++i) { - const auto item = qobject_cast(layout_->itemAt(i)->widget()); - - if (item) - item->setHovering(false); - } -} - -void -SuggestionsPopup::setHovering(int pos) -{ - const auto &item = layout_->itemAt(pos); - const auto &widget = qobject_cast(item->widget()); - - if (widget) - widget->setHovering(true); -} - -void -SuggestionsPopup::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} +} \ No newline at end of file diff --git a/src/popups/PopupItem.h b/src/popups/PopupItem.h new file mode 100644 index 00000000..1fc54bf7 --- /dev/null +++ b/src/popups/PopupItem.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +#include "../AvatarProvider.h" +#include "../Cache.h" +#include "../ChatPage.h" + +class Avatar; +struct SearchResult; + +class PopupItem : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor) + Q_PROPERTY(bool hovering READ hovering WRITE setHovering) + +public: + PopupItem(QWidget *parent); + + QString selectedText() const { return QString(); } + QColor hoverColor() const { return hoverColor_; } + void setHoverColor(QColor &color) { hoverColor_ = color; } + + bool hovering() const { return hovering_; } + void setHovering(const bool hover) { hovering_ = hover; }; + +protected: + void paintEvent(QPaintEvent *event) override; + +signals: + void clicked(const QString &text); + +protected: + QHBoxLayout *topLayout_; + Avatar *avatar_; + QColor hoverColor_; + + //! Set if the item is currently being + //! hovered during tab completion (cycling). + bool hovering_; +}; + +class UserItem : public PopupItem +{ + Q_OBJECT + +public: + UserItem(QWidget *parent, const QString &user_id); + QString selectedText() const { return userId_; } + void updateItem(const QString &user_id); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + void resolveAvatar(const QString &user_id); + + QLabel *userName_; + QString userId_; +}; + +class RoomItem : public PopupItem +{ + Q_OBJECT + +public: + RoomItem(QWidget *parent, const RoomSearchResult &res); + QString selectedText() const { return roomId_; } + void updateItem(const RoomSearchResult &res); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + QLabel *roomName_; + QString roomId_; + RoomSearchResult info_; +}; \ No newline at end of file diff --git a/src/popups/ReplyPopup.cpp b/src/popups/ReplyPopup.cpp new file mode 100644 index 00000000..a883739f --- /dev/null +++ b/src/popups/ReplyPopup.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "../Config.h" +#include "../Utils.h" +#include "../ui/Avatar.h" +#include "../ui/DropShadow.h" +#include "ReplyPopup.h" + +ReplyPopup::ReplyPopup(QWidget *parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_ShowWithoutActivating, true); + setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); + + layout_ = new QVBoxLayout(this); + layout_->setMargin(0); + layout_->setSpacing(0); +} + +void +ReplyPopup::setReplyContent(const QString &user, const QString &msg, const QString &srcEvent) +{ + QLayoutItem *child; + while ((child = layout_->takeAt(0)) != 0) { + delete child->widget(); + delete child; + } + // Create a new widget if there isn't already one in that + // layout position. + // if (!item) { + auto userItem = new UserItem(this, user); + auto *text = new QLabel(this); + text->setText(msg); + auto *event = new QLabel(this); + event->setText(srcEvent); + connect(userItem, &UserItem::clicked, this, &ReplyPopup::userSelected); + layout_->addWidget(userItem); + layout_->addWidget(text); + layout_->addWidget(event); + // } else { + // Update the current widget with the new data. + // auto userWidget = qobject_cast(item->widget()); + // if (userWidget) + // userWidget->updateItem(users.at(i).user_id); + // } + + adjustSize(); +} + +void +ReplyPopup::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/src/popups/ReplyPopup.h b/src/popups/ReplyPopup.h new file mode 100644 index 00000000..d8355e53 --- /dev/null +++ b/src/popups/ReplyPopup.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#include "../AvatarProvider.h" +#include "../Cache.h" +#include "../ChatPage.h" +#include "PopupItem.h" + +class ReplyPopup : public QWidget +{ + Q_OBJECT + +public: + explicit ReplyPopup(QWidget *parent = nullptr); + +public slots: + void setReplyContent(const QString &user, const QString &msg, const QString &srcEvent); + +protected: + void paintEvent(QPaintEvent *event) override; + +signals: + void userSelected(const QString &user); + +private: + QVBoxLayout *layout_; + +}; diff --git a/src/popups/SuggestionsPopup.cpp b/src/popups/SuggestionsPopup.cpp new file mode 100644 index 00000000..6861a76a --- /dev/null +++ b/src/popups/SuggestionsPopup.cpp @@ -0,0 +1,156 @@ +#include +#include +#include + +#include "../Config.h" +#include "SuggestionsPopup.h" +#include "../Utils.h" +#include "../ui/Avatar.h" +#include "../ui/DropShadow.h" + +SuggestionsPopup::SuggestionsPopup(QWidget *parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_ShowWithoutActivating, true); + setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); + + layout_ = new QVBoxLayout(this); + layout_->setMargin(0); + layout_->setSpacing(0); +} + +void +SuggestionsPopup::addRooms(const std::vector &rooms) +{ + if (rooms.empty()) { + hide(); + return; + } + + const size_t layoutCount = layout_->count(); + const size_t roomCount = rooms.size(); + + // Remove the extra widgets from the layout. + if (roomCount < layoutCount) + removeLayoutItemsAfter(roomCount - 1); + + for (size_t i = 0; i < roomCount; ++i) { + auto item = layout_->itemAt(i); + + // Create a new widget if there isn't already one in that + // layout position. + if (!item) { + auto room = new RoomItem(this, rooms.at(i)); + connect(room, &RoomItem::clicked, this, &SuggestionsPopup::itemSelected); + layout_->addWidget(room); + } else { + // Update the current widget with the new data. + auto room = qobject_cast(item->widget()); + if (room) + room->updateItem(rooms.at(i)); + } + } + + resetSelection(); + adjustSize(); + + resize(geometry().width(), 40 * rooms.size()); + + selectNextSuggestion(); +} + +void +SuggestionsPopup::addUsers(const QVector &users) +{ + if (users.isEmpty()) { + hide(); + return; + } + + const size_t layoutCount = layout_->count(); + const size_t userCount = users.size(); + + // Remove the extra widgets from the layout. + if (userCount < layoutCount) + removeLayoutItemsAfter(userCount - 1); + + for (size_t i = 0; i < userCount; ++i) { + auto item = layout_->itemAt(i); + + // Create a new widget if there isn't already one in that + // layout position. + if (!item) { + auto user = new UserItem(this, users.at(i).user_id); + connect(user, &UserItem::clicked, this, &SuggestionsPopup::itemSelected); + layout_->addWidget(user); + } else { + // Update the current widget with the new data. + auto userWidget = qobject_cast(item->widget()); + if (userWidget) + userWidget->updateItem(users.at(i).user_id); + } + } + + resetSelection(); + adjustSize(); + + selectNextSuggestion(); +} + +void +SuggestionsPopup::hoverSelection() +{ + resetHovering(); + setHovering(selectedItem_); + update(); +} + +void +SuggestionsPopup::selectNextSuggestion() +{ + selectedItem_++; + if (selectedItem_ >= layout_->count()) + selectFirstItem(); + + hoverSelection(); +} + +void +SuggestionsPopup::selectPreviousSuggestion() +{ + selectedItem_--; + if (selectedItem_ < 0) + selectLastItem(); + + hoverSelection(); +} + +void +SuggestionsPopup::resetHovering() +{ + for (int i = 0; i < layout_->count(); ++i) { + const auto item = qobject_cast(layout_->itemAt(i)->widget()); + + if (item) + item->setHovering(false); + } +} + +void +SuggestionsPopup::setHovering(int pos) +{ + const auto &item = layout_->itemAt(pos); + const auto &widget = qobject_cast(item->widget()); + + if (widget) + widget->setHovering(true); +} + +void +SuggestionsPopup::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/src/SuggestionsPopup.h b/src/popups/SuggestionsPopup.h similarity index 53% rename from src/SuggestionsPopup.h rename to src/popups/SuggestionsPopup.h index 72d6c7eb..4fbb97b3 100644 --- a/src/SuggestionsPopup.h +++ b/src/popups/SuggestionsPopup.h @@ -5,82 +5,11 @@ #include #include -#include "AvatarProvider.h" -#include "Cache.h" -#include "ChatPage.h" +#include "../AvatarProvider.h" +#include "../Cache.h" +#include "../ChatPage.h" +#include "PopupItem.h" -class Avatar; -struct SearchResult; - -class PopupItem : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor) - Q_PROPERTY(bool hovering READ hovering WRITE setHovering) - -public: - PopupItem(QWidget *parent); - - QString selectedText() const { return QString(); } - QColor hoverColor() const { return hoverColor_; } - void setHoverColor(QColor &color) { hoverColor_ = color; } - - bool hovering() const { return hovering_; } - void setHovering(const bool hover) { hovering_ = hover; }; - -protected: - void paintEvent(QPaintEvent *event) override; - -signals: - void clicked(const QString &text); - -protected: - QHBoxLayout *topLayout_; - Avatar *avatar_; - QColor hoverColor_; - - //! Set if the item is currently being - //! hovered during tab completion (cycling). - bool hovering_; -}; - -class UserItem : public PopupItem -{ - Q_OBJECT - -public: - UserItem(QWidget *parent, const QString &user_id); - QString selectedText() const { return userId_; } - void updateItem(const QString &user_id); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - void resolveAvatar(const QString &user_id); - - QLabel *userName_; - QString userId_; -}; - -class RoomItem : public PopupItem -{ - Q_OBJECT - -public: - RoomItem(QWidget *parent, const RoomSearchResult &res); - QString selectedText() const { return roomId_; } - void updateItem(const RoomSearchResult &res); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - QLabel *roomName_; - QString roomId_; - RoomSearchResult info_; -}; class SuggestionsPopup : public QWidget {