File: userlistmodel.cpp

package info (click to toggle)
quaternion 0.0.97.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,800 kB
  • sloc: cpp: 8,380; xml: 172; sh: 5; makefile: 2
file content (209 lines) | stat: -rw-r--r-- 7,066 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/**************************************************************************
 *                                                                        *
 * SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de>                        *
 *                                                                        *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *                                                                        *
 **************************************************************************/

#include "userlistmodel.h"

#include "../logging_categories.h"

#include <QtCore/QDebug>
#include <QtGui/QPixmap>
#include <QtGui/QPalette>
#include <QtGui/QFontMetrics>
// Injecting the dependency on a view is not so nice; but the way the model
// provides avatar decorations depends on the delegate size
#include <QtWidgets/QAbstractItemView>

#include <Quotient/connection.h>
#include <Quotient/ranges_extras.h>
#include <Quotient/room.h>
#include <Quotient/user.h>

using Quotient::RoomMember;

UserListModel::UserListModel(QAbstractItemView* parent)
    : QAbstractListModel(parent), m_currentRoom(nullptr)
{ }

void UserListModel::setRoom(Quotient::Room* room)
{
    if (m_currentRoom == room)
        return;

    using namespace Quotient;
    beginResetModel();
    if (m_currentRoom) {
        m_currentRoom->connection()->disconnect(this);
        m_currentRoom->disconnect(this);
        m_memberIds.clear();
    }
    m_currentRoom = room;
    if (m_currentRoom) {
        connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::userAdded);
        connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::userRemoved);
        connect(m_currentRoom, &Room::memberNameAboutToUpdate, this, &UserListModel::userRemoved);
        connect(m_currentRoom, &Room::memberNameUpdated, this, &UserListModel::userAdded);
        connect(m_currentRoom, &Room::memberListChanged, this, &UserListModel::membersChanged);
        connect(m_currentRoom, &Room::memberAvatarUpdated, this, &UserListModel::avatarChanged);
        connect(m_currentRoom->connection(), &Connection::loggedOut, this,
                [this] { setRoom(nullptr); });
        doFilter({});
        qCDebug(MODELS) << m_memberIds.count() << "member(s) in the room";
    }
    endResetModel();
}

Quotient::RoomMember UserListModel::userAt(QModelIndex index) const
{
    if (index.row() < 0 || index.row() >= m_memberIds.size())
        return {};
    return m_currentRoom->member(m_memberIds.at(index.row()));
}

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()";
        return QVariant();
    }
    auto m = userAt(index);
    if( role == Qt::DisplayRole )
    {
        return m.displayName();
    }
    const auto* view = static_cast<const QAbstractItemView*>(parent());
    if (role == Qt::DecorationRole) {
        // Convert avatar image to QIcon
        const auto dpi = view->devicePixelRatioF();
        if (auto av = m.avatar(static_cast<int>(view->iconSize().height() * dpi), [] {});
            !av.isNull()) {
            av.setDevicePixelRatio(dpi);
            return QIcon(QPixmap::fromImage(av));
        }
        // TODO: Show a different fallback icon for invited users
        return QIcon::fromTheme("user-available",
                                QIcon(":/irc-channel-joined"));
    }

    if (role == Qt::ToolTipRole)
    {
        auto tooltip =
            QStringLiteral("<b>%1</b><br>%2").arg(m.name().toHtmlEscaped(), m.id().toHtmlEscaped());
        // TODO: Find a new way to determine that the user is bridged
//        if (!user->bridged().isEmpty())
//            tooltip += "<br>" + tr("Bridged from: %1").arg(user->bridged());
        return tooltip;
    }

    if (role == Qt::ForegroundRole) {
        // FIXME: boilerplate with TimelineItem.qml:57
        const auto& palette = view->palette();
        return QColor::fromHslF(static_cast<float>(m.hueF()),
                                1 - palette.color(QPalette::Window).saturationF(),
                                0.9f - 0.7f * palette.color(QPalette::Window).lightnessF(),
                                palette.color(QPalette::ButtonText).alphaF());
    }

    return QVariant();
}

int UserListModel::rowCount(const QModelIndex& parent) const
{
    if( parent.isValid() )
        return 0;

    return m_memberIds.count();
}

void UserListModel::userAdded(const RoomMember& member)
{
    auto pos = findUserPos(member.id());
    if (pos != m_memberIds.size() && m_memberIds[pos] == member.id())
    {
        qCWarning(MODELS) << "Trying to add the user" << member.id()
                          << "but it's already in the user list";
        return;
    }
    beginInsertRows(QModelIndex(), pos, pos);
    m_memberIds.insert(pos, member.id());
    endInsertRows();
}

void UserListModel::userRemoved(const RoomMember& member)
{
    auto pos = findUserPos(member);
    if (pos == m_memberIds.size())
    {
        qCWarning(MODELS)
            << "Trying to remove a room member not in the user list:"
            << member.id();
        return;
    }

    beginRemoveRows(QModelIndex(), pos, pos);
    m_memberIds.removeAt(pos);
    endRemoveRows();
}

void UserListModel::filter(const QString& filterString)
{
    if (m_currentRoom == nullptr)
        return;

    beginResetModel();
    doFilter(filterString);
    endResetModel();
}

void UserListModel::refresh(const RoomMember& member, QVector<int> roles)
{
    auto pos = findUserPos(member);
    if ( pos != m_memberIds.size() )
        emit dataChanged(index(pos), index(pos), roles);
    else
        qCWarning(MODELS)
            << "Trying to access a room member not in the user list";
}

void UserListModel::avatarChanged(const RoomMember& m)
{
    refresh(m, {Qt::DecorationRole});
}

int UserListModel::findUserPos(const Quotient::RoomMember& m) const
{
    return findUserPos(m.disambiguatedName());
}

int UserListModel::findUserPos(const QString& username) const
{
    return static_cast<int>(Quotient::lowerBoundMemberIndex(m_memberIds, username, m_currentRoom));
}

void UserListModel::doFilter(const QString& filterString)
{
    QElapsedTimer et; et.start();

    auto filteredMembers = Quotient::rangeTo<QList>(
        std::views::filter(m_currentRoom->joinedMembers(),
                           Quotient::memberMatcher(filterString, Qt::CaseInsensitive)));
    std::ranges::sort(filteredMembers, Quotient::MemberSorter());
    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());
#else
    m_memberIds = QList(sortedIds.begin(), sortedIds.end());
#endif

    qCDebug(MODELS) << "Filtering" << m_memberIds.size() << "user(s) in"
                    << m_currentRoom->displayName() << "took" << et;
}