Merge remote-tracking branch 'main_repo/master' into git_is_pepega
This commit is contained in:
@@ -38,13 +38,7 @@ bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
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;
|
||||
return CompletionModel::compareStrings(this->string, that.string);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -133,7 +127,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!getSettings()->userCompletionOnlyWithAt)
|
||||
{
|
||||
for (const auto &name :
|
||||
usernames->subrange(Prefix(usernamePrefix)))
|
||||
@@ -191,4 +185,15 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||
}
|
||||
}
|
||||
|
||||
bool CompletionModel::compareStrings(const QString &a, const QString &b)
|
||||
{
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(a, b, Qt::CaseInsensitive);
|
||||
if (k == 0)
|
||||
return a > b;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -49,6 +49,8 @@ public:
|
||||
|
||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||
|
||||
static bool compareStrings(const QString &a, const QString &b);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
|
||||
+118
-191
@@ -4,244 +4,171 @@
|
||||
#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;
|
||||
template <typename T>
|
||||
struct SignalVectorItemEvent {
|
||||
const T &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
template <typename T>
|
||||
class SignalVector : 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)
|
||||
{
|
||||
}
|
||||
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
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()
|
||||
SignalVector()
|
||||
: readOnly_(new std::vector<T>())
|
||||
{
|
||||
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
|
||||
SignalVector(std::function<bool(const T &, const T &)> &&compare)
|
||||
: SignalVector()
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
||||
this->mutex_);
|
||||
itemCompare_ = std::move(compare);
|
||||
}
|
||||
|
||||
Iterator end() const
|
||||
virtual bool isSorted() const
|
||||
{
|
||||
return Iterator(
|
||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
||||
this->mutex_);
|
||||
return bool(this->itemCompare_);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
/// A read-only version of the vector which can be used concurrently.
|
||||
std::shared_ptr<const std::vector<T>> readOnly()
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_.empty();
|
||||
return this->readOnly_;
|
||||
}
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
/// This may only be called from the GUI thread.
|
||||
///
|
||||
/// @param item
|
||||
/// Item to be inserted.
|
||||
/// @param proposedIndex
|
||||
/// Index to insert at. `-1` will append at the end.
|
||||
/// Will be ignored if the vector is sorted.
|
||||
/// @param caller
|
||||
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||
/// signals.
|
||||
int insert(const T &item, int index = -1, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->vector_;
|
||||
if (this->isSorted())
|
||||
{
|
||||
auto it = std::lower_bound(this->items_.begin(), this->items_.end(),
|
||||
item, this->itemCompare_);
|
||||
index = it - this->items_.begin();
|
||||
this->items_.insert(it, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index == -1)
|
||||
index = this->items_.size();
|
||||
else
|
||||
assert(index >= 0 && index <= this->items_.size());
|
||||
|
||||
this->items_.insert(this->items_.begin() + index, item);
|
||||
}
|
||||
|
||||
SignalVectorItemEvent<T> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->itemsChanged_();
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
std::vector<TVectorItem> cloneVector() const
|
||||
/// This may only be called from the GUI thread.
|
||||
///
|
||||
/// @param item
|
||||
/// Item to be appended.
|
||||
/// @param caller
|
||||
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||
/// signals.
|
||||
int append(const T &item, void *caller = nullptr)
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
return this->vector_;
|
||||
return this->insert(item, -1, caller);
|
||||
}
|
||||
|
||||
void invokeDelayedItemsChanged()
|
||||
void removeAt(int index, void *caller = nullptr)
|
||||
{
|
||||
assertInGuiThread();
|
||||
assert(index >= 0 && index < int(this->items_.size()));
|
||||
|
||||
T item = this->items_[index];
|
||||
this->items_.erase(this->items_.begin() + index);
|
||||
|
||||
SignalVectorItemEvent<T> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->itemsChanged_();
|
||||
}
|
||||
|
||||
const std::vector<T> &raw() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->items_;
|
||||
}
|
||||
|
||||
[[deprecated]] std::vector<T> cloneVector()
|
||||
{
|
||||
return *this->readOnly();
|
||||
}
|
||||
|
||||
// mirror vector functions
|
||||
auto begin() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
return this->items_.begin();
|
||||
}
|
||||
|
||||
auto end() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
return this->items_.end();
|
||||
}
|
||||
|
||||
decltype(auto) operator[](size_t index)
|
||||
{
|
||||
assertInGuiThread();
|
||||
return this->items[index];
|
||||
}
|
||||
|
||||
auto empty()
|
||||
{
|
||||
assertInGuiThread();
|
||||
return this->items_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
void itemsChanged_()
|
||||
{
|
||||
// emit delayed event
|
||||
if (!this->itemsChangedTimer_.isActive())
|
||||
{
|
||||
this->itemsChangedTimer_.start();
|
||||
}
|
||||
|
||||
// update concurrent version
|
||||
this->readOnly_ = std::make_shared<const std::vector<T>>(this->items_);
|
||||
}
|
||||
|
||||
virtual bool isSorted() const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<TVectorItem> vector_;
|
||||
std::vector<T> items_;
|
||||
std::shared_ptr<const std::vector<T>> readOnly_;
|
||||
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;
|
||||
}
|
||||
std::function<bool(const T &, const T &)> itemCompare_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -25,11 +25,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void init(BaseSignalVector<TVectorItem> *vec)
|
||||
void initialize(SignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->vector_ = vec;
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
auto insert = [this](const SignalVectorItemEvent<TVectorItem> &args) {
|
||||
if (args.caller == this)
|
||||
{
|
||||
return;
|
||||
@@ -52,9 +52,9 @@ public:
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector())
|
||||
for (const TVectorItem &item : vec->raw())
|
||||
{
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
SignalVectorItemEvent<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
@@ -89,6 +89,12 @@ public:
|
||||
this->afterInit();
|
||||
}
|
||||
|
||||
SignalVectorModel<TVectorItem> *initialized(SignalVector<TVectorItem> *vec)
|
||||
{
|
||||
this->initialize(vec);
|
||||
return this;
|
||||
}
|
||||
|
||||
virtual ~SignalVectorModel()
|
||||
{
|
||||
for (Row &row : this->rows_)
|
||||
@@ -147,12 +153,12 @@ public:
|
||||
else
|
||||
{
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(vecRow, this);
|
||||
this->vector_->removeAt(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);
|
||||
this->vector_->insert(item, vecRow, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -219,7 +225,7 @@ public:
|
||||
void deleteRow(int row)
|
||||
{
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
this->vector_->removeAt(signalVectorRow);
|
||||
}
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
@@ -234,11 +240,71 @@ public:
|
||||
assert(row >= 0 && row < this->rows_.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector_->removeItem(signalVectorRow);
|
||||
this->vector_->removeAt(signalVectorRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList mimeTypes() const override
|
||||
{
|
||||
return {"chatterino_row_id"};
|
||||
}
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &list) const
|
||||
{
|
||||
if (list.length() == 1)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check if all indices are in the same row -> single row selected
|
||||
for (auto &&x : list)
|
||||
{
|
||||
if (x.row() != list.first().row())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data = new QMimeData;
|
||||
data->setData("chatterino_row_id", QByteArray::number(list[0].row()));
|
||||
return data;
|
||||
}
|
||||
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int /*row*/,
|
||||
int /*column*/, const QModelIndex &parent) override
|
||||
{
|
||||
if (data->hasFormat("chatterino_row_id") &&
|
||||
action & (Qt::DropAction::MoveAction | Qt::DropAction::CopyAction))
|
||||
{
|
||||
int from = data->data("chatterino_row_id").toInt();
|
||||
int to = parent.row();
|
||||
|
||||
if (from < 0 || from > this->vector_->raw().size() || to < 0 ||
|
||||
to > this->vector_->raw().size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (from != to)
|
||||
{
|
||||
auto item = this->vector_->raw()[from];
|
||||
this->vector_->removeAt(from);
|
||||
this->vector_->insert(item, to);
|
||||
}
|
||||
|
||||
// We return false since we remove items ourselves.
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::DropActions supportedDropActions() const override
|
||||
{
|
||||
return this->vector_->isSorted()
|
||||
? Qt::DropActions()
|
||||
: Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void afterInit()
|
||||
{
|
||||
@@ -326,7 +392,7 @@ protected:
|
||||
|
||||
private:
|
||||
std::vector<QMap<int, QVariant>> headerData_;
|
||||
BaseSignalVector<TVectorItem> *vector_;
|
||||
SignalVector<TVectorItem> *vector_;
|
||||
std::vector<Row> rows_;
|
||||
|
||||
int columnCount_;
|
||||
|
||||
Reference in New Issue
Block a user