diff --git a/CMakeLists.txt b/CMakeLists.txt index c2302974..f784cff2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,7 @@ if ((NOT DEFINED USE_INTREE_LIBQMC OR USE_INTREE_LIBQMC) endif () endif () if (NOT USE_INTREE_LIBQMC) - find_package(${QUOTIENT} 0.9.2 REQUIRED) + find_package(${QUOTIENT} 0.9.4 REQUIRED) if (NOT ${QUOTIENT}_FOUND) message(WARNING "libQuotient not found; configuration will most likely fail.") message(WARNING "Make sure you have installed libQuotient development files") diff --git a/client/models/userlistmodel.cpp b/client/models/userlistmodel.cpp index e4fda974..4f2a272f 100644 --- a/client/models/userlistmodel.cpp +++ b/client/models/userlistmodel.cpp @@ -9,13 +9,14 @@ #include "userlistmodel.h" #include "../logging_categories.h" +#include "../quaternionroom.h" #include -#include -#include #include -// Injecting the dependency on a view is not so nice; but the way the model -// provides avatar decorations depends on the delegate size +#include +#include +// Injecting the dependency on a view is not so nice; but the way the model provides avatar +// decorations depends on the delegate size, and some other defaults come from the view, too #include #include @@ -69,19 +70,15 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const if( !index.isValid() ) return QVariant(); - if( index.row() >= m_memberIds.count() ) - { - qCWarning(MODELS) << "UserListModel, something's wrong: index.row() >= " - "m_users.count()"; + if (QUO_ALARM(index.row() >= m_memberIds.count())) return QVariant(); - } + auto m = userAt(index); - if( role == Qt::DisplayRole ) - { - return m.displayName(); - } const auto* view = static_cast(parent()); - if (role == Qt::DecorationRole) { + + switch (role) { + case Qt::DisplayRole: return m.displayName(); + case Qt::DecorationRole: { // Convert avatar image to QIcon const auto dpi = view->devicePixelRatioF(); if (auto av = m.avatar(static_cast(view->iconSize().height() * dpi), [] {}); @@ -93,18 +90,18 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const return QIcon::fromTheme("user-available", QIcon(":/irc-channel-joined")); } - - if (role == Qt::ToolTipRole) - { + case Qt::ToolTipRole: { auto tooltip = - QStringLiteral("%1
%2").arg(m.name().toHtmlEscaped(), m.id().toHtmlEscaped()); + QLatin1String("%1
%2
").arg(m.name().toHtmlEscaped(), m.id().toHtmlEscaped()) + + tr("Power level: %1 (%2)") + .arg(m.powerLevel()) + .arg(static_cast(m_currentRoom)->powerGrade(m)); // TODO: Find a new way to determine that the user is bridged // if (!user->bridged().isEmpty()) // tooltip += "
" + tr("Bridged from: %1").arg(user->bridged()); return tooltip; } - - if (role == Qt::ForegroundRole) { + case Qt::ForegroundRole: { // FIXME: boilerplate with TimelineItem.qml:57 const auto& palette = view->palette(); return QColor::fromHslF(static_cast(m.hueF()), @@ -112,8 +109,15 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const 0.9f - 0.7f * palette.color(QPalette::Window).lightnessF(), palette.color(QPalette::ButtonText).alphaF()); } - - return QVariant(); + case Qt::FontRole: { + auto font = view->font(); + if (m_currentRoom->creatorIds().contains(m.id())) { + font.setBold(true); + } + return font; + } + default: return QVariant(); + } } int UserListModel::rowCount(const QModelIndex& parent) const @@ -179,14 +183,46 @@ void UserListModel::avatarChanged(const RoomMember& m) refresh(m, {Qt::DecorationRole}); } +struct MemberSorter { + bool operator()(const RoomMember& m1, const RoomMember& m2) const + { +#if Quotient_VERSION_MINOR < 10 + if (QUO_ALARM(!room)) + return false; + const auto m1IsCreator = room->creatorIds().contains(m1.id()); + const auto m2IsCreator = room->creatorIds().contains(m2.id()); +#else + const auto m1IsCreator = m1.isCreator(); + const auto m2IsCreator = m2.isCreator(); +#endif + if (m1IsCreator != m2IsCreator) + return m1IsCreator > m2IsCreator; + + return ms(m1, m2); + } + +#if Quotient_VERSION_MINOR < 10 + const Quotient::Room* room; +#endif + Quotient::MemberSorter ms{}; +}; + int UserListModel::findUserPos(const Quotient::RoomMember& m) const { - return findUserPos(m.disambiguatedName()); + return static_cast( + std::ranges::lower_bound(m_memberIds, m, +#if Quotient_VERSION_MINOR < 10 + MemberSorter{m_currentRoom}, +#else + MemberSorter{}, +#endif + std::bind_front(&Quotient::Room::member, m_currentRoom)) + - m_memberIds.begin()); } -int UserListModel::findUserPos(const QString& username) const +int UserListModel::findUserPos(const Quotient::UserId& mxId) const { - return static_cast(Quotient::lowerBoundMemberIndex(m_memberIds, username, m_currentRoom)); + return findUserPos(m_currentRoom->member(mxId)); } void UserListModel::doFilter(const QString& filterString) @@ -196,7 +232,11 @@ void UserListModel::doFilter(const QString& filterString) auto filteredMembers = Quotient::rangeTo( std::views::filter(m_currentRoom->joinedMembers(), Quotient::memberMatcher(filterString, Qt::CaseInsensitive))); - std::ranges::sort(filteredMembers, Quotient::MemberSorter()); +#if Quotient_VERSION_MINOR < 10 + std::ranges::sort(filteredMembers, MemberSorter{m_currentRoom}); +#else + std::ranges::sort(filteredMembers, MemberSorter{}); +#endif const auto sortedIds = std::views::transform(filteredMembers, &RoomMember::id); #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) m_memberIds.assign(sortedIds.begin(), sortedIds.end()); diff --git a/client/models/userlistmodel.h b/client/models/userlistmodel.h index 0d569951..7d20ced7 100644 --- a/client/models/userlistmodel.h +++ b/client/models/userlistmodel.h @@ -10,6 +10,8 @@ #include +#include + class QAbstractItemView; namespace Quotient @@ -45,9 +47,11 @@ class UserListModel: public QAbstractListModel private: Quotient::Room* m_currentRoom; - QList m_memberIds; + QList m_memberIds; int findUserPos(const Quotient::RoomMember &m) const; - int findUserPos(const QString& username) const; + int findUserPos(const Quotient::UserId& mxId) const; void doFilter(const QString& filterString); + + QString powerGrade(Quotient::Room* room, const Quotient::RoomMember& member) const; }; diff --git a/client/qml/TimelineItem.qml b/client/qml/TimelineItem.qml index 16c55ad2..82694da9 100644 --- a/client/qml/TimelineItem.qml +++ b/client/qml/TimelineItem.qml @@ -177,7 +177,8 @@ Item { cursorShape: Qt.PointingHandCursor } ToolTip.visible: authorInteractionHoverHandler.hovered - ToolTip.text: author.id + ToolTip.text: qsTr('%1\nPower level: %2 (%3)') + .arg(author.id).arg(author.powerLevel).arg(room.powerGrade(author)) TapHandler { acceptedButtons: Qt.LeftButton|Qt.MiddleButton diff --git a/client/quaternionroom.cpp b/client/quaternionroom.cpp index 8540a230..f57b788a 100644 --- a/client/quaternionroom.cpp +++ b/client/quaternionroom.cpp @@ -276,3 +276,17 @@ void QuaternionRoom::sendMessage(const QTextDocumentFragment& richText, ? std::make_unique(html, u"text/html"_s) : nullptr); } + +QString QuaternionRoom::powerGrade(const Quotient::RoomMember& member) const +{ + using namespace Quotient; + if (creatorIds().contains(member.id())) + return tr("creator"); + + auto pl = member.powerLevel(); + auto plEvent = currentState().get(); + return pl < plEvent->stateDefault() ? tr("user") + : pl < plEvent->powerLevelForEventType() ? tr("moderator") + : pl < plEvent->powerLevelForEventType() ? tr("administrator") + : tr("upgrading admin"); +} diff --git a/client/quaternionroom.h b/client/quaternionroom.h index 21c6591f..c33e38e6 100644 --- a/client/quaternionroom.h +++ b/client/quaternionroom.h @@ -62,6 +62,8 @@ class QuaternionRoom: public Quotient::Room void sendMessage(const QTextDocumentFragment& richText, HtmlFilter::Options htmlFilterOptions = HtmlFilter::Default); + Q_INVOKABLE QString powerGrade(const Quotient::RoomMember& member) const; + private: using EventPromise = QPromise; using EventId = Quotient::EventId; diff --git a/client/roomdialogs.cpp b/client/roomdialogs.cpp index c3751f1c..656ba443 100644 --- a/client/roomdialogs.cpp +++ b/client/roomdialogs.cpp @@ -93,6 +93,7 @@ RoomDialogBase::RoomDialogBase(const QString& title, QComboBox* RoomDialogBase::addVersionSelector(QLayout* layout) { auto* versionSelector = new QComboBox; + versionSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); layout->addWidget(versionSelector); { auto* specLink = @@ -104,13 +105,14 @@ QComboBox* RoomDialogBase::addVersionSelector(QLayout* layout) return versionSelector; } -void RoomDialogBase::refillVersionSelector(QComboBox* selector, - Connection* account) +void RoomDialogBase::refillVersionSelector(QComboBox* selector, Connection* account) { + if (futureRoomVersions.isRunning()) + futureRoomVersions.cancel(); selector->clear(); selector->addItem(tr("(loading)", "Loading room versions from the server"), QString()); selector->setEnabled(false); - account->loadCapabilities().then([selector, account] { + futureRoomVersions = account->loadCapabilities().then([selector, account] { selector->clear(); const auto& versions = account->availableRoomVersions(); if (versions.empty()) { @@ -136,13 +138,15 @@ void RoomDialogBase::refillVersionSelector(QComboBox* selector, }); } -void RoomDialogBase::addEssentials(QWidget* accountControl, - QLayout* versionBox) +void RoomDialogBase::addEssentials(QWidget* accountControl, QLayout* versionBox) { Q_ASSERT(accountControl != nullptr && versionBox != nullptr); auto* layout = essentialsLayout ? essentialsLayout : mainFormLayout; layout->insertRow(0, tr("Account"), accountControl); - layout->insertRow(1, tr("Room version"), versionBox); + auto* versionLabel = makeBuddyLabel(tr("Room version"), versionBox->itemAt(0)->widget()); + layout->insertRow(1, versionLabel, versionBox); + versionLabel->setSizePolicy(versionLabel->sizePolicy().horizontalPolicy(), + QSizePolicy::MinimumExpanding); } bool RoomDialogBase::checkRoomVersion(QString version, Connection* account) @@ -265,18 +269,20 @@ bool RoomSettingsDialog::validate() void RoomSettingsDialog::apply() { - using Quotient::Room; + using namespace Quotient; if (version->text() != room->version()) { setStatusMessage(tr("Creating the new room version, please wait")); - connectUntil(room, &Room::upgraded, this, - [this] (const QString&, Room* newRoom) { + room->upgrade(version->text()) + .then([this](Expected&& r) + { + if (r) { accept(); - static_cast(parent())->selectRoom(newRoom); - return true; - }); - connect(room, &Room::upgradeFailed, this, &Dialog::applyFailed, Qt::SingleShotConnection); - room->switchVersion(version->text()); + static_cast(parent())->selectRoom(r.value()); + } else { + applyFailed(r.error().message); + } + }).onCanceled([this] { applyFailed(tr("Upgrade was cancelled")); }); return; // It's either a version upgrade or everything else } if (roomName->text() != room->name()) diff --git a/client/roomdialogs.h b/client/roomdialogs.h index c9aec1c7..698e3883 100644 --- a/client/roomdialogs.h +++ b/client/roomdialogs.h @@ -8,6 +8,7 @@ #include "dialog.h" +#include #include namespace Quotient { @@ -56,6 +57,9 @@ class RoomDialogBase : public Dialog void refillVersionSelector(QComboBox* selector, Connection* account); void addEssentials(QWidget* accountControl, QLayout* versionBox); bool checkRoomVersion(QString version, Connection* account); + + private: + QFuture futureRoomVersions{}; }; class RoomSettingsDialog : public RoomDialogBase diff --git a/lib b/lib index c02f7197..d9af28dd 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit c02f719716b8cff20e17e49a26fb9e34f0719692 +Subproject commit d9af28dd4e033edb74235a3b33157506c0800786