items are now sortable with drag/drop

This commit is contained in:
Martin Rotter 2022-08-26 13:50:41 +02:00
parent 3d93a40284
commit f63af3a051
5 changed files with 249 additions and 225 deletions

View File

@ -22,8 +22,6 @@
#include <QStack>
#include <QTimer>
using RootItemPtr = RootItem*;
FeedsModel::FeedsModel(QObject* parent) : QAbstractItemModel(parent), m_rootItem(new RootItem()) {
setObjectName(QSL("FeedsModel"));
@ -75,69 +73,6 @@ QStringList FeedsModel::mimeTypes() const {
return QStringList() << QSL(MIME_TYPE_ITEM_POINTER);
}
bool FeedsModel::dropMimeData(const QMimeData* data,
Qt::DropAction action,
int row,
int column,
const QModelIndex& parent) {
Q_UNUSED(row)
Q_UNUSED(column)
if (action == Qt::DropAction::IgnoreAction) {
return true;
}
else if (action != Qt::DropAction::MoveAction) {
return false;
}
QByteArray dragged_items_data = data->data(QSL(MIME_TYPE_ITEM_POINTER));
if (dragged_items_data.isEmpty()) {
return false;
}
else {
QDataStream stream(&dragged_items_data, QIODevice::OpenModeFlag::ReadOnly);
while (!stream.atEnd()) {
quintptr pointer_to_item;
stream >> pointer_to_item;
// We have item we want to drag, we also determine the target item.
auto* dragged_item = RootItemPtr(pointer_to_item);
RootItem* target_item = itemForIndex(parent);
ServiceRoot* dragged_item_root = dragged_item->getParentServiceRoot();
ServiceRoot* target_item_root = target_item->getParentServiceRoot();
if (dragged_item == target_item || dragged_item->parent() == target_item) {
qDebug("Dragged item is equal to target item or its parent is equal to target item. Cancelling drag-drop "
"action.");
return false;
}
if (dragged_item_root != target_item_root) {
// Transferring of items between different accounts is not possible.
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot perform drag & drop operation"),
tr("You can't transfer dragged item into different account, this is not supported."),
QSystemTrayIcon::MessageIcon::Critical});
qDebugNN << LOGSEC_FEEDMODEL
<< "Dragged item cannot be dragged into different account. Cancelling drag-drop action.";
return false;
}
if (dragged_item->performDragDropChange(target_item)) {
// Drag & drop is supported by the dragged item and was
// completed on data level and in item hierarchy.
emit requireItemValidationAfterDragDrop(indexForItem(dragged_item));
}
}
return true;
}
return false;
}
Qt::DropActions FeedsModel::supportedDropActions() const {
return Qt::DropAction::MoveAction;
}
@ -368,7 +303,6 @@ RootItem* FeedsModel::itemForIndex(const QModelIndex& index) const {
QModelIndex FeedsModel::indexForItem(const RootItem* item) const {
if (item == nullptr || item->kind() == RootItem::Kind::Root) {
// Root item lies on invalid index.
return QModelIndex();
}
@ -598,6 +532,5 @@ QVariant FeedsModel::data(const QModelIndex& index, int role) const {
default:
return itemForIndex(index)->data(index.column(), role);
;
}
}

View File

@ -14,29 +14,27 @@ class ServiceEntryPoint;
class StandardServiceRoot;
class RSSGUARD_DLLSPEC FeedsModel : public QAbstractItemModel {
Q_OBJECT
Q_OBJECT
public:
explicit FeedsModel(QObject* parent = nullptr);
virtual ~FeedsModel();
// Model implementation.
QVariant data(const QModelIndex& index, int role) const;
virtual QVariant data(const QModelIndex& index, int role) const;
// Drag & drop.
QMimeData* mimeData(const QModelIndexList& indexes) const;
QStringList mimeTypes() const;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
Qt::DropActions supportedDropActions() const;
Qt::ItemFlags flags(const QModelIndex& index) const;
virtual QMimeData* mimeData(const QModelIndexList& indexes) const;
virtual QStringList mimeTypes() const;
virtual Qt::DropActions supportedDropActions() const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
// Other subclassed methods.
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QModelIndex index(int row, int column, const QModelIndex& parent) const;
QModelIndex parent(const QModelIndex& child) const;
int columnCount(const QModelIndex& parent) const;
int rowCount(const QModelIndex& parent) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const;
virtual QModelIndex parent(const QModelIndex& child) const;
virtual int columnCount(const QModelIndex& parent) const;
virtual int rowCount(const QModelIndex& parent) const;
// Returns counts of ALL/UNREAD (non-deleted) messages for the model.
int countOfAllMessages() const;
@ -136,7 +134,7 @@ class RSSGUARD_DLLSPEC FeedsModel : public QAbstractItemModel {
void messageCountsChanged(int unread_messages, bool any_feed_has_unread_messages);
// Emitted if any item requested that any view should expand it.
void itemExpandRequested(QList<RootItem*>items, bool expand);
void itemExpandRequested(QList<RootItem*> items, bool expand);
// Emitted if any item requested that its expand states should be explicitly saved.
// NOTE: Normally expand states are saved when application quits.
@ -145,9 +143,6 @@ class RSSGUARD_DLLSPEC FeedsModel : public QAbstractItemModel {
// Emitted when there is a need of reloading of displayed messages.
void reloadMessageListRequested(bool mark_selected_messages_read);
// There was some drag/drop operation, notify view about this.
void requireItemValidationAfterDragDrop(const QModelIndex& source_index);
private:
RootItem* m_rootItem;
QList<QString> m_headerData;

View File

@ -3,17 +3,21 @@
#include "core/feedsproxymodel.h"
#include "core/feedsmodel.h"
#include "database/databasequeries.h"
#include "definitions/definitions.h"
#include "gui/feedsview.h"
#include "miscellaneous/application.h"
#include "miscellaneous/regexfactory.h"
#include "services/abstract/rootitem.h"
#include <QMimeData>
#include <QTimer>
using RootItemPtr = RootItem*;
FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
: QSortFilterProxyModel(parent), m_sourceModel(source_model), m_view(nullptr),
m_selectedItem(nullptr), m_showUnreadOnly(false), m_sortAlphabetically(true) {
: QSortFilterProxyModel(parent), m_sourceModel(source_model), m_view(nullptr), m_selectedItem(nullptr),
m_showUnreadOnly(false), m_sortAlphabetically(true) {
setObjectName(QSL("FeedsProxyModel"));
setSortRole(Qt::ItemDataRole::EditRole);
@ -28,26 +32,28 @@ FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
// Smaller index means that item is "smaller" which
// means it should be more on top when sorting
// in ascending order.
m_priorities = {
RootItem::Kind::Category,
RootItem::Kind::Feed,
RootItem::Kind::Labels,
RootItem::Kind::Important,
RootItem::Kind::Unread,
RootItem::Kind::Bin
};
m_priorities = {RootItem::Kind::Category,
RootItem::Kind::Feed,
RootItem::Kind::Labels,
RootItem::Kind::Important,
RootItem::Kind::Unread,
RootItem::Kind::Bin};
}
FeedsProxyModel::~FeedsProxyModel() {
qDebugNN << LOGSEC_FEEDMODEL << "Destroying FeedsProxyModel instance";
}
QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const {
QModelIndexList FeedsProxyModel::match(const QModelIndex& start,
int role,
const QVariant& value,
int hits,
Qt::MatchFlags flags) const {
QModelIndexList result;
const int match_type = flags & 0x0F;
const Qt::CaseSensitivity cs = Qt::CaseSensitivity::CaseInsensitive;
const bool recurse = (flags& Qt::MatchFlag::MatchRecursive) > 0;
const bool wrap = (flags& Qt::MatchFlag::MatchWrap) > 0;
const bool recurse = (flags & Qt::MatchFlag::MatchRecursive) > 0;
const bool wrap = (flags & Qt::MatchFlag::MatchWrap) > 0;
const bool all_hits = (hits == -1);
QString entered_text;
const QModelIndex p = parent(start);
@ -88,7 +94,9 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const
#endif
if (QRegularExpression(entered_text,
QRegularExpression::PatternOption::CaseInsensitiveOption |
QRegularExpression::PatternOption::UseUnicodePropertiesOption).match(item_text).hasMatch()) {
QRegularExpression::PatternOption::UseUnicodePropertiesOption)
.match(item_text)
.hasMatch()) {
result.append(idx);
}
@ -97,7 +105,9 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const
case Qt::MatchFlag::MatchWildcard:
if (QRegularExpression(RegexFactory::wildcardToRegularExpression(entered_text),
QRegularExpression::PatternOption::CaseInsensitiveOption |
QRegularExpression::PatternOption::UseUnicodePropertiesOption).match(item_text).hasMatch()) {
QRegularExpression::PatternOption::UseUnicodePropertiesOption)
.match(item_text)
.hasMatch()) {
result.append(idx);
}
@ -135,9 +145,11 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const
}
if (recurse && hasChildren(idx)) {
result +=
match(index(0, idx.column(), idx), role, (entered_text.isEmpty() ? value : entered_text),
(all_hits ? -1 : hits - result.count()), flags);
result += match(index(0, idx.column(), idx),
role,
(entered_text.isEmpty() ? value : entered_text),
(all_hits ? -1 : hits - result.count()),
flags);
}
}
@ -148,6 +160,84 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const
return result;
}
bool FeedsProxyModel::dropMimeData(const QMimeData* data,
Qt::DropAction action,
int row,
int column,
const QModelIndex& parent) {
Q_UNUSED(column)
if (action == Qt::DropAction::IgnoreAction) {
return true;
}
else if (action != Qt::DropAction::MoveAction) {
return false;
}
QByteArray dragged_items_data = data->data(QSL(MIME_TYPE_ITEM_POINTER));
if (dragged_items_data.isEmpty()) {
return false;
}
else {
QDataStream stream(&dragged_items_data, QIODevice::OpenModeFlag::ReadOnly);
const bool order_change = row >= 0 && !m_sortAlphabetically;
const QModelIndex source_parent = mapToSource(parent);
while (!stream.atEnd()) {
quintptr pointer_to_item;
stream >> pointer_to_item;
// We have item we want to drag, we also determine the target item.
auto* dragged_item = RootItemPtr(pointer_to_item);
RootItem* target_item = m_sourceModel->itemForIndex(source_parent);
ServiceRoot* dragged_item_root = dragged_item->getParentServiceRoot();
ServiceRoot* target_item_root = target_item->getParentServiceRoot();
if ((dragged_item == target_item || dragged_item->parent() == target_item) && !order_change) {
qDebugNN << LOGSEC_FEEDMODEL
<< "Dragged item is equal to target item or its parent is equal to target item. Cancelling drag-drop "
"action.";
return false;
}
if (dragged_item_root != target_item_root) {
// Transferring of items between different accounts is not possible.
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot perform drag & drop operation"),
tr("You can't transfer dragged item into different account, this is not supported."),
QSystemTrayIcon::MessageIcon::Critical});
qDebugNN << LOGSEC_FEEDMODEL
<< "Dragged item cannot be dragged into different account. Cancelling drag-drop action.";
return false;
}
if (dragged_item != target_item && dragged_item->parent() != target_item &&
dragged_item->performDragDropChange(target_item)) {
// Drag & drop is supported by the dragged item and was
// completed on data level and in item hierarchy.
emit requireItemValidationAfterDragDrop(m_sourceModel->indexForItem(dragged_item));
}
if (order_change) {
auto db = qApp->database()->driver()->connection(metaObject()->className());
if (row > dragged_item->sortOrder()) {
row--;
}
DatabaseQueries::moveItem(dragged_item, false, false, row, db);
}
invalidate();
}
return true;
}
return false;
}
bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
if (left.isValid() && right.isValid()) {
// Make necessary castings.
@ -183,14 +273,13 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right
case RootItem::Kind::Feed:
case RootItem::Kind::Category:
case RootItem::Kind::ServiceRoot:
return sortOrder() == Qt::SortOrder::AscendingOrder
? left_item->sortOrder() < right_item->sortOrder()
: left_item->sortOrder() > right_item->sortOrder();
return sortOrder() == Qt::SortOrder::AscendingOrder ? left_item->sortOrder() < right_item->sortOrder()
: left_item->sortOrder() > right_item->sortOrder();
default:
return sortOrder() == Qt::SortOrder::AscendingOrder
? QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0
: QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) > 0;
? QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0
: QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) > 0;
}
}
}
@ -199,9 +288,8 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right
auto left_priority = m_priorities.indexOf(left_item->kind());
auto right_priority = m_priorities.indexOf(right_item->kind());
return sortOrder() == Qt::SortOrder::AscendingOrder
? left_priority < right_priority
: right_priority < left_priority;
return sortOrder() == Qt::SortOrder::AscendingOrder ? left_priority < right_priority
: right_priority < left_priority;
}
}
else {
@ -212,11 +300,9 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right
bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const {
bool should_show = filterAcceptsRowInternal(source_row, source_parent);
qDebugNN << LOGSEC_CORE
<< "Filter accepts row"
qDebugNN << LOGSEC_CORE << "Filter accepts row"
<< QUOTE_W_SPACE(m_sourceModel->itemForIndex(m_sourceModel->index(source_row, 0, source_parent))->title())
<< "and filter result is:"
<< QUOTE_W_SPACE_DOT(should_show);
<< "and filter result is:" << QUOTE_W_SPACE_DOT(should_show);
/*
if (should_show && (!filterRegularExpression().pattern().isEmpty() ||
@ -251,8 +337,7 @@ bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex
const RootItem* item = m_sourceModel->itemForIndex(idx);
if (item->kind() != RootItem::Kind::Category &&
item->kind() != RootItem::Kind::Feed &&
if (item->kind() != RootItem::Kind::Category && item->kind() != RootItem::Kind::Feed &&
item->kind() != RootItem::Kind::Label) {
// Some items are always visible.
return true;
@ -271,12 +356,15 @@ bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex
// particularly manifests itself if user uses "next unread item" action and
// "show unread only" is enabled too and user for example selects last unread
// article in a feed -> then the feed would disappear from list suddenly.
return
m_selectedItem == item || (item->countOfUnreadMessages() != 0 &&
QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent));
return m_selectedItem == item ||
(item->countOfUnreadMessages() != 0 && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent));
}
}
bool FeedsProxyModel::sortAlphabetically() const {
return m_sortAlphabetically;
}
void FeedsProxyModel::sort(int column, Qt::SortOrder order) {
QSortFilterProxyModel::sort(column, order);
}

View File

@ -11,17 +11,27 @@ class FeedsModel;
class FeedsView;
class FeedsProxyModel : public QSortFilterProxyModel {
Q_OBJECT
Q_OBJECT
public:
explicit FeedsProxyModel(FeedsModel* source_model, QObject* parent = nullptr);
virtual ~FeedsProxyModel();
virtual bool dropMimeData(const QMimeData* data,
Qt::DropAction action,
int row,
int column,
const QModelIndex& parent);
virtual void sort(int column, Qt::SortOrder order = Qt::SortOrder::AscendingOrder);
// Returns index list of items which "match" given value.
// Used for finding items according to entered title text.
virtual QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const;
virtual QModelIndexList match(const QModelIndex& start,
int role,
const QVariant& value,
int hits,
Qt::MatchFlags flags) const;
// Maps list of indexes.
QModelIndexList mapListToSource(const QModelIndexList& indexes) const;
@ -34,6 +44,7 @@ class FeedsProxyModel : public QSortFilterProxyModel {
void setSelectedItem(const RootItem* selected_item);
void setView(FeedsView* newView);
bool sortAlphabetically() const;
void setSortAlphabetically(bool sort_alphabetically);
public slots:
@ -42,6 +53,9 @@ class FeedsProxyModel : public QSortFilterProxyModel {
signals:
void expandAfterFilterIn(QModelIndex source_idx) const;
// There was some drag/drop operation, notify view about this.
void requireItemValidationAfterDragDrop(const QModelIndex& source_index);
protected:
virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;

View File

@ -29,8 +29,8 @@
FeedsView::FeedsView(QWidget* parent)
: BaseTreeView(parent), m_contextMenuService(nullptr), m_contextMenuBin(nullptr), m_contextMenuCategories(nullptr),
m_contextMenuFeeds(nullptr), m_contextMenuImportant(nullptr), m_contextMenuEmptySpace(nullptr), m_contextMenuOtherItems(nullptr),
m_contextMenuLabel(nullptr), m_dontSaveExpandState(false) {
m_contextMenuFeeds(nullptr), m_contextMenuImportant(nullptr), m_contextMenuEmptySpace(nullptr),
m_contextMenuOtherItems(nullptr), m_contextMenuLabel(nullptr), m_dontSaveExpandState(false) {
setObjectName(QSL("FeedsView"));
// Allocate models.
@ -40,10 +40,13 @@ FeedsView::FeedsView(QWidget* parent)
m_proxyModel->setView(this);
// Connections.
connect(m_sourceModel, &FeedsModel::requireItemValidationAfterDragDrop, this, &FeedsView::validateItemAfterDragDrop);
connect(m_sourceModel, &FeedsModel::itemExpandRequested, this, &FeedsView::onItemExpandRequested);
connect(m_sourceModel, &FeedsModel::itemExpandStateSaveRequested, this, &FeedsView::onItemExpandStateSaveRequested);
connect(header(), &QHeaderView::sortIndicatorChanged, this, &FeedsView::saveSortState);
connect(m_proxyModel,
&FeedsProxyModel::requireItemValidationAfterDragDrop,
this,
&FeedsView::validateItemAfterDragDrop);
connect(m_proxyModel, &FeedsProxyModel::expandAfterFilterIn, this, &FeedsView::expandItemDelayed);
connect(this, &FeedsView::expanded, this, &FeedsView::onIndexExpanded);
connect(this, &FeedsView::collapsed, this, &FeedsView::onIndexCollapsed);
@ -127,10 +130,10 @@ void FeedsView::addFeedIntoSelectedAccount() {
root->addNewFeed(selected, QGuiApplication::clipboard()->text(QClipboard::Mode::Clipboard));
}
else {
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Not supported by account"),
tr("Selected account does not support adding of new feeds."),
QSystemTrayIcon::MessageIcon::Warning });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Not supported by account"),
tr("Selected account does not support adding of new feeds."),
QSystemTrayIcon::MessageIcon::Warning});
}
}
}
@ -145,10 +148,10 @@ void FeedsView::addCategoryIntoSelectedAccount() {
root->addNewCategory(selectedItem());
}
else {
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Not supported by account"),
tr("Selected account does not support adding of new categories."),
QSystemTrayIcon::MessageIcon::Warning });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Not supported by account"),
tr("Selected account does not support adding of new categories."),
QSystemTrayIcon::MessageIcon::Warning});
}
}
}
@ -163,7 +166,7 @@ void FeedsView::expandCollapseCurrentItem(bool recursive) {
}
if (recursive) {
QList<QModelIndex> to_process = { index };
QList<QModelIndex> to_process = {index};
bool expa = !isExpanded(index);
while (!to_process.isEmpty()) {
@ -208,10 +211,10 @@ void FeedsView::editSelectedItem() {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Cannot edit item"),
tr("Selected item cannot be edited because another critical operation is ongoing."),
QSystemTrayIcon::MessageIcon::Warning });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot edit item"),
tr("Selected item cannot be edited because another critical operation is ongoing."),
QSystemTrayIcon::MessageIcon::Warning});
// Thus, cannot delete and quit the method.
return;
@ -221,10 +224,10 @@ void FeedsView::editSelectedItem() {
selectedItem()->editViaGui();
}
else {
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Cannot edit item"),
tr("Selected item cannot be edited, this is not (yet?) supported."),
QSystemTrayIcon::MessageIcon::Warning });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot edit item"),
tr("Selected item cannot be edited, this is not (yet?) supported."),
QSystemTrayIcon::MessageIcon::Warning});
}
// Changes are done, unlock the update master lock.
@ -236,10 +239,10 @@ void FeedsView::deleteSelectedItem() {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Cannot delete item"),
tr("Selected item cannot be deleted because another critical operation is ongoing."),
QSystemTrayIcon::MessageIcon::Warning });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot delete item"),
tr("Selected item cannot be deleted because another critical operation is ongoing."),
QSystemTrayIcon::MessageIcon::Warning});
// Thus, cannot delete and quit the method.
return;
@ -261,7 +264,8 @@ void FeedsView::deleteSelectedItem() {
tr("Deleting \"%1\"").arg(selected_item->title()),
tr("You are about to completely delete item \"%1\".").arg(selected_item->title()),
tr("Are you sure?"),
QString(), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QString(),
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::Yes) == QMessageBox::StandardButton::No) {
// User refused.
qApp->feedUpdateLock()->unlock();
@ -272,17 +276,18 @@ void FeedsView::deleteSelectedItem() {
if (!selected_item->deleteViaGui()) {
m_proxyModel->invalidate();
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Cannot delete \"%1\"").arg(selected_item->title()),
tr("This item cannot be deleted because something critically failed. Submit bug report."),
QSystemTrayIcon::MessageIcon::Critical });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot delete \"%1\"").arg(selected_item->title()),
tr("This item cannot be deleted because something critically failed. Submit bug report."),
QSystemTrayIcon::MessageIcon::Critical});
}
}
else {
qApp->showGuiMessage(Notification::Event::GeneralEvent, {
tr("Cannot delete \"%1\"").arg(selected_item->title()),
tr("This item cannot be deleted, because it does not support it\nor this functionality is not implemented yet."),
QSystemTrayIcon::MessageIcon::Critical });
qApp->showGuiMessage(Notification::Event::GeneralEvent,
{tr("Cannot delete \"%1\"").arg(selected_item->title()),
tr("This item cannot be deleted, because it does not support it\nor this functionality is "
"not implemented yet."),
QSystemTrayIcon::MessageIcon::Critical});
}
}
@ -395,7 +400,8 @@ QModelIndex FeedsView::nextUnreadItem(const QModelIndex& default_row) {
const QModelIndex starting_row = default_row;
while (true) {
bool has_unread = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(nconst_default_row))->countOfUnreadMessages() > 0;
bool has_unread =
m_sourceModel->itemForIndex(m_proxyModel->mapToSource(nconst_default_row))->countOfUnreadMessages() > 0;
if (has_unread) {
if (m_proxyModel->hasChildren(nconst_default_row)) {
@ -435,10 +441,9 @@ QMenu* FeedsView::initializeContextMenuBin(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuBin->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread);
m_contextMenuBin->addActions(QList<QAction*>() << qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread);
if (!specific_actions.isEmpty()) {
m_contextMenuBin->addSeparator();
@ -458,15 +463,15 @@ QMenu* FeedsView::initializeContextMenuService(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuService->addActions({ qApp->mainForm()->m_ui->m_actionUpdateSelectedItems,
qApp->mainForm()->m_ui->m_actionEditSelectedItem,
qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed,
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode,
qApp->mainForm()->m_ui->m_actionExpandCollapseItem,
qApp->mainForm()->m_ui->m_actionExpandCollapseItemRecursively,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread,
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem });
m_contextMenuService->addActions({qApp->mainForm()->m_ui->m_actionUpdateSelectedItems,
qApp->mainForm()->m_ui->m_actionEditSelectedItem,
qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed,
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode,
qApp->mainForm()->m_ui->m_actionExpandCollapseItem,
qApp->mainForm()->m_ui->m_actionExpandCollapseItemRecursively,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread,
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem});
auto cat_add = clicked_item->getParentServiceRoot()->supportsCategoryAdding();
auto feed_add = clicked_item->getParentServiceRoot()->supportsFeedAdding();
@ -483,8 +488,7 @@ QMenu* FeedsView::initializeContextMenuService(RootItem* clicked_item) {
m_contextMenuService->addAction(qApp->mainForm()->m_ui->m_actionAddFeedIntoSelectedItem);
}
if (!qApp->settings()->value(GROUP(Feeds),
SETTING(Feeds::SortAlphabetically)).toBool()) {
if (!qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::SortAlphabetically)).toBool()) {
m_contextMenuService->addSeparator();
m_contextMenuService->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveUp);
m_contextMenuService->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveDown);
@ -521,7 +525,8 @@ void FeedsView::focusInEvent(QFocusEvent* event) {
QTreeView::focusInEvent(event);
if (currentIndex().isValid()) {
selectionModel()->select(currentIndex(), QItemSelectionModel::SelectionFlag::Select | QItemSelectionModel::SelectionFlag::Rows);
selectionModel()->select(currentIndex(),
QItemSelectionModel::SelectionFlag::Select | QItemSelectionModel::SelectionFlag::Rows);
}
}
@ -554,14 +559,11 @@ void FeedsView::onIndexExpanded(const QModelIndex& idx) {
const RootItem* it = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(idx));
if (it != nullptr && (int(it->kind()) & int(RootItem::Kind::Category |
RootItem::Kind::ServiceRoot |
RootItem::Kind::Labels)) > 0) {
if (it != nullptr &&
(int(it->kind()) & int(RootItem::Kind::Category | RootItem::Kind::ServiceRoot | RootItem::Kind::Labels)) > 0) {
const QString setting_name = it->hashCode();
qApp->settings()->setValue(GROUP(CategoriesExpandStates),
setting_name,
true);
qApp->settings()->setValue(GROUP(CategoriesExpandStates), setting_name, true);
}
}
@ -575,14 +577,11 @@ void FeedsView::onIndexCollapsed(const QModelIndex& idx) {
RootItem* it = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(idx));
if (it != nullptr && (int(it->kind()) & int(RootItem::Kind::Category |
RootItem::Kind::ServiceRoot |
RootItem::Kind::Labels)) > 0) {
if (it != nullptr &&
(int(it->kind()) & int(RootItem::Kind::Category | RootItem::Kind::ServiceRoot | RootItem::Kind::Labels)) > 0) {
const QString setting_name = it->hashCode();
qApp->settings()->setValue(GROUP(CategoriesExpandStates),
setting_name,
false);
qApp->settings()->setValue(GROUP(CategoriesExpandStates), setting_name, false);
}
}
@ -596,9 +595,8 @@ void FeedsView::saveAllExpandStates() {
void FeedsView::saveExpandStates(RootItem* item) {
Settings* settings = qApp->settings();
QList<RootItem*> items = item->getSubTree(RootItem::Kind::Category |
RootItem::Kind::ServiceRoot |
RootItem::Kind::Labels);
QList<RootItem*> items =
item->getSubTree(RootItem::Kind::Category | RootItem::Kind::ServiceRoot | RootItem::Kind::Labels);
// Iterate all categories and save their expand statuses.
for (const RootItem* it : items) {
@ -606,9 +604,7 @@ void FeedsView::saveExpandStates(RootItem* item) {
QModelIndex source_index = sourceModel()->indexForItem(it);
QModelIndex visible_index = model()->mapFromSource(source_index);
settings->setValue(GROUP(CategoriesExpandStates),
setting_name,
isExpanded(visible_index));
settings->setValue(GROUP(CategoriesExpandStates), setting_name, isExpanded(visible_index));
}
}
@ -616,8 +612,7 @@ void FeedsView::loadAllExpandStates() {
const Settings* settings = qApp->settings();
QList<RootItem*> expandable_items;
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItem::Kind::Category |
RootItem::Kind::ServiceRoot |
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItem::Kind::Category | RootItem::Kind::ServiceRoot |
RootItem::Kind::Labels));
// Iterate all categories and save their expand statuses.
@ -629,13 +624,15 @@ void FeedsView::loadAllExpandStates() {
}
sortByColumn(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnFeeds)).toInt(),
static_cast<Qt::SortOrder>(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds)).toInt()));
static_cast<Qt::SortOrder>(qApp->settings()
->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds))
.toInt()));
}
void FeedsView::expandItemDelayed(const QModelIndex& source_idx) {
//QTimer::singleShot(100, this, [=] {
// Model requests to expand some items as they are visible and there is
// a filter active, so they maybe were not visible before.
// QTimer::singleShot(100, this, [=] {
// Model requests to expand some items as they are visible and there is
// a filter active, so they maybe were not visible before.
QModelIndex pidx = m_proxyModel->mapFromSource(source_idx);
// NOTE: These changes are caused by filtering mechanisms
@ -663,15 +660,15 @@ QMenu* FeedsView::initializeContextMenuCategories(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuCategories->addActions({ qApp->mainForm()->m_ui->m_actionUpdateSelectedItems,
qApp->mainForm()->m_ui->m_actionEditSelectedItem,
qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed,
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode,
qApp->mainForm()->m_ui->m_actionExpandCollapseItem,
qApp->mainForm()->m_ui->m_actionExpandCollapseItemRecursively,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread,
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem });
m_contextMenuCategories->addActions({qApp->mainForm()->m_ui->m_actionUpdateSelectedItems,
qApp->mainForm()->m_ui->m_actionEditSelectedItem,
qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed,
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode,
qApp->mainForm()->m_ui->m_actionExpandCollapseItem,
qApp->mainForm()->m_ui->m_actionExpandCollapseItemRecursively,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead,
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread,
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem});
auto cat_add = clicked_item->getParentServiceRoot()->supportsCategoryAdding();
auto feed_add = clicked_item->getParentServiceRoot()->supportsFeedAdding();
@ -688,8 +685,7 @@ QMenu* FeedsView::initializeContextMenuCategories(RootItem* clicked_item) {
m_contextMenuCategories->addAction(qApp->mainForm()->m_ui->m_actionAddFeedIntoSelectedItem);
}
if (!qApp->settings()->value(GROUP(Feeds),
SETTING(Feeds::SortAlphabetically)).toBool()) {
if (!qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::SortAlphabetically)).toBool()) {
m_contextMenuCategories->addSeparator();
m_contextMenuCategories->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveUp);
m_contextMenuCategories->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveDown);
@ -715,14 +711,13 @@ QMenu* FeedsView::initializeContextMenuFeeds(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuFeeds->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
qApp->mainForm()->m_ui->m_actionEditSelectedItem <<
qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem);
m_contextMenuFeeds->addActions(QList<QAction*>() << qApp->mainForm()->m_ui->m_actionUpdateSelectedItems
<< qApp->mainForm()->m_ui->m_actionEditSelectedItem
<< qApp->mainForm()->m_ui->m_actionCopyUrlSelectedFeed
<< qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread
<< qApp->mainForm()->m_ui->m_actionDeleteSelectedItem);
auto cat_add = clicked_item->getParentServiceRoot()->supportsCategoryAdding();
auto feed_add = clicked_item->getParentServiceRoot()->supportsFeedAdding();
@ -739,8 +734,7 @@ QMenu* FeedsView::initializeContextMenuFeeds(RootItem* clicked_item) {
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionAddFeedIntoSelectedItem);
}
if (!qApp->settings()->value(GROUP(Feeds),
SETTING(Feeds::SortAlphabetically)).toBool()) {
if (!qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::SortAlphabetically)).toBool()) {
m_contextMenuFeeds->addSeparator();
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveUp);
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveDown);
@ -766,10 +760,9 @@ QMenu* FeedsView::initializeContextMenuImportant(RootItem* clicked_item) {
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuImportant->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread);
m_contextMenuImportant->addActions(QList<QAction*>() << qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead
<< qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread);
if (!specific_actions.isEmpty()) {
m_contextMenuImportant->addSeparator();
@ -849,15 +842,17 @@ void FeedsView::setupAppearance() {
setExpandsOnDoubleClick(true);
setEditTriggers(QAbstractItemView::EditTrigger::NoEditTriggers);
setIndentation(FEEDS_VIEW_INDENTATION);
setAcceptDrops(false);
setAcceptDrops(true);
viewport()->setAcceptDrops(true);
setDragEnabled(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
setAllColumnsShowFocus(false);
setRootIsDecorated(false);
setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
setItemDelegate(new StyledItemDelegateWithoutFocus(qApp->settings()->value(GROUP(GUI),
SETTING(GUI::HeightRowFeeds)).toInt(),
setItemDelegate(new StyledItemDelegateWithoutFocus(qApp->settings()
->value(GROUP(GUI), SETTING(GUI::HeightRowFeeds))
.toInt(),
-1,
this));
}
@ -904,8 +899,7 @@ void FeedsView::contextMenuEvent(QContextMenuEvent* event) {
// Display context menu for feeds.
initializeContextMenuFeeds(clicked_item)->exec(event->globalPos());
}
else if (clicked_item->kind() == RootItem::Kind::Important ||
clicked_item->kind() == RootItem::Kind::Unread) {
else if (clicked_item->kind() == RootItem::Kind::Important || clicked_item->kind() == RootItem::Kind::Unread) {
initializeContextMenuImportant(clicked_item)->exec(event->globalPos());
}
else if (clicked_item->kind() == RootItem::Kind::Bin) {