diff --git a/src/irc/usermodel.cpp b/src/irc/usermodel.cpp index 5512736f..b9c87d3a 100644 --- a/src/irc/usermodel.cpp +++ b/src/irc/usermodel.cpp @@ -1,331 +1,335 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2017 Eike Hein */ #include "usermodel.h" #include "chatwindow.h" #include "nickinfo.h" #include "server.h" #include UserCompletionModel::UserCompletionModel(QObject *parent) : QSortFilterProxyModel(parent) { setSortRole(UserModel::LowercaseName); sort(0); } UserCompletionModel::~UserCompletionModel() { } Server *UserCompletionModel::server() const { return m_server; } void UserCompletionModel::setServer(Server *server) { if (m_server != server) { m_server = server; - QObject::connect(server, &QObject::destroyed, - this, [this]() { invalidateFilter(); }); + if (server) { + QObject::connect(server, &QObject::destroyed, + this, [this]() { invalidateFilter(); }); + } invalidateFilter(); } } QString UserCompletionModel::lastActiveUser() { QString name; uint latestTimeStamp = 0; for (int i = 0; i < rowCount(); ++i) { const QModelIndex &idx = index(i, 0); const uint timeStamp = idx.data(UserModel::TimeStamp).toUInt(); if (timeStamp > latestTimeStamp) { name = idx.data().toString(); latestTimeStamp = timeStamp; } } return name; } void UserCompletionModel::setSourceModel(QAbstractItemModel *sourceModel) { if (QSortFilterProxyModel::sourceModel() == sourceModel) { return; } QSortFilterProxyModel::setSourceModel(sourceModel); } QVariant UserCompletionModel::data(const QModelIndex &index, int role) const { if (role == Qt::EditRole /* Default role used by QCompleter */) { QString mangled(data(index, UserModel::LowercaseName).toString()); for (int i = mangled.length(); i >= 0; --i) { const QChar &c = mangled[i]; if (!c.isLetterOrNumber()) { mangled.remove(i, 1); } } return mangled; } return QSortFilterProxyModel::data(index, role); } bool UserCompletionModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) if (!m_server) { return false; } const QModelIndex &sourceIdx = sourceModel()->index(sourceRow, 0); return (m_server->loweredNickname() != sourceIdx.data(UserModel::LowercaseName).toString()); } FilteredUserModel::FilteredUserModel(QObject *parent) : QSortFilterProxyModel(parent) , m_filterView(nullptr) { setSortRole(UserModel::LowercaseName); sort(0); } FilteredUserModel::~FilteredUserModel() { } QObject *FilteredUserModel::filterView() const { return m_filterView; } void FilteredUserModel::setFilterView(QObject *view) { // WIPQTQUICK HACK Only filter for channels for now. const ChatWindow *chatWin = qobject_cast(view); if (view) { if (chatWin->getType() != ChatWindow::Channel) { setFilterView(nullptr); return; } } if (m_filterView != view) { m_filterView = view; - QObject::connect(view, &QObject::destroyed, - this, [this]() { invalidateFilter(); }); - if (view) { + QObject::connect(view, &QObject::destroyed, + this, [this]() { invalidateFilter(); }); + setSourceModel(chatWin->getServer()->getUserModel()); } m_channelNickCache.clear(); invalidateFilter(); emit filterViewChanged(); } } void FilteredUserModel::setSourceModel(QAbstractItemModel *sourceModel) { if (QSortFilterProxyModel::sourceModel() == sourceModel) { return; } QSortFilterProxyModel::setSourceModel(sourceModel); - QObject::connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, - [this](const QModelIndex &parent, int first, int last) { - Q_UNUSED(parent) + if (sourceModel) { + QObject::connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, + [this](const QModelIndex &parent, int first, int last) { + Q_UNUSED(parent) - for (int i = first; i <= last; ++i) { - const QModelIndex &sourceIdx = QSortFilterProxyModel::sourceModel()->index(i, 0); - m_channelNickCache.remove(static_cast(sourceIdx.internalPointer())); + for (int i = first; i <= last; ++i) { + const QModelIndex &sourceIdx = QSortFilterProxyModel::sourceModel()->index(i, 0); + m_channelNickCache.remove(static_cast(sourceIdx.internalPointer())); + } } - } - ); + ); + } } bool FilteredUserModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) if (!m_filterView) { return true; } // WIPQTQUICK HACK const ChatWindow *chatWin = qobject_cast(m_filterView); const QModelIndex &sourceIdx = sourceModel()->index(sourceRow, 0); const NickInfo *nickInfo = static_cast(sourceIdx.internalPointer()); Server *server = chatWin->getServer(); const QStringList &channels = server->getNickJoinedChannels(nickInfo->getNickname()); if (channels.contains(chatWin->getName())) { return true; } return false; } QVariant FilteredUserModel::data(const QModelIndex &index, int role) const { if (!m_filterView) { return QSortFilterProxyModel::data(index, role); } if (role == UserModel::TimeStamp) { const QModelIndex &sourceIdx = mapToSource(index); // WIPQTQUICK Oh my god the hoops/casts. const ChannelNickPtr channelNick = const_cast(this)->getChannelNick(static_cast(sourceIdx.internalPointer())); if (channelNick) { return channelNick->timeStamp(); } } return QSortFilterProxyModel::data(index, role); } ChannelNickPtr FilteredUserModel::getChannelNick(const NickInfo *nickInfo) { if (!m_filterView) { return ChannelNickPtr(); } const auto &it = m_channelNickCache.constFind(nickInfo); if (it != m_channelNickCache.constEnd()) { return *it; } // WIPQTQUICK HACK const ChatWindow *chatWin = qobject_cast(m_filterView); const ChannelNickPtr channelNick = chatWin->getServer()->getChannelNick(chatWin->getName(), nickInfo->getNickname()); m_channelNickCache.insert(nickInfo, channelNick); return channelNick; } UserModel::UserModel(QObject *parent) : QAbstractListModel(parent) { } UserModel::~UserModel() { } QHash UserModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } QModelIndex UserModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(column) Q_UNUSED(parent) if (row < 0 || row >= m_nicks.count()) { return QModelIndex(); } return createIndex(row, column, m_nicks.at(row).data()); } QVariant UserModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_nicks.count()) { return QVariant(); } const NickInfoPtr nick = m_nicks.at(index.row()); if (role == Qt::DisplayRole) { return nick->getNickname(); } else if (role == LowercaseName) { return nick->loweredNickname(); } return QVariant(); } int UserModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_nicks.count(); } void UserModel::add(const NickInfoPtr nickInfo) { beginInsertRows(QModelIndex(), m_nicks.count(), m_nicks.count()); m_nicks.append(nickInfo); endInsertRows(); } void UserModel::remove(const NickInfoPtr nickInfo) { const int row = m_nicks.indexOf(nickInfo); if (row != -1) { beginRemoveRows(QModelIndex(), row, row); m_nicks.remove(row); endRemoveRows(); } } void UserModel::changed(const NickInfoPtr nickInfo) { const int row = m_nicks.indexOf(nickInfo); if (row != -1) { const QModelIndex &idx = index(row, 0); emit dataChanged(idx, idx); // WIPQTQUICK TODO Only updated roles that actually changed. } } void UserModel::clear() { beginResetModel(); m_nicks.clear(); endResetModel(); } diff --git a/src/viewer/completer.cpp b/src/viewer/completer.cpp index 9459ffb7..5ff5832f 100644 --- a/src/viewer/completer.cpp +++ b/src/viewer/completer.cpp @@ -1,194 +1,194 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2017 Eike Hein */ #include "completer.h" #include "chatwindow.h" // WIPQTQUICK TODO Fix coupling #include "outputfilter.h" #include "server.h" // WIPQTQUICK TODO Fix coupling #include "usermodel.h" // WIPQTQUICK TODO Fix coupling #include #include MatchesModel::MatchesModel(Completer *completer) : QSortFilterProxyModel(completer) , m_completer(completer) { sort(0); } MatchesModel::~MatchesModel() { } QString MatchesModel::pinnedMatch() const { return m_pinnedMatch; } void MatchesModel::setPinnedMatch(const QString& pinnedMatch) { if (m_pinnedMatch != pinnedMatch) { m_pinnedMatch = pinnedMatch; // Force resort. setDynamicSortFilter(false); setDynamicSortFilter(true); } } QString MatchesModel::at(int row) { return data(index(row, 0)).toString(); } bool MatchesModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { if (!m_pinnedMatch.isEmpty()) { bool leftIsPin(sourceLeft.data().toString() == m_pinnedMatch); bool rightIsPin(sourceRight.data().toString() == m_pinnedMatch); if (leftIsPin && !rightIsPin) { return true; } else if (rightIsPin && !leftIsPin) { return false; } } return (sourceLeft.row() < sourceRight.row()); } bool MatchesModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) const QModelIndex &sourceIdx = sourceModel()->index(sourceRow, 0); // Filter out matches that are identical to the prefix. if (sourceIdx.data().toString() == m_completer->prefix()) { return false; } return true; } Completer::Completer(QObject *parent) : QObject(parent) , m_completer(new QCompleter(this)) , m_matchesModel(new MatchesModel(this)) , m_userCompletionModel(new UserCompletionModel(this)) , m_sortedCommandsModel(nullptr) { m_completer->setCompletionMode(QCompleter::InlineCompletion); m_completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); m_matchesModel->setSourceModel(m_completer->completionModel()); } Completer::~Completer() { } QObject *Completer::contextView() const { return m_contextView; } void Completer::setContextView(QObject *view) { if (m_contextView != view) { m_contextView = view; // WIPQTQUICK HACK const ChatWindow *chatWin = qobject_cast(view); - QObject::connect(view, &QObject::destroyed, this, - [this]() { - m_userCompletionModel->setSourceModel(nullptr); - m_matchesModel->setPinnedMatch(QString()); - } - ); - if (view) { + QObject::connect(view, &QObject::destroyed, this, + [this]() { + m_userCompletionModel->setSourceModel(nullptr); + m_matchesModel->setPinnedMatch(QString()); + } + ); + m_userCompletionModel->setServer(chatWin->getServer()); } m_completer->setCompletionPrefix(QString()); } } QAbstractItemModel *Completer::sourceModel() const { return m_sourceModel; } void Completer::setSourceModel(QAbstractItemModel *sourceModel) { if (m_sourceModel != sourceModel) { m_sourceModel = sourceModel; if (!m_completer->completionPrefix().isEmpty() && sourceModel) { m_userCompletionModel->setSourceModel(sourceModel); } } } QString Completer::prefix() const { return m_completer->completionPrefix(); } void Completer::setPrefix(const QString &prefix) { if (!m_contextView || !m_sourceModel) { return; } if (m_completer->completionPrefix() != prefix) { if (!prefix.isEmpty()) { if (prefix.startsWith(Preferences::self()->commandChar())) { if (!m_sortedCommandsModel) { QStringList sortedCommands; for (const QString &s : Konversation::OutputFilter::supportedCommands()) { sortedCommands.append(Preferences::self()->commandChar() + s); } m_sortedCommandsModel = new QStringListModel(sortedCommands); m_sortedCommandsModel->sort(0); } m_matchesModel->setPinnedMatch(QString()); m_completer->setModel(m_sortedCommandsModel); m_completer->setModelSorting(QCompleter::CaseSensitivelySortedModel); } else { m_userCompletionModel->setSourceModel(m_sourceModel); m_matchesModel->setPinnedMatch(m_userCompletionModel->lastActiveUser()); m_completer->setModel(m_userCompletionModel); m_completer->setModelSorting(QCompleter::UnsortedModel); } } else { m_completer->setModel(nullptr); m_userCompletionModel->setSourceModel(nullptr); } m_completer->setCompletionPrefix(prefix.toLower()); emit prefixChanged(); } } QAbstractItemModel *Completer::matches() const { return m_matchesModel; }