diff --git a/pri/install.pri b/pri/install.pri index b1a3f84e3..e390d0610 100644 --- a/pri/install.pri +++ b/pri/install.pri @@ -1,52 +1,69 @@ # Setup all public headers, this needs to be kept in # sync with truly used headers. INSTALL_HEADERS = \ +../librssguard/3rd-party/boolinq/boolinq.h \ +../librssguard/3rd-party/mimesis/mimesis.hpp \ +../librssguard/3rd-party/mimesis/quoted-printable.hpp \ ../librssguard/core/feeddownloader.h \ ../librssguard/core/feedsmodel.h \ ../librssguard/core/feedsproxymodel.h \ ../librssguard/core/message.h \ +../librssguard/core/messagefilter.h \ ../librssguard/core/messagesmodel.h \ +../librssguard/core/messagesmodelcache.cpp \ ../librssguard/core/messagesmodelcache.h \ ../librssguard/core/messagesmodelsqllayer.h \ ../librssguard/core/messagesproxymodel.h \ ../librssguard/definitions/definitions.h \ +../librssguard/dynamic-shortcuts \ +../librssguard/dynamic-shortcuts/dynamicshortcuts.cpp \ ../librssguard/dynamic-shortcuts/dynamicshortcuts.h \ +../librssguard/dynamic-shortcuts/dynamicshortcutswidget.cpp \ ../librssguard/dynamic-shortcuts/dynamicshortcutswidget.h \ -../librssguard/dynamic-shortcuts/shortcutbutton.h \ +../librssguard/dynamic-shortcuts/shortcutcatcher.cpp \ ../librssguard/dynamic-shortcuts/shortcutcatcher.h \ ../librssguard/exceptions/applicationexception.h \ +../librssguard/exceptions/filteringexception.h \ ../librssguard/exceptions/ioexception.h \ ../librssguard/gui/baselineedit.h \ ../librssguard/gui/basetoolbar.h \ -../librssguard/gui/colorlabel.h \ +../librssguard/gui/colortoolbutton.h \ +../librssguard/gui/comboboxwithstatus.cpp \ ../librssguard/gui/comboboxwithstatus.h \ ../librssguard/gui/dialogs/formabout.h \ ../librssguard/gui/dialogs/formaddaccount.h \ +../librssguard/gui/dialogs/formaddeditlabel.h \ ../librssguard/gui/dialogs/formbackupdatabasesettings.h \ ../librssguard/gui/dialogs/formdatabasecleanup.h \ ../librssguard/gui/dialogs/formmain.h \ +../librssguard/gui/dialogs/formmessagefiltersmanager.h \ ../librssguard/gui/dialogs/formrestoredatabasesettings.h \ ../librssguard/gui/dialogs/formsettings.h \ ../librssguard/gui/dialogs/formupdate.h \ -../librssguard/gui/dialogs/oauthlogin.h \ ../librssguard/gui/discoverfeedsbutton.h \ ../librssguard/gui/edittableview.h \ ../librssguard/gui/feedmessageviewer.h \ ../librssguard/gui/feedstoolbar.h \ ../librssguard/gui/feedsview.h \ ../librssguard/gui/guiutilities.h \ +../librssguard/gui/labelwithstatus.cpp \ ../librssguard/gui/labelwithstatus.h \ +../librssguard/gui/lineeditwithstatus.cpp \ ../librssguard/gui/lineeditwithstatus.h \ ../librssguard/gui/locationlineedit.h \ ../librssguard/gui/messagebox.h \ +../librssguard/gui/messagebrowser.h \ ../librssguard/gui/messagepreviewer.h \ +../librssguard/gui/messagessearchlineedit.cpp \ ../librssguard/gui/messagessearchlineedit.h \ ../librssguard/gui/messagestoolbar.h \ ../librssguard/gui/messagesview.h \ ../librssguard/gui/messagetextbrowser.h \ ../librssguard/gui/newspaperpreviewer.h \ ../librssguard/gui/plaintoolbutton.h \ +../librssguard/gui/searchtextwidget.cpp \ ../librssguard/gui/searchtextwidget.h \ +../librssguard/gui/searchtextwidget.ui \ ../librssguard/gui/settings/settingsbrowsermail.h \ ../librssguard/gui/settings/settingsdatabase.h \ ../librssguard/gui/settings/settingsdownloads.h \ @@ -55,9 +72,12 @@ INSTALL_HEADERS = \ ../librssguard/gui/settings/settingsgui.h \ ../librssguard/gui/settings/settingslocalization.h \ ../librssguard/gui/settings/settingspanel.h \ +../librssguard/gui/settings/settingsshortcuts.cpp \ ../librssguard/gui/settings/settingsshortcuts.h \ +../librssguard/gui/settings/settingsshortcuts.ui \ ../librssguard/gui/squeezelabel.h \ ../librssguard/gui/statusbar.h \ +../librssguard/gui/styleditemdelegatewithoutfocus.cpp \ ../librssguard/gui/styleditemdelegatewithoutfocus.h \ ../librssguard/gui/systemtrayicon.h \ ../librssguard/gui/tabbar.h \ @@ -69,13 +89,13 @@ INSTALL_HEADERS = \ ../librssguard/gui/treewidget.h \ ../librssguard/gui/webbrowser.h \ ../librssguard/gui/webviewer.h \ +../librssguard/gui/widgetwithstatus.cpp \ ../librssguard/gui/widgetwithstatus.h \ ../librssguard/miscellaneous/application.h \ ../librssguard/miscellaneous/autosaver.h \ ../librssguard/miscellaneous/databasecleaner.h \ ../librssguard/miscellaneous/databasefactory.h \ ../librssguard/miscellaneous/databasequeries.h \ -../librssguard/miscellaneous/debugging.h \ ../librssguard/miscellaneous/externaltool.h \ ../librssguard/miscellaneous/feedreader.h \ ../librssguard/miscellaneous/iconfactory.h \ @@ -88,13 +108,16 @@ INSTALL_HEADERS = \ ../librssguard/miscellaneous/simplecrypt/simplecrypt.h \ ../librssguard/miscellaneous/skinfactory.h \ ../librssguard/miscellaneous/systemfactory.h \ +../librssguard/miscellaneous/templates.h \ ../librssguard/miscellaneous/textfactory.h \ ../librssguard/network-web/adblock/adblockaddsubscriptiondialog.h \ ../librssguard/network-web/adblock/adblockdialog.h \ ../librssguard/network-web/adblock/adblockicon.h \ ../librssguard/network-web/adblock/adblockmanager.h \ +../librssguard/network-web/adblock/adblockmatcher.cpp \ ../librssguard/network-web/adblock/adblockmatcher.h \ ../librssguard/network-web/adblock/adblockrule.h \ +../librssguard/network-web/adblock/adblocksearchtree.cpp \ ../librssguard/network-web/adblock/adblocksearchtree.h \ ../librssguard/network-web/adblock/adblocksubscription.h \ ../librssguard/network-web/adblock/adblocktreewidget.h \ @@ -103,11 +126,15 @@ INSTALL_HEADERS = \ ../librssguard/network-web/downloader.h \ ../librssguard/network-web/downloadmanager.h \ ../librssguard/network-web/googlesuggest.h \ +../librssguard/network-web/httpresponse.cpp \ ../librssguard/network-web/httpresponse.h \ ../librssguard/network-web/networkfactory.h \ ../librssguard/network-web/networkurlinterceptor.h \ +../librssguard/network-web/oauth2service.cpp \ ../librssguard/network-web/oauth2service.h \ +../librssguard/network-web/oauthhttphandler.cpp \ ../librssguard/network-web/oauthhttphandler.h \ +../librssguard/network-web/rssguardschemehandler.cpp \ ../librssguard/network-web/rssguardschemehandler.h \ ../librssguard/network-web/silentnetworkaccessmanager.h \ ../librssguard/network-web/urlinterceptor.h \ @@ -117,11 +144,16 @@ INSTALL_HEADERS = \ ../librssguard/qtsingleapplication/qtlockedfile.h \ ../librssguard/qtsingleapplication/qtsingleapplication.h \ ../librssguard/qtsingleapplication/qtsinglecoreapplication.h \ +../librssguard/services/abstract/accountcheckmodel.cpp \ ../librssguard/services/abstract/accountcheckmodel.h \ +../librssguard/services/abstract/cacheforserviceroot.cpp \ ../librssguard/services/abstract/cacheforserviceroot.h \ ../librssguard/services/abstract/category.h \ ../librssguard/services/abstract/feed.h \ ../librssguard/services/abstract/gui/formfeeddetails.h \ +../librssguard/services/abstract/importantnode.h \ +../librssguard/services/abstract/label.h \ +../librssguard/services/abstract/labelsnode.h \ ../librssguard/services/abstract/recyclebin.h \ ../librssguard/services/abstract/rootitem.h \ ../librssguard/services/abstract/serviceentrypoint.h \ @@ -130,8 +162,11 @@ INSTALL_HEADERS = \ ../librssguard/services/gmail/gmailentrypoint.h \ ../librssguard/services/gmail/gmailfeed.h \ ../librssguard/services/gmail/gmailserviceroot.h \ +../librssguard/services/gmail/gui/emailrecipientcontrol.h \ ../librssguard/services/gmail/gui/formaddeditemail.h \ +../librssguard/services/gmail/gui/formdownloadattachment.cpp \ ../librssguard/services/gmail/gui/formdownloadattachment.h \ +../librssguard/services/gmail/gui/formdownloadattachment.ui \ ../librssguard/services/gmail/gui/formeditgmailaccount.h \ ../librssguard/services/gmail/network/gmailnetworkfactory.h \ ../librssguard/services/inoreader/definitions.h \ @@ -152,6 +187,7 @@ INSTALL_HEADERS = \ ../librssguard/services/standard/gui/formstandardcategorydetails.h \ ../librssguard/services/standard/gui/formstandardfeeddetails.h \ ../librssguard/services/standard/gui/formstandardimportexport.h \ +../librssguard/services/standard/jsonparser.h \ ../librssguard/services/standard/rdfparser.h \ ../librssguard/services/standard/rssparser.h \ ../librssguard/services/standard/standardcategory.h \ diff --git a/resources/icons.qrc b/resources/icons.qrc index f1dac90e4..4633dfd68 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -4,6 +4,7 @@ ./graphics/Faenza/actions/64/back.png ./graphics/Faenza/actions/64/call-start.png ./graphics/Faenza/actions/64/dialog-no.png + ./graphics/Faenza/actions/64/dialog-ok.png ./graphics/Faenza/actions/64/dialog-yes.png ./graphics/Faenza/actions/64/document-edit.png ./graphics/Faenza/actions/64/document-export.png @@ -74,6 +75,7 @@ ./graphics/Numix/22/actions/back.svg ./graphics/Numix/22/actions/call-start.svg ./graphics/Numix/22/actions/dialog-no.svg + ./graphics/Numix/22/actions/dialog-ok.svg ./graphics/Numix/22/actions/dialog-yes.svg ./graphics/Numix/22/actions/document-edit.svg ./graphics/Numix/22/actions/document-export.svg diff --git a/src/librssguard/gui/labelsmenu.cpp b/src/librssguard/gui/labelsmenu.cpp new file mode 100644 index 000000000..c0c3b084a --- /dev/null +++ b/src/librssguard/gui/labelsmenu.cpp @@ -0,0 +1,145 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/labelsmenu.h" + +#include "3rd-party/boolinq/boolinq.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" + +#include +#include +#include + +LabelsMenu::LabelsMenu(const QList& messages, const QList& labels, QWidget* parent) : QMenu(parent) { + setIcon(qApp->icons()->fromTheme(QSL("tag-folder"))); + setTitle(tr("Labels")); + + if (labels.isEmpty()) { + QAction* act_not_labels = new QAction("No labels found"); + + act_not_labels->setEnabled(false); + addAction(act_not_labels); + } + else { + QSqlDatabase db = qApp->database()->connection(metaObject()->className()); + + for (Label* label: boolinq::from(labels).orderBy([](const Label* label) { + return label->title().toLower(); + }).toStdList()) { + + auto count = boolinq::from(messages).count([&db, label](const Message& msg) { + return DatabaseQueries::isLabelAssignedToMessage(db, label, msg); + }); + + Qt::CheckState state = Qt::CheckState::Unchecked; + + if (count == messages.size()) { + state = Qt::CheckState::Checked; + } + else if (count > 0) { + state = Qt::CheckState::PartiallyChecked; + } + + addLabelAction(label, state); + } + } +} + +void LabelsMenu::keyPressEvent(QKeyEvent* event) { + LabelAction* act = qobject_cast(activeAction()); + + if (act != nullptr && event->key() == Qt::Key::Key_Space) { + act->toggle(); + } + else { + QMenu::keyPressEvent(event); + } +} + +void LabelsMenu::mousePressEvent(QMouseEvent* event) { + LabelAction* act = qobject_cast(activeAction()); + + if (act != nullptr) { + act->toggle(); + } + else { + QMenu::mousePressEvent(event); + } +} + +void LabelsMenu::mouseReleaseEvent(QMouseEvent* event) { + Q_UNUSED(event) +} + +void LabelsMenu::addLabelAction(const Label* label, Qt::CheckState state) { + auto* act = new LabelAction(label, this, this); + + act->setCheckState(state); + addAction(act); +} + +LabelAction::LabelAction(const Label* label, QWidget* parent_widget, QObject* parent) + : QAction(parent), m_label(label), m_parentWidget(parent_widget), m_checkState(Qt::CheckState::Unchecked) { + setText(label->title()); + setIconVisibleInMenu(true); + setIcon(label->icon()); + + connect(this, &LabelAction::checkStateChanged, this, &LabelAction::updateActionForState); + updateActionForState(); +} + +Qt::CheckState LabelAction::checkState() const { + return m_checkState; +} + +void LabelAction::setCheckState(Qt::CheckState state) { + if (state != m_checkState) { + m_checkState = state; + emit checkStateChanged(m_checkState); + } +} + +const Label* LabelAction::label() const { + return m_label; +} + +void LabelAction::toggle() { + if (m_checkState == Qt::CheckState::Unchecked) { + setCheckState(Qt::CheckState::Checked); + } + else { + setCheckState(Qt::CheckState::Unchecked); + } +} + +void LabelAction::updateActionForState() { + QColor highlight; + + switch (m_checkState) { + case Qt::CheckState::Checked: + highlight = Qt::GlobalColor::green; + break; + + case Qt::CheckState::PartiallyChecked: + highlight = QColor("#ff8c00"); + break; + + case Qt::CheckState::Unchecked: + default: + highlight = Qt::GlobalColor::transparent; + break; + } + + QPixmap copy_icon(m_label->icon().pixmap(48, 48)); + + if (m_checkState != Qt::CheckState::Unchecked) { + QPainter paint(©_icon); + + paint.setPen(QPen(Qt::GlobalColor::black, 4.0f)); + paint.setBrush(highlight); + paint.drawRect(0, 0, 22, 22); + } + + setIcon(copy_icon); +} diff --git a/src/librssguard/gui/labelsmenu.h b/src/librssguard/gui/labelsmenu.h new file mode 100644 index 000000000..d8fe6c13c --- /dev/null +++ b/src/librssguard/gui/labelsmenu.h @@ -0,0 +1,54 @@ +// For license of this file, see /LICENSE.md. + +#ifndef LABELSMENU_H +#define LABELSMENU_H + +#include +#include + +#include "services/abstract/label.h" + +class QCheckBox; + +class LabelsMenu : public QMenu { + Q_OBJECT + + public: + explicit LabelsMenu(const QList& messages, const QList& labels, QWidget* parent = nullptr); + + protected: + virtual void keyPressEvent(QKeyEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + + private: + void addLabelAction(const Label* label, Qt::CheckState state); +}; + +class LabelAction : public QAction { + Q_OBJECT + + public: + explicit LabelAction(const Label* label, QWidget* parent_widget, QObject* parent); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + + const Label* label() const; + + public slots: + void toggle(); + + signals: + void checkStateChanged(Qt::CheckState state); + + private slots: + void updateActionForState(); + + private: + const Label* m_label; + QWidget* m_parentWidget; + Qt::CheckState m_checkState; +}; + +#endif // LABELSMENU_H diff --git a/src/librssguard/gui/messagesview.cpp b/src/librssguard/gui/messagesview.cpp index f2d3828e9..ff20e37d4 100644 --- a/src/librssguard/gui/messagesview.cpp +++ b/src/librssguard/gui/messagesview.cpp @@ -6,6 +6,7 @@ #include "core/messagesmodel.h" #include "core/messagesproxymodel.h" #include "gui/dialogs/formmain.h" +#include "gui/labelsmenu.h" #include "gui/messagebox.h" #include "gui/styleditemdelegatewithoutfocus.h" #include "gui/treeviewcolumnsmenu.h" @@ -14,6 +15,7 @@ #include "miscellaneous/settings.h" #include "network-web/networkfactory.h" #include "network-web/webfactory.h" +#include "services/abstract/labelsnode.h" #include "services/abstract/serviceroot.h" #include @@ -192,31 +194,51 @@ void MessagesView::initializeContextMenu() { } m_contextMenu->clear(); + QList selected_messages; + if (m_sourceModel->loadedItem() != nullptr) { + QModelIndexList selected_indexes = selectionModel()->selectedRows(); + const QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes); + auto rows = boolinq::from(mapped_indexes).select([](const QModelIndex& idx) { + return idx.row(); + }).toStdList(); + + selected_messages = m_sourceModel->messagesAt(FROM_STD_LIST(QList, rows)); + } + + // External tools. QFileIconProvider icon_provider; - QMenu* menu = new QMenu(tr("Open with external tool"), m_contextMenu); + QMenu* menu_ext_tools = new QMenu(tr("Open with external tool"), m_contextMenu); - menu->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); + menu_ext_tools->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); for (const ExternalTool& tool : ExternalTool::toolsFromSettings()) { - QAction* act_tool = new QAction(QFileInfo(tool.executable()).fileName(), menu); + QAction* act_tool = new QAction(QFileInfo(tool.executable()).fileName(), menu_ext_tools); act_tool->setIcon(icon_provider.icon(tool.executable())); act_tool->setToolTip(tool.executable()); act_tool->setData(QVariant::fromValue(tool)); - menu->addAction(act_tool); + menu_ext_tools->addAction(act_tool); connect(act_tool, &QAction::triggered, this, &MessagesView::openSelectedMessagesWithExternalTool); } - if (menu->actions().isEmpty()) { + if (menu_ext_tools->actions().isEmpty()) { QAction* act_not_tools = new QAction("No external tools activated"); act_not_tools->setEnabled(false); - menu->addAction(act_not_tools); + menu_ext_tools->addAction(act_not_tools); } - m_contextMenu->addMenu(menu); + // Labels. + auto labels = m_sourceModel->loadedItem() != nullptr + ? m_sourceModel->loadedItem()->getParentServiceRoot()->labelsNode()->labels() + : QList(); + LabelsMenu* menu_labels = new LabelsMenu(selected_messages, labels, m_contextMenu); + + // Rest. + m_contextMenu->addMenu(menu_ext_tools); + m_contextMenu->addMenu(menu_labels); m_contextMenu->addActions( QList() << qApp->mainForm()->m_ui->m_actionSendMessageViaEmail @@ -232,13 +254,7 @@ void MessagesView::initializeContextMenu() { m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessages); } - QModelIndexList selected_indexes = selectionModel()->selectedRows(); - const QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes); - auto rows = boolinq::from(mapped_indexes).select([](const QModelIndex& idx) { - return idx.row(); - }).toStdList(); - auto messages = m_sourceModel->messagesAt(FROM_STD_LIST(QList, rows)); - auto extra_context_menu = m_sourceModel->loadedItem()->getParentServiceRoot()->contextMenuMessagesList(messages); + auto extra_context_menu = m_sourceModel->loadedItem()->getParentServiceRoot()->contextMenuMessagesList(selected_messages); if (!extra_context_menu.isEmpty()) { m_contextMenu->addSeparator(); diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index 811c7d458..ddabb39af 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -67,6 +67,7 @@ HEADERS += core/feeddownloader.h \ gui/feedstoolbar.h \ gui/feedsview.h \ gui/guiutilities.h \ + gui/labelsmenu.h \ gui/labelwithstatus.h \ gui/lineeditwithstatus.h \ gui/messagebox.h \ @@ -218,6 +219,7 @@ SOURCES += core/feeddownloader.cpp \ gui/feedstoolbar.cpp \ gui/feedsview.cpp \ gui/guiutilities.cpp \ + gui/labelsmenu.cpp \ gui/labelwithstatus.cpp \ gui/lineeditwithstatus.cpp \ gui/messagebox.cpp \