Normalize line endings in already existing files
This commit is contained in:
+47
-47
@@ -1,47 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class Atomic : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Atomic()
|
||||
{
|
||||
}
|
||||
|
||||
Atomic(T &&val)
|
||||
: value_(val)
|
||||
{
|
||||
}
|
||||
|
||||
T get() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void set(const T &val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = val;
|
||||
}
|
||||
|
||||
void set(T &&val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = std::move(val);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class Atomic : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Atomic()
|
||||
{
|
||||
}
|
||||
|
||||
Atomic(T &&val)
|
||||
: value_(val)
|
||||
{
|
||||
}
|
||||
|
||||
T get() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void set(const T &val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = val;
|
||||
}
|
||||
|
||||
void set(T &&val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = std::move(val);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+49
-49
@@ -1,49 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState {
|
||||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
{
|
||||
return QString::fromStdString(string);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers showSplitOverlayModifiers =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showAddSplitRegions =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||
|
||||
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
||||
|
||||
template <typename T>
|
||||
std::weak_ptr<T> weakOf(T *element)
|
||||
{
|
||||
return element->shared_from_this();
|
||||
}
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class CopyMode {
|
||||
Everything,
|
||||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState {
|
||||
None,
|
||||
Highlighted,
|
||||
NewMessage,
|
||||
};
|
||||
|
||||
inline QString qS(const std::string &string)
|
||||
{
|
||||
return QString::fromStdString(string);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers showSplitOverlayModifiers =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showAddSplitRegions =
|
||||
Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||
|
||||
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
||||
|
||||
template <typename T>
|
||||
std::weak_ptr<T> weakOf(T *element)
|
||||
{
|
||||
return element->shared_from_this();
|
||||
}
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class CopyMode {
|
||||
Everything,
|
||||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+195
-195
@@ -1,195 +1,195 @@
|
||||
#include "common/CompletionModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QtAlgorithms>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// TaggedString
|
||||
//
|
||||
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
|
||||
: string(_string)
|
||||
, type(_type)
|
||||
{
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isEmote() const
|
||||
{
|
||||
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
{
|
||||
if (this->isEmote() != that.isEmote())
|
||||
{
|
||||
return this->isEmote();
|
||||
}
|
||||
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
||||
if (k == 0)
|
||||
return this->string > that.string;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
//
|
||||
// CompletionModel
|
||||
//
|
||||
CompletionModel::CompletionModel(Channel &channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
}
|
||||
|
||||
int CompletionModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant CompletionModel::data(const QModelIndex &index, int) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
auto it = this->items_.begin();
|
||||
std::advance(it, index.row());
|
||||
return QVariant(it->string);
|
||||
}
|
||||
|
||||
int CompletionModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
{
|
||||
std::function<void(const QString &, TaggedString::Type)> addString;
|
||||
if (getSettings()->prefixOnlyEmoteCompletion)
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.contains(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->itemsMutex_);
|
||||
this->items_.clear();
|
||||
|
||||
if (prefix.length() < 2)
|
||||
return;
|
||||
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
|
||||
{
|
||||
// account emotes
|
||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
||||
{
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames)
|
||||
{
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// Usernames
|
||||
if (prefix.length() >= UsernameSet::PrefixLength)
|
||||
{
|
||||
auto usernames = channel->accessChatters();
|
||||
|
||||
QString usernamePrefix = prefix;
|
||||
QString usernamePostfix =
|
||||
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
||||
: QString();
|
||||
|
||||
if (usernamePrefix.startsWith("@"))
|
||||
{
|
||||
usernamePrefix.remove(0, 1);
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString("@" + name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString(name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bttv Global
|
||||
for (auto &emote : *channel->globalBttv().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||
}
|
||||
|
||||
// Ffz Global
|
||||
for (auto &emote : *channel->globalFfz().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
|
||||
// Bttv Channel
|
||||
for (auto &emote : *channel->bttvEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Ffz Channel
|
||||
for (auto &emote : *channel->ffzEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if (prefix.startsWith(":"))
|
||||
{
|
||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
||||
for (auto &m : emojiShortCodes)
|
||||
{
|
||||
addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items_)
|
||||
{
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
|
||||
{
|
||||
addString(command, TaggedString::Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "common/CompletionModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <QtAlgorithms>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// TaggedString
|
||||
//
|
||||
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
|
||||
: string(_string)
|
||||
, type(_type)
|
||||
{
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isEmote() const
|
||||
{
|
||||
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
{
|
||||
if (this->isEmote() != that.isEmote())
|
||||
{
|
||||
return this->isEmote();
|
||||
}
|
||||
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
||||
if (k == 0)
|
||||
return this->string > that.string;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
//
|
||||
// CompletionModel
|
||||
//
|
||||
CompletionModel::CompletionModel(Channel &channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
}
|
||||
|
||||
int CompletionModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant CompletionModel::data(const QModelIndex &index, int) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
auto it = this->items_.begin();
|
||||
std::advance(it, index.row());
|
||||
return QVariant(it->string);
|
||||
}
|
||||
|
||||
int CompletionModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
{
|
||||
std::function<void(const QString &, TaggedString::Type)> addString;
|
||||
if (getSettings()->prefixOnlyEmoteCompletion)
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.contains(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->itemsMutex_);
|
||||
this->items_.clear();
|
||||
|
||||
if (prefix.length() < 2)
|
||||
return;
|
||||
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
|
||||
{
|
||||
// account emotes
|
||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
||||
{
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames)
|
||||
{
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// Usernames
|
||||
if (prefix.length() >= UsernameSet::PrefixLength)
|
||||
{
|
||||
auto usernames = channel->accessChatters();
|
||||
|
||||
QString usernamePrefix = prefix;
|
||||
QString usernamePostfix =
|
||||
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
||||
: QString();
|
||||
|
||||
if (usernamePrefix.startsWith("@"))
|
||||
{
|
||||
usernamePrefix.remove(0, 1);
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString("@" + name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
{
|
||||
addString(name + usernamePostfix,
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bttv Global
|
||||
for (auto &emote : *channel->globalBttv().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||
}
|
||||
|
||||
// Ffz Global
|
||||
for (auto &emote : *channel->globalFfz().emotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
|
||||
// Bttv Channel
|
||||
for (auto &emote : *channel->bttvEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Ffz Channel
|
||||
for (auto &emote : *channel->ffzEmotes())
|
||||
{
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if (prefix.startsWith(":"))
|
||||
{
|
||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
||||
for (auto &m : emojiShortCodes)
|
||||
{
|
||||
addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items_)
|
||||
{
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
|
||||
{
|
||||
addString(command, TaggedString::Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
struct TaggedString {
|
||||
enum Type {
|
||||
Username,
|
||||
|
||||
// emotes
|
||||
EmoteStart,
|
||||
FFZGlobalEmote,
|
||||
FFZChannelEmote,
|
||||
BTTVGlobalEmote,
|
||||
BTTVChannelEmote,
|
||||
TwitchGlobalEmote,
|
||||
TwitchSubscriberEmote,
|
||||
Emoji,
|
||||
EmoteEnd,
|
||||
// end emotes
|
||||
|
||||
Command,
|
||||
};
|
||||
|
||||
TaggedString(const QString &string, Type type);
|
||||
|
||||
bool isEmote() const;
|
||||
bool operator<(const TaggedString &that) const;
|
||||
|
||||
QString string;
|
||||
Type type;
|
||||
};
|
||||
|
||||
public:
|
||||
CompletionModel(Channel &channel);
|
||||
|
||||
virtual int columnCount(const QModelIndex &) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int) const override;
|
||||
virtual int rowCount(const QModelIndex &) const override;
|
||||
|
||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
std::set<TaggedString> items_;
|
||||
mutable std::mutex itemsMutex_;
|
||||
Channel &channel_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
struct TaggedString {
|
||||
enum Type {
|
||||
Username,
|
||||
|
||||
// emotes
|
||||
EmoteStart,
|
||||
FFZGlobalEmote,
|
||||
FFZChannelEmote,
|
||||
BTTVGlobalEmote,
|
||||
BTTVChannelEmote,
|
||||
TwitchGlobalEmote,
|
||||
TwitchSubscriberEmote,
|
||||
Emoji,
|
||||
EmoteEnd,
|
||||
// end emotes
|
||||
|
||||
Command,
|
||||
};
|
||||
|
||||
TaggedString(const QString &string, Type type);
|
||||
|
||||
bool isEmote() const;
|
||||
bool operator<(const TaggedString &that) const;
|
||||
|
||||
QString string;
|
||||
Type type;
|
||||
};
|
||||
|
||||
public:
|
||||
CompletionModel(Channel &channel);
|
||||
|
||||
virtual int columnCount(const QModelIndex &) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int) const override;
|
||||
virtual int rowCount(const QModelIndex &) const override;
|
||||
|
||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
std::set<TaggedString> items_;
|
||||
mutable std::mutex itemsMutex_;
|
||||
Channel &channel_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+73
-73
@@ -1,73 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !this->hasElement();
|
||||
}
|
||||
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
operator NullablePtr<const T>() const
|
||||
{
|
||||
return NullablePtr<const T>(this->element_);
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !this->hasElement();
|
||||
}
|
||||
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
operator NullablePtr<const T>() const
|
||||
{
|
||||
return NullablePtr<const T>(this->element_);
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ProviderId { Twitch, Irc };
|
||||
//
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ProviderId { Twitch, Irc };
|
||||
//
|
||||
} // namespace chatterino
|
||||
|
||||
+247
-247
@@ -1,247 +1,247 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
struct SignalVectorItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
||||
|
||||
public:
|
||||
struct Iterator
|
||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
||||
: it_(std::move(it))
|
||||
, lock_(mutex)
|
||||
, mutex_(mutex)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator &other)
|
||||
: it_(other.it_)
|
||||
, lock_(other.mutex_)
|
||||
, mutex_(other.mutex_)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator &operator=(const Iterator &other)
|
||||
{
|
||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
||||
this->mutex_ = other.mutex_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TVectorItem &operator*()
|
||||
{
|
||||
return it_.operator*();
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
++this->it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other)
|
||||
{
|
||||
return this->it_ == other.it_;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other)
|
||||
{
|
||||
return this->it_ != other.it_;
|
||||
}
|
||||
|
||||
auto operator-(const Iterator &other)
|
||||
{
|
||||
return this->it_ - other.it_;
|
||||
}
|
||||
|
||||
private:
|
||||
VecIt it_;
|
||||
std::shared_lock<std::shared_mutex> lock_;
|
||||
std::reference_wrapper<std::shared_mutex> mutex_;
|
||||
};
|
||||
|
||||
ReadOnlySignalVector()
|
||||
{
|
||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||
[this] { this->delayedItemsChanged.invoke(); });
|
||||
this->itemsChangedTimer_.setInterval(100);
|
||||
this->itemsChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
virtual ~ReadOnlySignalVector() = default;
|
||||
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
void invokeDelayedItemsChanged()
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
if (!this->itemsChangedTimer_.isActive())
|
||||
{
|
||||
this->itemsChangedTimer_.start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool isSorted() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
QTimer itemsChangedTimer_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
// returns the actual index of the inserted item
|
||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
||||
void *caller = nullptr) = 0;
|
||||
|
||||
void removeItem(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
assert(index >= 0 && index < int(this->vector_.size()));
|
||||
|
||||
TVectorItem item = this->vector_[index];
|
||||
|
||||
this->vector_.erase(this->vector_.begin() + index);
|
||||
lock.unlock(); // manual unlock
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->invokeDelayedItemsChanged();
|
||||
}
|
||||
|
||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
||||
{
|
||||
return this->insertItem(item, -1, caller);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
if (index == -1)
|
||||
{
|
||||
index = this->vector_.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(index >= 0 && index <= this->vector_.size());
|
||||
}
|
||||
|
||||
this->vector_.insert(this->vector_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem, typename Compare>
|
||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
int index = -1;
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(),
|
||||
this->vector_.end(), item, Compare{});
|
||||
index = it - this->vector_.begin();
|
||||
this->vector_.insert(it, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
struct SignalVectorItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
||||
|
||||
public:
|
||||
struct Iterator
|
||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
||||
: it_(std::move(it))
|
||||
, lock_(mutex)
|
||||
, mutex_(mutex)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator &other)
|
||||
: it_(other.it_)
|
||||
, lock_(other.mutex_)
|
||||
, mutex_(other.mutex_)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator &operator=(const Iterator &other)
|
||||
{
|
||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
||||
this->mutex_ = other.mutex_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TVectorItem &operator*()
|
||||
{
|
||||
return it_.operator*();
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
++this->it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other)
|
||||
{
|
||||
return this->it_ == other.it_;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other)
|
||||
{
|
||||
return this->it_ != other.it_;
|
||||
}
|
||||
|
||||
auto operator-(const Iterator &other)
|
||||
{
|
||||
return this->it_ - other.it_;
|
||||
}
|
||||
|
||||
private:
|
||||
VecIt it_;
|
||||
std::shared_lock<std::shared_mutex> lock_;
|
||||
std::reference_wrapper<std::shared_mutex> mutex_;
|
||||
};
|
||||
|
||||
ReadOnlySignalVector()
|
||||
{
|
||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||
[this] { this->delayedItemsChanged.invoke(); });
|
||||
this->itemsChangedTimer_.setInterval(100);
|
||||
this->itemsChangedTimer_.setSingleShot(true);
|
||||
}
|
||||
virtual ~ReadOnlySignalVector() = default;
|
||||
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_;
|
||||
}
|
||||
|
||||
void invokeDelayedItemsChanged()
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
if (!this->itemsChangedTimer_.isActive())
|
||||
{
|
||||
this->itemsChangedTimer_.start();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool isSorted() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
QTimer itemsChangedTimer_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
// returns the actual index of the inserted item
|
||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
||||
void *caller = nullptr) = 0;
|
||||
|
||||
void removeItem(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
assert(index >= 0 && index < int(this->vector_.size()));
|
||||
|
||||
TVectorItem item = this->vector_[index];
|
||||
|
||||
this->vector_.erase(this->vector_.begin() + index);
|
||||
lock.unlock(); // manual unlock
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->invokeDelayedItemsChanged();
|
||||
}
|
||||
|
||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
||||
{
|
||||
return this->insertItem(item, -1, caller);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
if (index == -1)
|
||||
{
|
||||
index = this->vector_.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(index >= 0 && index <= this->vector_.size());
|
||||
}
|
||||
|
||||
this->vector_.insert(this->vector_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TVectorItem, typename Compare>
|
||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
||||
void *caller = nullptr) override
|
||||
{
|
||||
assertInGuiThread();
|
||||
int index = -1;
|
||||
|
||||
{
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = std::lower_bound(this->vector_.begin(),
|
||||
this->vector_.end(), item, Compare{});
|
||||
index = it - this->vector_.begin();
|
||||
this->vector_.insert(it, item);
|
||||
}
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
}
|
||||
|
||||
virtual bool isSorted() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+357
-357
@@ -1,357 +1,357 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStandardItem>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
class SignalVectorModel : public QAbstractTableModel,
|
||||
pajlada::Signals::SignalHolder
|
||||
{
|
||||
public:
|
||||
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
||||
: QAbstractTableModel(parent)
|
||||
, columnCount_(columnCount)
|
||||
{
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
this->headerData_.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void init(BaseSignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->vector_ = vec;
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// get row index
|
||||
int index = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
// get row items
|
||||
std::vector<QStandardItem *> row = this->createRow();
|
||||
this->getRowFromItem(args.item, row);
|
||||
|
||||
// insert row
|
||||
index = this->beforeInsert(args.item, row, index);
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(row, args.item));
|
||||
this->endInsertRows();
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector())
|
||||
{
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
|
||||
this->managedConnect(vec->itemInserted, insert);
|
||||
|
||||
this->managedConnect(vec->itemRemoved, [this](auto args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int row = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(row >= 0 && row <= this->rows_.size());
|
||||
|
||||
// remove row
|
||||
std::vector<QStandardItem *> items =
|
||||
std::move(this->rows_[row].items);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
this->rows_.erase(this->rows_.begin() + row);
|
||||
this->endRemoveRows();
|
||||
|
||||
this->afterRemoved(args.item, items, row);
|
||||
|
||||
for (QStandardItem *item : items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
});
|
||||
|
||||
this->afterInit();
|
||||
}
|
||||
|
||||
virtual ~SignalVectorModel()
|
||||
{
|
||||
for (Row &row : this->rows_)
|
||||
{
|
||||
for (QStandardItem *item : row.items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->rows_.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->columnCount_;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column]->data(role);
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role) override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
Row &rowItem = this->rows_[row];
|
||||
|
||||
rowItem.items[column]->setData(value, role);
|
||||
|
||||
if (rowItem.isCustomRow)
|
||||
{
|
||||
this->customRowSetData(rowItem.items, column, value, role, row);
|
||||
}
|
||||
else
|
||||
{
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(vecRow, this);
|
||||
|
||||
assert(this->rows_[row].original);
|
||||
TVectorItem item = this->getItemFromRow(
|
||||
this->rows_[row].items, this->rows_[row].original.get());
|
||||
this->vector_->insertItem(item, vecRow, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role) const override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto it = this->headerData_[section].find(role);
|
||||
if (it == this->headerData_[section].end())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool setHeaderData(int section, Qt::Orientation orientation,
|
||||
const QVariant &value,
|
||||
int role = Qt::DisplayRole) override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->headerData_[section][role] = value;
|
||||
|
||||
emit this->headerDataChanged(Qt::Horizontal, section, section);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return this->rows_[row].items[column]->flags();
|
||||
}
|
||||
|
||||
QStandardItem *getItem(int row, int column)
|
||||
{
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column];
|
||||
}
|
||||
|
||||
void deleteRow(int row)
|
||||
{
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
}
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
{
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(row >= 0 && row < this->rows_.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void afterInit()
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TVectorItem &original) = 0;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row) = 0;
|
||||
|
||||
virtual int beforeInsert(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
virtual void afterRemoved(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(std::move(row), true));
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void removeCustomRow(int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
assert(this->rows_[index].isCustomRow);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
this->rows_.erase(this->rows_.begin() + index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
std::vector<QStandardItem *> createRow()
|
||||
{
|
||||
std::vector<QStandardItem *> row;
|
||||
for (int i = 0; i < this->columnCount_; i++)
|
||||
{
|
||||
row.push_back(new QStandardItem());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
struct Row {
|
||||
std::vector<QStandardItem *> items;
|
||||
boost::optional<TVectorItem> original;
|
||||
bool isCustomRow;
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
||||
bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, original(_original)
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<QMap<int, QVariant>> headerData_;
|
||||
BaseSignalVector<TVectorItem> *vector_;
|
||||
std::vector<Row> rows_;
|
||||
|
||||
int columnCount_;
|
||||
|
||||
// returns the related index of the SignalVector
|
||||
int getVectorIndexFromModelIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
// returns the related index of the model
|
||||
int getModelIndexFromVectorIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStandardItem>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TVectorItem>
|
||||
class SignalVectorModel : public QAbstractTableModel,
|
||||
pajlada::Signals::SignalHolder
|
||||
{
|
||||
public:
|
||||
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
||||
: QAbstractTableModel(parent)
|
||||
, columnCount_(columnCount)
|
||||
{
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
this->headerData_.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void init(BaseSignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->vector_ = vec;
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// get row index
|
||||
int index = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
// get row items
|
||||
std::vector<QStandardItem *> row = this->createRow();
|
||||
this->getRowFromItem(args.item, row);
|
||||
|
||||
// insert row
|
||||
index = this->beforeInsert(args.item, row, index);
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(row, args.item));
|
||||
this->endInsertRows();
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector())
|
||||
{
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
|
||||
this->managedConnect(vec->itemInserted, insert);
|
||||
|
||||
this->managedConnect(vec->itemRemoved, [this](auto args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int row = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(row >= 0 && row <= this->rows_.size());
|
||||
|
||||
// remove row
|
||||
std::vector<QStandardItem *> items =
|
||||
std::move(this->rows_[row].items);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
this->rows_.erase(this->rows_.begin() + row);
|
||||
this->endRemoveRows();
|
||||
|
||||
this->afterRemoved(args.item, items, row);
|
||||
|
||||
for (QStandardItem *item : items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
});
|
||||
|
||||
this->afterInit();
|
||||
}
|
||||
|
||||
virtual ~SignalVectorModel()
|
||||
{
|
||||
for (Row &row : this->rows_)
|
||||
{
|
||||
for (QStandardItem *item : row.items)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->rows_.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
return this->columnCount_;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column]->data(role);
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role) override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
Row &rowItem = this->rows_[row];
|
||||
|
||||
rowItem.items[column]->setData(value, role);
|
||||
|
||||
if (rowItem.isCustomRow)
|
||||
{
|
||||
this->customRowSetData(rowItem.items, column, value, role, row);
|
||||
}
|
||||
else
|
||||
{
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(vecRow, this);
|
||||
|
||||
assert(this->rows_[row].original);
|
||||
TVectorItem item = this->getItemFromRow(
|
||||
this->rows_[row].items, this->rows_[row].original.get());
|
||||
this->vector_->insertItem(item, vecRow, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role) const override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto it = this->headerData_[section].find(role);
|
||||
if (it == this->headerData_[section].end())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool setHeaderData(int section, Qt::Orientation orientation,
|
||||
const QVariant &value,
|
||||
int role = Qt::DisplayRole) override
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->headerData_[section][role] = value;
|
||||
|
||||
emit this->headerDataChanged(Qt::Horizontal, section, section);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return this->rows_[row].items[column]->flags();
|
||||
}
|
||||
|
||||
QStandardItem *getItem(int row, int column)
|
||||
{
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
return rows_[row].items[column];
|
||||
}
|
||||
|
||||
void deleteRow(int row)
|
||||
{
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
}
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
{
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(row >= 0 && row < this->rows_.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void afterInit()
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TVectorItem &original) = 0;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row) = 0;
|
||||
|
||||
virtual int beforeInsert(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
virtual void afterRemoved(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows_.insert(this->rows_.begin() + index,
|
||||
Row(std::move(row), true));
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void removeCustomRow(int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows_.size());
|
||||
assert(this->rows_[index].isCustomRow);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
this->rows_.erase(this->rows_.begin() + index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
std::vector<QStandardItem *> createRow()
|
||||
{
|
||||
std::vector<QStandardItem *> row;
|
||||
for (int i = 0; i < this->columnCount_; i++)
|
||||
{
|
||||
row.push_back(new QStandardItem());
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
struct Row {
|
||||
std::vector<QStandardItem *> items;
|
||||
boost::optional<TVectorItem> original;
|
||||
bool isCustomRow;
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
||||
bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, original(_original)
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<QMap<int, QVariant>> headerData_;
|
||||
BaseSignalVector<TVectorItem> *vector_;
|
||||
std::vector<Row> rows_;
|
||||
|
||||
int columnCount_;
|
||||
|
||||
// returns the related index of the SignalVector
|
||||
int getVectorIndexFromModelIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
// returns the related index of the model
|
||||
int getModelIndexFromVectorIndex(int index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto &row : this->rows_)
|
||||
{
|
||||
if (row.isCustomRow)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (i == index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+125
-125
@@ -1,125 +1,125 @@
|
||||
#include "UsernameSet.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// UsernameSet
|
||||
//
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
||||
{
|
||||
return this->items.begin();
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::end() const
|
||||
{
|
||||
return this->items.end();
|
||||
}
|
||||
|
||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
||||
{
|
||||
auto it = this->firstKeyForPrefix.find(prefix);
|
||||
if (it != this->firstKeyForPrefix.end())
|
||||
{
|
||||
auto start = this->items.find(it->second);
|
||||
auto end = start;
|
||||
|
||||
while (end != this->items.end() && prefix.isStartOf(*end))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
return {this->items.end(), this->items.end()};
|
||||
}
|
||||
|
||||
std::set<QString>::size_type UsernameSet::size() const
|
||||
{
|
||||
return this->items.size();
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(value);
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(std::move(value));
|
||||
}
|
||||
|
||||
void UsernameSet::insertPrefix(const QString &value)
|
||||
{
|
||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||
|
||||
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
||||
string = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
||||
: start_(start)
|
||||
, end_(end)
|
||||
{
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
||||
{
|
||||
return this->start_;
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
||||
{
|
||||
return this->end_;
|
||||
}
|
||||
|
||||
//
|
||||
// Prefix
|
||||
//
|
||||
|
||||
Prefix::Prefix(const QString &string)
|
||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
||||
{
|
||||
}
|
||||
|
||||
bool Prefix::operator==(const Prefix &other) const
|
||||
{
|
||||
return std::tie(this->first, this->second) ==
|
||||
std::tie(other.first, other.second);
|
||||
}
|
||||
|
||||
bool Prefix::operator!=(const Prefix &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Prefix::isStartOf(const QString &string) const
|
||||
{
|
||||
if (string.size() == 0)
|
||||
{
|
||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
||||
}
|
||||
else if (string.size() == 1)
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == QChar('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == string[1].toLower();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
#include "UsernameSet.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// UsernameSet
|
||||
//
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
||||
{
|
||||
return this->items.begin();
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::end() const
|
||||
{
|
||||
return this->items.end();
|
||||
}
|
||||
|
||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
||||
{
|
||||
auto it = this->firstKeyForPrefix.find(prefix);
|
||||
if (it != this->firstKeyForPrefix.end())
|
||||
{
|
||||
auto start = this->items.find(it->second);
|
||||
auto end = start;
|
||||
|
||||
while (end != this->items.end() && prefix.isStartOf(*end))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
return {this->items.end(), this->items.end()};
|
||||
}
|
||||
|
||||
std::set<QString>::size_type UsernameSet::size() const
|
||||
{
|
||||
return this->items.size();
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(value);
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(std::move(value));
|
||||
}
|
||||
|
||||
void UsernameSet::insertPrefix(const QString &value)
|
||||
{
|
||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||
|
||||
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
||||
string = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
||||
: start_(start)
|
||||
, end_(end)
|
||||
{
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
||||
{
|
||||
return this->start_;
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
||||
{
|
||||
return this->end_;
|
||||
}
|
||||
|
||||
//
|
||||
// Prefix
|
||||
//
|
||||
|
||||
Prefix::Prefix(const QString &string)
|
||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
||||
{
|
||||
}
|
||||
|
||||
bool Prefix::operator==(const Prefix &other) const
|
||||
{
|
||||
return std::tie(this->first, this->second) ==
|
||||
std::tie(other.first, other.second);
|
||||
}
|
||||
|
||||
bool Prefix::operator!=(const Prefix &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Prefix::isStartOf(const QString &string) const
|
||||
{
|
||||
if (string.size() == 0)
|
||||
{
|
||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
||||
}
|
||||
else if (string.size() == 1)
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == QChar('\0');
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == string[1].toLower();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+86
-86
@@ -1,86 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Prefix
|
||||
{
|
||||
public:
|
||||
Prefix(const QString &string);
|
||||
bool operator==(const Prefix &other) const;
|
||||
bool operator!=(const Prefix &other) const;
|
||||
bool isStartOf(const QString &string) const;
|
||||
|
||||
private:
|
||||
QChar first;
|
||||
QChar second;
|
||||
|
||||
friend struct std::hash<Prefix>;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
{
|
||||
return (size_t(prefix.first.unicode()) << 16) |
|
||||
size_t(prefix.second.unicode());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const QString &lhs, const QString &rhs) const
|
||||
{
|
||||
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UsernameSet
|
||||
{
|
||||
public:
|
||||
static constexpr int PrefixLength = 2;
|
||||
|
||||
using Iterator = std::set<QString>::iterator;
|
||||
using ConstIterator = std::set<QString>::const_iterator;
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(ConstIterator start, ConstIterator end);
|
||||
|
||||
ConstIterator begin();
|
||||
ConstIterator end();
|
||||
|
||||
private:
|
||||
ConstIterator start_;
|
||||
ConstIterator end_;
|
||||
};
|
||||
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
Range subrange(const Prefix &prefix) const;
|
||||
|
||||
std::set<QString>::size_type size() const;
|
||||
|
||||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
std::set<QString, CaseInsensitiveLess> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Prefix
|
||||
{
|
||||
public:
|
||||
Prefix(const QString &string);
|
||||
bool operator==(const Prefix &other) const;
|
||||
bool operator!=(const Prefix &other) const;
|
||||
bool isStartOf(const QString &string) const;
|
||||
|
||||
private:
|
||||
QChar first;
|
||||
QChar second;
|
||||
|
||||
friend struct std::hash<Prefix>;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
{
|
||||
return (size_t(prefix.first.unicode()) << 16) |
|
||||
size_t(prefix.second.unicode());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const QString &lhs, const QString &rhs) const
|
||||
{
|
||||
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UsernameSet
|
||||
{
|
||||
public:
|
||||
static constexpr int PrefixLength = 2;
|
||||
|
||||
using Iterator = std::set<QString>::iterator;
|
||||
using ConstIterator = std::set<QString>::const_iterator;
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(ConstIterator start, ConstIterator end);
|
||||
|
||||
ConstIterator begin();
|
||||
ConstIterator end();
|
||||
|
||||
private:
|
||||
ConstIterator start_;
|
||||
ConstIterator end_;
|
||||
};
|
||||
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
Range subrange(const Prefix &prefix) const;
|
||||
|
||||
std::set<QString>::size_type size() const;
|
||||
|
||||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
std::set<QString, CaseInsensitiveLess> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+15
-15
@@ -1,15 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-2"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
# define CHATTERINO_OS "macos"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
# define CHATTERINO_OS "linux"
|
||||
#else
|
||||
# define CHATTERINO_OS "unknown"
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.1.4-beta-2"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
# define CHATTERINO_OS "macos"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
# define CHATTERINO_OS "linux"
|
||||
#else
|
||||
# define CHATTERINO_OS "unknown"
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user