Merged plugin-system into master.

This commit is contained in:
Martin Rotter 2015-12-16 06:41:12 +01:00
commit 00a8822b00
132 changed files with 18495 additions and 11488 deletions

View File

@ -65,8 +65,8 @@ project(rssguard)
set(APP_NAME "RSS Guard")
set(APP_LOW_NAME "rssguard")
set(APP_VERSION "2.5.3")
set(FILE_VERSION "2,5,3,0")
set(APP_VERSION "3.0.0")
set(FILE_VERSION "3,0,0,0")
set(APP_AUTHOR "Martin Rotter")
set(APP_URL "http://bitbucket.org/skunkos/rssguard")
set(APP_URL_ISSUES "http://bitbucket.org/skunkos/rssguard/issues")
@ -343,17 +343,18 @@ set(APP_SOURCES
src/qtsingleapplication/qtsinglecoreapplication.cpp
src/qtsingleapplication/qtsingleapplication.cpp
# QT-JSON sources.
src/qt-json/json.cpp
# GUI sources.
src/gui/dialogs/formmain.cpp
src/gui/dialogs/formsettings.cpp
src/gui/dialogs/formabout.cpp
src/gui/dialogs/formcategorydetails.cpp
src/gui/dialogs/formfeeddetails.cpp
src/gui/dialogs/formupdate.cpp
src/gui/dialogs/formimportexport.cpp
src/gui/dialogs/formdatabasecleanup.cpp
src/gui/dialogs/formbackupdatabasesettings.cpp
src/gui/dialogs/formrestoredatabasesettings.cpp
src/gui/dialogs/formaddaccount.cpp
src/gui/notifications/notification.cpp
src/gui/systemtrayicon.cpp
src/gui/baselineedit.cpp
@ -412,14 +413,37 @@ set(APP_SOURCES
src/core/messagesproxymodel.cpp
src/core/feedsmodel.cpp
src/core/feedsproxymodel.cpp
src/core/category.cpp
src/core/rootitem.cpp
src/core/feed.cpp
src/core/parsingfactory.cpp
src/core/feeddownloader.cpp
src/core/feedsimportexportmodel.cpp
src/core/recyclebin.cpp
src/core/feedsselection.cpp
src/core/message.cpp
# ABSTRACT service sources.
src/services/abstract/rootitem.cpp
src/services/abstract/serviceentrypoint.cpp
src/services/abstract/feed.cpp
src/services/abstract/category.cpp
src/services/abstract/serviceroot.cpp
src/services/abstract/recyclebin.cpp
# STANDARD feed service sources.
src/services/standard/gui/formstandardcategorydetails.cpp
src/services/standard/gui/formstandardfeeddetails.cpp
src/services/standard/gui/formstandardimportexport.cpp
src/services/standard/standardfeedsimportexportmodel.cpp
src/services/standard/standardserviceentrypoint.cpp
src/services/standard/standardcategory.cpp
src/services/standard/standardfeed.cpp
src/services/standard/standardserviceroot.cpp
src/services/standard/standardrecyclebin.cpp
# TT-RSS feed service sources.
src/services/tt-rss/ttrssserviceentrypoint.cpp
src/services/tt-rss/ttrssserviceroot.cpp
src/services/tt-rss/ttrssfeed.cpp
src/services/tt-rss/ttrsscategory.cpp
src/services/tt-rss/ttrssrecyclebin.cpp
src/services/tt-rss/gui/formeditaccount.cpp
src/services/tt-rss/network/ttrssnetworkfactory.cpp
# NETWORK-WEB sources.
src/network-web/basenetworkaccessmanager.cpp
@ -462,13 +486,11 @@ set(APP_HEADERS
src/gui/dialogs/formmain.h
src/gui/dialogs/formsettings.h
src/gui/dialogs/formabout.h
src/gui/dialogs/formcategorydetails.h
src/gui/dialogs/formfeeddetails.h
src/gui/dialogs/formimportexport.h
src/gui/dialogs/formbackupdatabasesettings.h
src/gui/dialogs/formrestoredatabasesettings.h
src/gui/dialogs/formdatabasecleanup.h
src/gui/dialogs/formupdate.h
src/gui/dialogs/formaddaccount.h
src/gui/notifications/notification.h
src/gui/systemtrayicon.h
src/gui/baselineedit.h
@ -518,7 +540,30 @@ set(APP_HEADERS
src/core/feedsmodel.h
src/core/feedsproxymodel.h
src/core/feeddownloader.h
src/core/feedsimportexportmodel.h
# ABSTRACT service headers.
src/services/abstract/rootitem.h
src/services/abstract/feed.h
src/services/abstract/category.h
src/services/abstract/serviceroot.h
src/services/abstract/recyclebin.h
# STANDARD service headers.
src/services/standard/standardfeedsimportexportmodel.h
src/services/standard/gui/formstandardcategorydetails.h
src/services/standard/gui/formstandardfeeddetails.h
src/services/standard/gui/formstandardimportexport.h
src/services/standard/standardcategory.h
src/services/standard/standardfeed.h
src/services/standard/standardserviceroot.h
src/services/standard/standardrecyclebin.h
# TT-RSS service headers.
src/services/tt-rss/ttrssserviceroot.h
src/services/tt-rss/ttrssrecyclebin.h;
src/services/tt-rss/ttrssfeed.h
src/services/tt-rss/ttrsscategory.h
src/services/tt-rss/gui/formeditaccount.h
# NETWORK-WEB headers.
src/network-web/webpage.h
@ -548,13 +593,21 @@ set(APP_FORMS
src/gui/dialogs/formmain.ui
src/gui/dialogs/formsettings.ui
src/gui/dialogs/formabout.ui
src/gui/dialogs/formcategorydetails.ui
src/gui/dialogs/formfeeddetails.ui
src/gui/toolbareditor.ui
src/gui/dialogs/formimportexport.ui
src/gui/dialogs/formbackupdatabasesettings.ui
src/gui/dialogs/formrestoredatabasesettings.ui
src/gui/dialogs/formdatabasecleanup.ui
src/gui/dialogs/formaddaccount.ui
src/gui/toolbareditor.ui
# STANDARD service forms.
src/services/standard/gui/formstandardcategorydetails.ui
src/services/standard/gui/formstandardfeeddetails.ui
src/services/standard/gui/formstandardimportexport.ui
# TT-RSS service forms.
src/services/tt-rss/gui/formeditaccount.ui
# NETWORK forms.
src/network-web/downloadmanager.ui
src/network-web/downloaditem.ui

View File

@ -2,7 +2,7 @@ RSS Guard
=========
Welcome to RSS Guard website. You can find here basic information.
RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework.
RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization.
- - -
Contacts
@ -66,6 +66,8 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno
RSS Guard is written in C++. It is pretty fast even with tons of messages loaded. The core features are:
* **support for online feed synchronization via plugins**,
* Tiny Tiny RSS (from RSS Guard 3.0.0).
* multiplatformity,
* support for all feed formats,
* simplicity,
@ -113,10 +115,9 @@ RSS Guard is written in C++. It is pretty fast even with tons of messages loaded
* Qt library is the only dependency,
* open-source development model and friendly author waiting for your feedback,
* no ads, no hidden costs.
- - -
Philosophy
----------
RSS Guard tends to be independent software. It's free, it's open-source. RSS Guard will never depend on other services - this includes online news aggregators like Feedly, The Old Reader and others.
That's why RSS Guard will never integrate those services unless someone else codes support for them on his own. Remember, RSS Guard supports online synchronization via MySQL/MariaDB or you can use Dropbox to synchronize SQLite data storage.
RSS Guard tends to be independent software. It's free, it's open-source. RSS Guard accepts donations but only to SUPPORT its development.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -12,17 +12,40 @@ CREATE TABLE IF NOT EXISTS Information (
inf_value TEXT NOT NULL
);
-- !
INSERT INTO Information VALUES (1, 'schema_version', '3');
INSERT INTO Information VALUES (1, 'schema_version', '4');
-- !
CREATE TABLE IF NOT EXISTS Accounts (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL
);
-- !
INSERT INTO Accounts (type) VALUES ('std-rss');
-- !
CREATE TABLE IF NOT EXISTS TtRssAccounts (
id INTEGER,
username TEXT NOT NULL,
password TEXT,
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
auth_username TEXT,
auth_password TEXT,
url TEXT NOT NULL,
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
FOREIGN KEY (id) REFERENCES Accounts (id)
);
DROP TABLE IF EXISTS Categories;
-- !
CREATE TABLE IF NOT EXISTS Categories (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
parent_id INTEGER NOT NULL,
title VARCHAR(100) NOT NULL UNIQUE CHECK (title != ''),
title VARCHAR(100) NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT NOT NULL CHECK (date_created != 0),
icon BLOB
date_created BIGINT,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
DROP TABLE IF EXISTS Feeds;
@ -31,45 +54,40 @@ CREATE TABLE IF NOT EXISTS Feeds (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT NOT NULL CHECK (date_created != 0),
date_created BIGINT,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT NOT NULL CHECK (encoding != ''),
url VARCHAR(100) NOT NULL UNIQUE CHECK (url != ''),
encoding TEXT,
url VARCHAR(100),
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 5),
type INTEGER NOT NULL CHECK (type >= 0)
);
-- !
DROP TABLE IF EXISTS FeedsData;
-- !
CREATE TABLE IF NOT EXISTS FeedsData (
feed_id INTEGER NOT NULL,
feed_key VARCHAR(100) NOT NULL,
feed_value TEXT,
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,
PRIMARY KEY (feed_id, feed_key),
FOREIGN KEY (feed_id) REFERENCES Feeds (id)
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
DROP TABLE IF EXISTS Messages;
-- !
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
is_read INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_read >= 0 AND is_read <= 1),
is_deleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_deleted >= 0 AND is_deleted <= 1),
is_important INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_important >= 0 AND is_important <= 1),
feed INTEGER NOT NULL,
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0 ,
feed TEXT NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
url TEXT NOT NULL,
author TEXT NOT NULL,
url TEXT,
author TEXT,
date_created BIGINT NOT NULL CHECK (date_created != 0),
contents TEXT,
is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1),
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0 ,
enclosures TEXT,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (feed) REFERENCES Feeds (id)
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);

View File

@ -6,17 +6,41 @@ CREATE TABLE IF NOT EXISTS Information (
inf_value TEXT NOT NULL
);
-- !
INSERT INTO Information VALUES (1, 'schema_version', '3');
INSERT INTO Information VALUES (1, 'schema_version', '4');
-- !
CREATE TABLE IF NOT EXISTS Accounts (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL
);
-- !
INSERT INTO Accounts (type) VALUES ('std-rss');
-- !
CREATE TABLE IF NOT EXISTS TtRssAccounts (
id INTEGER,
username TEXT NOT NULL,
password TEXT,
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
auth_username TEXT,
auth_password TEXT,
url TEXT NOT NULL,
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
FOREIGN KEY (id) REFERENCES Accounts (id)
);
-- !
DROP TABLE IF EXISTS Categories;
-- !
CREATE TABLE IF NOT EXISTS Categories (
id INTEGER PRIMARY KEY,
parent_id INTEGER NOT NULL,
title TEXT NOT NULL UNIQUE CHECK (title != ''),
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
icon BLOB
date_created INTEGER,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
DROP TABLE IF EXISTS Feeds;
@ -25,45 +49,40 @@ CREATE TABLE IF NOT EXISTS Feeds (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
date_created INTEGER,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT NOT NULL CHECK (encoding != ''),
url TEXT NOT NULL UNIQUE CHECK (url != ''),
encoding TEXT,
url TEXT,
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
type INTEGER NOT NULL CHECK (type >= 0)
);
-- !
DROP TABLE IF EXISTS FeedsData;
-- !
CREATE TABLE IF NOT EXISTS FeedsData (
feed_id INTEGER NOT NULL,
feed_key TEXT NOT NULL,
feed_value TEXT,
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,
PRIMARY KEY (feed_id, feed_key),
FOREIGN KEY (feed_id) REFERENCES Feeds (id)
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
DROP TABLE IF EXISTS Messages;
-- !
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY,
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT (0),
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT (0),
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT (0),
feed INTEGER NOT NULL,
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0,
feed TEXT NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
url TEXT NOT NULL,
author TEXT NOT NULL,
url TEXT,
author TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
contents TEXT,
is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1),
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0,
enclosures TEXT,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (feed) REFERENCES Feeds (id)
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);

View File

@ -0,0 +1,68 @@
CREATE TABLE Accounts (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL
);
-- !
INSERT INTO Accounts (type) VALUES ('std-rss');
-- !
DROP TABLE IF EXISTS FeedsData;
-- !
CREATE TABLE TtRssAccounts (
id INTEGER,
username TEXT NOT NULL,
password TEXT,
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
auth_username TEXT,
auth_password TEXT,
url TEXT NOT NULL,
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
FOREIGN KEY (id) REFERENCES Accounts (id)
);
-- !
ALTER TABLE Messages
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
-- !
ALTER TABLE Messages
ADD COLUMN custom_id TEXT;
-- !
ALTER TABLE Messages
DROP FOREIGN KEY feed;
-- !
ALTER TABLE Messages
MODIFY feed TEXT NOT NULL;
-- !
ALTER TABLE Messages
MODIFY author TEXT;
-- !
ALTER TABLE Messages
MODIFY url TEXT;
-- !
ALTER TABLE Feeds
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
-- !
ALTER TABLE Feeds
ADD COLUMN custom_id TEXT;
-- !
ALTER TABLE Feeds
MODIFY date_created BIGINT;
-- !
ALTER TABLE Feeds
MODIFY encoding TEXT;
-- !
ALTER TABLE Feeds
MODIFY url VARCHAR(100);
-- !
ALTER TABLE Feeds
MODIFY type INTEGER;
-- !
ALTER TABLE Categories
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
-- !
ALTER TABLE Categories
ADD COLUMN custom_id TEXT;
-- !
ALTER TABLE Categories
MODIFY date_created BIGINT;
-- !
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';

View File

@ -0,0 +1,103 @@
CREATE TABLE Accounts (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL
);
-- !
INSERT INTO Accounts (type) VALUES ('std-rss');
-- !
DROP TABLE IF EXISTS FeedsData;
-- !
CREATE TABLE TtRssAccounts (
id INTEGER,
username TEXT NOT NULL,
password TEXT,
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
auth_username TEXT,
auth_password TEXT,
url TEXT NOT NULL,
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
FOREIGN KEY (id) REFERENCES Accounts (id)
);
-- !
CREATE TABLE backup_Messages AS SELECT * FROM Messages;
-- !
DROP TABLE Messages;
-- !
CREATE TABLE Messages (
id INTEGER PRIMARY KEY,
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0,
feed TEXT NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
url TEXT,
author TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
contents TEXT,
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0,
enclosures TEXT,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
INSERT INTO Messages (id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id)
SELECT id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, 1 FROM backup_Messages;
-- !
DROP TABLE backup_Messages;
-- !
CREATE TABLE backup_Feeds AS SELECT * FROM Feeds;
-- !
DROP TABLE Feeds;
-- !
CREATE TABLE Feeds (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT,
url TEXT,
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
INSERT INTO Feeds (id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, account_id)
SELECT id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, 1 FROM backup_Feeds;
-- !
DROP TABLE backup_Feeds;
-- !
CREATE TABLE backup_Categories AS SELECT * FROM Categories;
-- !
DROP TABLE Categories;
-- !
CREATE TABLE Categories (
id INTEGER PRIMARY KEY,
parent_id INTEGER NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
INSERT INTO Categories (id, parent_id, title, description, date_created, icon, account_id)
SELECT id, parent_id, title, description, date_created, icon, 1 FROM backup_Categories;
-- !
DROP TABLE backup_Categories;
-- !
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';

View File

@ -12,6 +12,30 @@
</style>
</head>
<body>
<center><h2>3.0.0</h2></center>
Added:
<ul>
<li style="color: red;">Brand new "service plugin system" - HIGHLY EXPERIMENTAL and REWRITTEN from scratch. Expect bugs and misunderstandings now! Major parts of RSS Guard were completely rewritten. Note that some functionality might be TEMPORARILY removed.</li>
<li>Added ability to completely disable notifications (bug #128).</li>
<li>Added ability to hide status bar.</li>
<li>Added ability to go to next <font style="font-weight: bold;">unread</font> message. (partially bug #112)</li>
</ul>
Fixed:
<ul>
<li>Tiny Tiny RSS plugin now supports HTTP authentication (Basic, NTLM, Digest). (bug #132)
<li>Fixed bug with updating feed. (bug #131)</li>
<li>Solved problem when user selects HUGE number of individual messages and marks them read/unread. Reselecting them after change may cause RSS Guard to hang.</li>
<li>Better info in popup notification when many feeds are updated.</li>
<li>Fixed obtaining of contents in RSS 2.0 feed entries. (bug #130)</li>
<li>Improved popup informing about changes in newly installed version.</li>
<li>Icons in notification popups are now smaller (22 x 22 pixels).</li>
<li>Encoding selection widget in feed add/edit dialog now detects encodings via case insensitive string matching.</li>
<li>When removing download item from download manager via DELETE key, then "Cleanup" button is correctly disabled.</li>
</ul>
<hr/>
<center><h2>2.5.2</h2></center>
Added:

14
src/core/feeddownloader.cpp Normal file → Executable file
View File

@ -17,11 +17,12 @@
#include "core/feeddownloader.h"
#include "core/feed.h"
#include "services/abstract/feed.h"
#include "definitions/definitions.h"
#include <QThread>
#include <QDebug>
#include <QMetaType>
FeedDownloader::FeedDownloader(QObject *parent) : QObject(parent) {
@ -32,7 +33,7 @@ FeedDownloader::~FeedDownloader() {
qDebug("Destroying FeedDownloader instance.");
}
void FeedDownloader::updateFeeds(const QList<Feed*> &feeds) {
void FeedDownloader::updateFeeds(const QList<Feed*> &feeds) {
qDebug().nospace() << "Performing feed updates in thread: \'" << QThread::currentThreadId() << "\'.";
// Job starts now.
@ -66,10 +67,15 @@ QString FeedDownloadResults::getOverview(int how_many_feeds) {
QStringList result;
// TODO: Maybe enhance the formatting of this output.
for (int i = 0, number_items_output = qMin(how_many_feeds, m_updatedFeeds.size()); i < number_items_output; i++) {
result.append(m_updatedFeeds.at(i).first + QSL(": ") + QString::number(m_updatedFeeds.at(i).second));
}
return result.join(QSL("\n"));
QString res_str = result.join(QSL("\n"));
if (m_updatedFeeds.size() > how_many_feeds) {
res_str += QObject::tr("\n\n+ %n other feeds.", 0, m_updatedFeeds.size() - how_many_feeds);
}
return res_str;
}

0
src/core/feeddownloader.h Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

165
src/core/feedsmodel.h Normal file → Executable file
View File

@ -20,46 +20,41 @@
#include <QAbstractItemModel>
#include "core/messagesmodel.h"
#include "core/rootitem.h"
#include <QIcon>
#include "core/message.h"
#include "services/abstract/rootitem.h"
#include "core/feeddownloader.h"
class DatabaseCleaner;
class Category;
class Feed;
class RecycleBin;
class FeedsImportExportModel;
class ServiceRoot;
class ServiceEntryPoint;
class StandardServiceRoot;
class QTimer;
typedef QList<QPair<int, Category*> > CategoryAssignment;
typedef QPair<int, Category*> CategoryAssignmentItem;
typedef QList<QPair<int, Feed*> > FeedAssignment;
typedef QPair<int, Feed*> FeedAssignmentItem;
class FeedsModel : public QAbstractItemModel {
Q_OBJECT
friend class Feed;
friend class Category;
public:
// Constructors and destructors.
explicit FeedsModel(QObject *parent = 0);
virtual ~FeedsModel();
DatabaseCleaner *databaseCleaner();
// Model implementation.
inline QVariant data(const QModelIndex &index, int role) const {
// Return data according to item.
return itemForIndex(index)->data(index.column(), role);
}
// 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;
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;
@ -75,20 +70,23 @@ class FeedsModel : public QAbstractItemModel {
return m_rootItem->countOfUnreadMessages();
}
void reloadCountsOfWholeModel();
// Removes item with given index.
bool removeItem(const QModelIndex &index);
// NOTE: Also deletes item from memory.
void removeItem(const QModelIndex &index);
// Standard category manipulators.
bool addCategory(Category *category, RootItem *parent);
bool editCategory(Category *original_category, Category *new_category_data);
// Returns all activated service roots.
// NOTE: Service root nodes are lying directly UNDER
// the model root item.
QList<ServiceRoot*> serviceRoots();
// Standard feed manipulators.
bool addFeed(Feed *feed, RootItem *parent);
// Determines if there is any account activated from given entry point.
bool containsServiceRootFromEntryPoint(ServiceEntryPoint *point);
// New feed is just temporary feed, it is not added to the model.
// It is used to fetch its data to the original feed
// and the original feed is moved if needed.
bool editFeed(Feed *original_feed, Feed *new_feed_data);
// Direct and the only global accessor to standard service root.
// NOTE: Standard service root is always activated.
StandardServiceRoot *standardServiceRoot();
// Returns the list of feeds which should be updated
// according to auto-update schedule.
@ -100,27 +98,15 @@ class FeedsModel : public QAbstractItemModel {
// Returns (undeleted) messages for given feeds.
// This is usually used for displaying whole feeds
// in "newspaper" mode.
QList<Message> messagesForFeeds(const QList<Feed*> &feeds);
QList<Message> messagesForItem(RootItem *item);
// Returns all categories, each pair
// consists of ID of parent item and pointer to category.
QHash<int, Category*> allCategories();
// Returns categories from the subtree with given root node, each pair
// consists of ID of parent item and pointer to category.
QHash<int, Category*> categoriesForItem(RootItem *root);
// Returns list of all categories contained in the model.
QList<Category*> allCategories();
// Returns list of all feeds contained in the model.
QList<Feed*> allFeeds();
// Get list of feeds from tree with particular item
// as root. If root itself is a feed, then it is returned.
QList<Feed*> feedsForItem(RootItem *root);
// Returns list of ALL CHILD feeds which belong to given parent indexes.
QList<Feed*> feedsForIndexes(const QModelIndexList &indexes);
// Returns ALL CHILD feeds contained within single index.
// Returns ALL RECURSIVE CHILD feeds contained within single index.
QList<Feed*> feedsForIndex(const QModelIndex &index);
// Returns pointer to feed if it lies on given index
@ -131,15 +117,14 @@ class FeedsModel : public QAbstractItemModel {
// or NULL if no category lies on given index.
Category *categoryForIndex(const QModelIndex &index) const;
// Returns pointer to recycle bin if lies on given index
// or NULL if no recycle bin lies on given index.
RecycleBin *recycleBinForIndex(const QModelIndex &index) const;
// Returns feed/category which lies at the specified index or
// root item if index is invalid.
RootItem *itemForIndex(const QModelIndex &index) const;
// Returns source QModelIndex on which lies given item.
// NOTE: This goes through all available indexes and
// checks their bound items manually, there is no
// other way to to this.
QModelIndex indexForItem(RootItem *item) const;
// Determines if any feed has any new messages.
@ -150,13 +135,6 @@ class FeedsModel : public QAbstractItemModel {
return m_rootItem;
}
// Takes structure residing under given root item and adds feeds/categories from
// it to active structure.
bool mergeModel(FeedsImportExportModel *model, QString &output_message);
// Access to recycle bin.
RecycleBin *recycleBin() const;
// Resets global auto-update intervals according to settings
// and starts/stop the timer as needed.
void updateAutoUpdateStatus();
@ -164,10 +142,31 @@ class FeedsModel : public QAbstractItemModel {
// Does necessary job before quitting this component.
void quit();
// Schedules given feeds for update.
void updateFeeds(const QList<Feed*> &feeds);
// Adds given service root account.
bool addServiceAccount(ServiceRoot *root);
// Loads feed/categories from the database.
void loadActivatedServiceAccounts();
public slots:
// Schedules all feeds from all accounts for update.
void updateAllFeeds();
// Checks if new parent node is different from one used by original node.
// If it is, then it reassigns original_node to new parent.
void reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent);
void removeItem(RootItem *deleting_item);
bool restoreAllBins();
bool emptyAllBins();
// Feeds operations.
bool markFeedsRead(const QList<Feed*> &feeds, int read);
bool markFeedsDeleted(const QList<Feed*> &feeds, int deleted, bool read_only);
bool markItemRead(RootItem *item, RootItem::ReadStatus read);
bool markItemCleared(RootItem *item, bool clean_read_only);
// Signals that properties (probably counts)
// of ALL items have changed.
@ -178,32 +177,54 @@ class FeedsModel : public QAbstractItemModel {
// NOTE: This reloads all parent valid indexes too.
void reloadChangedLayout(QModelIndexList list);
// Invalidates data under index for the item.
void reloadChangedItem(RootItem *item);
// Notifies other components about messages
// counts.
void notifyWithCounts();
private slots:
void onItemDataChanged(QList<RootItem*> items);
// Is executed when next auto-update round could be done.
void executeNextAutoUpdate();
protected:
// Returns converted ids of given feeds
// which are suitable as IN clause for SQL queries.
QStringList textualFeedIds(const QList<Feed*> &feeds);
// Loads feed/categories from the database.
void loadFromDatabase();
// Takes lists of feeds/categories and assembles
// them into the tree structure.
void assembleCategories(CategoryAssignment categories);
void assembleFeeds(FeedAssignment feeds);
// Reacts on feed updates.
void onFeedUpdatesStarted();
void onFeedUpdatesProgress(Feed *feed, int current, int total);
void onFeedUpdatesFinished(FeedDownloadResults results);
signals:
void requireItemValidationAfterDragDrop(const QModelIndex &source_index);
// Update of feeds is finished.
void feedsUpdateFinished();
// Counts of unread messages are changed in some feeds,
// notify view about this shit.
void readFeedsFilterInvalidationRequested();
// Emitted when model requests update of some feeds.
void feedsUpdateRequested(const QList<Feed*> feeds);
// Emitted if counts of messages are changed.
void messageCountsChanged(int unread_messages, int total_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);
// 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.
// NOTE: View will probably expand dropped index.
void requireItemValidationAfterDragDrop(const QModelIndex &source_index);
private:
// Returns converted ids of given feeds
// which are suitable as IN clause for SQL queries.
QStringList textualFeedIds(const QList<Feed*> &feeds);
RootItem *m_rootItem;
RecycleBin *m_recycleBin;
QList<QString> m_headerData;
QList<QString> m_tooltipData;
QIcon m_countsIcon;
@ -213,6 +234,12 @@ class FeedsModel : public QAbstractItemModel {
bool m_globalAutoUpdateEnabled;
int m_globalAutoUpdateInitialInterval;
int m_globalAutoUpdateRemainingInterval;
QThread *m_feedDownloaderThread;
FeedDownloader *m_feedDownloader;
QThread *m_dbCleanerThread;
DatabaseCleaner *m_dbCleaner;
};
#endif // FEEDSMODEL_H

View File

@ -20,9 +20,11 @@
#include "definitions/definitions.h"
#include "miscellaneous/application.h"
#include "core/feedsmodel.h"
#include "core/category.h"
#include "core/feed.h"
#include "core/rootitem.h"
#include "services/abstract/rootitem.h"
#include "services/standard/standardcategory.h"
#include "services/standard/standardfeed.h"
#include <QTimer>
FeedsProxyModel::FeedsProxyModel(QObject *parent)
@ -37,6 +39,8 @@ FeedsProxyModel::FeedsProxyModel(QObject *parent)
setFilterRole(Qt::EditRole);
setDynamicSortFilter(false);
setSourceModel(m_sourceModel);
connect(m_sourceModel, SIGNAL(readFeedsFilterInvalidationRequested()), this, SLOT(invalidateReadFeedsFilter()));
}
FeedsProxyModel::~FeedsProxyModel() {
@ -45,18 +49,18 @@ FeedsProxyModel::~FeedsProxyModel() {
QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const {
QModelIndexList result;
uint matchType = flags & 0x0F;
uint match_type = flags & 0x0F;
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
bool recurse = flags & Qt::MatchRecursive;
bool wrap = flags & Qt::MatchWrap;
bool allHits = (hits == -1);
bool all_hits = (hits == -1);
QString entered_text;
QModelIndex p = parent(start);
int from = start.row();
int to = rowCount(p);
for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) {
for (int r = from; (r < to) && (allHits || result.count() < hits); ++r) {
for (int r = from; (r < to) && (all_hits || result.count() < hits); ++r) {
QModelIndex idx = index(r, start.column(), p);
if (!idx.isValid()) {
@ -64,10 +68,10 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const
}
QModelIndex mapped_idx = mapToSource(idx);
QVariant item_value = m_sourceModel->data(m_sourceModel->index(mapped_idx.row(), FDS_MODEL_TITLE_INDEX, mapped_idx.parent()), role);
QVariant item_value = m_sourceModel->itemForIndex(mapped_idx)->title();
// QVariant based matching.
if (matchType == Qt::MatchExactly) {
if (match_type == Qt::MatchExactly) {
if (value == item_value) {
result.append(idx);
}
@ -80,7 +84,7 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const
QString item_text = item_value.toString();
switch (matchType) {
switch (match_type) {
case Qt::MatchRegExp:
if (QRegExp(entered_text, cs).exactMatch(item_text)) {
result.append(idx);
@ -121,7 +125,7 @@ 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), (allHits ? -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);
}
}
@ -155,15 +159,15 @@ bool FeedsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right
return QString::localeAwareCompare(left_item->title(), right_item->title()) < 0;
}
}
else if (left_item->kind() == RootItem::Bin) {
else if (left_item->kind() == RootItemKind::Bin) {
// Left item is recycle bin. Make sure it is "biggest" item if we have selected ascending order.
return sortOrder() == Qt::DescendingOrder;
}
else if (right_item->kind() == RootItem::Bin) {
else if (right_item->kind() == RootItemKind::Bin) {
// Right item is recycle bin. Make sure it is "smallest" item if we have selected descending order.
return sortOrder() == Qt::AscendingOrder;
}
else if (left_item->kind() == RootItem::Feeed) {
else if (left_item->kind() == RootItemKind::Feed) {
// Left item is feed, right item is category.
return false;
}
@ -193,7 +197,7 @@ bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source
RootItem *item = m_sourceModel->itemForIndex(idx);
if (item->kind() == RootItem::Bin) {
if (item->kind() == RootItemKind::Bin || item->kind() == RootItemKind::ServiceRoot) {
// Recycle bin is always displayed.
return true;
}
@ -202,7 +206,9 @@ bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source
return true;
}
else {
return item->countOfUnreadMessages() > 0;
// NOTE: If item has < 0 of unread message it may mean, that the count
// of unread messages is not (yet) known, display that item too.
return item->countOfUnreadMessages() != 0;
}
}
@ -218,6 +224,14 @@ bool FeedsProxyModel::showUnreadOnly() const {
return m_showUnreadOnly;
}
void FeedsProxyModel::invalidateReadFeedsFilter(bool set_new_value, bool show_unread_only) {
if (set_new_value) {
setShowUnreadOnly(show_unread_only);
}
QTimer::singleShot(0, this, SLOT(invalidateFilter()));
}
void FeedsProxyModel::setShowUnreadOnly(bool show_unread_only) {
m_showUnreadOnly = show_unread_only;
qApp->settings()->setValue(GROUP(Feeds), Feeds::ShowOnlyUnreadFeeds, show_unread_only);

View File

@ -18,12 +18,11 @@
#ifndef FEEDSPROXYMODEL_H
#define FEEDSPROXYMODEL_H
#include "rootitem.h"
#include <QSortFilterProxyModel>
class FeedsModel;
class RootItem;
class FeedsProxyModel : public QSortFilterProxyModel {
Q_OBJECT
@ -38,6 +37,8 @@ class FeedsProxyModel : public QSortFilterProxyModel {
return m_sourceModel;
}
// Returns index list of items which "match" given value.
// Used for finding items according to entered title text.
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const;
// Maps list of indexes.
@ -50,14 +51,16 @@ class FeedsProxyModel : public QSortFilterProxyModel {
void setSelectedItem(RootItem *selected_item);
public slots:
void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false);
private slots:
void invalidateFilter();
protected:
private:
// Compares two rows of data.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
private:
// Source model pointer.
FeedsModel *m_sourceModel;

View File

@ -1,75 +0,0 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "core/feedsselection.h"
#include "core/rootitem.h"
#include "core/category.h"
#include "core/feed.h"
#include "definitions/definitions.h"
FeedsSelection::FeedsSelection(RootItem *root_of_selection) : m_selectedItem(root_of_selection) {
}
FeedsSelection::FeedsSelection(const FeedsSelection &other) {
m_selectedItem = other.selectedItem();
}
FeedsSelection::~FeedsSelection() {
}
FeedsSelection::SelectionMode FeedsSelection::mode() {
if (m_selectedItem == NULL) {
return FeedsSelection::NoMode;
}
switch (m_selectedItem->kind()) {
case RootItem::Bin:
return FeedsSelection::MessagesFromRecycleBin;
case RootItem::Cattegory:
case RootItem::Feeed:
return FeedsSelection::MessagesFromFeeds;
default:
return FeedsSelection::NoMode;
}
}
RootItem *FeedsSelection::selectedItem() const {
return m_selectedItem;
}
QString FeedsSelection::generateListOfIds() {
if (m_selectedItem != NULL &&
(m_selectedItem->kind() == RootItem::Feeed || m_selectedItem->kind() == RootItem::Cattegory)) {
QList<RootItem*> children = m_selectedItem->getRecursiveChildren();
QStringList stringy_ids;
foreach (RootItem *child, children) {
if (child->kind() == RootItem::Feeed) {
stringy_ids.append(QString::number(child->id()));
}
}
return stringy_ids.join(QSL(", "));
}
else {
return QString();
}
}

104
src/core/message.cpp Executable file
View File

@ -0,0 +1,104 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "core/message.h"
#include "miscellaneous/textfactory.h"
#include <QVariant>
Enclosure::Enclosure(const QString &url, const QString &mime) : m_url(url), m_mimeType(mime) {
}
QList<Enclosure> Enclosures::decodeEnclosuresFromString(const QString &enclosures_data) {
QList<Enclosure> enclosures;
foreach (const QString &single_enclosure, enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR, QString::SkipEmptyParts)) {
Enclosure enclosure;
if (single_enclosure.contains(ECNLOSURES_INNER_SEPARATOR)) {
QStringList mime_url = single_enclosure.split(ECNLOSURES_INNER_SEPARATOR);
enclosure.m_mimeType = QByteArray::fromBase64(mime_url.at(0).toLocal8Bit());
enclosure.m_url = QByteArray::fromBase64(mime_url.at(1).toLocal8Bit());
}
else {
enclosure.m_url = QByteArray::fromBase64(single_enclosure.toLocal8Bit());
}
enclosures.append(enclosure);
}
return enclosures;
}
QString Enclosures::encodeEnclosuresToString(const QList<Enclosure> &enclosures) {
QStringList enclosures_str;
foreach (const Enclosure &enclosure, enclosures) {
if (enclosure.m_mimeType.isEmpty()) {
enclosures_str.append(enclosure.m_url.toLocal8Bit().toBase64());
}
else {
enclosures_str.append(QString(enclosure.m_mimeType.toLocal8Bit().toBase64()) +
ECNLOSURES_INNER_SEPARATOR +
enclosure.m_url.toLocal8Bit().toBase64());
}
}
return enclosures_str.join(QString(ENCLOSURES_OUTER_SEPARATOR));
}
Message::Message() {
m_title = m_url = m_author = m_contents = m_feedId = m_customId = "";
m_enclosures = QList<Enclosure>();
m_accountId = m_id = 0;
m_isRead = m_isImportant = false;
}
Message Message::fromSqlRecord(const QSqlRecord &record, bool *result) {
if (record.count() != MSG_DB_CUSTOM_ID_INDEX + 1) {
if (result != NULL) {
*result = false;
return Message();
}
}
Message message;
message.m_id = record.value(MSG_DB_ID_INDEX).toInt();
message.m_isRead = record.value(MSG_DB_READ_INDEX).toBool();
//message = record.value(MSG_DB_DELETED_INDEX).toInt();
message.m_isImportant = record.value(MSG_DB_IMPORTANT_INDEX).toBool();
message.m_feedId = record.value(MSG_DB_FEED_INDEX).toString();
message.m_title = record.value(MSG_DB_TITLE_INDEX).toString();
message.m_url = record.value(MSG_DB_URL_INDEX).toString();
message.m_author = record.value(MSG_DB_AUTHOR_INDEX).toString();
message.m_created = TextFactory::parseDateTime(record.value(MSG_DB_DCREATED_INDEX).value<qint64>());
message.m_contents = record.value(MSG_DB_CONTENTS_INDEX).toString();
//message = record.value(MSG_DB_PDELETED_INDEX).toInt();
message.m_enclosures = Enclosures::decodeEnclosuresFromString(record.value(MSG_DB_ENCLOSURES_INDEX).toString());
message.m_accountId = record.value(MSG_DB_ACCOUNT_ID_INDEX).toInt();
message.m_customId = record.value(MSG_DB_CUSTOM_ID_INDEX).toString();
if (result != NULL) {
*result = true;
}
return message;
}

72
src/core/message.h Executable file
View File

@ -0,0 +1,72 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef MESSAGE_H
#define MESSAGE_H
#include "definitions/definitions.h"
#include <QDateTime>
#include <QStringList>
#include <QSqlRecord>
// Represents single enclosure.
struct Enclosure {
QString m_url;
QString m_mimeType;
explicit Enclosure(const QString &url = QString(), const QString &mime = QString());
};
// Represents single enclosure.
class Enclosures {
public:
static QList<Enclosure> decodeEnclosuresFromString(const QString &enclosures_data);
static QString encodeEnclosuresToString(const QList<Enclosure> &enclosures);
};
// Represents single message.
class Message {
public:
explicit Message();
// Creates Message from given record, which contains
// row from query SELECT * FROM Messages WHERE ....;
static Message fromSqlRecord(const QSqlRecord &record, bool *result = NULL);
QString m_title;
QString m_url;
QString m_author;
QString m_contents;
QDateTime m_created;
QString m_feedId;
int m_accountId;
int m_id;
QString m_customId;
bool m_isRead;
bool m_isImportant;
QList<Enclosure> m_enclosures;
// Is true if "created" date was obtained directly
// from the feed, otherwise is false
bool m_createdFromFeed;
};
#endif // MESSAGE_H

View File

@ -22,6 +22,8 @@
#include "miscellaneous/textfactory.h"
#include "miscellaneous/databasefactory.h"
#include "miscellaneous/iconfactory.h"
#include "gui/dialogs/formmain.h"
#include "services/abstract/serviceroot.h"
#include <QSqlRecord>
#include <QSqlError>
@ -30,7 +32,7 @@
MessagesModel::MessagesModel(QObject *parent)
: QSqlTableModel(parent, qApp->database()->connection(QSL("MessagesModel"), DatabaseFactory::FromSettings)),
m_messageFilter(NoHighlighting), m_customDateFormat(QString()) {
m_messageHighlighter(NoHighlighting), m_customDateFormat(QString()) {
setObjectName(QSL("MessagesModel"));
setupFonts();
setupIcons();
@ -42,7 +44,7 @@ MessagesModel::MessagesModel(QObject *parent)
// via model, but via DIRECT SQL calls are used to do persistent messages.
setEditStrategy(QSqlTableModel::OnManualSubmit);
setTable(QSL("Messages"));
loadMessages(FeedsSelection());
loadMessages(NULL);
}
MessagesModel::~MessagesModel() {
@ -55,11 +57,9 @@ void MessagesModel::setupIcons() {
m_unreadIcon = qApp->icons()->fromTheme(QSL("mail-mark-unread"));
}
FeedsSelection MessagesModel::loadedSelection() const {
return m_currentSelection;
}
void MessagesModel::fetchAllData() {
select();
void MessagesModel::fetchAll() {
while (canFetchMore()) {
fetchMore();
}
@ -71,25 +71,34 @@ void MessagesModel::setupFonts() {
m_boldFont.setBold(true);
}
void MessagesModel::loadMessages(const FeedsSelection &selection) {
m_currentSelection = selection;
void MessagesModel::loadMessages(RootItem *item) {
m_selectedItem = item;
if (m_currentSelection.mode() == FeedsSelection::MessagesFromRecycleBin) {
setFilter(QSL("is_deleted = 1 AND is_pdeleted = 0"));
if (item == NULL) {
setFilter("true != true");
}
else {
QString assembled_ids = m_currentSelection.generateListOfIds();
setFilter(QString(QSL("feed IN (%1) AND is_deleted = 0")).arg(assembled_ids));
qDebug("Loading messages from feeds: %s.", qPrintable(assembled_ids));
if (!item->getParentServiceRoot()->loadMessagesForItem(item, this)) {
setFilter("true != true");
qWarning("Loading of messages from item '%s' failed.", qPrintable(item->title()));
qApp->showGuiMessage(tr("Loading of messages from item '%1' failed.").arg(item->title()),
tr("Loading of messages failed, maybe messages could not be downloaded."),
QSystemTrayIcon::Critical,
qApp->mainForm(),
true);
}
}
select();
fetchAll();
fetchAllData();
}
void MessagesModel::filterMessages(MessagesModel::MessageFilter filter) {
m_messageFilter = filter;
bool MessagesModel::submitAll() {
qFatal("Submitting changes via model is not allowed.");
return false;
}
void MessagesModel::highlightMessages(MessagesModel::MessageHighlighter highlight) {
m_messageHighlighter = highlight;
emit layoutAboutToBeChanged();
emit layoutChanged();
}
@ -98,6 +107,14 @@ int MessagesModel::messageId(int row_index) const {
return data(row_index, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
}
RootItem::Importance MessagesModel::messageImportance(int row_index) const {
return (RootItem::Importance) data(row_index, MSG_DB_IMPORTANT_INDEX, Qt::EditRole).toInt();
}
RootItem *MessagesModel::loadedItem() const {
return m_selectedItem;
}
void MessagesModel::updateDateFormat() {
if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool()) {
m_customDateFormat = qApp->settings()->value(GROUP(Messages), SETTING(Messages::CustomDateFormat)).toString();
@ -112,20 +129,8 @@ void MessagesModel::reloadWholeLayout() {
emit layoutChanged();
}
Message MessagesModel::messageAt(int row_index) const {
QSqlRecord rec = record(row_index);
Message message;
// Fill Message object with details.
message.m_author = rec.value(MSG_DB_AUTHOR_INDEX).toString();
message.m_contents = rec.value(MSG_DB_CONTENTS_INDEX).toString();
message.m_enclosures = Enclosures::decodeEnclosuresFromString(rec.value(MSG_DB_ENCLOSURES_INDEX).toString());
message.m_title = rec.value(MSG_DB_TITLE_INDEX).toString();
message.m_url = rec.value(MSG_DB_URL_INDEX).toString();
message.m_feedId = rec.value(MSG_DB_FEED_INDEX).toInt();
message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value<qint64>()).toLocalTime();
return message;
Message MessagesModel::messageAt(int row_index) const {
return Message::fromSqlRecord(record(row_index));
}
void MessagesModel::setupHeaderData() {
@ -140,7 +145,9 @@ void MessagesModel::setupHeaderData() {
/*: Tooltip for creation date of message.*/ tr("Created on") <<
/*: Tooltip for contents of message.*/ tr("Contents") <<
/*: Tooltip for "pdeleted" column in msg list.*/ tr("Permanently deleted") <<
/*: Tooltip for attachments of message.*/ tr("Attachments");
/*: Tooltip for attachments of message.*/ tr("Attachments") <<
/*: Tooltip for account ID of message.*/ tr("Account ID") <<
/*: Tooltip for custom ID of message.*/ tr("Custom ID");
m_tooltipData << tr("Id of the message.") << tr("Is message read?") <<
tr("Is message deleted?") << tr("Is message important?") <<
@ -148,13 +155,18 @@ void MessagesModel::setupHeaderData() {
tr("Title of the message.") << tr("Url of the message.") <<
tr("Author of the message.") << tr("Creation date of the message.") <<
tr("Contents of the message.") << tr("Is message permanently deleted from recycle bin?") <<
tr("List of attachments.");
tr("List of attachments.") << tr("Account ID of the message.") << tr("Custom ID of the message");
}
Qt::ItemFlags MessagesModel::flags(const QModelIndex &index) const {
Q_UNUSED(index)
#if QT_VERSION >= 0x050000
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
#else
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
#endif
}
QVariant MessagesModel::data(int row, int column, int role) const {
@ -196,7 +208,7 @@ QVariant MessagesModel::data(const QModelIndex &idx, int role) const {
return QSqlTableModel::data(index(idx.row(), MSG_DB_READ_INDEX)).toInt() == 1 ? m_normalFont : m_boldFont;
case Qt::ForegroundRole:
switch (m_messageFilter) {
switch (m_messageHighlighter) {
case HighlightImportant:
return QSqlTableModel::data(index(idx.row(), MSG_DB_IMPORTANT_INDEX)).toInt() == 1 ? QColor(Qt::blue) : QVariant();
@ -227,17 +239,17 @@ QVariant MessagesModel::data(const QModelIndex &idx, int role) const {
}
}
bool MessagesModel::setMessageRead(int row_index, int read) {
bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
if (data(row_index, MSG_DB_READ_INDEX, Qt::EditRole).toInt() == read) {
// Read status is the same is the one currently set.
// In that case, no extra work is needed.
return true;
}
QSqlDatabase db_handle = database();
Message message = messageAt(row_index);
if (!db_handle.transaction()) {
qWarning("Starting transaction for message read change.");
if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList<Message>() << message, read)) {
// Cannot change read status of the item. Abort.
return false;
}
@ -247,172 +259,163 @@ bool MessagesModel::setMessageRead(int row_index, int read) {
if (!working_change) {
// If rewriting in the model failed, then cancel all actions.
qDebug("Setting of new data to the model failed for message read change.");
db_handle.rollback();
return false;
}
int message_id;
QSqlQuery query_read_msg(db_handle);
QSqlQuery query_read_msg(database());
query_read_msg.setForwardOnly(true);
if (!query_read_msg.prepare(QSL("UPDATE Messages SET is_read = :read WHERE id = :id;"))) {
qWarning("Query preparation failed for message read change.");
db_handle.rollback();
return false;
}
// Rewrite the actual data in the database itself.
message_id = messageId(row_index);
query_read_msg.bindValue(QSL(":id"), message_id);
query_read_msg.bindValue(QSL(":read"), read);
query_read_msg.exec();
query_read_msg.bindValue(QSL(":id"), message.m_id);
query_read_msg.bindValue(QSL(":read"), (int) read);
// Commit changes.
if (db_handle.commit()) {
// If commit succeeded, then emit changes, so that view
// can reflect.
emit dataChanged(index(row_index, 0), index(row_index, columnCount() - 1));
emit messageCountsChanged(m_currentSelection.mode(), false, false);
return true;
if (query_read_msg.exec()) {
return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList<Message>() << message, read);
}
else {
return db_handle.rollback();;
return false;
}
}
bool MessagesModel::switchMessageImportance(int row_index) {
QSqlDatabase db_handle = database();
QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX);
RootItem::Importance current_importance = (RootItem::Importance) data(target_index, Qt::EditRole).toInt();
RootItem::Importance next_importance = current_importance == RootItem::Important ?
RootItem::NotImportant : RootItem::Important;
Message message = messageAt(row_index);
QPair<Message,RootItem::Importance> pair(message, next_importance);
if (!db_handle.transaction()) {
qWarning("Starting transaction for message importance switch failed.");
if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem,
QList<QPair<Message,RootItem::Importance> >() << pair)) {
return false;
}
QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX);
int current_importance = data(target_index, Qt::EditRole).toInt();
// Rewrite "visible" data in the model.
bool working_change = current_importance == 1 ?
setData(target_index, 0) :
setData(target_index, 1);
bool working_change = setData(target_index, next_importance);
if (!working_change) {
// If rewriting in the model failed, then cancel all actions.
qDebug("Setting of new data to the model failed for message importance change.");
db_handle.rollback();
return false;
}
int message_id;
QSqlQuery query_importance_msg(db_handle);
QSqlQuery query_importance_msg(database());
query_importance_msg.setForwardOnly(true);
if (!query_importance_msg.prepare(QSL("UPDATE Messages SET is_important = :important WHERE id = :id;"))) {
qWarning("Query preparation failed for message importance switch.");
db_handle.rollback();
return false;
}
message_id = messageId(row_index);
query_importance_msg.bindValue(QSL(":id"), message_id);
query_importance_msg.bindValue(QSL(":important"), current_importance == 1 ? 0 : 1);
query_importance_msg.exec();
query_importance_msg.bindValue(QSL(":id"), message.m_id);
query_importance_msg.bindValue(QSL(":important"), (int) next_importance);
// Commit changes.
if (db_handle.commit()) {
// If commit succeeded, then emit changes, so that view
// can reflect.
emit dataChanged(index(row_index, 0), index(row_index, columnCount() - 1));
return true;
if (query_importance_msg.exec()) {
return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem,
QList<QPair<Message,RootItem::Importance> >() << pair);
}
else {
return db_handle.rollback();
return false;
}
}
bool MessagesModel::switchBatchMessageImportance(const QModelIndexList &messages) {
QSqlDatabase db_handle = database();
QSqlQuery query_read_msg(db_handle);
QSqlQuery query_read_msg(database());
QStringList message_ids;
QList<QPair<Message,RootItem::Importance> > message_states;
query_read_msg.setForwardOnly(true);
// Obtain IDs of all desired messages.
foreach (const QModelIndex &message, messages) {
message_ids.append(QString::number(messageId(message.row())));
Message msg = messageAt(message.row());
RootItem::Importance message_importance = messageImportance((message.row()));
message_states.append(QPair<Message,RootItem::Importance>(msg, message_importance));
message_ids.append(QString::number(msg.m_id));
}
if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem, message_states)) {
return false;
}
if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_important = NOT is_important WHERE id IN (%1);"))
.arg(message_ids.join(QSL(", "))))) {
select();
fetchAll();
//emit messageCountsChanged(false);
return true;
fetchAllData();
return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem, message_states);
}
else {
return false;
}
}
bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages, int deleted) {
QSqlDatabase db_handle = database();
QSqlQuery query_read_msg(db_handle);
bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages) {
QStringList message_ids;
query_read_msg.setForwardOnly(true);
QList<Message> msgs;
// Obtain IDs of all desired messages.
foreach (const QModelIndex &message, messages) {
message_ids.append(QString::number(messageId(message.row())));
Message msg = messageAt(message.row());
msgs.append(msg);
message_ids.append(QString::number(msg.m_id));
}
if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesDelete(m_selectedItem, msgs)) {
return false;
}
QSqlQuery query_read_msg(database());
QString sql_delete_query;
if (m_currentSelection.mode() == FeedsSelection::MessagesFromFeeds) {
sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")),
QString::number(deleted));
query_read_msg.setForwardOnly(true);
if (m_selectedItem->kind() != RootItemKind::Bin) {
sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = 1 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")));
}
else {
sql_delete_query = QString(QSL("UPDATE Messages SET is_pdeleted = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")),
QString::number(deleted));
sql_delete_query = QString(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")));
}
if (query_read_msg.exec(sql_delete_query)) {
select();
fetchAll();
emit messageCountsChanged(m_currentSelection.mode(), true, false);
return true;
fetchAllData();
return m_selectedItem->getParentServiceRoot()->onAfterMessagesDelete(m_selectedItem, msgs);
}
else {
return false;
}
}
bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, int read) {
QSqlDatabase db_handle = database();
QSqlQuery query_read_msg(db_handle);
bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read) {
QStringList message_ids;
query_read_msg.setForwardOnly(true);
QList<Message> msgs;
// Obtain IDs of all desired messages.
foreach (const QModelIndex &message, messages) {
message_ids.append(QString::number(messageId(message.row())));
Message msg = messageAt(message.row());
msgs.append(msg);
message_ids.append(QString::number(msg.m_id));
}
if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_read = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")),
QString::number(read)))) {
select();
fetchAll();
if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, msgs, read)) {
return false;
}
emit messageCountsChanged(m_currentSelection.mode(), false, false);
return true;
QSqlQuery query_read_msg(database());
query_read_msg.setForwardOnly(true);
if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_read = %2 WHERE id IN (%1);"))
.arg(message_ids.join(QSL(", ")), read == RootItem::Read ? QSL("1") : QSL("0")))) {
fetchAllData();
return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, msgs, read);
}
else {
return false;
@ -420,30 +423,30 @@ bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, int re
}
bool MessagesModel::setBatchMessagesRestored(const QModelIndexList &messages) {
if (m_currentSelection.mode() == FeedsSelection::MessagesFromFeeds) {
qDebug("Cannot restore non-deleted messages.");
return false;
}
QSqlDatabase db_handle = database();
QSqlQuery query_read_msg(db_handle);
QStringList message_ids;
query_read_msg.setForwardOnly(true);
QList<Message> msgs;
// Obtain IDs of all desired messages.
foreach (const QModelIndex &message, messages) {
message_ids.append(QString::number(messageId(message.row())));
Message msg = messageAt(message.row());
msgs.append(msg);
message_ids.append(QString::number(msg.m_id));
}
if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesRestoredFromBin(m_selectedItem, msgs)) {
return false;
}
QSqlQuery query_read_msg(database());
QString sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = 0 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")));
if (query_read_msg.exec(sql_delete_query)) {
select();
fetchAll();
query_read_msg.setForwardOnly(true);
emit messageCountsChanged(m_currentSelection.mode(), true, true);
return true;
if (query_read_msg.exec(sql_delete_query)) {
fetchAllData();
return m_selectedItem->getParentServiceRoot()->onAfterMessagesRestoredFromBin(m_selectedItem, msgs);
}
else {
return false;

View File

@ -20,97 +20,21 @@
#include "definitions/definitions.h"
#include "core/feedsselection.h"
#include "core/message.h"
#include "services/abstract/rootitem.h"
#include <QSqlTableModel>
#include <QFont>
#include <QIcon>
#include <QDateTime>
// Represents single enclosuresh
struct Enclosure {
QString m_url;
QString m_mimeType;
explicit Enclosure(const QString &url = QString(), const QString &mime = QString()) : m_url(url), m_mimeType(mime) {
}
};
// Represents single enclosure.
class Enclosures {
public:
static QList<Enclosure> decodeEnclosuresFromString(const QString &enclosures_data) {
QList<Enclosure> enclosures;
foreach (const QString &single_enclosure, enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR, QString::SkipEmptyParts)) {
Enclosure enclosure;
if (single_enclosure.contains(ECNLOSURES_INNER_SEPARATOR)) {
QStringList mime_url = single_enclosure.split(ECNLOSURES_INNER_SEPARATOR);
enclosure.m_mimeType = QByteArray::fromBase64(mime_url.at(0).toLocal8Bit());
enclosure.m_url = QByteArray::fromBase64(mime_url.at(1).toLocal8Bit());
}
else {
enclosure.m_url = QByteArray::fromBase64(single_enclosure.toLocal8Bit());
}
enclosures.append(enclosure);
}
return enclosures;
}
static QString encodeEnclosuresToString(const QList<Enclosure> &enclosures) {
QStringList enclosures_str;
foreach (const Enclosure &enclosure, enclosures) {
if (enclosure.m_mimeType.isEmpty()) {
enclosures_str.append(enclosure.m_url.toLocal8Bit().toBase64());
}
else {
enclosures_str.append(QString(enclosure.m_mimeType.toLocal8Bit().toBase64()) +
ECNLOSURES_INNER_SEPARATOR +
enclosure.m_url.toLocal8Bit().toBase64());
}
}
return enclosures_str.join(QString(ENCLOSURES_OUTER_SEPARATOR));
}
};
// Represents single message.
class Message {
public:
explicit Message() {
m_title = m_url = m_author = m_contents = "";
m_feedId = 0;
m_enclosures = QList<Enclosure>();
}
QString m_title;
QString m_url;
QString m_author;
QString m_contents;
QDateTime m_created;
int m_feedId;
QList<Enclosure> m_enclosures;
// Is true if "created" date was obtained directly
// from the feed, otherwise is false
bool m_createdFromFeed;
};
class MessagesModel : public QSqlTableModel {
Q_OBJECT
public:
// Enum which describes basic filtering schemes
// for messages.
enum MessageFilter {
enum MessageHighlighter {
NoHighlighting = 100,
HighlightUnread = 101,
HighlightImportant = 102
@ -129,15 +53,9 @@ class MessagesModel : public QSqlTableModel {
// Returns message at given index.
Message messageAt(int row_index) const;
int messageId(int row_index) const;
RootItem::Importance messageImportance(int row_index) const;
FeedsSelection loadedSelection() const;
public slots:
// To disable persistent changes submissions.
inline bool submitAll() {
qFatal("Submitting changes via model is not allowed.");
return false;
}
RootItem *loadedItem() const;
void updateDateFormat();
void reloadWholeLayout();
@ -147,7 +65,7 @@ class MessagesModel : public QSqlTableModel {
// NOTE: Model is NOT reset after one of these methods are applied
// but changes ARE written to the database.
bool switchMessageImportance(int row_index);
bool setMessageRead(int row_index, int read);
bool setMessageRead(int row_index, RootItem::ReadStatus read);
// BATCH messages manipulators.
// NOTE: These methods are used for changing of attributes of
@ -155,37 +73,32 @@ class MessagesModel : public QSqlTableModel {
// NOTE: Model is reset after one of these methods is applied and
// changes ARE written to the database.
bool switchBatchMessageImportance(const QModelIndexList &messages);
bool setBatchMessagesDeleted(const QModelIndexList &messages, int deleted);
bool setBatchMessagesRead(const QModelIndexList &messages, int read);
bool setBatchMessagesDeleted(const QModelIndexList &messages);
bool setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read);
bool setBatchMessagesRestored(const QModelIndexList &messages);
// Fetches ALL available data to the model.
void fetchAll();
void fetchAllData();
// Filters messages
void highlightMessages(MessageHighlighter highlight);
// Loads messages of given feeds.
void loadMessages(const FeedsSelection &selection);
void loadMessages(RootItem *item);
void filterMessages(MessageFilter filter);
signals:
// Emitted if some persistent change is made which affects count of "unread/all" messages.
void messageCountsChanged(FeedsSelection::SelectionMode mode, bool total_msg_count_changed, bool any_msg_restored);
protected:
// Sets up header data.
void setupHeaderData();
// Creates "normal" and "bold" fonts.
void setupFonts();
// Sets up all icons which are used directly by this model.
void setupIcons();
private slots:
// To disable persistent changes submissions.
bool submitAll();
private:
MessageFilter m_messageFilter;
void setupHeaderData();
void setupFonts();
void setupIcons();
MessageHighlighter m_messageHighlighter;
QString m_customDateFormat;
FeedsSelection m_currentSelection;
RootItem *m_selectedItem;
QList<QString> m_headerData;
QList<QString> m_tooltipData;
@ -197,6 +110,6 @@ class MessagesModel : public QSqlTableModel {
QIcon m_unreadIcon;
};
Q_DECLARE_METATYPE(MessagesModel::MessageFilter)
Q_DECLARE_METATYPE(MessagesModel::MessageHighlighter)
#endif // MESSAGESMODEL_H

View File

@ -38,6 +38,37 @@ MessagesProxyModel::~MessagesProxyModel() {
qDebug("Destroying MessagesProxyModel instance.");
}
QModelIndex MessagesProxyModel::getNextPreviousUnreadItemIndex(int default_row) {
bool started_from_zero = default_row == 0;
QModelIndex next_index = getNextUnreadItemIndex(default_row, rowCount() - 1);
// There is no next message, check previous.
if (!next_index.isValid() && !started_from_zero) {
next_index = getNextUnreadItemIndex(0, default_row - 1);
}
return next_index;
}
QModelIndex MessagesProxyModel::getNextUnreadItemIndex(int default_row, int max_row) {
while (default_row <= max_row) {
// Get info if the message is read or not.
QModelIndex proxy_index = index(default_row, MSG_DB_READ_INDEX);
bool is_read = m_sourceModel->data(mapToSource(proxy_index).row(),
MSG_DB_READ_INDEX, Qt::EditRole).toInt() == 1;
if (!is_read) {
// We found unread message, mark it.
return proxy_index;
}
else {
default_row++;
}
}
return QModelIndex();
}
bool MessagesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
if (left.column() == MSG_DB_TITLE_INDEX && right.column() == MSG_DB_TITLE_INDEX) {
return QString::localeAwareCompare(m_sourceModel->data(left).toString(),

7
src/core/messagesproxymodel.h Normal file → Executable file
View File

@ -36,6 +36,8 @@ class MessagesProxyModel : public QSortFilterProxyModel {
return m_sourceModel;
}
QModelIndex getNextPreviousUnreadItemIndex(int default_row);
// Maps list of indexes.
QModelIndexList mapListToSource(const QModelIndexList &indexes);
QModelIndexList mapListFromSource(const QModelIndexList &indexes, bool deep = false);
@ -43,11 +45,12 @@ class MessagesProxyModel : public QSortFilterProxyModel {
// Fix for matching indexes with respect to specifics of the message model.
QModelIndexList match(const QModelIndex &start, int role, const QVariant &entered_value, int hits, Qt::MatchFlags flags) const;
protected:
private:
QModelIndex getNextUnreadItemIndex(int default_row, int max_row);
// Compares two rows of data.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
private:
// Source model pointer.
MessagesModel *m_sourceModel;
};

View File

@ -73,7 +73,7 @@ QList<Message> ParsingFactory::parseAsATOM10(const QString &data) {
for (int i = 0; i < elem_links.size(); i++) {
QDomElement link = elem_links.at(i).toElement();
if (link.attribute(QSL("rel")) == QL1S("enclosure")) {
if (link.attribute(QSL("rel")) == QSL("enclosure")) {
new_message.m_enclosures.append(Enclosure(link.attribute(QSL("href")), link.attribute(QSL("type"))));
qDebug("Adding enclosure '%s' for the message.", qPrintable(new_message.m_enclosures.last().m_url));
@ -99,7 +99,7 @@ QList<Message> ParsingFactory::parseAsATOM10(const QString &data) {
new_message.m_created = current_time;
}
// TODO: There is a difference between "" and QString() in terms of NULL SQL values!
// WARNING: There is a difference between "" and QString() in terms of NULL SQL values!
// This is because of difference in QString::isNull() and QString::isEmpty(), the "" is not null
// while QString() is.
if (new_message.m_author.isNull()) {
@ -203,12 +203,12 @@ QList<Message> ParsingFactory::parseAsRSS20(const QString &data) {
// Deal with titles & descriptions.
QString elem_title = message_item.namedItem(QSL("title")).toElement().text().simplified();
QString elem_description = message_item.namedItem(QSL("description")).toElement().text();
QString elem_description = message_item.namedItem(QSL("encoded")).toElement().text();
QString elem_enclosure = message_item.namedItem(QSL("enclosure")).toElement().attribute(QSL("url"));
QString elem_enclosure_type = message_item.namedItem(QSL("enclosure")).toElement().attribute(QSL("type"));
if (elem_description.isEmpty()) {
elem_description = message_item.namedItem(QSL("encoded")).toElement().text();
elem_description = message_item.namedItem(QSL("description")).toElement().text();
}
// Now we obtained maximum of information for title & description.

View File

@ -1,185 +0,0 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "core/recyclebin.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include <QSqlQuery>
RecycleBin::RecycleBin(RootItem *parent)
: RootItem(parent) {
m_kind = RootItem::Bin;
m_icon = qApp->icons()->fromTheme(QSL("folder-recycle-bin"));
m_id = ID_RECYCLE_BIN;
m_title = tr("Recycle bin");
m_description = tr("Recycle bin contains all deleted messages from all feeds.");
m_creationDate = QDateTime::currentDateTime();
updateCounts(true);
}
RecycleBin::~RecycleBin() {
qDebug("Destroying RecycleBin instance.");
}
int RecycleBin::childCount() const {
return 0;
}
void RecycleBin::appendChild(RootItem *child) {
Q_UNUSED(child)
}
int RecycleBin::countOfUnreadMessages() const {
return m_unreadCount;
}
int RecycleBin::countOfAllMessages() const {
return m_totalCount;
}
QVariant RecycleBin::data(int column, int role) const {
switch (role) {
case Qt::DisplayRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_title;
}
else if (column == FDS_MODEL_COUNTS_INDEX) {
return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString()
.replace(PLACEHOLDER_UNREAD_COUNTS, QString::number(countOfUnreadMessages()))
.replace(PLACEHOLDER_ALL_COUNTS, QString::number(countOfAllMessages()));
}
else {
return QVariant();
}
case Qt::EditRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_title;
}
else if (column == FDS_MODEL_COUNTS_INDEX) {
return countOfUnreadMessages();
}
else {
return QVariant();
}
case Qt::FontRole:
return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont;
case Qt::DecorationRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_icon;
}
else {
return QVariant();
}
case Qt::ToolTipRole:
return tr("Recycle bin\n%1").arg(tr("%n deleted message(s).", 0, countOfAllMessages()));
case Qt::TextAlignmentRole:
if (column == FDS_MODEL_COUNTS_INDEX) {
return Qt::AlignCenter;
}
else {
return QVariant();
}
default:
return QVariant();
}
}
bool RecycleBin::empty() {
QSqlDatabase db_handle = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for recycle bin emptying.");
return false;
}
QSqlQuery query_empty_bin(db_handle);
query_empty_bin.setForwardOnly(true);
if (!query_empty_bin.exec(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE is_deleted = 1;"))) {
qWarning("Query execution failed for recycle bin emptying.");
db_handle.rollback();
return false;
}
// Commit changes.
if (db_handle.commit()) {
return true;
}
else {
return db_handle.rollback();
}
}
bool RecycleBin::restore() {
QSqlDatabase db_handle = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for recycle bin restoring.");
return false;
}
QSqlQuery query_empty_bin(db_handle);
query_empty_bin.setForwardOnly(true);
if (!query_empty_bin.exec(QSL("UPDATE Messages SET is_deleted = 0 WHERE is_deleted = 1 AND is_pdeleted = 0;"))) {
qWarning("Query execution failed for recycle bin restoring.");
db_handle.rollback();
return false;
}
// Commit changes.
if (db_handle.commit()) {
return true;
}
else {
return db_handle.rollback();
}
}
void RecycleBin::updateCounts(bool update_total_count) {
QSqlDatabase database = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings);
QSqlQuery query_all(database);
query_all.setForwardOnly(true);
if (query_all.exec(QSL("SELECT count(*) FROM Messages WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0;")) && query_all.next()) {
m_unreadCount = query_all.value(0).toInt();
}
else {
m_unreadCount = 0;
}
if (update_total_count) {
if (query_all.exec(QSL("SELECT count(*) FROM Messages WHERE is_deleted = 1 AND is_pdeleted = 0;")) && query_all.next()) {
m_totalCount = query_all.value(0).toInt();
}
else {
m_totalCount = 0;
}
}
}

View File

@ -1,175 +0,0 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "core/rootitem.h"
#include "core/category.h"
#include "core/feed.h"
#include "core/recyclebin.h"
#include "miscellaneous/application.h"
#include <QVariant>
RootItem::RootItem(RootItem *parent_item)
: m_kind(RootItem::Root),
m_id(NO_PARENT_CATEGORY),
m_title(QString()),
m_description(QString()),
m_icon(QIcon()),
m_creationDate(QDateTime()),
m_childItems(QList<RootItem*>()),
m_parentItem(parent_item) {
setupFonts();
}
RootItem::~RootItem() {
qDeleteAll(m_childItems);
}
void RootItem::setupFonts() {
m_normalFont = Application::font("FeedsView");
m_boldFont = m_normalFont;
m_boldFont.setBold(true);
}
int RootItem::row() const {
if (m_parentItem) {
return m_parentItem->m_childItems.indexOf(const_cast<RootItem*>(this));
}
else {
// This item has no parent. Therefore, its row index is 0.
return 0;
}
}
QVariant RootItem::data(int column, int role) const {
Q_UNUSED(column)
Q_UNUSED(role)
// Do not return anything for the root item.
return QVariant();
}
int RootItem::countOfAllMessages() const {
int total_count = 0;
foreach (RootItem *child_item, m_childItems) {
if (child_item->kind() != RootItem::Bin) {
total_count += child_item->countOfAllMessages();
}
}
return total_count;
}
QList<RootItem*> RootItem::getRecursiveChildren() {
QList<RootItem*> children;
if (kind() == RootItem::Feeed) {
// Root itself is a FEED.
children.append(this);
}
else {
// Root itself is a CATEGORY or ROOT item.
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested categories.
while (!traversable_items.isEmpty()) {
RootItem *active_category = traversable_items.takeFirst();
foreach (RootItem *child, active_category->childItems()) {
if (child->kind() == RootItem::Feeed) {
// This child is feed.
children.append(child);
}
else if (child->kind() == RootItem::Cattegory) {
// This child is category, add its child feeds too.
traversable_items.append(child);
}
}
}
}
return children;
}
bool RootItem::removeChild(RootItem *child) {
return m_childItems.removeOne(child);
}
RecycleBin *RootItem::toRecycleBin() {
return static_cast<RecycleBin*>(this);
}
Category *RootItem::toCategory() {
return static_cast<Category*>(this);
}
Feed *RootItem::toFeed() {
return static_cast<Feed*>(this);
}
RootItem *RootItem::child(RootItem::Kind kind_of_child, const QString &identifier) {
foreach (RootItem *child, childItems()) {
if (child->kind() == kind_of_child) {
if ((kind_of_child == Cattegory && child->title() == identifier) ||
(kind_of_child == Feeed && child->toFeed()->url() == identifier)) {
return child;
}
}
}
return NULL;
}
int RootItem::countOfUnreadMessages() const {
int total_count = 0;
foreach (RootItem *child_item, m_childItems) {
if (child_item->kind() != RootItem::Bin) {
total_count += child_item->countOfUnreadMessages();
}
}
return total_count;
}
bool RootItem::removeChild(int index) {
if (index >= 0 && index < m_childItems.size()) {
m_childItems.removeAt(index);
return true;
}
else {
return false;
}
}
bool RootItem::isEqual(RootItem *lhs, RootItem *rhs) {
return (lhs->kind() == rhs->kind()) && (lhs->id() == rhs->id());
}
bool RootItem::lessThan(RootItem *lhs, RootItem *rhs) {
if (lhs->kind() == rhs->kind()) {
return lhs->id() < rhs->id();
}
else {
return false;
}
}

View File

@ -1,210 +0,0 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef ROOTITEM_H
#define ROOTITEM_H
#include <QIcon>
#include <QDateTime>
#include <QFont>
class RecycleBin;
class Category;
class Feed;
// Represents ROOT item of FeedsModel.
// NOTE: This class is derived to add functionality for
// all other non-root items of FeedsModel.
class RootItem {
public:
// Describes the kind of the item.
enum Kind {
Root = 1001,
Bin = 1002,
Feeed = 1003,
Cattegory = 1004
};
// Constructors and destructors.
explicit RootItem(RootItem *parent_item = NULL);
virtual ~RootItem();
// Basic operations.
inline virtual RootItem *parent() const {
return m_parentItem;
}
inline virtual void setParent(RootItem *parent_item) {
m_parentItem = parent_item;
}
inline virtual RootItem *child(int row) {
return m_childItems.value(row);
}
virtual RootItem *child(RootItem::Kind kind_of_child, const QString &identifier);
inline virtual int childCount() const {
return m_childItems.size();
}
inline virtual void appendChild(RootItem *child) {
m_childItems.append(child);
child->setParent(this);
}
virtual int row() const;
virtual QVariant data(int column, int role) const;
// Each item offers "counts" of messages.
// Returns counts of messages of all child items summed up.
virtual int countOfUnreadMessages() const;
virtual int countOfAllMessages() const;
// This method is used to permanently
// "remove" (or "unregister") this item.
// This typically removes item and its
// "children" (for example messages or child feeds)
// from the database.
// Returns true if "I" was removed.
virtual bool removeItself() {
return false;
}
// Access to children.
inline QList<RootItem*> childItems() const {
return m_childItems;
}
// Checks whether THIS object is child (direct or indirect)
// of the given root.
bool isChildOf(RootItem *root) {
if (root == NULL) {
return false;
}
RootItem *this_item = this;
while (this_item->kind() != RootItem::Root) {
if (root->childItems().contains(this_item)) {
return true;
}
else {
this_item = this_item->parent();
}
}
return false;
}
bool isParentOf(RootItem *child) {
if (child == NULL) {
return false;
}
else {
return child->isChildOf(this);
}
}
// Removes all children from this item.
// NOTE: Children are NOT freed from the memory.
inline void clearChildren() {
m_childItems.clear();
}
QList<RootItem*> getRecursiveChildren();
// Removes particular child at given index.
// NOTE: Child is NOT freed from the memory.
bool removeChild(int index);
bool removeChild(RootItem *child);
inline Kind kind() const {
return m_kind;
}
// Each item has icon.
inline QIcon icon() const {
return m_icon;
}
inline void setIcon(const QIcon &icon) {
m_icon = icon;
}
// Each item has some kind of id. Usually taken from primary key attribute from DB.
inline int id() const {
return m_id;
}
inline void setId(int id) {
m_id = id;
}
// Each item has its title.
inline QString title() const {
return m_title;
}
inline void setTitle(const QString &title) {
m_title = title;
}
inline QDateTime creationDate() const {
return m_creationDate;
}
inline void setCreationDate(const QDateTime &creation_date) {
m_creationDate = creation_date;
}
inline QString description() const {
return m_description;
}
inline void setDescription(const QString &description) {
m_description = description;
}
// Converters
RecycleBin *toRecycleBin();
Category *toCategory();
Feed *toFeed();
// Compares two model items.
static bool isEqual(RootItem *lhs, RootItem *rhs);
static bool lessThan(RootItem *lhs, RootItem *rhs);
protected:
void setupFonts();
Kind m_kind;
int m_id;
QString m_title;
QString m_description;
QIcon m_icon;
QDateTime m_creationDate;
QFont m_normalFont;
QFont m_boldFont;
QList<RootItem*> m_childItems;
RootItem *m_parentItem;
};
#endif // ROOTITEM_H

View File

@ -38,6 +38,9 @@
#define APP_USERAGENT QString("@APP_NAME@/@APP_VERSION@ (@APP_URL@) on @CMAKE_SYSTEM@")
#define APP_DONATE_URL "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XMWPLPK893VH4"
#define SERVICE_CODE_STD_RSS "std-rss"
#define SERVICE_CODE_TT_RSS "tt-rss"
#define ENCLOSURES_OUTER_SEPARATOR '#'
#define ECNLOSURES_INNER_SEPARATOR '&'
#define URI_SCHEME_FEED "feed://"
@ -50,6 +53,7 @@
#define URL_REGEXP "^(http|https|feed|ftp):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])?$"
#define USER_AGENT_HTTP_HEADER "User-Agent"
#define TEXT_TITLE_LIMIT 30
#define RESELECT_MESSAGE_THRESSHOLD 500
#define MAX_ZOOM_FACTOR 10.0
#define ICON_SIZE_SETTINGS 16
#define NO_PARENT_CATEGORY -1
@ -68,7 +72,7 @@
#define INTERNAL_URL_BLANK "about:blank"
#define DEFAULT_AUTO_UPDATE_INTERVAL 15
#define AUTO_UPDATE_INTERVAL 60000
#define STARTUP_UPDATE_DELAY 15000
#define STARTUP_UPDATE_DELAY 30000
#define TIMEZONE_OFFSET_LIMIT 6
#define CHANGE_EVENT_DELAY 250
#define FLAG_ICON_SUBFOLDER "flags"
@ -82,10 +86,11 @@
#define ACCEPT_HEADER_FOR_FEED_DOWNLOADER "application/atom+xml,application/xml;q=0.9,text/xml;q=0.8,*/*;q=0.7"
#define MIME_TYPE_ITEM_POINTER "@APP_LOW_NAME@/itempointer"
#define DOWNLOADER_ICON_SIZE 48
#define NOTIFICATION_ICON_SIZE 64
#define NOTIFICATION_ICON_SIZE 32
#define GOOGLE_SEARCH_URL "https://www.google.com/search?q=%1&ie=utf-8&oe=utf-8"
#define GOOGLE_SUGGEST_URL "http://suggestqueries.google.com/complete/search?output=toolbar&hl=en&q=%1"
#define ENCRYPTION_FILE_NAME "key.private"
#define RELOAD_MODEL_BORDER_NUM 10
#define FEED_INITIAL_OPML_PATTERN "feeds-%1.opml"
@ -116,7 +121,7 @@
#define APP_DB_SQLITE_FILE "database.db"
// Keep this in sync with schema versions declared in SQL initialization code.
#define APP_DB_SCHEMA_VERSION "3"
#define APP_DB_SCHEMA_VERSION "4"
#define APP_DB_UPDATE_FILE_PATTERN "db_update_%1_%2_%3.sql"
#define APP_DB_COMMENT_SPLIT "-- !\n"
#define APP_DB_WEB_PATH "data/database/web"
@ -179,6 +184,8 @@
#define MSG_DB_CONTENTS_INDEX 9
#define MSG_DB_PDELETED_INDEX 10
#define MSG_DB_ENCLOSURES_INDEX 11
#define MSG_DB_ACCOUNT_ID_INDEX 12
#define MSG_DB_CUSTOM_ID_INDEX 13
// Indexes of columns as they are DEFINED IN THE TABLE for CATEGORIES.
#define CAT_DB_ID_INDEX 0
@ -187,6 +194,8 @@
#define CAT_DB_DESCRIPTION_INDEX 3
#define CAT_DB_DCREATED_INDEX 4
#define CAT_DB_ICON_INDEX 5
#define CAT_DB_ACCOUNT_ID_INDEX 6
#define CAT_DB_CUSTOM_ID_INDEX 7
// Indexes of columns as they are DEFINED IN THE TABLE for FEEDS.
#define FDS_DB_ID_INDEX 0
@ -203,6 +212,8 @@
#define FDS_DB_UPDATE_TYPE_INDEX 11
#define FDS_DB_UPDATE_INTERVAL_INDEX 12
#define FDS_DB_TYPE_INDEX 13
#define FDS_DB_ACCOUNT_ID_INDEX 14
#define FDS_DB_CUSTOM_ID_INDEX 15
// Indexes of columns for feed models.
#define FDS_MODEL_TITLE_INDEX 0

View File

@ -71,7 +71,7 @@ void DynamicShortcutsWidget::populate(QList<QAction*> actions) {
int row_id = 0;
// TODO: Maybe separate actions into "categories". Each category will start with label.
// FIXME: Maybe separate actions into "categories". Each category will start with label.
// I will assign each QAaction a property called "category" with some enum value.
// Like:
// File, FeedsCategories, Messages, Tools, WebBrowser, Help

View File

@ -126,7 +126,7 @@ void FormAbout::loadLicenseAndInformation() {
m_ui->m_txtInfo->setText(tr("<body>%5 is a (very) tiny feed reader."
"<br><br>This software is distributed under the terms of GNU General Public License, version 3."
"<br><br>Contacts:"
"<ul><li><a href=\"mailto://%1\">%1</a> ~email</li>"
"<ul><li><a href=\"mailto://%1\">%1</a> ~e-mail</li>"
"<li><a href=\"%2\">%2</a> ~website</li></ul>"
"You can obtain source code for %5 from its website."
"<br><br><br>Copyright (C) 2011-%3 %4</body>").arg(APP_EMAIL,

8
src/gui/dialogs/formabout.ui Normal file → Executable file
View File

@ -104,7 +104,7 @@
<locale language="English" country="UnitedStates"/>
</property>
<property name="currentIndex">
<number>3</number>
<number>2</number>
</property>
<widget class="QWidget" name="m_tabInfo">
<attribute name="title">
@ -166,8 +166,8 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>685</width>
<height>184</height>
<width>98</width>
<height>69</height>
</rect>
</property>
<property name="autoFillBackground">
@ -242,7 +242,7 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>83</width>
<width>98</width>
<height>69</height>
</rect>
</property>

View File

@ -0,0 +1,91 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "gui/dialogs/formaddaccount.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "core/feedsmodel.h"
#include "services/standard/standardserviceentrypoint.h"
#if defined(Q_OS_OS2)
#include "gui/messagebox.h"
#endif
FormAddAccount::FormAddAccount(const QList<ServiceEntryPoint*> &entry_points, FeedsModel *model, QWidget *parent)
: QDialog(parent), m_ui(new Ui::FormAddAccount), m_model(model), m_entryPoints(entry_points) {
m_ui->setupUi(this);
// Set flags and attributes.
setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog | Qt::WindowSystemMenuHint);
setWindowIcon(qApp->icons()->fromTheme(QSL("item-new")));
#if defined(Q_OS_OS2)
MessageBox::iconify(m_ui->m_buttonBox);
#endif
connect(m_ui->m_listEntryPoints, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(addSelectedAccount()));
connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(addSelectedAccount()));
connect(m_ui->m_listEntryPoints, SIGNAL(itemSelectionChanged()), this, SLOT(displayActiveEntryPointDetails()));
loadEntryPoints();
}
FormAddAccount::~FormAddAccount() {
delete m_ui;
}
void FormAddAccount::addSelectedAccount() {
accept();
ServiceEntryPoint *point = selectedEntryPoint();
ServiceRoot *new_root = point->createNewRoot();
if (new_root != NULL) {
m_model->addServiceAccount(new_root);
}
else {
qCritical("Cannot create new account.");
}
}
void FormAddAccount::displayActiveEntryPointDetails() {
ServiceEntryPoint *point = selectedEntryPoint();
m_ui->m_txtAuthor->setText(point->author());
m_ui->m_txtDescription->setText(point->description());
m_ui->m_txtName->setText(point->name());
m_ui->m_txtVersion->setText(point->version());
}
ServiceEntryPoint *FormAddAccount::selectedEntryPoint() {
return m_entryPoints.at(m_ui->m_listEntryPoints->currentRow());
}
void FormAddAccount::loadEntryPoints() {
foreach (ServiceEntryPoint *entry_point, m_entryPoints) {
QListWidgetItem *item = new QListWidgetItem(entry_point->icon(), entry_point->name(), m_ui->m_listEntryPoints);
if (entry_point->isSingleInstanceService() && m_model->containsServiceRootFromEntryPoint(entry_point)) {
// Oops, this item cannot be added, it is single instance and is already added.
item->setFlags(Qt::NoItemFlags);
item->setToolTip(tr("This account can be added only once."));
}
}
m_ui->m_listEntryPoints->setCurrentRow(m_entryPoints.size() - 1);
}

View File

@ -0,0 +1,53 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef FORMADDACCOUNT_H
#define FORMADDACCOUNT_H
#include <QDialog>
#include "ui_formaddaccount.h"
namespace Ui {
class FormAddAccount;
}
class ServiceEntryPoint;
class FeedsModel;
class FormAddAccount : public QDialog {
Q_OBJECT
public:
explicit FormAddAccount(const QList<ServiceEntryPoint*> &entry_points, FeedsModel *model, QWidget *parent = 0);
virtual ~FormAddAccount();
private slots:
void addSelectedAccount();
void displayActiveEntryPointDetails();
private:
ServiceEntryPoint *selectedEntryPoint();
void loadEntryPoints();
Ui::FormAddAccount *m_ui;
FeedsModel *m_model;
QList<ServiceEntryPoint*> m_entryPoints;
};
#endif // FORMADDACCOUNT_H

144
src/gui/dialogs/formaddaccount.ui Executable file
View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FormAddAccount</class>
<widget class="QDialog" name="FormAddAccount">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>685</width>
<height>271</height>
</rect>
</property>
<property name="windowTitle">
<string>Add new account</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QListWidget" name="m_listEntryPoints">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="m_grpEntryPointDetails">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Details</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="m_txtName">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="m_txtVersion">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Author</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="m_txtAuthor">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QTextEdit" name="m_txtDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="m_buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>m_buttonBox</sender>
<signal>rejected()</signal>
<receiver>FormAddAccount</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -36,10 +36,13 @@
#include "gui/dialogs/formabout.h"
#include "gui/dialogs/formsettings.h"
#include "gui/dialogs/formupdate.h"
#include "gui/dialogs/formimportexport.h"
#include "gui/dialogs/formbackupdatabasesettings.h"
#include "gui/dialogs/formrestoredatabasesettings.h"
#include "gui/dialogs/formaddaccount.h"
#include "gui/notifications/notification.h"
#include "services/abstract/serviceroot.h"
#include "services/abstract/recyclebin.h"
#include "services/standard/gui/formstandardimportexport.h"
#include <QCloseEvent>
#include <QSessionManager>
@ -91,8 +94,6 @@ QList<QAction*> FormMain::allActions() {
// Add basic actions.
actions << m_ui->m_actionSettings;
actions << m_ui->m_actionDownloadManager;
actions << m_ui->m_actionImportFeeds;
actions << m_ui->m_actionExportFeeds;
actions << m_ui->m_actionRestoreDatabaseSettings;
actions << m_ui->m_actionBackupDatabaseSettings;
actions << m_ui->m_actionRestart;
@ -104,6 +105,7 @@ QList<QAction*> FormMain::allActions() {
actions << m_ui->m_actionSwitchMainMenu;
actions << m_ui->m_actionSwitchToolBars;
actions << m_ui->m_actionSwitchListHeaders;
actions << m_ui->m_actionSwitchStatusBar;
actions << m_ui->m_actionSwitchMessageListOrientation;
// Add web browser actions
@ -115,32 +117,28 @@ QList<QAction*> FormMain::allActions() {
actions << m_ui->m_actionOpenSelectedSourceArticlesExternally;
actions << m_ui->m_actionOpenSelectedSourceArticlesInternally;
actions << m_ui->m_actionOpenSelectedMessagesInternally;
actions << m_ui->m_actionMarkAllFeedsRead;
actions << m_ui->m_actionMarkSelectedFeedsAsRead;
actions << m_ui->m_actionMarkSelectedFeedsAsUnread;
actions << m_ui->m_actionClearSelectedFeeds;
actions << m_ui->m_actionMarkAllItemsRead;
actions << m_ui->m_actionMarkSelectedItemsAsRead;
actions << m_ui->m_actionMarkSelectedItemsAsUnread;
actions << m_ui->m_actionClearSelectedItems;
actions << m_ui->m_actionClearAllItems;
actions << m_ui->m_actionShowOnlyUnreadItems;
actions << m_ui->m_actionMarkSelectedMessagesAsRead;
actions << m_ui->m_actionMarkSelectedMessagesAsUnread;
actions << m_ui->m_actionSwitchImportanceOfSelectedMessages;
actions << m_ui->m_actionDeleteSelectedMessages;
actions << m_ui->m_actionUpdateAllFeeds;
actions << m_ui->m_actionUpdateSelectedFeeds;
actions << m_ui->m_actionEditSelectedFeedCategory;
actions << m_ui->m_actionDeleteSelectedFeedCategory;
actions << m_ui->m_actionUpdateAllItems;
actions << m_ui->m_actionUpdateSelectedItems;
actions << m_ui->m_actionEditSelectedItem;
actions << m_ui->m_actionDeleteSelectedItem;
actions << m_ui->m_actionServiceAdd;
actions << m_ui->m_actionViewSelectedItemsNewspaperMode;
actions << m_ui->m_actionAddCategory;
actions << m_ui->m_actionAddFeed;
actions << m_ui->m_actionSelectNextFeedCategory;
actions << m_ui->m_actionSelectPreviousFeedCategory;
actions << m_ui->m_actionSelectNextItem;
actions << m_ui->m_actionSelectPreviousItem;
actions << m_ui->m_actionSelectNextMessage;
actions << m_ui->m_actionSelectPreviousMessage;
actions << m_ui->m_actionFetchFeedMetadata;
actions << m_ui->m_actionExpandCollapseFeedCategory;
// Add recycle bin actions.
actions << m_ui->m_actionRestoreRecycleBin;
actions << m_ui->m_actionEmptyRecycleBin;
actions << m_ui->m_actionRestoreSelectedMessagesFromRecycleBin;
actions << m_ui->m_actionSelectNextUnreadMessage;
actions << m_ui->m_actionExpandCollapseItem;
return actions;
}
@ -157,8 +155,8 @@ void FormMain::prepareMenus() {
// Add needed items to the menu.
m_trayMenu->addAction(m_ui->m_actionSwitchMainWindow);
m_trayMenu->addSeparator();
m_trayMenu->addAction(m_ui->m_actionUpdateAllFeeds);
m_trayMenu->addAction(m_ui->m_actionMarkAllFeedsRead);
m_trayMenu->addAction(m_ui->m_actionUpdateAllItems);
m_trayMenu->addAction(m_ui->m_actionMarkAllItemsRead);
m_trayMenu->addSeparator();
m_trayMenu->addAction(m_ui->m_actionSettings);
m_trayMenu->addAction(m_ui->m_actionQuit);
@ -175,8 +173,106 @@ void FormMain::switchFullscreenMode() {
}
}
void FormMain::switchMainMenu() {
m_ui->m_menuBar->setVisible(m_ui->m_actionSwitchMainMenu->isChecked());
void FormMain::updateAddItemMenu() {
// NOTE: Clear here deletes items from memory but only those OWNED by the menu.
m_ui->m_menuAddItem->clear();
foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAddItem);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
QList<QAction*> root_actions = activated_root->addItemMenu();
if (root_actions.isEmpty()) {
QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")),
tr("No possible actions"),
m_ui->m_menuAddItem);
no_action->setEnabled(false);
root_menu->addAction(no_action);
}
else {
root_menu->addActions(root_actions);
}
m_ui->m_menuAddItem->addMenu(root_menu);
}
}
void FormMain::updateRecycleBinMenu() {
m_ui->m_menuRecycleBin->clear();
foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuRecycleBin);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
RecycleBin *bin = activated_root->recycleBin();
if (bin == NULL) {
QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")),
tr("No recycle bin"),
m_ui->m_menuRecycleBin);
no_action->setEnabled(false);
root_menu->addAction(no_action);
}
else {
QAction *restore_action = new QAction(qApp->icons()->fromTheme(QSL("recycle-bin-restore-all")),
tr("Restore recycle bin"),
m_ui->m_menuRecycleBin);
QAction *empty_action = new QAction(qApp->icons()->fromTheme(QSL("recycle-bin-empty")),
tr("Empty recycle bin"),
m_ui->m_menuRecycleBin);
connect(restore_action, SIGNAL(triggered()), bin, SLOT(restore()));
connect(empty_action, SIGNAL(triggered()), bin, SLOT(empty()));
root_menu->addAction(restore_action);
root_menu->addAction(empty_action);
}
m_ui->m_menuRecycleBin->addMenu(root_menu);
}
if (!m_ui->m_menuRecycleBin->isEmpty()) {
m_ui->m_menuRecycleBin->addSeparator();
}
m_ui->m_menuRecycleBin->addAction(m_ui->m_actionRestoreAllRecycleBins);
m_ui->m_menuRecycleBin->addAction(m_ui->m_actionEmptyAllRecycleBins);
}
void FormMain::updateAccountsMenu() {
m_ui->m_menuAccounts->clear();
foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAccounts);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
QList<QAction*> root_actions = activated_root->serviceMenu();
if (root_actions.isEmpty()) {
QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")),
tr("No possible actions"),
m_ui->m_menuAccounts);
no_action->setEnabled(false);
root_menu->addAction(no_action);
}
else {
root_menu->addActions(root_actions);
}
m_ui->m_menuAccounts->addMenu(root_menu);
}
if (m_ui->m_menuAccounts->actions().size() > 0) {
m_ui->m_menuAccounts->addSeparator();
}
m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceAdd);
m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceEdit);
m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceDelete);
}
void FormMain::switchVisibility(bool force_hide) {
@ -220,8 +316,6 @@ void FormMain::setupIcons() {
m_ui->m_actionCleanupDatabase->setIcon(icon_theme_factory->fromTheme(QSL("cleanup-database")));
m_ui->m_actionReportBugGitHub->setIcon(icon_theme_factory->fromTheme(QSL("application-report-bug")));
m_ui->m_actionReportBugBitBucket->setIcon(icon_theme_factory->fromTheme(QSL("application-report-bug")));
m_ui->m_actionExportFeeds->setIcon(icon_theme_factory->fromTheme(QSL("document-export")));
m_ui->m_actionImportFeeds->setIcon(icon_theme_factory->fromTheme(QSL("document-import")));
m_ui->m_actionBackupDatabaseSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-export")));
m_ui->m_actionRestoreDatabaseSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-import")));
m_ui->m_actionDonate->setIcon(icon_theme_factory->fromTheme(QSL("application-donate")));
@ -234,14 +328,10 @@ void FormMain::setupIcons() {
m_ui->m_actionSwitchMainMenu->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-menu")));
m_ui->m_actionSwitchToolBars->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list")));
m_ui->m_actionSwitchListHeaders->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list")));
m_ui->m_actionSwitchStatusBar->setIcon(icon_theme_factory->fromTheme(QSL("dialog-information")));
m_ui->m_actionSwitchMessageListOrientation->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-layout-direction")));
m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-switch")));
// Recycle bin.
m_ui->m_actionEmptyRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-empty")));
m_ui->m_actionRestoreRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-all")));
m_ui->m_actionRestoreSelectedMessagesFromRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-one")));
// Web browser.
m_ui->m_actionAddBrowser->setIcon(icon_theme_factory->fromTheme(QSL("list-add")));
m_ui->m_actionCloseCurrentTab->setIcon(icon_theme_factory->fromTheme(QSL("list-remove")));
@ -254,18 +344,16 @@ void FormMain::setupIcons() {
// Feeds/messages.
m_ui->m_menuAddItem->setIcon(icon_theme_factory->fromTheme(QSL("item-new")));
m_ui->m_actionUpdateAllFeeds->setIcon(icon_theme_factory->fromTheme(QSL("item-update-all")));
m_ui->m_actionUpdateSelectedFeeds->setIcon(icon_theme_factory->fromTheme(QSL("item-update-selected")));
m_ui->m_actionClearSelectedFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove")));
m_ui->m_actionClearAllFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove")));
m_ui->m_actionDeleteSelectedFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("item-remove")));
m_ui->m_actionUpdateAllItems->setIcon(icon_theme_factory->fromTheme(QSL("item-update-all")));
m_ui->m_actionUpdateSelectedItems->setIcon(icon_theme_factory->fromTheme(QSL("item-update-selected")));
m_ui->m_actionClearSelectedItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove")));
m_ui->m_actionClearAllItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove")));
m_ui->m_actionDeleteSelectedItem->setIcon(icon_theme_factory->fromTheme(QSL("item-remove")));
m_ui->m_actionDeleteSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove")));
m_ui->m_actionAddCategory->setIcon(icon_theme_factory->fromTheme(QSL("folder-category")));
m_ui->m_actionAddFeed->setIcon(icon_theme_factory->fromTheme(QSL("folder-feed")));
m_ui->m_actionEditSelectedFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("item-edit")));
m_ui->m_actionMarkAllFeedsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read")));
m_ui->m_actionMarkSelectedFeedsAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read")));
m_ui->m_actionMarkSelectedFeedsAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionEditSelectedItem->setIcon(icon_theme_factory->fromTheme(QSL("item-edit")));
m_ui->m_actionMarkAllItemsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read")));
m_ui->m_actionMarkSelectedItemsAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read")));
m_ui->m_actionMarkSelectedItemsAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionMarkSelectedMessagesAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read")));
m_ui->m_actionMarkSelectedMessagesAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionSwitchImportanceOfSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-favorite")));
@ -274,13 +362,19 @@ void FormMain::setupIcons() {
m_ui->m_actionOpenSelectedMessagesInternally->setIcon(icon_theme_factory->fromTheme(QSL("item-open-internal")));
m_ui->m_actionSendMessageViaEmail->setIcon(icon_theme_factory->fromTheme(QSL("item-send-email")));
m_ui->m_actionViewSelectedItemsNewspaperMode->setIcon(icon_theme_factory->fromTheme(QSL("item-newspaper")));
m_ui->m_actionSelectNextFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("go-down")));
m_ui->m_actionSelectPreviousFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("go-up")));
m_ui->m_actionSelectNextItem->setIcon(icon_theme_factory->fromTheme(QSL("go-down")));
m_ui->m_actionSelectPreviousItem->setIcon(icon_theme_factory->fromTheme(QSL("go-up")));
m_ui->m_actionSelectNextMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-down")));
m_ui->m_actionSelectPreviousMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-up")));
m_ui->m_actionShowOnlyUnreadFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionFetchFeedMetadata->setIcon(icon_theme_factory->fromTheme(QSL("download-manager")));
m_ui->m_actionExpandCollapseFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("expand-collapse")));
m_ui->m_actionSelectNextUnreadMessage->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionShowOnlyUnreadItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionExpandCollapseItem->setIcon(icon_theme_factory->fromTheme(QSL("expand-collapse")));
m_ui->m_actionRestoreSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-one")));
m_ui->m_actionRestoreAllRecycleBins->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-all")));
m_ui->m_actionEmptyAllRecycleBins->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-empty")));
m_ui->m_actionServiceAdd->setIcon(icon_theme_factory->fromTheme(QSL("item-new")));
m_ui->m_actionServiceEdit->setIcon(icon_theme_factory->fromTheme(QSL("item-edit")));
m_ui->m_actionServiceDelete->setIcon(icon_theme_factory->fromTheme(QSL("item-remove")));
// Setup icons for underlying components: opened web browsers...
foreach (WebBrowser *browser, WebBrowser::runningWebBrowsers()) {
@ -319,9 +413,10 @@ void FormMain::loadSize() {
m_ui->m_tabWidget->feedMessageViewer()->loadSize();
m_ui->m_actionSwitchToolBars->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ToolbarsVisible)).toBool());
m_ui->m_actionSwitchListHeaders->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ListHeadersVisible)).toBool());
m_ui->m_actionSwitchStatusBar->setChecked(settings->value(GROUP(GUI), SETTING(GUI::StatusBarVisible)).toBool());
// Make sure that only unread feeds are shown if user has that feature set on.
m_ui->m_actionShowOnlyUnreadFeeds->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool());
m_ui->m_actionShowOnlyUnreadItems->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool());
}
void FormMain::saveSize() {
@ -342,6 +437,7 @@ void FormMain::saveSize() {
settings->setValue(GROUP(GUI), GUI::MainWindowInitialSize, size());
settings->setValue(GROUP(GUI), GUI::MainWindowStartsMaximized, is_maximized);
settings->setValue(GROUP(GUI), GUI::MainWindowStartsFullscreen, is_fullscreen);
settings->setValue(GROUP(GUI), GUI::StatusBarVisible, m_ui->m_actionSwitchStatusBar->isChecked());
m_ui->m_tabWidget->feedMessageViewer()->saveSize();
}
@ -351,18 +447,25 @@ void FormMain::createConnections() {
connect(m_statusBar->fullscreenSwitcher(), SIGNAL(toggled(bool)), m_ui->m_actionFullscreen, SLOT(setChecked(bool)));
connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), m_statusBar->fullscreenSwitcher(), SLOT(setChecked(bool)));
connect(m_ui->m_menuAddItem, SIGNAL(aboutToShow()), this, SLOT(updateAddItemMenu()));
connect(m_ui->m_menuRecycleBin, SIGNAL(aboutToShow()), this, SLOT(updateRecycleBinMenu()));
connect(m_ui->m_menuAccounts, SIGNAL(aboutToShow()), this, SLOT(updateAccountsMenu()));
connect(m_ui->m_actionServiceDelete, SIGNAL(triggered()), m_ui->m_actionDeleteSelectedItem, SIGNAL(triggered()));
connect(m_ui->m_actionServiceEdit, SIGNAL(triggered()), m_ui->m_actionEditSelectedItem, SIGNAL(triggered()));
// Menu "File" connections.
connect(m_ui->m_actionExportFeeds, SIGNAL(triggered()), this, SLOT(exportFeeds()));
connect(m_ui->m_actionImportFeeds, SIGNAL(triggered()), this, SLOT(importFeeds()));
connect(m_ui->m_actionBackupDatabaseSettings, SIGNAL(triggered()), this, SLOT(backupDatabaseSettings()));
connect(m_ui->m_actionRestoreDatabaseSettings, SIGNAL(triggered()), this, SLOT(restoreDatabaseSettings()));
connect(m_ui->m_actionRestart, SIGNAL(triggered()), qApp, SLOT(restart()));
connect(m_ui->m_actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(m_ui->m_actionServiceAdd, SIGNAL(triggered()), this, SLOT(showAddAccountDialog()));
// Menu "View" connections.
connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), this, SLOT(switchFullscreenMode()));
connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), this, SLOT(switchMainMenu()));
connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), m_ui->m_menuBar, SLOT(setVisible(bool)));
connect(m_ui->m_actionSwitchMainWindow, SIGNAL(triggered()), this, SLOT(switchVisibility()));
connect(m_ui->m_actionSwitchStatusBar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool)));
// Menu "Tools" connections.
connect(m_ui->m_actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
@ -410,20 +513,6 @@ void FormMain::loadWebBrowserMenu(int index) {
m_ui->m_actionCloseCurrentTab->setEnabled(m_ui->m_tabWidget->tabBar()->tabType(index) == TabBar::Closable);
}
void FormMain::exportFeeds() {
QPointer<FormImportExport> form = new FormImportExport(this);
form.data()->setMode(FeedsImportExportModel::Export);
form.data()->exec();
delete form.data();
}
void FormMain::importFeeds() {
QPointer<FormImportExport> form = new FormImportExport(this);
form.data()->setMode(FeedsImportExportModel::Import);
form.data()->exec();
delete form.data();
}
void FormMain::backupDatabaseSettings() {
QPointer<FormBackupDatabaseSettings> form = new FormBackupDatabaseSettings(this);
form.data()->exec();
@ -476,6 +565,14 @@ void FormMain::showWiki() {
}
}
void FormMain::showAddAccountDialog() {
QPointer<FormAddAccount> form_update = new FormAddAccount(qApp->feedServices(),
tabWidget()->feedMessageViewer()->feedsView()->sourceModel(),
this);
form_update.data()->exec();
delete form_update.data();
}
void FormMain::reportABugOnGitHub() {
if (!WebFactory::instance()->openUrlInExternalBrowser(APP_URL_ISSUES_NEW_GITHUB)) {
qApp->showGuiMessage(tr("Cannot open external browser"),

View File

@ -21,7 +21,6 @@
#include "ui_formmain.h"
#include <QMainWindow>
#include <QUrl>
class StatusBar;
@ -64,19 +63,6 @@ class FormMain : public QMainWindow {
void loadSize();
void saveSize();
protected:
// Creates all needed menus and sets them up.
void prepareMenus();
// Creates needed connections for this window.
void createConnections();
// Event handler reimplementations.
void changeEvent(QEvent *event);
// Sets up proper icons for this widget.
void setupIcons();
public slots:
// Displays window on top or switches its visibility.
void display();
@ -87,27 +73,39 @@ class FormMain : public QMainWindow {
// Turns on/off fullscreen mode
void switchFullscreenMode();
// Switches visibility of main menu.
void switchMainMenu();
private slots:
void updateAddItemMenu();
void updateRecycleBinMenu();
void updateAccountsMenu();
protected slots:
// Loads web browser menu if user selects to change tabs.
void loadWebBrowserMenu(int index);
// Displays various dialogs.
void exportFeeds();
void importFeeds();
void backupDatabaseSettings();
void restoreDatabaseSettings();
void showSettings();
void showAbout();
void showUpdates();
void showWiki();
void showAddAccountDialog();
void reportABugOnGitHub();
void reportABugOnBitBucket();
void donate();
private:
// Event handler reimplementations.
void changeEvent(QEvent *event);
// Creates all needed menus and sets them up.
void prepareMenus();
// Creates needed connections for this window.
void createConnections();
// Sets up proper icons for this widget.
void setupIcons();
Ui::FormMain *m_ui;
QMenu *m_trayMenu;
StatusBar *m_statusBar;

View File

@ -55,9 +55,6 @@
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="m_actionImportFeeds"/>
<addaction name="m_actionExportFeeds"/>
<addaction name="separator"/>
<addaction name="m_actionRestoreDatabaseSettings"/>
<addaction name="m_actionBackupDatabaseSettings"/>
<addaction name="separator"/>
@ -88,6 +85,7 @@
<addaction name="m_actionSwitchMainMenu"/>
<addaction name="m_actionSwitchToolBars"/>
<addaction name="m_actionSwitchListHeaders"/>
<addaction name="m_actionSwitchStatusBar"/>
</widget>
<addaction name="m_menuShowHide"/>
<addaction name="m_actionSwitchMainWindow"/>
@ -128,36 +126,33 @@
</widget>
<widget class="QMenu" name="m_menuFeeds">
<property name="title">
<string>Fee&amp;ds &amp;&amp; categories</string>
<string>Feeds &amp;&amp; categories</string>
</property>
<widget class="QMenu" name="m_menuAddItem">
<property name="title">
<string>Add &amp;new feed/category</string>
<string>Add &amp;new item</string>
</property>
<addaction name="m_actionAddCategory"/>
<addaction name="m_actionAddFeed"/>
</widget>
<addaction name="m_actionUpdateAllFeeds"/>
<addaction name="m_actionUpdateSelectedFeeds"/>
<addaction name="m_actionUpdateAllItems"/>
<addaction name="m_actionUpdateSelectedItems"/>
<addaction name="separator"/>
<addaction name="m_menuAddItem"/>
<addaction name="m_actionEditSelectedFeedCategory"/>
<addaction name="m_actionDeleteSelectedFeedCategory"/>
<addaction name="m_actionFetchFeedMetadata"/>
<addaction name="m_actionEditSelectedItem"/>
<addaction name="m_actionDeleteSelectedItem"/>
<addaction name="separator"/>
<addaction name="m_actionShowOnlyUnreadFeeds"/>
<addaction name="m_actionExpandCollapseFeedCategory"/>
<addaction name="m_actionShowOnlyUnreadItems"/>
<addaction name="m_actionExpandCollapseItem"/>
<addaction name="separator"/>
<addaction name="m_actionSelectNextFeedCategory"/>
<addaction name="m_actionSelectPreviousFeedCategory"/>
<addaction name="m_actionSelectNextItem"/>
<addaction name="m_actionSelectPreviousItem"/>
<addaction name="separator"/>
<addaction name="m_actionMarkAllFeedsRead"/>
<addaction name="m_actionClearAllFeeds"/>
<addaction name="m_actionMarkAllItemsRead"/>
<addaction name="m_actionClearAllItems"/>
<addaction name="separator"/>
<addaction name="m_actionViewSelectedItemsNewspaperMode"/>
<addaction name="m_actionMarkSelectedFeedsAsRead"/>
<addaction name="m_actionMarkSelectedFeedsAsUnread"/>
<addaction name="m_actionClearSelectedFeeds"/>
<addaction name="m_actionMarkSelectedItemsAsRead"/>
<addaction name="m_actionMarkSelectedItemsAsUnread"/>
<addaction name="m_actionClearSelectedItems"/>
</widget>
<widget class="QMenu" name="m_menuMessages">
<property name="title">
@ -170,22 +165,32 @@
<addaction name="separator"/>
<addaction name="m_actionSelectNextMessage"/>
<addaction name="m_actionSelectPreviousMessage"/>
<addaction name="m_actionSelectNextUnreadMessage"/>
<addaction name="separator"/>
<addaction name="m_actionMarkSelectedMessagesAsRead"/>
<addaction name="m_actionMarkSelectedMessagesAsUnread"/>
<addaction name="m_actionSwitchImportanceOfSelectedMessages"/>
<addaction name="m_actionDeleteSelectedMessages"/>
<addaction name="m_actionRestoreSelectedMessages"/>
</widget>
<widget class="QMenu" name="m_menuRecycleBin">
<property name="title">
<string>&amp;Recycle bin</string>
<string>&amp;Recycle bin(s)</string>
</property>
<addaction name="m_actionEmptyRecycleBin"/>
<addaction name="m_actionRestoreRecycleBin"/>
<addaction name="m_actionRestoreSelectedMessagesFromRecycleBin"/>
<addaction name="m_actionRestoreAllRecycleBins"/>
<addaction name="m_actionEmptyAllRecycleBins"/>
</widget>
<widget class="QMenu" name="m_menuAccounts">
<property name="title">
<string>&amp;Accounts</string>
</property>
<addaction name="m_actionServiceAdd"/>
<addaction name="m_actionServiceEdit"/>
<addaction name="m_actionServiceDelete"/>
</widget>
<addaction name="m_menuFile"/>
<addaction name="m_menuView"/>
<addaction name="m_menuAccounts"/>
<addaction name="m_menuFeeds"/>
<addaction name="m_menuMessages"/>
<addaction name="m_menuRecycleBin"/>
@ -225,6 +230,9 @@
<property name="toolTip">
<string>Displays extra info about this application.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
</property>
@ -261,6 +269,9 @@
<property name="toolTip">
<string>Close all tabs except current one.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionCloseCurrentTab">
<property name="text">
@ -269,109 +280,135 @@
<property name="toolTip">
<string>Close current web browser tab.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionUpdateAllFeeds">
<action name="m_actionUpdateAllItems">
<property name="text">
<string>Update &amp;all feeds</string>
<string>Update &amp;all items</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+U</string>
</property>
</action>
<action name="m_actionUpdateSelectedFeeds">
<action name="m_actionUpdateSelectedItems">
<property name="text">
<string>Update &amp;selected feeds</string>
<string>Update &amp;selected items</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+U</string>
</property>
</action>
<action name="m_actionEditSelectedFeedCategory">
<action name="m_actionEditSelectedItem">
<property name="text">
<string>&amp;Edit selected feed/category</string>
<string>&amp;Edit selected item</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionDeleteSelectedFeedCategory">
<action name="m_actionDeleteSelectedItem">
<property name="text">
<string>&amp;Delete selected feed/category</string>
<string>&amp;Delete selected item</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionMarkSelectedMessagesAsRead">
<property name="text">
<string>Mark &amp;selected messages as &amp;read</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionMarkSelectedMessagesAsUnread">
<property name="text">
<string>Mark &amp;selected messages as &amp;unread</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchImportanceOfSelectedMessages">
<property name="text">
<string>Switch &amp;importance of selected messages</string>
</property>
</action>
<action name="m_actionMarkSelectedFeedsAsRead">
<property name="text">
<string>&amp;Mark selected feeds as read</string>
</property>
<property name="toolTip">
<string>Mark all messages (without message filters) from selected feeds as read.</string>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionMarkSelectedFeedsAsUnread">
<action name="m_actionMarkSelectedItemsAsRead">
<property name="text">
<string>&amp;Mark selected feeds as unread</string>
<string>&amp;Mark selected items as read</string>
</property>
<property name="toolTip">
<string>Mark all messages (without message filters) from selected feeds as unread.</string>
<string>Mark all messages (without message filters) from selected items as read.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionMarkSelectedItemsAsUnread">
<property name="text">
<string>&amp;Mark selected items as unread</string>
</property>
<property name="toolTip">
<string>Mark all messages (without message filters) from selected items as unread.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionDeleteSelectedMessages">
<property name="text">
<string>&amp;Delete selected messages</string>
</property>
</action>
<action name="m_actionClearSelectedFeeds">
<property name="text">
<string>&amp;Clean selected feeds</string>
</property>
<property name="toolTip">
<string>Deletes all messages from selected feeds.</string>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionAddFeed">
<action name="m_actionClearSelectedItems">
<property name="text">
<string>New &amp;feed</string>
<string>&amp;Clean selected items</string>
</property>
<property name="toolTip">
<string>Add new feed.</string>
<string>Deletes all messages from selected items.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionOpenSelectedSourceArticlesExternally">
<property name="text">
<string>Open selected source articles in &amp;external browser</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionOpenSelectedMessagesInternally">
<property name="text">
<string>Open selected messages in &amp;internal browser</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionOpenSelectedSourceArticlesInternally">
<property name="text">
<string>Open selected source articles in &amp;internal browser</string>
</property>
</action>
<action name="m_actionAddCategory">
<property name="text">
<string>New &amp;category</string>
</property>
<property name="toolTip">
<string>Add new category.</string>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionNoActions">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>No actions available</string>
</property>
@ -382,20 +419,26 @@
<string notr="true"/>
</property>
</action>
<action name="m_actionMarkAllFeedsRead">
<action name="m_actionMarkAllItemsRead">
<property name="text">
<string>&amp;Mark all feeds as &amp;read</string>
<string>&amp;Mark all items as &amp;read</string>
</property>
<property name="toolTip">
<string>Marks all messages in all feeds read. This does not take message filters into account.</string>
<string>Marks all messages in all items read. This does not take message filters into account.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionViewSelectedItemsNewspaperMode">
<property name="text">
<string>View selected feeds in &amp;newspaper mode</string>
<string>View selected items in &amp;newspaper mode</string>
</property>
<property name="toolTip">
<string>Displays all messages from selected feeds/categories in a new &quot;newspaper mode&quot; tab. Note that messages are not set as read automatically.</string>
<string>Displays all messages from selected item in a new &quot;newspaper mode&quot; tab. Note that messages are not set as read automatically.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchMainWindow">
@ -408,6 +451,9 @@
<property name="toolTip">
<string>Hides main window if it is visible and shows it if it is hidden.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchFeedsList">
<property name="checkable">
@ -423,28 +469,28 @@
<string notr="true">L</string>
</property>
</action>
<action name="m_actionClearAllFeeds">
<action name="m_actionClearAllItems">
<property name="text">
<string>&amp;Clean all feeds</string>
<string>&amp;Clean all items</string>
</property>
<property name="toolTip">
<string>Deletes all messages from all feeds.</string>
<string>Deletes all messages from all items.</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+C</string>
</property>
</action>
<action name="m_actionSelectNextFeedCategory">
<action name="m_actionSelectNextItem">
<property name="text">
<string>Select &amp;next feed/category</string>
<string>Select &amp;next item</string>
</property>
<property name="shortcut">
<string notr="true">S</string>
</property>
</action>
<action name="m_actionSelectPreviousFeedCategory">
<action name="m_actionSelectPreviousItem">
<property name="text">
<string>Select &amp;previous feed/category</string>
<string>Select &amp;previous item</string>
</property>
<property name="shortcut">
<string notr="true">A</string>
@ -473,6 +519,9 @@
<property name="toolTip">
<string>Check if new update for the application is available for download.</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchMainMenu">
<property name="checkable">
@ -563,22 +612,6 @@
<string notr="true">H</string>
</property>
</action>
<action name="m_actionImportFeeds">
<property name="text">
<string>&amp;Import feeds</string>
</property>
<property name="toolTip">
<string>Imports feeds you want from selected file.</string>
</property>
</action>
<action name="m_actionExportFeeds">
<property name="text">
<string>&amp;Export feeds</string>
</property>
<property name="toolTip">
<string>Exports feeds you want to selected file.</string>
</property>
</action>
<action name="m_actionReportBugBitBucket">
<property name="text">
<string>Report a bug (BitBucket)...</string>
@ -591,41 +624,41 @@
<property name="text">
<string>&amp;Donate via PayPal</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionDisplayWiki">
<property name="text">
<string>Display &amp;wiki</string>
</property>
</action>
<action name="m_actionEmptyRecycleBin">
<property name="text">
<string>&amp;Empty recycle bin</string>
</property>
</action>
<action name="m_actionRestoreRecycleBin">
<property name="text">
<string>&amp;Restore all messages</string>
</property>
</action>
<action name="m_actionRestoreSelectedMessagesFromRecycleBin">
<property name="text">
<string>Restore &amp;selected messages</string>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionRestart">
<property name="text">
<string>&amp;Restart</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionRestoreDatabaseSettings">
<property name="text">
<string>&amp;Restore database/settings</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionBackupDatabaseSettings">
<property name="text">
<string>&amp;Backup database/settings</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchMessageListOrientation">
<property name="text">
@ -639,11 +672,17 @@
<property name="text">
<string>&amp;Downloads</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSendMessageViaEmail">
<property name="text">
<string>Send selected message via e-mail</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionCleanupDatabase">
<property name="text">
@ -653,33 +692,95 @@
<string notr="true">Ctrl+Shift+Del</string>
</property>
</action>
<action name="m_actionShowOnlyUnreadFeeds">
<action name="m_actionShowOnlyUnreadItems">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show only unread feeds/categories</string>
<string>Show only unread items</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+U</string>
<string notr="true">U</string>
</property>
</action>
<action name="m_actionFetchFeedMetadata">
<action name="m_actionExpandCollapseItem">
<property name="text">
<string>&amp;Fetch feed metadata</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+F</string>
</property>
</action>
<action name="m_actionExpandCollapseFeedCategory">
<property name="text">
<string>&amp;Expand/collapse selected feed/category</string>
<string>&amp;Expand/collapse selected item</string>
</property>
<property name="shortcut">
<string notr="true">E</string>
</property>
</action>
<action name="m_actionServiceAdd">
<property name="text">
<string>&amp;Add new account</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionRestoreSelectedMessages">
<property name="text">
<string>&amp;Restore selected messages</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionRestoreAllRecycleBins">
<property name="text">
<string>&amp;Restore all recycle bins</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionEmptyAllRecycleBins">
<property name="text">
<string>&amp;Empty all recycle bins</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSelectNextUnreadMessage">
<property name="text">
<string>Select next &amp;unread message</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionSwitchStatusBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Status bar</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionServiceEdit">
<property name="text">
<string>&amp;Edit selected account</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
<action name="m_actionServiceDelete">
<property name="text">
<string>&amp;Delete selected account</string>
</property>
<property name="shortcut">
<string notr="true"/>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -84,7 +84,7 @@ FormSettings::FormSettings(QWidget *parent) : QDialog(parent), m_ui(new Ui::Form
<< /*: Skin list name column. */ tr("Name")
<< /*: Version column of skin list. */ tr("Version")
<< tr("Author")
<< tr("Email"));
<< tr("E-mail"));
#if QT_VERSION >= 0x050000
// Setup languages.
@ -732,6 +732,7 @@ void FormSettings::loadInterface() {
m_ui->m_cmbNotificationPosition->addItem(tr("Bottom-right corner"), Qt::BottomRightCorner);
m_ui->m_cmbNotificationPosition->addItem(tr("Top-right corner"), Qt::TopRightCorner);
m_ui->m_cmbNotificationPosition->setCurrentIndex(m_ui->m_cmbNotificationPosition->findData(static_cast<Qt::Corner>(settings->value(GROUP(GUI), SETTING(GUI::FancyNotificationsPosition)).toInt())));
m_ui->m_grpBaseNotifications->setChecked(settings->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool());
// Load settings of icon theme.
QString current_theme = qApp->icons()->currentIconTheme();
@ -834,6 +835,7 @@ void FormSettings::saveInterface() {
// Save notifications.
settings->setValue(GROUP(GUI), GUI::UseFancyNotifications, m_ui->m_grpNotifications->isChecked());
settings->setValue(GROUP(GUI), GUI::EnableNotifications, m_ui->m_grpBaseNotifications->isChecked());
settings->setValue(GROUP(GUI), GUI::FancyNotificationsPosition, static_cast<Qt::Corner>(m_ui->m_cmbNotificationPosition->itemData(m_ui->m_cmbNotificationPosition->currentIndex()).toInt()));
// Save selected icon theme.

3
src/gui/dialogs/formsettings.h Normal file → Executable file
View File

@ -48,10 +48,9 @@ class FormSettings : public QDialog {
protected:
// Does check of controls before dialog can be submitted.
bool doSaveCheck();
bool eventFilter(QObject *obj, QEvent *e);
protected slots:
private slots:
// Displays "restart" dialog if some critical settings changed.
void promptForRestart();

View File

@ -88,7 +88,7 @@
<item row="0" column="1">
<widget class="QStackedWidget" name="m_stackedSettings">
<property name="currentIndex">
<number>3</number>
<number>1</number>
</property>
<widget class="QWidget" name="m_pageGeneral">
<layout class="QFormLayout" name="formLayout_5">
@ -636,24 +636,39 @@ Authors of this application are NOT responsible for lost data.</string>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="m_grpNotifications">
<widget class="QGroupBox" name="m_grpBaseNotifications">
<property name="title">
<string>Fancy &amp;&amp; modern popup notifications (This uses OS native notifications via D-Bus if available.)</string>
<string>Enable notifications</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout_23">
<item row="0" column="0">
<widget class="QLabel" name="m_lblNotificationPosition">
<property name="text">
<string>Notification position</string>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QGroupBox" name="m_grpNotifications">
<property name="title">
<string>Fancy &amp;&amp; modern popup notifications (This uses OS native notifications via D-Bus if available.)</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout_23">
<item row="0" column="0">
<widget class="QLabel" name="m_lblNotificationPosition">
<property name="text">
<string>Notification position</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbNotificationPosition"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbNotificationPosition"/>
</item>
</layout>
</widget>
</item>
@ -1765,5 +1780,21 @@ Authors of this application are NOT responsible for lost data.</string>
</hint>
</hints>
</connection>
<connection>
<sender>m_grpBaseNotifications</sender>
<signal>toggled(bool)</signal>
<receiver>m_grpNotifications</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>624</x>
<y>82</y>
</hint>
<hint type="destinationlabel">
<x>624</x>
<y>89</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -25,9 +25,10 @@
#include "miscellaneous/databasecleaner.h"
#include "core/messagesproxymodel.h"
#include "core/feeddownloader.h"
#include "core/feed.h"
#include "core/feedsselection.h"
#include "core/feedsimportexportmodel.h"
#include "core/feedsproxymodel.h"
#include "services/standard/standardserviceroot.h"
#include "services/standard/standardfeed.h"
#include "services/standard/standardfeedsimportexportmodel.h"
#include "network-web/webbrowser.h"
#include "gui/messagesview.h"
#include "gui/feedsview.h"
@ -63,60 +64,36 @@ FeedMessageViewer::FeedMessageViewer(QWidget *parent)
m_toolBarMessages(new MessagesToolBar(tr("Toolbar for messages"), this)),
m_messagesView(new MessagesView(this)),
m_feedsView(new FeedsView(this)),
m_messagesBrowser(new WebBrowser(this)),
m_feedDownloaderThread(NULL),
m_dbCleanerThread(NULL),
m_feedDownloader(NULL),
m_dbCleaner(NULL) {
m_messagesBrowser(new WebBrowser(this)) {
initialize();
initializeViews();
loadMessageViewerFonts();
createConnections();
// Now, update all feeds if user has set it.
m_feedsView->updateAllFeedsOnStartup();
}
FeedMessageViewer::~FeedMessageViewer() {
qDebug("Destroying FeedMessageViewer instance.");
}
DatabaseCleaner *FeedMessageViewer::databaseCleaner() {
if (m_dbCleaner == NULL) {
m_dbCleaner = new DatabaseCleaner();
m_dbCleanerThread = new QThread();
// Downloader setup.
qRegisterMetaType<CleanerOrders>("CleanerOrders");
m_dbCleaner->moveToThread(m_dbCleanerThread);
connect(m_dbCleanerThread, SIGNAL(finished()), m_dbCleanerThread, SLOT(deleteLater()));
// Connections are made, start the feed downloader thread.
m_dbCleanerThread->start();
}
return m_dbCleaner;
}
void FeedMessageViewer::saveSize() {
Settings *settings = qApp->settings();
m_feedsView->saveExpandedStates();
// Store offsets of splitters.
settings->setValue(GROUP(GUI), GUI::SplitterFeeds, QString(m_feedSplitter->saveState().toBase64()));
settings->setValue(GROUP(GUI), GUI::SplitterMessages, QString(m_messageSplitter->saveState().toBase64()));
// States of splitters are stored, let's store
// widths of columns.
int width_column_author = m_messagesView->columnWidth(MSG_DB_AUTHOR_INDEX);
int width_column_date = m_messagesView->columnWidth(MSG_DB_DCREATED_INDEX);
if (width_column_author != 0 && width_column_date != 0) {
settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX), width_column_author);
settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_DCREATED_INDEX), width_column_date);
}
// Store "visibility" of toolbars and list headers.
settings->setValue(GROUP(GUI), GUI::ToolbarsVisible, m_toolBarsEnabled);
settings->setValue(GROUP(GUI), GUI::ListHeadersVisible, m_listHeadersEnabled);
@ -125,13 +102,11 @@ void FeedMessageViewer::saveSize() {
void FeedMessageViewer::loadSize() {
Settings *settings = qApp->settings();
int default_msg_section_size = m_messagesView->header()->defaultSectionSize();
m_feedsView->loadExpandedStates();
// Restore offsets of splitters.
m_feedSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterFeeds)).toString().toLocal8Bit()));
m_messageSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterMessages)).toString().toLocal8Bit()));
// Splitters are restored, now, restore widths of columns.
m_messagesView->setColumnWidth(MSG_DB_AUTHOR_INDEX, settings->value(GROUP(GUI),
KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX),
@ -144,7 +119,7 @@ void FeedMessageViewer::loadSize() {
void FeedMessageViewer::loadMessageViewerFonts() {
Settings *settings = qApp->settings();
QWebSettings *view_settings = m_messagesBrowser->view()->settings();
view_settings->setFontFamily(QWebSettings::StandardFont, settings->value(GROUP(Messages),
SETTING(Messages::PreviewerFontStandard)).toString());
}
@ -153,69 +128,14 @@ void FeedMessageViewer::quit() {
// Quit the feeds model (stops auto-update timer etc.).
m_feedsView->sourceModel()->quit();
// Close worker threads.
if (m_feedDownloaderThread != NULL && m_feedDownloaderThread->isRunning()) {
qDebug("Quitting feed downloader thread.");
m_feedDownloaderThread->quit();
if (!m_feedDownloaderThread->wait(CLOSE_LOCK_TIMEOUT)) {
qCritical("Feed downloader thread is running despite it was told to quit. Terminating it.");
m_feedDownloaderThread->terminate();
}
}
if (m_dbCleanerThread != NULL && m_dbCleanerThread->isRunning()) {
qDebug("Quitting database cleaner thread.");
m_dbCleanerThread->quit();
if (!m_dbCleanerThread->wait(CLOSE_LOCK_TIMEOUT)) {
qCritical("Database cleaner thread is running despite it was told to quit. Terminating it.");
m_dbCleanerThread->terminate();
}
}
// Close workers.
if (m_feedDownloader != NULL) {
qDebug("Feed downloader exists. Deleting it from memory.");
m_feedDownloader->deleteLater();
}
if (m_dbCleaner != NULL) {
qDebug("Database cleaner exists. Deleting it from memory.");
m_dbCleaner->deleteLater();
}
if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) {
m_feedsView->clearAllReadMessages();
}
}
void FeedMessageViewer::loadInitialFeeds() {
QString target_opml_file = APP_INITIAL_FEEDS_PATH + QDir::separator() + FEED_INITIAL_OPML_PATTERN;
QString current_locale = qApp->localization()->loadedLanguage();
QString file_to_load;
bool FeedMessageViewer::areToolBarsEnabled() const {
return m_toolBarsEnabled;
}
if (QFile::exists(target_opml_file.arg(current_locale))) {
file_to_load = target_opml_file.arg(current_locale);
}
else if (QFile::exists(target_opml_file.arg(DEFAULT_LOCALE))) {
file_to_load = target_opml_file.arg(DEFAULT_LOCALE);
}
FeedsImportExportModel model;
QString output_msg;
try {
model.importAsOPML20(IOFactory::readTextFile(file_to_load));
model.checkAllItems();
if (m_feedsView->sourceModel()->mergeModel(&model, output_msg)) {
m_feedsView->expandAll();
}
}
catch (ApplicationException &ex) {
MessageBox::show(this, QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message());
}
bool FeedMessageViewer::areListHeadersEnabled() const {
return m_listHeadersEnabled;
}
void FeedMessageViewer::switchMessageSplitterOrientation() {
@ -239,61 +159,29 @@ void FeedMessageViewer::setListHeadersEnabled(bool enable) {
m_messagesView->header()->setVisible(enable);
}
void FeedMessageViewer::updateTrayIconStatus(int unread_messages, int total_messages, bool any_unread_messages) {
Q_UNUSED(total_messages)
if (SystemTrayIcon::isSystemTrayActivated()) {
qApp->trayIcon()->setNumber(unread_messages, any_unread_messages);
}
}
void FeedMessageViewer::onFeedUpdatesStarted() {
//: Text display in status bar when feed update is started.
qApp->mainForm()->statusBar()->showProgressFeeds(0, tr("Feed update started"));
}
void FeedMessageViewer::onFeedUpdatesProgress(Feed *feed, int current, int total) {
// Some feed got updated.
m_feedsView->updateCountsOfParticularFeed(feed, true);
qApp->mainForm()->statusBar()->showProgressFeeds((current * 100.0) / total,
//: Text display in status bar when particular feed is updated.
tr("Updated feed '%1'").arg(feed->title()));
}
void FeedMessageViewer::onFeedUpdatesFinished(FeedDownloadResults results) {
qApp->feedUpdateLock()->unlock();
qApp->mainForm()->statusBar()->clearProgressFeeds();
m_messagesView->reloadSelections(true);
if (!results.m_updatedFeeds.isEmpty()) {
// Now, inform about results via GUI message/notification.
qApp->showGuiMessage(tr("New messages downloaded"), results.getOverview(10), QSystemTrayIcon::NoIcon,
0, false, qApp->icons()->fromTheme(QSL("item-update-all")));
}
}
void FeedMessageViewer::switchFeedComponentVisibility() {
m_feedsWidget->setVisible(!m_feedsWidget->isVisible());
}
void FeedMessageViewer::toggleShowOnlyUnreadFeeds() {
QAction *origin = qobject_cast<QAction*>(sender());
if (origin == NULL) {
m_feedsView->invalidateReadFeedsFilter(true, false);
m_feedsView->model()->invalidateReadFeedsFilter(true, false);
}
else {
m_feedsView->invalidateReadFeedsFilter(true, origin->isChecked());
m_feedsView->model()->invalidateReadFeedsFilter(true, origin->isChecked());
}
}
void FeedMessageViewer::updateMessageButtonsAvailability() {
bool one_message_selected = m_messagesView->selectionModel()->selectedRows().size() == 1;
bool atleast_one_message_selected = !m_messagesView->selectionModel()->selectedRows().isEmpty();
bool recycle_bin_selected = m_messagesView->sourceModel()->loadedSelection().mode() == FeedsSelection::MessagesFromRecycleBin;
bool bin_loaded = m_messagesView->sourceModel()->loadedItem() != NULL && m_messagesView->sourceModel()->loadedItem()->kind() == RootItemKind::Bin;
FormMain *form_main = qApp->mainForm();
form_main->m_ui->m_actionDeleteSelectedMessages->setEnabled(atleast_one_message_selected);
form_main->m_ui->m_actionRestoreSelectedMessages->setEnabled(atleast_one_message_selected && bin_loaded);
form_main->m_ui->m_actionMarkSelectedMessagesAsRead->setEnabled(atleast_one_message_selected);
form_main->m_ui->m_actionMarkSelectedMessagesAsUnread->setEnabled(atleast_one_message_selected);
form_main->m_ui->m_actionOpenSelectedMessagesInternally->setEnabled(atleast_one_message_selected);
@ -301,63 +189,61 @@ void FeedMessageViewer::updateMessageButtonsAvailability() {
form_main->m_ui->m_actionOpenSelectedSourceArticlesInternally->setEnabled(atleast_one_message_selected);
form_main->m_ui->m_actionSendMessageViaEmail->setEnabled(one_message_selected);
form_main->m_ui->m_actionSwitchImportanceOfSelectedMessages->setEnabled(atleast_one_message_selected);
form_main->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin->setEnabled(recycle_bin_selected && atleast_one_message_selected);
}
void FeedMessageViewer::updateFeedButtonsAvailability() {
bool critical_action_running = qApp->feedUpdateLock()->isLocked();
bool feed_selected = !m_feedsView->selectionModel()->selectedRows().isEmpty();
RootItem *selected_item = feedsView()->selectedItem();
bool anything_selected = selected_item != NULL;
bool feed_selected = anything_selected && selected_item->kind() == RootItemKind::Feed;
bool category_selected = anything_selected && selected_item->kind() == RootItemKind::Category;
bool service_selected = anything_selected && selected_item->kind() == RootItemKind::ServiceRoot;
FormMain *form_main = qApp->mainForm();
form_main->m_ui->m_actionAddCategory->setEnabled(!critical_action_running);
form_main->m_ui->m_actionAddFeed->setEnabled(!critical_action_running);
form_main->m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running);
form_main->m_ui->m_actionCleanupDatabase->setEnabled(!critical_action_running);
form_main->m_ui->m_actionClearSelectedFeeds->setEnabled(feed_selected);
form_main->m_ui->m_actionDeleteSelectedFeedCategory->setEnabled(!critical_action_running && feed_selected);
form_main->m_ui->m_actionEditSelectedFeedCategory->setEnabled(!critical_action_running && feed_selected);
form_main->m_ui->m_actionImportFeeds->setEnabled(!critical_action_running);
form_main->m_ui->m_actionMarkSelectedFeedsAsRead->setEnabled(feed_selected);
form_main->m_ui->m_actionMarkSelectedFeedsAsUnread->setEnabled(feed_selected);
form_main->m_ui->m_actionUpdateAllFeeds->setEnabled(!critical_action_running);
form_main->m_ui->m_actionUpdateSelectedFeeds->setEnabled(!critical_action_running && feed_selected);
form_main->m_ui->m_actionViewSelectedItemsNewspaperMode->setEnabled(feed_selected);
form_main->m_ui->m_actionFetchFeedMetadata->setEnabled(feed_selected);
form_main->m_ui->m_actionExpandCollapseFeedCategory->setEnabled(feed_selected);
form_main->m_ui->m_actionClearSelectedItems->setEnabled(anything_selected);
form_main->m_ui->m_actionDeleteSelectedItem->setEnabled(!critical_action_running && anything_selected);
form_main->m_ui->m_actionEditSelectedItem->setEnabled(!critical_action_running && anything_selected);
form_main->m_ui->m_actionMarkSelectedItemsAsRead->setEnabled(anything_selected);
form_main->m_ui->m_actionMarkSelectedItemsAsUnread->setEnabled(anything_selected);
form_main->m_ui->m_actionUpdateAllItems->setEnabled(!critical_action_running);
form_main->m_ui->m_actionUpdateSelectedItems->setEnabled(!critical_action_running && (feed_selected || category_selected || service_selected));
form_main->m_ui->m_actionViewSelectedItemsNewspaperMode->setEnabled(anything_selected);
form_main->m_ui->m_actionExpandCollapseItem->setEnabled(anything_selected);
form_main->m_ui->m_actionServiceDelete->setEnabled(service_selected);
form_main->m_ui->m_actionServiceEdit->setEnabled(service_selected);
form_main->m_ui->m_menuAddItem->setEnabled(!critical_action_running);
form_main->m_ui->m_menuAccounts->setEnabled(!critical_action_running);
form_main->m_ui->m_menuRecycleBin->setEnabled(!critical_action_running);
}
void FeedMessageViewer::createConnections() {
FormMain *form_main = qApp->mainForm();
// Filtering & searching.
connect(m_toolBarMessages, SIGNAL(messageSearchPatternChanged(QString)), m_messagesView, SLOT(searchMessages(QString)));
connect(m_toolBarMessages, SIGNAL(messageFilterChanged(MessagesModel::MessageFilter)), m_messagesView, SLOT(filterMessages(MessagesModel::MessageFilter)));
connect(m_toolBarMessages, SIGNAL(messageFilterChanged(MessagesModel::MessageHighlighter)), m_messagesView, SLOT(filterMessages(MessagesModel::MessageHighlighter)));
// Message changers.
connect(m_messagesView, SIGNAL(currentMessagesRemoved()), m_messagesBrowser, SLOT(clear()));
connect(m_messagesView, SIGNAL(currentMessagesChanged(QList<Message>)), m_messagesBrowser, SLOT(navigateToMessages(QList<Message>)));
connect(m_messagesView, SIGNAL(currentMessagesRemoved()), this, SLOT(updateMessageButtonsAvailability()));
connect(m_messagesView, SIGNAL(currentMessagesChanged(QList<Message>)), this, SLOT(updateMessageButtonsAvailability()));
connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), this, SLOT(updateFeedButtonsAvailability()));
connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), this, SLOT(updateFeedButtonsAvailability()));
connect(qApp->feedUpdateLock(), SIGNAL(locked()), this, SLOT(updateFeedButtonsAvailability()));
connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability()));
// If user selects feeds, load their messages.
connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), m_messagesView, SLOT(loadFeeds(FeedsSelection)));
// If user changes status of some messages, recalculate message counts.
connect(m_messagesView->sourceModel(), SIGNAL(messageCountsChanged(FeedsSelection::SelectionMode,bool,bool)),
m_feedsView, SLOT(receiveMessageCountsChange(FeedsSelection::SelectionMode,bool,bool)));
connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), m_messagesView, SLOT(loadItem(RootItem*)));
// State of many messages is changed, then we need
// to reload selections.
connect(m_feedsView, SIGNAL(feedsNeedToBeReloaded(bool)), m_messagesView, SLOT(reloadSelections(bool)));
// If counts of unread/all messages change, update the tray icon.
connect(m_feedsView, SIGNAL(messageCountsChanged(int,int,bool)), this, SLOT(updateTrayIconStatus(int,int,bool)));
connect(m_feedsView->sourceModel(), SIGNAL(reloadMessageListRequested(bool)), m_messagesView, SLOT(reloadSelections(bool)));
connect(m_feedsView->sourceModel(), SIGNAL(feedsUpdateFinished()), this, SLOT(onFeedsUpdateFinished()));
// Message openers.
connect(m_messagesView, SIGNAL(openLinkMiniBrowser(QString)), m_messagesBrowser, SLOT(navigateToUrl(QString)));
@ -367,10 +253,7 @@ void FeedMessageViewer::createConnections() {
form_main->m_ui->m_tabWidget, SLOT(addLinkedBrowser(QString)));
connect(m_feedsView, SIGNAL(openMessagesInNewspaperView(QList<Message>)),
form_main->m_ui->m_tabWidget, SLOT(addBrowserWithMessages(QList<Message>)));
// Downloader connections.
connect(m_feedsView, SIGNAL(feedsUpdateRequested(QList<Feed*>)), this, SLOT(updateFeeds(QList<Feed*>)));
// Toolbar forwardings.
connect(form_main->m_ui->m_actionCleanupDatabase,
SIGNAL(triggered()), this, SLOT(showDbCleanupAssistant()));
@ -378,8 +261,6 @@ void FeedMessageViewer::createConnections() {
SIGNAL(triggered()), m_messagesView, SLOT(switchSelectedMessagesImportance()));
connect(form_main->m_ui->m_actionDeleteSelectedMessages,
SIGNAL(triggered()), m_messagesView, SLOT(deleteSelectedMessages()));
connect(form_main->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin,
SIGNAL(triggered()), m_messagesView, SLOT(restoreSelectedMessages()));
connect(form_main->m_ui->m_actionMarkSelectedMessagesAsRead,
SIGNAL(triggered()), m_messagesView, SLOT(markSelectedMessagesRead()));
connect(form_main->m_ui->m_actionMarkSelectedMessagesAsUnread,
@ -392,56 +273,54 @@ void FeedMessageViewer::createConnections() {
SIGNAL(triggered()), m_messagesView, SLOT(openSelectedMessagesInternally()));
connect(form_main->m_ui->m_actionSendMessageViaEmail,
SIGNAL(triggered()), m_messagesView, SLOT(sendSelectedMessageViaEmail()));
connect(form_main->m_ui->m_actionMarkAllFeedsRead,
SIGNAL(triggered()), m_feedsView, SLOT(markAllFeedsRead()));
connect(form_main->m_ui->m_actionMarkSelectedFeedsAsRead,
SIGNAL(triggered()), m_feedsView, SLOT(markSelectedFeedsRead()));
connect(form_main->m_ui->m_actionExpandCollapseFeedCategory,
connect(form_main->m_ui->m_actionMarkAllItemsRead,
SIGNAL(triggered()), m_feedsView, SLOT(markAllItemsRead()));
connect(form_main->m_ui->m_actionMarkSelectedItemsAsRead,
SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemRead()));
connect(form_main->m_ui->m_actionExpandCollapseItem,
SIGNAL(triggered()), m_feedsView, SLOT(expandCollapseCurrentItem()));
connect(form_main->m_ui->m_actionFetchFeedMetadata, SIGNAL(triggered()),
m_feedsView, SLOT(fetchMetadataForSelectedFeed()));
connect(form_main->m_ui->m_actionMarkSelectedFeedsAsUnread,
SIGNAL(triggered()), m_feedsView, SLOT(markSelectedFeedsUnread()));
connect(form_main->m_ui->m_actionClearSelectedFeeds,
connect(form_main->m_ui->m_actionMarkSelectedItemsAsUnread,
SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemUnread()));
connect(form_main->m_ui->m_actionClearSelectedItems,
SIGNAL(triggered()), m_feedsView, SLOT(clearSelectedFeeds()));
connect(form_main->m_ui->m_actionClearAllFeeds,
connect(form_main->m_ui->m_actionClearAllItems,
SIGNAL(triggered()), m_feedsView, SLOT(clearAllFeeds()));
connect(form_main->m_ui->m_actionUpdateSelectedFeeds,
SIGNAL(triggered()), m_feedsView, SLOT(updateSelectedFeeds()));
connect(form_main->m_ui->m_actionUpdateAllFeeds,
SIGNAL(triggered()), m_feedsView, SLOT(updateAllFeeds()));
connect(form_main->m_ui->m_actionAddCategory,
SIGNAL(triggered()), m_feedsView, SLOT(addNewCategory()));
connect(form_main->m_ui->m_actionAddFeed,
SIGNAL(triggered()), m_feedsView, SLOT(addNewFeed()));
connect(form_main->m_ui->m_actionEditSelectedFeedCategory,
connect(form_main->m_ui->m_actionUpdateSelectedItems,
SIGNAL(triggered()), m_feedsView, SLOT(updateSelectedItems()));
connect(form_main->m_ui->m_actionUpdateAllItems,
SIGNAL(triggered()), m_feedsView, SLOT(updateAllItems()));
connect(form_main->m_ui->m_actionEditSelectedItem,
SIGNAL(triggered()), m_feedsView, SLOT(editSelectedItem()));
connect(form_main->m_ui->m_actionViewSelectedItemsNewspaperMode,
SIGNAL(triggered()), m_feedsView, SLOT(openSelectedFeedsInNewspaperMode()));
connect(form_main->m_ui->m_actionEmptyRecycleBin,
SIGNAL(triggered()), m_feedsView, SLOT(emptyRecycleBin()));
connect(form_main->m_ui->m_actionRestoreRecycleBin,
SIGNAL(triggered()), m_feedsView, SLOT(restoreRecycleBin()));
connect(form_main->m_ui->m_actionDeleteSelectedFeedCategory,
SIGNAL(triggered()), m_feedsView, SLOT(openSelectedItemsInNewspaperMode()));
connect(form_main->m_ui->m_actionDeleteSelectedItem,
SIGNAL(triggered()), m_feedsView, SLOT(deleteSelectedItem()));
connect(form_main->m_ui->m_actionSwitchFeedsList,
SIGNAL(triggered()), this, SLOT(switchFeedComponentVisibility()));
connect(form_main->m_ui->m_actionSelectNextFeedCategory,
connect(form_main->m_ui->m_actionSelectNextItem,
SIGNAL(triggered()), m_feedsView, SLOT(selectNextItem()));
connect(form_main->m_ui->m_actionSwitchToolBars,
SIGNAL(toggled(bool)), this, SLOT(setToolBarsEnabled(bool)));
connect(form_main->m_ui->m_actionSwitchListHeaders,
SIGNAL(toggled(bool)), this, SLOT(setListHeadersEnabled(bool)));
connect(form_main->m_ui->m_actionSelectPreviousFeedCategory,
connect(form_main->m_ui->m_actionSelectPreviousItem,
SIGNAL(triggered()), m_feedsView, SLOT(selectPreviousItem()));
connect(form_main->m_ui->m_actionSelectNextMessage,
SIGNAL(triggered()), m_messagesView, SLOT(selectNextItem()));
connect(form_main->m_ui->m_actionSelectNextUnreadMessage,
SIGNAL(triggered()), m_messagesView, SLOT(selectNextUnreadItem()));
connect(form_main->m_ui->m_actionSelectPreviousMessage,
SIGNAL(triggered()), m_messagesView, SLOT(selectPreviousItem()));
connect(form_main->m_ui->m_actionSwitchMessageListOrientation, SIGNAL(triggered()),
this, SLOT(switchMessageSplitterOrientation()));
connect(form_main->m_ui->m_actionShowOnlyUnreadFeeds, SIGNAL(toggled(bool)),
connect(form_main->m_ui->m_actionShowOnlyUnreadItems, SIGNAL(toggled(bool)),
this, SLOT(toggleShowOnlyUnreadFeeds()));
connect(form_main->m_ui->m_actionRestoreSelectedMessages, SIGNAL(triggered()),
m_messagesView, SLOT(restoreSelectedMessages()));
connect(form_main->m_ui->m_actionRestoreAllRecycleBins, SIGNAL(triggered()),
m_feedsView->sourceModel(), SLOT(restoreAllBins()));
connect(form_main->m_ui->m_actionEmptyAllRecycleBins, SIGNAL(triggered()),
m_feedsView->sourceModel(), SLOT(emptyAllBins()));
}
void FeedMessageViewer::initialize() {
@ -450,15 +329,15 @@ void FeedMessageViewer::initialize() {
m_toolBarFeeds->setMovable(false);
m_toolBarFeeds->setAllowedAreas(Qt::TopToolBarArea);
m_toolBarFeeds->loadChangeableActions();
m_toolBarMessages->setFloatable(false);
m_toolBarMessages->setMovable(false);
m_toolBarMessages->setAllowedAreas(Qt::TopToolBarArea);
m_toolBarMessages->loadChangeableActions();
// Finish web/message browser setup.
m_messagesBrowser->setNavigationBarVisible(false);
// Now refresh visual setup.
refreshVisualProperties();
}
@ -468,12 +347,12 @@ void FeedMessageViewer::initializeViews() {
m_messagesWidget = new QWidget(this);
m_feedSplitter = new QSplitter(Qt::Horizontal, this);
m_messageSplitter = new QSplitter(Qt::Vertical, this);
// Instantiate needed components.
QVBoxLayout *central_layout = new QVBoxLayout(this);
QVBoxLayout *feed_layout = new QVBoxLayout(m_feedsWidget);
QVBoxLayout *message_layout = new QVBoxLayout(m_messagesWidget);
// Set layout properties.
central_layout->setMargin(0);
central_layout->setSpacing(0);
@ -481,11 +360,11 @@ void FeedMessageViewer::initializeViews() {
feed_layout->setSpacing(0);
message_layout->setMargin(0);
message_layout->setSpacing(0);
// Set views.
m_feedsView->setFrameStyle(QFrame::NoFrame);
m_messagesView->setFrameStyle(QFrame::NoFrame);
// Setup message splitter.
m_messageSplitter->setObjectName(QSL("MessageSplitter"));
m_messageSplitter->setHandleWidth(1);
@ -493,30 +372,30 @@ void FeedMessageViewer::initializeViews() {
m_messageSplitter->setChildrenCollapsible(false);
m_messageSplitter->addWidget(m_messagesView);
m_messageSplitter->addWidget(m_messagesBrowser);
// Assemble message-related components to single widget.
message_layout->addWidget(m_toolBarMessages);
message_layout->addWidget(m_messageSplitter);
// Assemble feed-related components to another widget.
feed_layout->addWidget(m_toolBarFeeds);
feed_layout->addWidget(m_feedsView);
// Assembler everything together.
m_feedSplitter->setHandleWidth(1);
m_feedSplitter->setOpaqueResize(false);
m_feedSplitter->setChildrenCollapsible(false);
m_feedSplitter->addWidget(m_feedsWidget);
m_feedSplitter->addWidget(m_messagesWidget);
// Add toolbar and main feeds/messages widget to main layout.
central_layout->addWidget(m_feedSplitter);
setTabOrder(m_feedsView, m_messagesView);
setTabOrder(m_messagesView, m_toolBarFeeds);
setTabOrder(m_toolBarFeeds, m_toolBarMessages);
setTabOrder(m_toolBarMessages, m_messagesBrowser);
updateMessageButtonsAvailability();
updateFeedButtonsAvailability();
}
@ -524,14 +403,14 @@ void FeedMessageViewer::initializeViews() {
void FeedMessageViewer::showDbCleanupAssistant() {
if (qApp->feedUpdateLock()->tryLock()) {
QPointer<FormDatabaseCleanup> form_pointer = new FormDatabaseCleanup(this);
form_pointer.data()->setCleaner(databaseCleaner());
form_pointer.data()->setCleaner(m_feedsView->sourceModel()->databaseCleaner());
form_pointer.data()->exec();
delete form_pointer.data();
qApp->feedUpdateLock()->unlock();
m_messagesView->reloadSelections(false);
m_feedsView->updateCountsOfAllFeeds(true);
m_feedsView->sourceModel()->reloadCountsOfWholeModel();
}
else {
qApp->showGuiMessage(tr("Cannot cleanup database"),
@ -543,36 +422,11 @@ void FeedMessageViewer::showDbCleanupAssistant() {
void FeedMessageViewer::refreshVisualProperties() {
Qt::ToolButtonStyle button_style = static_cast<Qt::ToolButtonStyle>(qApp->settings()->value(GROUP(GUI),
SETTING(GUI::ToolbarStyle)).toInt());
m_toolBarFeeds->setToolButtonStyle(button_style);
m_toolBarMessages->setToolButtonStyle(button_style);
}
void FeedMessageViewer::updateFeeds(QList<Feed *> feeds) {
if (!qApp->feedUpdateLock()->tryLock()) {
qApp->showGuiMessage(tr("Cannot update all items"),
tr("You cannot update all items because another another critical operation is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm(), true);
return;
}
if (m_feedDownloader == NULL) {
m_feedDownloader = new FeedDownloader();
m_feedDownloaderThread = new QThread();
// Downloader setup.
qRegisterMetaType<QList<Feed*> >("QList<Feed*>");
m_feedDownloader->moveToThread(m_feedDownloaderThread);
connect(this, SIGNAL(feedsUpdateRequested(QList<Feed*>)), m_feedDownloader, SLOT(updateFeeds(QList<Feed*>)));
connect(m_feedDownloaderThread, SIGNAL(finished()), m_feedDownloaderThread, SLOT(deleteLater()));
connect(m_feedDownloader, SIGNAL(finished(FeedDownloadResults)), this, SLOT(onFeedUpdatesFinished(FeedDownloadResults)));
connect(m_feedDownloader, SIGNAL(started()), this, SLOT(onFeedUpdatesStarted()));
connect(m_feedDownloader, SIGNAL(progress(Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(Feed*,int,int)));
// Connections are made, start the feed downloader thread.
m_feedDownloaderThread->start();
}
emit feedsUpdateRequested(feeds);
void FeedMessageViewer::onFeedsUpdateFinished() {
m_messagesView->reloadSelections(true);
}

37
src/gui/feedmessageviewer.h Normal file → Executable file
View File

@ -29,8 +29,7 @@ class MessagesView;
class MessagesToolBar;
class FeedsToolBar;
class FeedsView;
class DatabaseCleaner;
class Feed;
class StandardFeed;
class QToolBar;
class QSplitter;
class QProgressBar;
@ -65,8 +64,6 @@ class FeedMessageViewer : public TabContent {
return m_toolBarFeeds;
}
DatabaseCleaner *databaseCleaner();
// Loads/saves sizes and states of ALL
// underlying widgets, this contains primarily
// splitters, toolbar and views.
@ -79,17 +76,10 @@ class FeedMessageViewer : public TabContent {
// stops any child widgets/workers.
void quit();
inline bool areToolBarsEnabled() const {
return m_toolBarsEnabled;
}
inline bool areListHeadersEnabled() const {
return m_listHeadersEnabled;
}
bool areToolBarsEnabled() const;
bool areListHeadersEnabled() const;
public slots:
void loadInitialFeeds();
// Switches orientation horizontal/vertical.
void switchMessageSplitterOrientation();
@ -103,21 +93,15 @@ class FeedMessageViewer : public TabContent {
// Reloads some changeable visual settings.
void refreshVisualProperties();
void updateFeeds(QList<Feed*> feeds);
private slots:
// Updates counts of messages for example in tray icon.
void updateTrayIconStatus(int unread_messages, int total_messages, bool any_unread_messages);
// Reacts on feed updates.
void onFeedUpdatesStarted();
void onFeedUpdatesProgress(Feed *feed, int current, int total);
void onFeedUpdatesFinished(FeedDownloadResults results);
// Called when feed update finishes.
void onFeedsUpdateFinished();
// Switches visibility of feed list and related
// toolbar.
void switchFeedComponentVisibility();
// Toggles displayed feeds.
void toggleShowOnlyUnreadFeeds();
void updateMessageButtonsAvailability();
@ -133,10 +117,6 @@ class FeedMessageViewer : public TabContent {
// Sets up connections.
void createConnections();
signals:
// Emitted if user/application requested updating of some feeds.
void feedsUpdateRequested(const QList<Feed*> feeds);
private:
bool m_toolBarsEnabled;
bool m_listHeadersEnabled;
@ -151,11 +131,6 @@ class FeedMessageViewer : public TabContent {
QWidget *m_feedsWidget;
QWidget *m_messagesWidget;
WebBrowser *m_messagesBrowser;
QThread *m_feedDownloaderThread;
QThread *m_dbCleanerThread;
FeedDownloader *m_feedDownloader;
DatabaseCleaner *m_dbCleaner;
};
#endif // FEEDMESSAGEVIEWER_H

View File

@ -18,21 +18,20 @@
#include "gui/feedsview.h"
#include "definitions/definitions.h"
#include "core/feed.h"
#include "core/feedsmodel.h"
#include "core/feedsproxymodel.h"
#include "core/rootitem.h"
#include "core/category.h"
#include "core/recyclebin.h"
#include "core/feed.h"
#include "services/abstract/rootitem.h"
#include "miscellaneous/systemfactory.h"
#include "miscellaneous/mutex.h"
#include "gui/systemtrayicon.h"
#include "gui/messagebox.h"
#include "gui/styleditemdelegatewithoutfocus.h"
#include "gui/dialogs/formmain.h"
#include "gui/dialogs/formcategorydetails.h"
#include "gui/dialogs/formfeeddetails.h"
#include "services/abstract/feed.h"
#include "services/standard/standardcategory.h"
#include "services/standard/standardfeed.h"
#include "services/standard/gui/formstandardcategorydetails.h"
#include "services/standard/gui/formstandardfeeddetails.h"
#include <QMenu>
#include <QHeaderView>
@ -47,7 +46,7 @@ FeedsView::FeedsView(QWidget *parent)
m_contextMenuCategories(NULL),
m_contextMenuFeeds(NULL),
m_contextMenuEmptySpace(NULL),
m_contextMenuRecycleBin(NULL) {
m_contextMenuOtherItems(NULL) {
setObjectName(QSL("FeedsView"));
// Allocate models.
@ -56,7 +55,7 @@ FeedsView::FeedsView(QWidget *parent)
// Connections.
connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex)));
connect(m_sourceModel, SIGNAL(feedsUpdateRequested(QList<Feed*>)), this, SIGNAL(feedsUpdateRequested(QList<Feed*>)));
connect(m_sourceModel, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SLOT(onItemExpandRequested(QList<RootItem*>,bool)));
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortState(int,Qt::SortOrder)));
setModel(m_proxyModel);
@ -94,53 +93,44 @@ RootItem *FeedsView::selectedItem() const {
if (selected_rows.isEmpty()) {
return NULL;
}
RootItem *selected_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(selected_rows.at(0)));
return selected_item == m_sourceModel->rootItem() ? NULL : selected_item;
}
Category *FeedsView::selectedCategory() const {
QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex());
return m_sourceModel->categoryForIndex(current_mapped);
}
Feed *FeedsView::selectedFeed() const {
QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex());
return m_sourceModel->feedForIndex(current_mapped);
}
RecycleBin *FeedsView::selectedRecycleBin() const{
QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex());
return m_sourceModel->recycleBinForIndex(current_mapped);
else {
RootItem *selected_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(selected_rows.at(0)));
return selected_item == m_sourceModel->rootItem() ? NULL : selected_item;
}
}
void FeedsView::saveExpandedStates() {
Settings *settings = qApp->settings();
QList<RootItem*> expandable_items;
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot));
// Iterate all categories and save their expand statuses.
foreach (Category *category, sourceModel()->allCategories().values()) {
foreach (RootItem *item, expandable_items) {
QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
settings->setValue(GROUP(Categories),
QString::number(category->id()),
isExpanded(model()->mapFromSource(sourceModel()->indexForItem(category))));
setting_name,
isExpanded(model()->mapFromSource(sourceModel()->indexForItem(item))));
}
}
void FeedsView::loadExpandedStates() {
Settings *settings = qApp->settings();
QList<RootItem*> expandable_items;
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot));
// Iterate all categories and save their expand statuses.
foreach (Category *category, sourceModel()->allCategories().values()) {
setExpanded(model()->mapFromSource(sourceModel()->indexForItem(category)),
settings->value(GROUP(Categories), QString::number(category->id()), true).toBool());
}
}
foreach (RootItem *item, expandable_items) {
QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
void FeedsView::invalidateReadFeedsFilter(bool set_new_value, bool show_unread_only) {
if (set_new_value) {
m_proxyModel->setShowUnreadOnly(show_unread_only);
setExpanded(model()->mapFromSource(sourceModel()->indexForItem(item)),
settings->value(GROUP(Categories), setting_name, item->childCount() > 0).toBool());
}
QTimer::singleShot(0, m_proxyModel, SLOT(invalidateFilter()));
sortByColumn(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnFeeds)).toInt(),
static_cast<Qt::SortOrder>(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds)).toInt()));
}
void FeedsView::expandCollapseCurrentItem() {
@ -156,137 +146,20 @@ void FeedsView::expandCollapseCurrentItem() {
}
}
void FeedsView::updateAllFeeds() {
emit feedsUpdateRequested(allFeeds());
void FeedsView::updateAllItems() {
m_sourceModel->updateAllFeeds();
}
void FeedsView::updateSelectedFeeds() {
emit feedsUpdateRequested(selectedFeeds());
}
void FeedsView::updateAllFeedsOnStartup() {
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) {
qDebug("Requesting update for all feeds on application startup.");
QTimer::singleShot(STARTUP_UPDATE_DELAY, this, SLOT(updateAllFeeds()));
}
}
void FeedsView::setSelectedFeedsClearStatus(int clear) {
m_sourceModel->markFeedsDeleted(selectedFeeds(), clear, 0);
updateCountsOfSelectedFeeds(true);
emit feedsNeedToBeReloaded(true);
}
void FeedsView::setAllFeedsClearStatus(int clear) {
m_sourceModel->markFeedsDeleted(allFeeds(), clear, 0);
updateCountsOfAllFeeds(true);
emit feedsNeedToBeReloaded(true);
void FeedsView::updateSelectedItems() {
m_sourceModel->updateFeeds(selectedFeeds());
}
void FeedsView::clearSelectedFeeds() {
setSelectedFeedsClearStatus(1);
m_sourceModel->markItemCleared(selectedItem(), false);
}
void FeedsView::clearAllFeeds() {
setAllFeedsClearStatus(1);
}
void FeedsView::addNewCategory() {
if (!qApp->feedUpdateLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot add standard category"),
tr("You cannot add new standard category now because another critical operation is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm(), true);
return;
}
QPointer<FormCategoryDetails> form_pointer = new FormCategoryDetails(m_sourceModel, this);
form_pointer.data()->exec(NULL, selectedItem());
delete form_pointer.data();
// Changes are done, unlock the update master lock.
qApp->feedUpdateLock()->unlock();
}
void FeedsView::editCategory(Category *category) {
QPointer<FormCategoryDetails> form_pointer = new FormCategoryDetails(m_sourceModel, this);
form_pointer.data()->exec(category, NULL);
delete form_pointer.data();
}
void FeedsView::addNewFeed() {
if (!qApp->feedUpdateLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot add standard feed"),
tr("You cannot add new standard feed now because another critical operation is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm(), true);
return;
}
QPointer<FormFeedDetails> form_pointer = new FormFeedDetails(m_sourceModel, this);
form_pointer.data()->exec(NULL, selectedItem());
delete form_pointer.data();
// Changes are done, unlock the update master lock.
qApp->feedUpdateLock()->unlock();
}
void FeedsView::editFeed(Feed *feed) {
QPointer<FormFeedDetails> form_pointer = new FormFeedDetails(m_sourceModel, this);
form_pointer.data()->exec(feed, NULL);
delete form_pointer.data();
}
void FeedsView::receiveMessageCountsChange(FeedsSelection::SelectionMode mode,
bool total_msg_count_changed,
bool any_msg_restored) {
// If the change came from recycle bin mode, then:
// a) total count of message was changed AND no message was restored - some messages
// were permanently deleted from recycle bin --> we need to update counts of
// just recycle bin, including total counts.
// b) total count of message was changed AND some message was restored - some messages
// were restored --> we need to update counts from all items and bin, including total counts.
// c) total count of message was not changed - state of some messages was switched, no
// deletings or restorings were made --> update counts of just recycle bin, excluding total counts.
//
// If the change came from feed mode, then:
// a) total count of message was changed - some messages were deleted --> we need to update
// counts of recycle bin, including total counts and update counts of selected feeds, including
// total counts.
// b) total count of message was not changed - some messages switched state --> we need to update
// counts of just selected feeds.
if (mode == FeedsSelection::MessagesFromRecycleBin) {
if (total_msg_count_changed) {
if (any_msg_restored) {
updateCountsOfAllFeeds(true);
}
else {
updateCountsOfRecycleBin(true);
}
}
else {
updateCountsOfRecycleBin(false);
}
}
else {
updateCountsOfSelectedFeeds(total_msg_count_changed);
}
invalidateReadFeedsFilter();
m_sourceModel->markItemCleared(m_sourceModel->rootItem(), false);
}
void FeedsView::editSelectedItem() {
@ -297,19 +170,19 @@ void FeedsView::editSelectedItem() {
qApp->showGuiMessage(tr("Cannot edit item"),
tr("Selected item cannot be edited because another critical operation is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm(), true);
// Thus, cannot delete and quit the method.
return;
}
Category *category;
Feed *feed;
if ((category = selectedCategory()) != NULL) {
editCategory(category);
if (selectedItem()->canBeEdited()) {
selectedItem()->editViaGui();
}
else if ((feed = selectedFeed()) != NULL) {
editFeed(feed);
else {
qApp->showGuiMessage(tr("Cannot edit item"),
tr("Selected item cannot be edited, this is not (yet?) supported."),
QSystemTrayIcon::Warning,
qApp->mainForm(),
true);
}
// Changes are done, unlock the update master lock.
@ -337,158 +210,74 @@ void FeedsView::deleteSelectedItem() {
return;
}
if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, tr("Delete feed/category"),
tr("You are about to delete selected feed or category."), tr("Do you really want to delete selected item?"),
QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No) {
// User changed his mind.
qApp->feedUpdateLock()->unlock();
return;
}
RootItem *selected_item = selectedItem();
if (m_sourceModel->removeItem(m_proxyModel->mapToSource(current_index))) {
// Item WAS removed, update counts.
notifyWithCounts();
}
else {
// Item WAS NOT removed, either database-related error occurred
// or update is undergoing.
qApp->showGuiMessage(tr("Deletion of item failed."),
tr("Selected item was not deleted due to error."),
QSystemTrayIcon::Warning, qApp->mainForm(), true);
if (selected_item != NULL) {
if (selected_item->canBeDeleted()) {
// Ask user first.
if (MessageBox::show(qApp->mainForm(),
QMessageBox::Question,
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::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No) {
// User refused.
qApp->feedUpdateLock()->unlock();
return;
}
// We have deleteable item selected, remove it via GUI.
if (!selected_item->deleteViaGui()) {
qApp->showGuiMessage(tr("Cannot delete \"%1\"").arg(selected_item->title()),
tr("This item cannot be deleted because something critically failed. Submit bug report."),
QSystemTrayIcon::Critical,
qApp->mainForm(),
true);
}
}
else {
qApp->showGuiMessage(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::Critical,
qApp->mainForm(),
true);
}
}
// Changes are done, unlock the update master lock.
qApp->feedUpdateLock()->unlock();
}
void FeedsView::markSelectedFeedsReadStatus(int read) {
m_sourceModel->markFeedsRead(selectedFeeds(), read);
updateCountsOfSelectedFeeds(false);
emit feedsNeedToBeReloaded(read == 1);
void FeedsView::markSelectedItemReadStatus(RootItem::ReadStatus read) {
m_sourceModel->markItemRead(selectedItem(), read);
}
void FeedsView::markSelectedFeedsRead() {
markSelectedFeedsReadStatus(1);
void FeedsView::markSelectedItemRead() {
markSelectedItemReadStatus(RootItem::Read);
}
void FeedsView::markSelectedFeedsUnread() {
markSelectedFeedsReadStatus(0);
void FeedsView::markSelectedItemUnread() {
markSelectedItemReadStatus(RootItem::Unread);
}
void FeedsView::markAllFeedsReadStatus(int read) {
m_sourceModel->markFeedsRead(allFeeds(), read);
updateCountsOfAllFeeds(false);
emit feedsNeedToBeReloaded(read == 1);
void FeedsView::markAllItemsReadStatus(RootItem::ReadStatus read) {
m_sourceModel->markItemRead(m_sourceModel->rootItem(), read);
}
void FeedsView::markAllFeedsRead() {
markAllFeedsReadStatus(1);
void FeedsView::markAllItemsRead() {
markAllItemsReadStatus(RootItem::Read);
}
void FeedsView::fetchMetadataForSelectedFeed() {
Feed *selected_feed = selectedFeed();
if (selected_feed != NULL) {
selected_feed->fetchMetadataForItself();
m_sourceModel->reloadChangedLayout(QModelIndexList() << m_proxyModel->mapToSource(selectionModel()->selectedRows(0).at(0)));
}
}
void FeedsView::clearAllReadMessages() {
m_sourceModel->markFeedsDeleted(allFeeds(), 1, 1);
}
void FeedsView::openSelectedFeedsInNewspaperMode() {
QList<Message> messages = m_sourceModel->messagesForFeeds(selectedFeeds());
void FeedsView::openSelectedItemsInNewspaperMode() {
QList<Message> messages = m_sourceModel->messagesForItem(selectedItem());
if (!messages.isEmpty()) {
emit openMessagesInNewspaperView(messages);
QTimer::singleShot(0, this, SLOT(markSelectedFeedsRead()));
QTimer::singleShot(0, this, SLOT(markSelectedItemRead()));
}
}
void FeedsView::emptyRecycleBin() {
if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, tr("Permanently delete messages"),
tr("You are about to permanenty delete all messages from your recycle bin."),
tr("Do you really want to empty your recycle bin?"),
QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
m_sourceModel->recycleBin()->empty();
updateCountsOfSelectedFeeds(true);
emit feedsNeedToBeReloaded(true);
}
}
void FeedsView::restoreRecycleBin() {
m_sourceModel->recycleBin()->restore();
updateCountsOfAllFeeds(true);
emit feedsNeedToBeReloaded(true);
}
void FeedsView::updateCountsOfSelectedFeeds(bool update_total_too) {
foreach (Feed *feed, selectedFeeds()) {
feed->updateCounts(update_total_too);
}
QModelIndexList selected_indexes = m_proxyModel->mapListToSource(selectionModel()->selectedRows());
if (update_total_too) {
// Number of items in recycle bin has changed.
m_sourceModel->recycleBin()->updateCounts(true);
// We need to refresh data for recycle bin too.
selected_indexes.append(m_sourceModel->indexForItem(m_sourceModel->recycleBin()));
}
// Make sure that selected view reloads changed indexes.
m_sourceModel->reloadChangedLayout(selected_indexes);
notifyWithCounts();
}
void FeedsView::updateCountsOfRecycleBin(bool update_total_too) {
m_sourceModel->recycleBin()->updateCounts(update_total_too);
m_sourceModel->reloadChangedLayout(QModelIndexList() << m_sourceModel->indexForItem(m_sourceModel->recycleBin()));
notifyWithCounts();
}
void FeedsView::updateCountsOfAllFeeds(bool update_total_too) {
foreach (Feed *feed, allFeeds()) {
feed->updateCounts(update_total_too);
}
if (update_total_too) {
// Number of items in recycle bin has changed.
m_sourceModel->recycleBin()->updateCounts(true);
}
// Make sure that all views reloads its data.
m_sourceModel->reloadWholeLayout();
notifyWithCounts();
}
void FeedsView::updateCountsOfParticularFeed(Feed *feed, bool update_total_too) {
QModelIndex index = m_sourceModel->indexForItem(feed);
if (index.isValid()) {
feed->updateCounts(update_total_too, false);
m_sourceModel->reloadChangedLayout(QModelIndexList() << index);
}
invalidateReadFeedsFilter();
notifyWithCounts();
}
void FeedsView::selectNextItem() {
// NOTE: Bug #122 requested to not expand in here.
/*
if (!isExpanded(currentIndex())) {
expand(currentIndex());
}
*/
QModelIndex index_next = moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier);
if (index_next.isValid()) {
@ -500,62 +289,97 @@ void FeedsView::selectNextItem() {
void FeedsView::selectPreviousItem() {
QModelIndex index_previous = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier);
// NOTE: Bug #122 requested to not expand in here.
/*
if (!isExpanded(index_previous)) {
expand(index_previous);
index_previous = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier);
}
*/
if (index_previous.isValid()) {
setCurrentIndex(index_previous);
setFocus();
}
}
void FeedsView::initializeContextMenuCategories() {
m_contextMenuCategories = new QMenu(tr("Context menu for categories"), this);
void FeedsView::switchVisibility() {
setVisible(!isVisible());
}
QMenu *FeedsView::initializeContextMenuCategories(RootItem *clicked_item) {
if (m_contextMenuCategories == NULL) {
m_contextMenuCategories = new QMenu(tr("Context menu for categories"), this);
}
else {
m_contextMenuCategories->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
m_contextMenuCategories->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedFeeds <<
qApp->mainForm()->m_ui->m_actionEditSelectedFeedCategory <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
qApp->mainForm()->m_ui->m_actionEditSelectedItem <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsUnread <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedFeedCategory);
m_contextMenuCategories->addSeparator();
m_contextMenuCategories->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionAddCategory <<
qApp->mainForm()->m_ui->m_actionAddFeed);
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem);
if (!specific_actions.isEmpty()) {
m_contextMenuCategories->addSeparator();
m_contextMenuCategories->addActions(specific_actions);
}
return m_contextMenuCategories;
}
void FeedsView::initializeContextMenuFeeds() {
m_contextMenuFeeds = new QMenu(tr("Context menu for categories"), this);
QMenu *FeedsView::initializeContextMenuFeeds(RootItem *clicked_item) {
if (m_contextMenuFeeds == NULL) {
m_contextMenuFeeds = new QMenu(tr("Context menu for categories"), this);
}
else {
m_contextMenuFeeds->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
m_contextMenuFeeds->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedFeeds <<
qApp->mainForm()->m_ui->m_actionEditSelectedFeedCategory <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
qApp->mainForm()->m_ui->m_actionEditSelectedItem <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsUnread <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedFeedCategory <<
qApp->mainForm()->m_ui->m_actionFetchFeedMetadata);
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedItem);
if (!specific_actions.isEmpty()) {
m_contextMenuFeeds->addSeparator();
m_contextMenuFeeds->addActions(specific_actions);
}
return m_contextMenuFeeds;
}
void FeedsView::initializeContextMenuEmptySpace() {
m_contextMenuEmptySpace = new QMenu(tr("Context menu for empty space"), this);
m_contextMenuEmptySpace->addAction(qApp->mainForm()->m_ui->m_actionUpdateAllFeeds);
m_contextMenuEmptySpace->addSeparator();
m_contextMenuEmptySpace->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionAddCategory <<
qApp->mainForm()->m_ui->m_actionAddFeed);
QMenu *FeedsView::initializeContextMenuEmptySpace() {
if (m_contextMenuEmptySpace == NULL) {
m_contextMenuEmptySpace = new QMenu(tr("Context menu for empty space"), this);
m_contextMenuEmptySpace->addAction(qApp->mainForm()->m_ui->m_actionUpdateAllItems);
m_contextMenuEmptySpace->addSeparator();
}
return m_contextMenuEmptySpace;
}
void FeedsView::initializeContextMenuRecycleBin() {
m_contextMenuRecycleBin = new QMenu(tr("Context menu for recycle bin"), this);
m_contextMenuRecycleBin->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionRestoreRecycleBin <<
qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin <<
qApp->mainForm()->m_ui->m_actionEmptyRecycleBin);
QMenu *FeedsView::initializeContextMenuOtherItem(RootItem *clicked_item) {
if (m_contextMenuOtherItems == NULL) {
m_contextMenuOtherItems = new QMenu(tr("Context menu for other items"), this);
}
else {
m_contextMenuOtherItems->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
if (!specific_actions.isEmpty()) {
m_contextMenuOtherItems->addSeparator();
m_contextMenuOtherItems->addActions(specific_actions);
}
else {
m_contextMenuOtherItems->addAction(qApp->mainForm()->m_ui->m_actionNoActions);
}
return m_contextMenuOtherItems;
}
void FeedsView::setupAppearance() {
@ -586,9 +410,6 @@ void FeedsView::setupAppearance() {
setItemDelegate(new StyledItemDelegateWithoutFocus(this));
header()->setStretchLastSection(false);
header()->setSortIndicatorShown(false);
sortByColumn(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnFeeds)).toInt(),
static_cast<Qt::SortOrder>(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds)).toInt()));
}
void FeedsView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
@ -596,8 +417,8 @@ void FeedsView::selectionChanged(const QItemSelection &selected, const QItemSele
m_proxyModel->setSelectedItem(selected_item);
QTreeView::selectionChanged(selected, deselected);
emit feedsSelected(FeedsSelection(selected_item));
invalidateReadFeedsFilter();
emit itemSelected(selected_item);
m_proxyModel->invalidateReadFeedsFilter();
}
void FeedsView::keyPressEvent(QKeyEvent *event) {
@ -615,41 +436,21 @@ void FeedsView::contextMenuEvent(QContextMenuEvent *event) {
QModelIndex mapped_index = model()->mapToSource(clicked_index);
RootItem *clicked_item = sourceModel()->itemForIndex(mapped_index);
if (clicked_item->kind() == RootItem::Cattegory) {
if (clicked_item->kind() == RootItemKind::Category) {
// Display context menu for categories.
if (m_contextMenuCategories == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenuCategories();
}
m_contextMenuCategories->exec(event->globalPos());
initializeContextMenuCategories(clicked_item)->exec(event->globalPos());
}
else if (clicked_item->kind() == RootItem::Feeed) {
else if (clicked_item->kind() == RootItemKind::Feed) {
// Display context menu for feeds.
if (m_contextMenuFeeds == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenuFeeds();
}
m_contextMenuFeeds->exec(event->globalPos());
initializeContextMenuFeeds(clicked_item)->exec(event->globalPos());
}
else if (clicked_item->kind() == RootItem::Bin) {
// Display context menu for recycle bin.
if (m_contextMenuRecycleBin == NULL) {
initializeContextMenuRecycleBin();
}
m_contextMenuRecycleBin->exec(event->globalPos());
else {
initializeContextMenuOtherItem(clicked_item)->exec(event->globalPos());
}
}
else {
// Display menu for empty space.
if (m_contextMenuEmptySpace == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenuEmptySpace();
}
m_contextMenuEmptySpace->exec(event->globalPos());
initializeContextMenuEmptySpace()->exec(event->globalPos());
}
}
@ -666,3 +467,13 @@ void FeedsView::validateItemAfterDragDrop(const QModelIndex &source_index) {
setCurrentIndex(mapped);
}
}
void FeedsView::onItemExpandRequested(const QList<RootItem*> &items, bool exp) {
foreach (RootItem *item, items) {
QModelIndex source_index = m_sourceModel->indexForItem(item);
QModelIndex proxy_index = m_proxyModel->mapFromSource(source_index);
setExpanded(proxy_index, !exp);
setExpanded(proxy_index, exp);
}
}

View File

@ -18,19 +18,16 @@
#ifndef FEEDSVIEW_H
#define FEEDSVIEW_H
#include <QStyledItemDelegate>
#include <QTreeView>
#include "core/messagesmodel.h"
#include "core/feedsmodel.h"
#include "core/feedsselection.h"
#include "miscellaneous/settings.h"
#include <QStyledItemDelegate>
class FeedsProxyModel;
class Feed;
class Category;
class QTimer;
class FeedsView : public QTreeView {
Q_OBJECT
@ -59,96 +56,62 @@ class FeedsView : public QTreeView {
// Returns pointers to selected feed/category if they are really
// selected.
RootItem *selectedItem() const;
Category *selectedCategory() const;
Feed *selectedFeed() const;
RecycleBin *selectedRecycleBin() const;
// Saves/loads expand states of all nodes (feeds/categories) of the list to/from settings.
void saveExpandedStates();
void loadExpandedStates();
public slots:
void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false);
void expandCollapseCurrentItem();
void fetchMetadataForSelectedFeed();
// Feed updating.
void updateAllFeeds();
void updateAllFeedsOnStartup();
void updateSelectedFeeds();
void updateAllItems();
void updateSelectedItems();
// Feed read/unread manipulators.
void markSelectedFeedsReadStatus(int read);
void markSelectedFeedsRead();
void markSelectedFeedsUnread();
void markAllFeedsReadStatus(int read);
void markAllFeedsRead();
void markSelectedItemRead();
void markSelectedItemUnread();
void markAllItemsRead();
// Newspaper accessors.
void openSelectedFeedsInNewspaperMode();
// Recycle bin operators.
void emptyRecycleBin();
void restoreRecycleBin();
void openSelectedItemsInNewspaperMode();
// Feed clearers.
void setSelectedFeedsClearStatus(int clear);
void setAllFeedsClearStatus(int clear);
void clearSelectedFeeds();
void clearAllFeeds();
void clearAllReadMessages();
// Base manipulators.
void editSelectedItem();
void deleteSelectedItem();
// Standard category manipulators.
void addNewCategory();
void editCategory(Category *category);
// Standard feed manipulators.
void addNewFeed();
void editFeed(Feed *feed);
// Is called when counts of messages are changed externally,
// typically from message view.
void receiveMessageCountsChange(FeedsSelection::SelectionMode mode, bool total_msg_count_changed, bool any_msg_restored);
// Reloads counts for selected feeds.
void updateCountsOfSelectedFeeds(bool update_total_too);
// Reloads counts of recycle bin.
void updateCountsOfRecycleBin(bool update_total_too);
// Reloads counts for all feeds.
void updateCountsOfAllFeeds(bool update_total_too);
// Reloads counts for particular feed.
void updateCountsOfParticularFeed(Feed *feed, bool update_total_too);
// Notifies other components about messages
// counts.
inline void notifyWithCounts() {
emit messageCountsChanged(m_sourceModel->countOfUnreadMessages(),
m_sourceModel->countOfAllMessages(),
m_sourceModel->hasAnyFeedNewMessages());
}
// Selects next/previous item (feed/category) in the list.
void selectNextItem();
void selectPreviousItem();
// Switches visibility of the widget.
void switchVisibility() {
setVisible(!isVisible());
}
void switchVisibility();
protected:
signals:
// Emitted if user selects new feeds.
void itemSelected(RootItem *item);
// Requests opening of given messages in newspaper mode.
void openMessagesInNewspaperView(const QList<Message> &messages);
private slots:
void markSelectedItemReadStatus(RootItem::ReadStatus read);
void markAllItemsReadStatus(RootItem::ReadStatus read);
void saveSortState(int column, Qt::SortOrder order);
void validateItemAfterDragDrop(const QModelIndex &source_index);
void onItemExpandRequested(const QList<RootItem*> &items, bool exp);
private:
// Initializes context menus.
void initializeContextMenuCategories();
void initializeContextMenuFeeds();
void initializeContextMenuEmptySpace();
void initializeContextMenuRecycleBin();
QMenu *initializeContextMenuCategories(RootItem *clicked_item);
QMenu *initializeContextMenuFeeds(RootItem *clicked_item);
QMenu *initializeContextMenuEmptySpace();
QMenu *initializeContextMenuOtherItem(RootItem *clicked_item);
// Sets up appearance of this widget.
void setupAppearance();
@ -162,31 +125,10 @@ class FeedsView : public QTreeView {
// Show custom context menu.
void contextMenuEvent(QContextMenuEvent *event);
private slots:
void saveSortState(int column, Qt::SortOrder order);
void validateItemAfterDragDrop(const QModelIndex &source_index);
signals:
// Emitted if user/application requested updating of some feeds.
void feedsUpdateRequested(const QList<Feed*> feeds);
// Emitted if counts of messages are changed.
void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages);
// Emitted if currently selected feeds needs to be reloaded.
void feedsNeedToBeReloaded(bool mark_current_index_read);
// Emitted if user selects new feeds.
void feedsSelected(const FeedsSelection &selection);
// Requests opening of given messages in newspaper mode.
void openMessagesInNewspaperView(const QList<Message> &messages);
private:
QMenu *m_contextMenuCategories;
QMenu *m_contextMenuFeeds;
QMenu *m_contextMenuEmptySpace;
QMenu *m_contextMenuRecycleBin;
QMenu *m_contextMenuOtherItems;
FeedsModel *m_sourceModel;
FeedsProxyModel *m_proxyModel;

0
src/gui/labelwithstatus.cpp Normal file → Executable file
View File

View File

@ -103,7 +103,7 @@ void MessagesToolBar::handleMessageHighlighterChange(QAction *action) {
m_btnMessageHighlighter->setIcon(action->icon());
m_btnMessageHighlighter->setToolTip(action->text());
emit messageFilterChanged(action->data().value<MessagesModel::MessageFilter>());
emit messageFilterChanged(action->data().value<MessagesModel::MessageHighlighter>());
}
void MessagesToolBar::initializeSearchBox() {

2
src/gui/messagestoolbar.h Normal file → Executable file
View File

@ -56,7 +56,7 @@ class MessagesToolBar : public BaseToolBar {
void messageSearchPatternChanged(const QString &pattern);
// Emitted if message filter is changed.
void messageFilterChanged(MessagesModel::MessageFilter filter);
void messageFilterChanged(MessagesModel::MessageHighlighter filter);
private slots:
// Called when highlighter gets changed.

View File

@ -28,6 +28,7 @@
#include <QKeyEvent>
#include <QScrollBar>
#include <QTimer>
#include <QMenu>
@ -78,8 +79,7 @@ void MessagesView::reloadSelections(bool mark_current_index_read) {
QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes);
// Reload the model now.
m_sourceModel->select();
m_sourceModel->fetchAll();
m_sourceModel->fetchAllData();
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
@ -141,23 +141,18 @@ void MessagesView::contextMenuEvent(QContextMenuEvent *event) {
return;
}
if (m_contextMenu == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenu();
}
if (sourceModel()->loadedSelection().mode() == FeedsSelection::MessagesFromRecycleBin) {
m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin);
}
else {
m_contextMenu->removeAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin);
}
// Context menu is not initialized, initialize.
initializeContextMenu();
m_contextMenu->exec(event->globalPos());
}
void MessagesView::initializeContextMenu() {
m_contextMenu = new QMenu(tr("Context menu for messages"), this);
if (m_contextMenu == NULL) {
m_contextMenu = new QMenu(tr("Context menu for messages"), this);
}
m_contextMenu->clear();
m_contextMenu->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionSendMessageViaEmail <<
qApp->mainForm()->m_ui->m_actionOpenSelectedSourceArticlesExternally <<
@ -166,8 +161,11 @@ void MessagesView::initializeContextMenu() {
qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsUnread <<
qApp->mainForm()->m_ui->m_actionSwitchImportanceOfSelectedMessages <<
qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages <<
qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin);
qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages);
if (m_sourceModel->loadedItem() != NULL && m_sourceModel->loadedItem()->kind() == RootItemKind::Bin) {
m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessages);
}
}
void MessagesView::mousePressEvent(QMouseEvent *event) {
@ -201,10 +199,6 @@ void MessagesView::mousePressEvent(QMouseEvent *event) {
}
}
void MessagesView::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
QTreeView::currentChanged(current, previous);
}
void MessagesView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
QModelIndexList selected_rows = selectionModel()->selectedRows();
QModelIndex current_index = currentIndex();
@ -218,7 +212,7 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS
if (!m_batchUnreadSwitch) {
// Set this message as read only if current item
// wasn't changed by "mark selected messages unread" action.
m_sourceModel->setMessageRead(mapped_current_index.row(), 1);
m_sourceModel->setMessageRead(mapped_current_index.row(), RootItem::Read);
}
emit currentMessagesChanged(QList<Message>() << m_sourceModel->messageAt(m_proxyModel->mapToSource(selected_rows.at(0)).row()));
@ -234,8 +228,8 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS
QTreeView::selectionChanged(selected, deselected);
}
void MessagesView::loadFeeds(const FeedsSelection &selection) {
m_sourceModel->loadMessages(selection);
void MessagesView::loadItem(RootItem *item) {
m_sourceModel->loadMessages(item);
int col = qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnMessages)).toInt();
Qt::SortOrder ord = static_cast<Qt::SortOrder>(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderMessages)).toInt());
@ -264,8 +258,8 @@ void MessagesView::openSelectedSourceMessagesExternally() {
}
// Finally, mark opened messages as read.
if (selectionModel()->selectedRows().size() > 1) {
markSelectedMessagesRead();
if (!selectionModel()->selectedRows().isEmpty()) {
QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead()));
}
}
@ -285,8 +279,8 @@ void MessagesView::openSelectedSourceMessagesInternally() {
}
// Finally, mark opened messages as read.
if (selectionModel()->selectedRows().size() > 1) {
markSelectedMessagesRead();
if (!selectionModel()->selectedRows().isEmpty()) {
QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead()));
}
}
@ -308,7 +302,7 @@ void MessagesView::openSelectedMessagesInternally() {
emit openMessagesInNewspaperView(messages);
// Finally, mark opened messages as read.
markSelectedMessagesRead();
QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead()));
}
}
@ -326,14 +320,14 @@ void MessagesView::sendSelectedMessageViaEmail() {
}
void MessagesView::markSelectedMessagesRead() {
setSelectedMessagesReadStatus(1);
setSelectedMessagesReadStatus(RootItem::Read);
}
void MessagesView::markSelectedMessagesUnread() {
setSelectedMessagesReadStatus(0);
setSelectedMessagesReadStatus(RootItem::Unread);
}
void MessagesView::setSelectedMessagesReadStatus(int read) {
void MessagesView::setSelectedMessagesReadStatus(RootItem::ReadStatus read) {
QModelIndex current_index = selectionModel()->currentIndex();
if (!current_index.isValid()) {
@ -350,7 +344,7 @@ void MessagesView::setSelectedMessagesReadStatus(int read) {
selected_indexes = m_proxyModel->mapListFromSource(mapped_indexes, true);
current_index = m_proxyModel->mapFromSource(m_sourceModel->index(mapped_current_index.row(), mapped_current_index.column()));
if (read == 0) {
if (read == RootItem::Unread) {
// User selected to mark some messages as unread, if one
// of them will be marked as current, then it will be read again.
m_batchUnreadSwitch = true;
@ -372,7 +366,7 @@ void MessagesView::deleteSelectedMessages() {
QModelIndexList selected_indexes = selectionModel()->selectedRows();
QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes);
m_sourceModel->setBatchMessagesDeleted(mapped_indexes, 1);
m_sourceModel->setBatchMessagesDeleted(mapped_indexes);
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
int row_count = m_sourceModel->rowCount();
@ -400,24 +394,21 @@ void MessagesView::restoreSelectedMessages() {
QModelIndexList selected_indexes = selectionModel()->selectedRows();
QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes);
if (m_sourceModel->setBatchMessagesRestored(mapped_indexes)) {
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
m_sourceModel->setBatchMessagesRestored(mapped_indexes);
sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
int row_count = m_sourceModel->rowCount();
if (row_count > 0) {
QModelIndex last_item = current_index.row() < row_count ?
m_proxyModel->index(current_index.row(),
MSG_DB_TITLE_INDEX) :
m_proxyModel->index(row_count - 1,
MSG_DB_TITLE_INDEX);
int row_count = m_sourceModel->rowCount();
if (row_count > 0) {
QModelIndex last_item = current_index.row() < row_count ?
m_proxyModel->index(current_index.row(), MSG_DB_TITLE_INDEX) :
m_proxyModel->index(row_count - 1, MSG_DB_TITLE_INDEX);
setCurrentIndex(last_item);
scrollTo(last_item);
reselectIndexes(QModelIndexList() << last_item);
}
else {
emit currentMessagesRemoved();
}
setCurrentIndex(last_item);
scrollTo(last_item);
reselectIndexes(QModelIndexList() << last_item);
}
else {
emit currentMessagesRemoved();
}
}
@ -439,20 +430,23 @@ void MessagesView::switchSelectedMessagesImportance() {
current_index = m_proxyModel->mapFromSource(m_sourceModel->index(mapped_current_index.row(),
mapped_current_index.column()));
m_batchUnreadSwitch = true;
setCurrentIndex(current_index);
scrollTo(current_index);
reselectIndexes(selected_indexes);
m_batchUnreadSwitch = false;
}
void MessagesView::reselectIndexes(const QModelIndexList &indexes) {
QItemSelection selection;
if (indexes.size() < RESELECT_MESSAGE_THRESSHOLD) {
QItemSelection selection;
foreach (const QModelIndex &index, indexes) {
// TODO: THIS IS very slow. Try to select 4000 messages and hit "mark as read" button.
selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
foreach (const QModelIndex &index, indexes) {
selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
}
selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
void MessagesView::selectNextItem() {
@ -475,6 +469,30 @@ void MessagesView::selectPreviousItem() {
}
}
void MessagesView::selectNextUnreadItem() {
// FIXME: Use this to solve #112.
QModelIndexList selected_rows = selectionModel()->selectedRows();
int active_row;
if (!selected_rows.isEmpty()) {
// Okay, something is selected, start from it.
active_row = selected_rows.at(0).row();
}
else {
active_row = 0;
}
QModelIndex next_unread = m_proxyModel->getNextPreviousUnreadItemIndex(active_row);
if (next_unread.isValid()) {
// We found unread message, mark it.
setCurrentIndex(next_unread);
selectionModel()->select(next_unread, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
setFocus();
}
}
void MessagesView::searchMessages(const QString &pattern) {
m_proxyModel->setFilterRegExp(pattern);
@ -487,8 +505,8 @@ void MessagesView::searchMessages(const QString &pattern) {
}
}
void MessagesView::filterMessages(MessagesModel::MessageFilter filter) {
m_sourceModel->filterMessages(filter);
void MessagesView::filterMessages(MessagesModel::MessageHighlighter filter) {
m_sourceModel->highlightMessages(filter);
}
void MessagesView::adjustColumns() {
@ -531,6 +549,8 @@ void MessagesView::adjustColumns() {
hideColumn(MSG_DB_CONTENTS_INDEX);
hideColumn(MSG_DB_PDELETED_INDEX);
hideColumn(MSG_DB_ENCLOSURES_INDEX);
hideColumn(MSG_DB_ACCOUNT_ID_INDEX);
hideColumn(MSG_DB_CUSTOM_ID_INDEX);
qDebug("Adjusting column resize modes for MessagesView.");
}

View File

@ -20,7 +20,7 @@
#include "core/messagesmodel.h"
#include "core/feedsselection.h"
#include "services/abstract/rootitem.h"
#include <QTreeView>
#include <QHeaderView>
@ -47,9 +47,6 @@ class MessagesView : public QTreeView {
return m_sourceModel;
}
// Creates needed connections.
void createConnections();
public slots:
void keyboardSearch(const QString &search);
@ -60,7 +57,7 @@ class MessagesView : public QTreeView {
void reloadSelections(bool mark_current_index_read);
// Loads un-deleted messages from selected feeds.
void loadFeeds(const FeedsSelection &selection);
void loadItem(RootItem *item);
// Message manipulators.
void openSelectedSourceMessagesExternally();
@ -70,7 +67,7 @@ class MessagesView : public QTreeView {
void sendSelectedMessageViaEmail();
// Works with SELECTED messages only.
void setSelectedMessagesReadStatus(int read);
void setSelectedMessagesReadStatus(RootItem::ReadStatus read);
void markSelectedMessagesRead();
void markSelectedMessagesUnread();
void switchSelectedMessagesImportance();
@ -79,10 +76,11 @@ class MessagesView : public QTreeView {
void selectNextItem();
void selectPreviousItem();
void selectNextUnreadItem();
// Searchs the visible message according to given pattern.
void searchMessages(const QString &pattern);
void filterMessages(MessagesModel::MessageFilter filter);
void filterMessages(MessagesModel::MessageHighlighter filter);
private slots:
// Marks given indexes as selected.
@ -94,20 +92,6 @@ class MessagesView : public QTreeView {
// Saves current sort state.
void saveSortState(int column, Qt::SortOrder order);
protected:
// Initializes context menu.
void initializeContextMenu();
// Sets up appearance.
void setupAppearance();
// Event reimplementations.
void contextMenuEvent(QContextMenuEvent *event);
void mousePressEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void currentChanged(const QModelIndex &current, const QModelIndex &previous);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
signals:
// Link/message openers.
void openLinkNewTab(const QString &link);
@ -119,6 +103,21 @@ class MessagesView : public QTreeView {
void currentMessagesRemoved();
private:
// Creates needed connections.
void createConnections();
// Initializes context menu.
void initializeContextMenu();
// Sets up appearance.
void setupAppearance();
// Event reimplementations.
void contextMenuEvent(QContextMenuEvent *event);
void mousePressEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
QMenu *m_contextMenu;
MessagesProxyModel *m_proxyModel;

7
src/gui/notifications/notification.cpp Normal file → Executable file
View File

@ -66,10 +66,14 @@ Notification::~Notification() {
qDebug("Destroying Notification instance.");
}
bool Notification::areNotificationsActivated() {
bool Notification::areFancyNotificationsEnabled() {
return qApp->settings()->value(GROUP(GUI), SETTING(GUI::UseFancyNotifications)).toBool();
}
bool Notification::areNotificationsEnabled() {
return qApp->settings()->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool();
}
void Notification::notify(const QString &text, const QString &title, const QIcon &icon,
QObject *invokation_target, const char *invokation_slot) {
cancel();
@ -98,7 +102,6 @@ void Notification::notify(const QString &text, const QString &title, const QIcon
argument_list << hints; // hints
argument_list << (int)-1; // timeout in ms
// TODO: obrazky https://dev.visucore.com/bitcoin/doxygen/notificator_8cpp_source.html
QDBusMessage response = m_dBusInterface->callWithArgumentList(QDBus::AutoDetect, "Notify", argument_list);
if (response.arguments().size() == 1) {

3
src/gui/notifications/notification.h Normal file → Executable file
View File

@ -35,7 +35,8 @@ class Notification : public QWidget {
explicit Notification();
virtual ~Notification();
static bool areNotificationsActivated();
static bool areFancyNotificationsEnabled();
static bool areNotificationsEnabled();
public slots:
// Main methods for using the netofication.

View File

@ -124,7 +124,7 @@ void SystemTrayIcon::setNumber(int number, bool any_new_message) {
QPixmap background(m_plainPixmap);
QPainter tray_painter;
// TODO: Here draw different background instead of different color of number.
// FIXME: Here draw different background instead of different color of number.
tray_painter.begin(&background);
tray_painter.setPen(any_new_message ? Qt::blue : Qt::black);
tray_painter.setRenderHint(QPainter::SmoothPixmapTransform, true);

View File

@ -71,7 +71,6 @@ void TabWidget::openMainMenu() {
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuView);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuFeeds);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuMessages);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuRecycleBin);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuWebBrowser);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuTools);
m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuHelp);

View File

@ -72,6 +72,9 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
// Register needed metatypes.
qRegisterMetaType<QList<RootItem*> >("QList<RootItem*>");
// Add an extra path for non-system icon themes and set current icon theme
// and skin.
qApp->icons()->setupSearchPaths();
@ -97,7 +100,7 @@ int main(int argc, char *argv[]) {
main_window.setWindowTitle(APP_LONG_NAME);
// Now is a good time to initialize dynamic keyboard shortcuts.
DynamicShortcuts::load(qApp->userActions());
DynamicShortcuts::load(qApp->userActions());
// Display main window.
if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MainWindowStartsHidden)).toBool() && SystemTrayIcon::isSystemTrayActivated()) {
@ -107,32 +110,33 @@ int main(int argc, char *argv[]) {
else {
qDebug("Showing the main window when the application is starting.");
main_window.show();
if (qApp->settings()->value(GROUP(General), SETTING(General::FirstRun)).toBool()) {
// This is the first time user runs this application.
qApp->settings()->setValue(GROUP(General), General::FirstRun, false);
if (MessageBox::show(&main_window, QMessageBox::Question, QObject::tr("Load initial feeds"),
QObject::tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME),
QObject::tr("Do you want to load initial set of feeds?"),
QString(), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
qApp->mainForm()->tabWidget()->feedMessageViewer()->loadInitialFeeds();
}
}
}
// Display tray icon if it is enabled and available.
if (SystemTrayIcon::isSystemTrayActivated()) {
qApp->showTrayIcon();
if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) {
QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup()));
}
}
// Load activated accounts.
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->loadActivatedServiceAccounts();
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadExpandedStates();
// Setup single-instance behavior.
QObject::connect(&application, SIGNAL(messageReceived(QString)), &application, SLOT(processExecutionMessage(QString)));
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1 %2.").arg(APP_NAME, APP_VERSION), QSystemTrayIcon::NoIcon);
if (qApp->isFirstRun() || qApp->isFirstRun(APP_VERSION)) {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n"
"version by clicking this popup notification.").arg(APP_LONG_NAME),
QSystemTrayIcon::NoIcon, 0, false, QIcon(), &main_window, SLOT(showAbout()));
}
else {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.").arg(APP_LONG_NAME), QSystemTrayIcon::NoIcon);
}
if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) {
QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup()));
}
// Enter global event loop.
return Application::exec();

View File

@ -28,6 +28,10 @@
#include "exceptions/applicationexception.h"
#include "adblock/adblockmanager.h"
#include "services/abstract/serviceroot.h"
#include "services/standard/standardserviceentrypoint.h"
#include "services/tt-rss/ttrssserviceentrypoint.h"
#include <QSessionManager>
#include <QThread>
#include <QProcess>
@ -35,7 +39,7 @@
Application::Application(const QString &id, int &argc, char **argv)
: QtSingleApplication(id, argc, argv),
m_updateFeedsLock(NULL), m_userActions(QList<QAction*>()), m_mainForm(NULL),
m_updateFeedsLock(NULL), m_feedServices(QList<ServiceEntryPoint*>()), m_userActions(QList<QAction*>()), m_mainForm(NULL),
m_trayIcon(NULL), m_settings(NULL), m_system(NULL), m_skins(NULL),
m_localization(NULL), m_icons(NULL), m_database(NULL), m_downloadManager(NULL), m_shouldRestart(false),
m_notification(NULL) {
@ -46,6 +50,17 @@ Application::Application(const QString &id, int &argc, char **argv)
Application::~Application() {
delete m_updateFeedsLock;
qDeleteAll(m_feedServices);
}
QList<ServiceEntryPoint*> Application::feedServices() {
if (m_feedServices.isEmpty()) {
// NOTE: All installed services create their entry points here.
m_feedServices.append(new StandardServiceEntryPoint());
m_feedServices.append(new TtRssServiceEntryPoint());
}
return m_feedServices;
}
QList<QAction*> Application::userActions() {
@ -56,6 +71,22 @@ QList<QAction*> Application::userActions() {
return m_userActions;
}
bool Application::isFirstRun() {
return settings()->value(GROUP(General), SETTING(General::FirstRun)).toBool();
}
bool Application::isFirstRun(const QString &version) {
return settings()->value(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, true).toBool();
}
void Application::eliminateFirstRun() {
settings()->setValue(GROUP(General), General::FirstRun, false);
}
void Application::eliminateFirstRun(const QString &version) {
settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, false);
}
IconFactory *Application::icons() {
if (m_icons == NULL) {
m_icons = new IconFactory(this);
@ -139,7 +170,8 @@ void Application::processExecutionMessage(const QString &message) {
SystemTrayIcon *Application::trayIcon() {
if (m_trayIcon == NULL) {
m_trayIcon = new SystemTrayIcon(APP_ICON_PATH, APP_ICON_PLAIN_PATH, m_mainForm);
connect(m_trayIcon, SIGNAL(shown()), m_mainForm->tabWidget()->feedMessageViewer()->feedsView(), SLOT(notifyWithCounts()));
connect(m_trayIcon, SIGNAL(shown()),
m_mainForm->tabWidget()->feedMessageViewer()->feedsView()->sourceModel(), SLOT(notifyWithCounts()));
}
return m_trayIcon;
@ -166,19 +198,25 @@ void Application::showGuiMessage(const QString &title, const QString &message,
QSystemTrayIcon::MessageIcon message_type, QWidget *parent,
bool show_at_least_msgbox, const QIcon &custom_icon,
QObject *invokation_target, const char *invokation_slot) {
if (Notification::areNotificationsActivated()) {
// Show OSD instead if tray icon bubble, depending on settings.
if (custom_icon.isNull()) {
notification()->notify(message, title, message_type, invokation_target, invokation_slot);
if (Notification::areNotificationsEnabled()) {
if (Notification::areFancyNotificationsEnabled()) {
// Show OSD instead if tray icon bubble, depending on settings.
if (custom_icon.isNull()) {
notification()->notify(message, title, message_type, invokation_target, invokation_slot);
}
else {
notification()->notify(message, title, custom_icon, invokation_target, invokation_slot);
}
return;
}
else {
notification()->notify(message, title, custom_icon, invokation_target, invokation_slot);
else if (SystemTrayIcon::isSystemTrayActivated()) {
trayIcon()->showMessage(title, message, message_type, TRAY_ICON_BUBBLE_TIMEOUT, invokation_target, invokation_slot);
return;
}
}
else if (SystemTrayIcon::isSystemTrayActivated()) {
trayIcon()->showMessage(title, message, message_type, TRAY_ICON_BUBBLE_TIMEOUT, invokation_target, invokation_slot);
}
else if (show_at_least_msgbox) {
if (show_at_least_msgbox) {
// Tray icon or OSD is not available, display simple text box.
MessageBox::show(parent, (QMessageBox::Icon) message_type, title, message);
}
@ -202,6 +240,9 @@ void Application::onSaveState(QSessionManager &manager) {
}
void Application::onAboutToQuit() {
eliminateFirstRun();
eliminateFirstRun(APP_VERSION);
// Make sure that we obtain close lock BEFORE even trying to quit the application.
bool locked_safely = feedUpdateLock()->tryLock(4 * CLOSE_LOCK_TIMEOUT);
@ -250,14 +291,6 @@ void Application::onAboutToQuit() {
}
}
bool Application::shouldRestart() const {
return m_shouldRestart;
}
void Application::setShouldRestart(bool shouldRestart) {
m_shouldRestart = shouldRestart;
}
void Application::restart() {
m_shouldRestart = true;
quit();

View File

@ -30,6 +30,7 @@
#include "gui/systemtrayicon.h"
#include "gui/notifications/notification.h"
#include "network-web/downloadmanager.h"
#include "services/abstract/serviceentrypoint.h"
#include <QList>
@ -54,8 +55,19 @@ class Application : public QtSingleApplication {
explicit Application(const QString &id, int &argc, char **argv);
virtual ~Application();
// List of all installed "feed service plugins", including obligatory
// "standard" service entry point.
QList<ServiceEntryPoint*> feedServices();
// Globally accessible actions.
QList<QAction*> userActions();
// Check whether this application starts for the first time (ever).
bool isFirstRun();
// Check whether GIVEN VERSION of the application starts for the first time.
bool isFirstRun(const QString &version);
inline SystemFactory *system() {
if (m_system == NULL) {
m_system = new SystemFactory(this);
@ -154,10 +166,8 @@ class Application : public QtSingleApplication {
return static_cast<Application*>(QCoreApplication::instance());
}
bool shouldRestart() const;
void setShouldRestart(bool shouldRestart);
public slots:
// Restarts the application.
void restart();
// Processes incoming message from another RSS Guard instance.
@ -170,6 +180,9 @@ class Application : public QtSingleApplication {
void onAboutToQuit();
private:
void eliminateFirstRun();
void eliminateFirstRun(const QString &version);
// This read-write lock is used by application on its close.
// Application locks this lock for WRITING.
// This means that if application locks that lock, then
@ -183,6 +196,7 @@ class Application : public QtSingleApplication {
// tries to lock the lock for writing), then no other
// action will be allowed to lock for reading.
Mutex *m_updateFeedsLock;
QList<ServiceEntryPoint*> m_feedServices;
QList<QAction*> m_userActions;
FormMain *m_mainForm;
SystemTrayIcon *m_trayIcon;

View File

@ -203,7 +203,9 @@ QSqlDatabase DatabaseFactory::sqliteInitializeInMemoryDatabase() {
copy_contents.exec(QString("ATTACH DATABASE '%1' AS 'storage';").arg(file_database.databaseName()));
// Copy all stuff.
QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") << QSL("FeedsData") << QSL("Messages");
// WARNING: All tables belong here.
QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") <<
QSL("Accounts") << QSL("TtRssAccounts") << QSL("Messages");
foreach (const QString &table, tables) {
copy_contents.exec(QString("INSERT INTO main.%1 SELECT * FROM storage.%1;").arg(table));
@ -290,22 +292,25 @@ QSqlDatabase DatabaseFactory::sqliteInitializeFileBasedDatabase(const QString &c
}
database.commit();
query_db.finish();
qDebug("File-based SQLite database backend should be ready now.");
}
else {
query_db.next();
QString installed_db_schema = query_db.value(0).toString();
query_db.finish();
if (!updateDatabaseSchema(database, installed_db_schema)) {
qFatal("Database schema was not updated from '%s' to '%s' successully.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
else {
qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
if (installed_db_schema < APP_DB_SCHEMA_VERSION) {
if (sqliteUpdateDatabaseSchema(database, installed_db_schema)) {
qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
else {
qFatal("Database schema was not updated from '%s' to '%s' successully.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
}
qDebug("File-based SQLite database connection '%s' to file '%s' seems to be established.",
@ -313,8 +318,6 @@ QSqlDatabase DatabaseFactory::sqliteInitializeFileBasedDatabase(const QString &c
qPrintable(QDir::toNativeSeparators(database.databaseName())));
qDebug("File-based SQLite database has version '%s'.", qPrintable(installed_db_schema));
}
query_db.finish();
}
// Everything is initialized now.
@ -327,24 +330,18 @@ QString DatabaseFactory::sqliteDatabaseFilePath() const {
return m_sqliteDatabaseFilePath + QDir::separator() + APP_DB_SQLITE_FILE;
}
bool DatabaseFactory::updateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version) {
switch (m_activeDatabaseDriver) {
case SQLITE:
case SQLITE_MEMORY:
return sqliteUpdateDatabaseSchema(database, source_db_schema_version);
case MYSQL:
return mysqlUpdateDatabaseSchema(database, source_db_schema_version);
default:
return false;
}
}
bool DatabaseFactory::sqliteUpdateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version) {
int working_version = QString(source_db_schema_version).remove('.').toInt();
int current_version = QString(APP_DB_SCHEMA_VERSION).remove('.').toInt();
// Now, it would be good to create backup of SQLite DB file.
if (IOFactory::copyFile(sqliteDatabaseFilePath(), sqliteDatabaseFilePath() + ".bak")) {
qDebug("Creating backup of SQLite DB file.");
}
else {
qFatal("Creation of backup SQLite DB file failed.");
}
while (working_version != current_version) {
QString update_file_name = QString(APP_MISC_PATH) + QDir::separator() +
QString(APP_DB_UPDATE_FILE_PATTERN).arg(QSL("sqlite"),
@ -469,8 +466,9 @@ void DatabaseFactory::sqliteSaveMemoryDatabase() {
copy_contents.exec(QString(QSL("ATTACH DATABASE '%1' AS 'storage';")).arg(file_database.databaseName()));
// Copy all stuff.
QStringList tables; tables << QSL("Categories") << QSL("Feeds") << QSL("FeedsData") <<
QSL("Messages");
// WARNING: All tables belong here.
QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") <<
QSL("Accounts") << QSL("TtRssAccounts") << QSL("Messages");
foreach (const QString &table, tables) {
copy_contents.exec(QString(QSL("DELETE FROM storage.%1;")).arg(table));
@ -609,15 +607,18 @@ QSqlDatabase DatabaseFactory::mysqlInitializeDatabase(const QString &connection_
QString installed_db_schema = query_db.value(0).toString();
if (!mysqlUpdateDatabaseSchema(database, installed_db_schema)) {
qFatal("Database schema was not updated from '%s' to '%s' successully.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
else {
qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
if (installed_db_schema < APP_DB_SCHEMA_VERSION) {
if (mysqlUpdateDatabaseSchema(database, installed_db_schema)) {
qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
else {
qFatal("Database schema was not updated from '%s' to '%s' successully.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
}
}

View File

@ -116,9 +116,6 @@ class DatabaseFactory : public QObject {
// application session.
void determineDriver();
// Updates DB schema if necessary.
bool updateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version);
// Holds the type of currently activated database backend.
UsedDriver m_activeDatabaseDriver;

View File

@ -92,7 +92,7 @@ QByteArray IOFactory::readTextFile(const QString &file_path) {
}
}
void IOFactory::writeTextFile(const QString &file_path, const QByteArray &data) {
void IOFactory::writeTextFile(const QString &file_path, const QByteArray &data, const QString &encoding) {
QFile input_file(file_path);
QTextStream stream(&input_file);

View File

@ -52,7 +52,7 @@ class IOFactory {
// Throws exception when no such file exists.
static QByteArray readTextFile(const QString &file_path);
static void writeTextFile(const QString &file_path, const QByteArray &data);
static void writeTextFile(const QString &file_path, const QByteArray &data, const QString &encoding = "UTF-8");
// Copies file, overwrites destination.
static bool copyFile(const QString &source, const QString &destination);

View File

@ -82,7 +82,7 @@ DKEY GUI::ToolbarStyle = "toolbar_style";
DVALUE(Qt::ToolButtonStyle) GUI::ToolbarStyleDef = Qt::ToolButtonIconOnly;
DKEY GUI::FeedsToolbarActions = "feeds_toolbar";
DVALUE(char*) GUI::FeedsToolbarActionsDef = "m_actionUpdateAllFeeds,m_actionMarkAllFeedsRead";
DVALUE(char*) GUI::FeedsToolbarActionsDef = "m_actionUpdateAllItems,m_actionMarkAllItemsRead";
DKEY GUI::MainWindowInitialSize = "window_size";
DKEY GUI::MainWindowInitialPosition = "window_position";
@ -105,12 +105,18 @@ DVALUE(bool) GUI::ToolbarsVisibleDef = true;
DKEY GUI::ListHeadersVisible = "enable_list_headers";
DVALUE(bool) GUI::ListHeadersVisibleDef = true;
DKEY GUI::StatusBarVisible = "enable_status_bar";
DVALUE(bool) GUI::StatusBarVisibleDef = true;
DKEY GUI::HideMainWindowWhenMinimized = "hide_when_minimized";
DVALUE(bool) GUI::HideMainWindowWhenMinimizedDef = false;
DKEY GUI::UseTrayIcon = "use_tray_icon";
DVALUE(bool) GUI::UseTrayIconDef = true;
DKEY GUI::EnableNotifications = "enable_notifications";
DVALUE(bool) GUI::EnableNotificationsDef = true;
DKEY GUI::UseFancyNotifications = "use_fancy_notifications";
DVALUE(bool) GUI::UseFancyNotificationsDef = true;

View File

@ -120,12 +120,18 @@ namespace GUI {
KEY ListHeadersVisible;
VALUE(bool) ListHeadersVisibleDef;
KEY StatusBarVisible;
VALUE(bool) StatusBarVisibleDef;
KEY HideMainWindowWhenMinimized;
VALUE(bool) HideMainWindowWhenMinimizedDef;
KEY UseTrayIcon;
VALUE(bool) UseTrayIconDef;
KEY EnableNotifications;
VALUE(bool) EnableNotificationsDef;
KEY UseFancyNotifications;
VALUE(bool) UseFancyNotificationsDef;
@ -360,6 +366,7 @@ class Settings : public QSettings {
// Creates settings file in correct location.
static Settings *setupSettings(QObject *parent);
// Returns properties of the actual application-wide settings.
static SettingsProperties determineProperties();
private:

View File

@ -172,6 +172,20 @@ bool SystemFactory::removeTrolltechJunkRegistryKeys() {
}
#endif
QString SystemFactory::getUsername() const {
QString name = qgetenv("USER");
if (name.isEmpty()) {
name = qgetenv("USERNAME");
}
if (name.isEmpty()) {
name = tr("anonymous");
}
return name;
}
QPair<UpdateInfo, QNetworkReply::NetworkError> SystemFactory::checkForUpdates() {
QPair<UpdateInfo, QNetworkReply::NetworkError> result;
QByteArray releases_xml;
@ -258,6 +272,6 @@ void SystemFactory::checkForUpdatesOnStartup() {
qApp->showGuiMessage(tr("New version available"),
tr("Click the bubble for more information."),
QSystemTrayIcon::Information,
NULL, false, QIcon(), qApp->mainForm(), SLOT(showUpdates()));
NULL, true, QIcon(), qApp->mainForm(), SLOT(showUpdates()));
}
}

View File

@ -80,9 +80,18 @@ class SystemFactory : public QObject {
QString getAutostartDesktopFileLocation();
#endif
// Retrieves username of currently logged-in user.
QString getUsername() const;
// Tries to download list with new updates.
QPair<UpdateInfo, QNetworkReply::NetworkError> checkForUpdates();
// Check whether given pointer belongs to instance of given class or not.
template<typename Base, typename T>
static bool isInstanceOf(T *ptr) {
return dynamic_cast<Base*>(ptr) != NULL;
}
// Checks if update is newer than current application version.
static bool isUpdateNewer(const QString &update_version);

View File

@ -24,8 +24,9 @@
Downloader::Downloader(QObject *parent)
: QObject(parent), m_activeReply(NULL), m_downloadManager(new SilentNetworkAccessManager(this)),
m_timer(new QTimer(this)), m_customHeaders(QHash<QByteArray, QByteArray>()), m_lastOutputData(QByteArray()),
m_lastOutputError(QNetworkReply::NoError), m_lastContentType(QVariant()) {
m_timer(new QTimer(this)), m_customHeaders(QHash<QByteArray, QByteArray>()), m_inputData(QByteArray()),
m_targetProtected(false), m_targetUsername(QString()), m_targetPassword(QString()),
m_lastOutputData(QByteArray()), m_lastOutputError(QNetworkReply::NoError), m_lastContentType(QVariant()) {
m_timer->setInterval(DOWNLOAD_TIMEOUT);
m_timer->setSingleShot(true);
@ -37,17 +38,11 @@ Downloader::~Downloader() {
m_downloadManager->deleteLater();
}
void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username, const QString &password) {
void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username,
const QString &password) {
QNetworkRequest request;
QObject originatingObject;
QString non_const_url = url;
// Set credential information as originating object.
originatingObject.setProperty("protected", protected_contents);
originatingObject.setProperty("username", username);
originatingObject.setProperty("password", password);
request.setOriginatingObject(&originatingObject);
foreach (const QByteArray &header_name, m_customHeaders.keys()) {
request.setRawHeader(header_name, m_customHeaders.value(header_name));
}
@ -63,11 +58,45 @@ void Downloader::downloadFile(const QString &url, int timeout, bool protected_co
request.setUrl(non_const_url);
}
m_targetProtected = protected_contents;
m_targetUsername = username;
m_targetPassword = password;
runGetRequest(request);
}
void Downloader::uploadData(const QString &url, const QByteArray &data, int timeout,
bool protected_contents, const QString &username, const QString &password) {
QNetworkRequest request;
QString non_const_url = url;
foreach (const QByteArray &header_name, m_customHeaders.keys()) {
request.setRawHeader(header_name, m_customHeaders.value(header_name));
}
m_inputData = data;
// Set url for this request and fire it up.
m_timer->setInterval(timeout);
if (non_const_url.startsWith(URI_SCHEME_FEED)) {
qDebug("Replacing URI schemes for '%s'.", qPrintable(non_const_url));
request.setUrl(non_const_url.replace(QRegExp(QString('^') + URI_SCHEME_FEED), QString(URI_SCHEME_HTTP)));
}
else {
request.setUrl(non_const_url);
}
m_targetProtected = protected_contents;
m_targetUsername = username;
m_targetPassword = password;
runPostRequest(request, m_inputData);
}
void Downloader::finished() {
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
QNetworkAccessManager::Operation reply_operation = reply->operation();
m_timer->stop();
@ -89,7 +118,12 @@ void Downloader::finished() {
m_activeReply->deleteLater();
m_activeReply = NULL;
runGetRequest(request);
if (reply_operation == QNetworkAccessManager::GetOperation) {
runGetRequest(request);
}
else if (reply_operation == QNetworkAccessManager::PostOperation) {
runPostRequest(request, m_inputData);
}
}
else {
// No redirection is indicated. Final file is obtained in our "reply" object.
@ -120,10 +154,26 @@ void Downloader::timeout() {
}
}
void Downloader::runPostRequest(const QNetworkRequest &request, const QByteArray &data) {
m_timer->start();
m_activeReply = m_downloadManager->post(request, data);
m_activeReply->setProperty("protected", m_targetProtected);
m_activeReply->setProperty("username", m_targetUsername);
m_activeReply->setProperty("password", m_targetPassword);
connect(m_activeReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(progressInternal(qint64,qint64)));
connect(m_activeReply, SIGNAL(finished()), this, SLOT(finished()));
}
void Downloader::runGetRequest(const QNetworkRequest &request) {
m_timer->start();
m_activeReply = m_downloadManager->get(request);
m_activeReply->setProperty("protected", m_targetProtected);
m_activeReply->setProperty("username", m_targetUsername);
m_activeReply->setProperty("password", m_targetPassword);
connect(m_activeReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(progressInternal(qint64,qint64)));
connect(m_activeReply, SIGNAL(finished()), this, SLOT(finished()));
}

View File

@ -49,6 +49,12 @@ class Downloader : public QObject {
void downloadFile(const QString &url, int timeout = DOWNLOAD_TIMEOUT, bool protected_contents = false,
const QString &username = QString(), const QString &password = QString());
// Performs asynchronous upload of given data as HTTP POST.
// User needs to setup "Content-Encoding" header which
// matches encoding of the data.
void uploadData(const QString &url, const QByteArray &data, int timeout = DOWNLOAD_TIMEOUT,
bool protected_contents = false, const QString &username = QString(), const QString &password = QString());
signals:
// Emitted when new progress is known.
void progress(qint64 bytes_received, qint64 bytes_total);
@ -65,6 +71,7 @@ class Downloader : public QObject {
void timeout();
private:
void runPostRequest(const QNetworkRequest &request, const QByteArray &data);
void runGetRequest(const QNetworkRequest &request);
private:
@ -72,6 +79,11 @@ class Downloader : public QObject {
SilentNetworkAccessManager *m_downloadManager;
QTimer *m_timer;
QHash<QByteArray, QByteArray> m_customHeaders;
QByteArray m_inputData;
bool m_targetProtected;
QString m_targetUsername;
QString m_targetPassword;
// Response data.
QByteArray m_lastOutputData;

View File

@ -559,6 +559,10 @@ QNetworkAccessManager *DownloadManager::networkManager() const {
return m_networkManager;
}
int DownloadManager::totalDownloads() const {
return m_downloads.size();
}
void DownloadManager::itemFinished() {
emit downloadFinished();
}
@ -789,6 +793,11 @@ bool DownloadModel::removeRows(int row, int count, const QModelIndex &parent) {
}
m_downloadManager->m_autoSaver->changeOccurred();
if (m_downloadManager->totalDownloads() == 0) {
m_downloadManager->m_ui->m_btnCleanup->setEnabled(false);
}
return true;
}

1
src/network-web/downloadmanager.h Normal file → Executable file
View File

@ -111,6 +111,7 @@ class DownloadManager : public TabContent {
WebBrowser *webBrowser();
QNetworkAccessManager *networkManager() const;
int totalDownloads() const;
int activeDownloads() const;
int downloadProgress() const;

View File

@ -148,6 +148,27 @@ QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QList<QString> &u
return network_result;
}
NetworkResult NetworkFactory::uploadData(const QString &url, int timeout, const QByteArray &input_data,
const QString &input_content_type, QByteArray &output,
bool protected_contents, const QString &username, const QString &password) {
Downloader downloader;
QEventLoop loop;
NetworkResult result;
downloader.appendRawHeader("Content-Type", input_content_type.toLocal8Bit());
// We need to quit event loop when the download finishes.
QObject::connect(&downloader, SIGNAL(completed(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
downloader.uploadData(url, input_data, timeout, protected_contents, username, password);
loop.exec();
output = downloader.lastOutputData();
result.first = downloader.lastOutputError();
result.second = downloader.lastContentType();
return result;
}
NetworkResult NetworkFactory::downloadFeedFile(const QString &url, int timeout,
QByteArray &output, bool protected_contents,
const QString &username, const QString &password) {

5
src/network-web/networkfactory.h Normal file → Executable file
View File

@ -43,6 +43,11 @@ class NetworkFactory {
// given URL belongs to.
static QNetworkReply::NetworkError downloadIcon(const QList<QString> &urls, int timeout, QIcon &output);
static NetworkResult uploadData(const QString &url, int timeout, const QByteArray &input_data,
const QString &input_content_type, QByteArray &output,
bool protected_contents = false, const QString &username = QString(),
const QString &password = QString());
static NetworkResult downloadFeedFile(const QString &url, int timeout, QByteArray &output,
bool protected_contents = false, const QString &username = QString(),
const QString &password = QString());

12
src/network-web/silentnetworkaccessmanager.cpp Normal file → Executable file
View File

@ -44,20 +44,20 @@ SilentNetworkAccessManager *SilentNetworkAccessManager::instance() {
}
void SilentNetworkAccessManager::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) {
QObject *originating_object = reply->request().originatingObject();
QList<QString> keys = authenticator->options().keys();
if (originating_object->property("protected").toBool()) {
if (reply->property("protected").toBool()) {
// This feed contains authentication information, it is good.
authenticator->setUser(originating_object->property("username").toString());
authenticator->setPassword(originating_object->property("password").toString());
authenticator->setUser(reply->property("username").toString());
authenticator->setPassword(reply->property("password").toString());
reply->setProperty("authentication-given", true);
qDebug("Feed '%s' requested authentication and got it.", qPrintable(reply->url().toString()));
qDebug("Item '%s' requested authentication and got it.", qPrintable(reply->url().toString()));
}
else {
reply->setProperty("authentication-given", false);
// Authentication is required but this feed does not contain it.
qWarning("Feed '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString()));
qWarning("Item '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString()));
}
}

View File

@ -27,6 +27,7 @@
#include "gui/tabwidget.h"
#include "gui/feedmessageviewer.h"
#include "gui/feedsview.h"
#include "services/standard/standardserviceroot.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
@ -214,7 +215,19 @@ void WebBrowser::onIconChanged() {
void WebBrowser::addFeedFromWebsite(const QString &feed_link) {
qApp->clipboard()->setText(feed_link);
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->addNewFeed();
StandardServiceRoot *service = qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->standardServiceRoot();
if (service != NULL) {
service->addNewFeed();
}
else {
qApp->showGuiMessage(tr("Cannot add feed"),
tr("You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first.").arg(APP_NAME),
QSystemTrayIcon::Warning,
qApp->mainForm(),
true);
}
}
void WebBrowser::onTitleChanged(const QString &new_title) {

View File

@ -39,7 +39,7 @@ WebBrowserNetworkAccessManager::~WebBrowserNetworkAccessManager() {
void WebBrowserNetworkAccessManager::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) {
Q_UNUSED(authenticator);
// TODO: Support authentication for web pages.
// FIXME: Support authentication for web pages.
qDebug("URL '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString()));
}

603
src/qt-json/json.cpp Executable file
View File

@ -0,0 +1,603 @@
/**
* QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
* Copyright (C) 2011 Eeli Reilin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file json.cpp
*/
#include <QDateTime>
#include <QStringList>
#include "json.h"
namespace QtJson {
static QString dateFormat, dateTimeFormat;
static QString sanitizeString(QString str);
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep);
static QVariant parseValue(const QString &json, int &index, bool &success);
static QVariant parseObject(const QString &json, int &index, bool &success);
static QVariant parseArray(const QString &json, int &index, bool &success);
static QVariant parseString(const QString &json, int &index, bool &success);
static QVariant parseNumber(const QString &json, int &index);
static int lastIndexOfNumber(const QString &json, int index);
static void eatWhitespace(const QString &json, int &index);
static int lookAhead(const QString &json, int index);
static int nextToken(const QString &json, int &index);
template<typename T>
QByteArray serializeMap(const T &map, bool &success) {
QByteArray str = "{ ";
QList<QByteArray> pairs;
for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) {
QByteArray serializedValue = serialize(it.value());
if (serializedValue.isNull()) {
success = false;
break;
}
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
}
str += join(pairs, ", ");
str += " }";
return str;
}
void insert(QVariant &v, const QString &key, const QVariant &value);
void append(QVariant &v, const QVariant &value);
template<typename T>
void cloneMap(QVariant &json, const T &map) {
for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) {
insert(json, it.key(), (*it));
}
}
template<typename T>
void cloneList(QVariant &json, const T &list) {
for (typename T::const_iterator it = list.begin(), itend = list.end(); it != itend; ++it) {
append(json, (*it));
}
}
/**
* parse
*/
QVariant parse(const QString &json) {
bool success = true;
return parse(json, success);
}
/**
* parse
*/
QVariant parse(const QString &json, bool &success) {
success = true;
// Return an empty QVariant if the JSON data is either null or empty
if (!json.isNull() || !json.isEmpty()) {
QString data = json;
// We'll start from index 0
int index = 0;
// Parse the first value
QVariant value = parseValue(data, index, success);
// Return the parsed value
return value;
} else {
// Return the empty QVariant
return QVariant();
}
}
/**
* clone
*/
QVariant clone(const QVariant &data) {
QVariant v;
if (data.type() == QVariant::Map) {
cloneMap(v, data.toMap());
} else if (data.type() == QVariant::Hash) {
cloneMap(v, data.toHash());
} else if (data.type() == QVariant::List) {
cloneList(v, data.toList());
} else if (data.type() == QVariant::StringList) {
cloneList(v, data.toStringList());
} else {
v = QVariant(data);
}
return v;
}
/**
* insert value (map case)
*/
void insert(QVariant &v, const QString &key, const QVariant &value) {
if (!v.canConvert<QVariantMap>()) v = QVariantMap();
QVariantMap *p = (QVariantMap *)v.data();
p->insert(key, clone(value));
}
/**
* append value (list case)
*/
void append(QVariant &v, const QVariant &value) {
if (!v.canConvert<QVariantList>()) v = QVariantList();
QVariantList *p = (QVariantList *)v.data();
p->append(value);
}
QByteArray serialize(const QVariant &data) {
bool success = true;
return serialize(data, success);
}
QByteArray serialize(const QVariant &data, bool &success) {
QByteArray str;
success = true;
if (!data.isValid()) { // invalid or null?
str = "null";
} else if ((data.type() == QVariant::List) ||
(data.type() == QVariant::StringList)) { // variant is a list?
QList<QByteArray> values;
const QVariantList list = data.toList();
Q_FOREACH(const QVariant& v, list) {
QByteArray serializedValue = serialize(v);
if (serializedValue.isNull()) {
success = false;
break;
}
values << serializedValue;
}
str = "[ " + join( values, ", " ) + " ]";
} else if (data.type() == QVariant::Hash) { // variant is a hash?
str = serializeMap<>(data.toHash(), success);
} else if (data.type() == QVariant::Map) { // variant is a map?
str = serializeMap<>(data.toMap(), success);
} else if ((data.type() == QVariant::String) ||
(data.type() == QVariant::ByteArray)) {// a string or a byte array?
str = sanitizeString(data.toString()).toUtf8();
} else if (data.type() == QVariant::Double) { // double?
double value = data.toDouble(&success);
if (success) {
str = QByteArray::number(value, 'g');
if (!str.contains(".") && ! str.contains("e")) {
str += ".0";
}
}
} else if (data.type() == QVariant::Bool) { // boolean value?
str = data.toBool() ? "true" : "false";
} else if (data.type() == QVariant::ULongLong) { // large unsigned number?
str = QByteArray::number(data.value<qulonglong>());
} else if (data.canConvert<qlonglong>()) { // any signed number?
str = QByteArray::number(data.value<qlonglong>());
} else if (data.canConvert<long>()) { //TODO: this code is never executed because all smaller types can be converted to qlonglong
str = QString::number(data.value<long>()).toUtf8();
} else if (data.type() == QVariant::DateTime) { // datetime value?
str = sanitizeString(dateTimeFormat.isEmpty()
? data.toDateTime().toString()
: data.toDateTime().toString(dateTimeFormat)).toUtf8();
} else if (data.type() == QVariant::Date) { // date value?
str = sanitizeString(dateTimeFormat.isEmpty()
? data.toDate().toString()
: data.toDate().toString(dateFormat)).toUtf8();
} else if (data.canConvert<QString>()) { // can value be converted to string?
// this will catch QUrl, ... (all other types which can be converted to string)
str = sanitizeString(data.toString()).toUtf8();
} else {
success = false;
}
if (success) {
return str;
}
return QByteArray();
}
QString serializeStr(const QVariant &data) {
return QString::fromUtf8(serialize(data));
}
QString serializeStr(const QVariant &data, bool &success) {
return QString::fromUtf8(serialize(data, success));
}
/**
* \enum JsonToken
*/
enum JsonToken {
JsonTokenNone = 0,
JsonTokenCurlyOpen = 1,
JsonTokenCurlyClose = 2,
JsonTokenSquaredOpen = 3,
JsonTokenSquaredClose = 4,
JsonTokenColon = 5,
JsonTokenComma = 6,
JsonTokenString = 7,
JsonTokenNumber = 8,
JsonTokenTrue = 9,
JsonTokenFalse = 10,
JsonTokenNull = 11
};
static QString sanitizeString(QString str) {
str.replace(QLatin1String("\\"), QLatin1String("\\\\"));
str.replace(QLatin1String("\""), QLatin1String("\\\""));
str.replace(QLatin1String("\b"), QLatin1String("\\b"));
str.replace(QLatin1String("\f"), QLatin1String("\\f"));
str.replace(QLatin1String("\n"), QLatin1String("\\n"));
str.replace(QLatin1String("\r"), QLatin1String("\\r"));
str.replace(QLatin1String("\t"), QLatin1String("\\t"));
return QString(QLatin1String("\"%1\"")).arg(str);
}
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep) {
QByteArray res;
Q_FOREACH(const QByteArray &i, list) {
if (!res.isEmpty()) {
res += sep;
}
res += i;
}
return res;
}
/**
* parseValue
*/
static QVariant parseValue(const QString &json, int &index, bool &success) {
// Determine what kind of data we should parse by
// checking out the upcoming token
switch(lookAhead(json, index)) {
case JsonTokenString:
return parseString(json, index, success);
case JsonTokenNumber:
return parseNumber(json, index);
case JsonTokenCurlyOpen:
return parseObject(json, index, success);
case JsonTokenSquaredOpen:
return parseArray(json, index, success);
case JsonTokenTrue:
nextToken(json, index);
return QVariant(true);
case JsonTokenFalse:
nextToken(json, index);
return QVariant(false);
case JsonTokenNull:
nextToken(json, index);
return QVariant();
case JsonTokenNone:
break;
}
// If there were no tokens, flag the failure and return an empty QVariant
success = false;
return QVariant();
}
/**
* parseObject
*/
static QVariant parseObject(const QString &json, int &index, bool &success) {
QVariantMap map;
int token;
// Get rid of the whitespace and increment index
nextToken(json, index);
// Loop through all of the key/value pairs of the object
bool done = false;
while (!done) {
// Get the upcoming token
token = lookAhead(json, index);
if (token == JsonTokenNone) {
success = false;
return QVariantMap();
} else if (token == JsonTokenComma) {
nextToken(json, index);
} else if (token == JsonTokenCurlyClose) {
nextToken(json, index);
return map;
} else {
// Parse the key/value pair's name
QString name = parseString(json, index, success).toString();
if (!success) {
return QVariantMap();
}
// Get the next token
token = nextToken(json, index);
// If the next token is not a colon, flag the failure
// return an empty QVariant
if (token != JsonTokenColon) {
success = false;
return QVariant(QVariantMap());
}
// Parse the key/value pair's value
QVariant value = parseValue(json, index, success);
if (!success) {
return QVariantMap();
}
// Assign the value to the key in the map
map[name] = value;
}
}
// Return the map successfully
return QVariant(map);
}
/**
* parseArray
*/
static QVariant parseArray(const QString &json, int &index, bool &success) {
QVariantList list;
nextToken(json, index);
bool done = false;
while(!done) {
int token = lookAhead(json, index);
if (token == JsonTokenNone) {
success = false;
return QVariantList();
} else if (token == JsonTokenComma) {
nextToken(json, index);
} else if (token == JsonTokenSquaredClose) {
nextToken(json, index);
break;
} else {
QVariant value = parseValue(json, index, success);
if (!success) {
return QVariantList();
}
list.push_back(value);
}
}
return QVariant(list);
}
/**
* parseString
*/
static QVariant parseString(const QString &json, int &index, bool &success) {
QString s;
QChar c;
eatWhitespace(json, index);
c = json[index++];
bool complete = false;
while(!complete) {
if (index == json.size()) {
break;
}
c = json[index++];
if (c == '\"') {
complete = true;
break;
} else if (c == '\\') {
if (index == json.size()) {
break;
}
c = json[index++];
if (c == '\"') {
s.append('\"');
} else if (c == '\\') {
s.append('\\');
} else if (c == '/') {
s.append('/');
} else if (c == 'b') {
s.append('\b');
} else if (c == 'f') {
s.append('\f');
} else if (c == 'n') {
s.append('\n');
} else if (c == 'r') {
s.append('\r');
} else if (c == 't') {
s.append('\t');
} else if (c == 'u') {
int remainingLength = json.size() - index;
if (remainingLength >= 4) {
QString unicodeStr = json.mid(index, 4);
int symbol = unicodeStr.toInt(0, 16);
s.append(QChar(symbol));
index += 4;
} else {
break;
}
}
} else {
s.append(c);
}
}
if (!complete) {
success = false;
return QVariant();
}
return QVariant(s);
}
/**
* parseNumber
*/
static QVariant parseNumber(const QString &json, int &index) {
eatWhitespace(json, index);
int lastIndex = lastIndexOfNumber(json, index);
int charLength = (lastIndex - index) + 1;
QString numberStr;
numberStr = json.mid(index, charLength);
index = lastIndex + 1;
bool ok;
if (numberStr.contains('.')) {
return QVariant(numberStr.toDouble(NULL));
} else if (numberStr.startsWith('-')) {
int i = numberStr.toInt(&ok);
if (!ok) {
qlonglong ll = numberStr.toLongLong(&ok);
return ok ? ll : QVariant(numberStr);
}
return i;
} else {
uint u = numberStr.toUInt(&ok);
if (!ok) {
qulonglong ull = numberStr.toULongLong(&ok);
return ok ? ull : QVariant(numberStr);
}
return u;
}
}
/**
* lastIndexOfNumber
*/
static int lastIndexOfNumber(const QString &json, int index) {
int lastIndex;
for(lastIndex = index; lastIndex < json.size(); lastIndex++) {
if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) {
break;
}
}
return lastIndex -1;
}
/**
* eatWhitespace
*/
static void eatWhitespace(const QString &json, int &index) {
for(; index < json.size(); index++) {
if (QString(" \t\n\r").indexOf(json[index]) == -1) {
break;
}
}
}
/**
* lookAhead
*/
static int lookAhead(const QString &json, int index) {
int saveIndex = index;
return nextToken(json, saveIndex);
}
/**
* nextToken
*/
static int nextToken(const QString &json, int &index) {
eatWhitespace(json, index);
if (index == json.size()) {
return JsonTokenNone;
}
QChar c = json[index];
index++;
switch(c.toLatin1()) {
case '{': return JsonTokenCurlyOpen;
case '}': return JsonTokenCurlyClose;
case '[': return JsonTokenSquaredOpen;
case ']': return JsonTokenSquaredClose;
case ',': return JsonTokenComma;
case '"': return JsonTokenString;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-': return JsonTokenNumber;
case ':': return JsonTokenColon;
}
index--; // ^ WTF?
int remainingLength = json.size() - index;
// True
if (remainingLength >= 4) {
if (json[index] == 't' && json[index + 1] == 'r' &&
json[index + 2] == 'u' && json[index + 3] == 'e') {
index += 4;
return JsonTokenTrue;
}
}
// False
if (remainingLength >= 5) {
if (json[index] == 'f' && json[index + 1] == 'a' &&
json[index + 2] == 'l' && json[index + 3] == 's' &&
json[index + 4] == 'e') {
index += 5;
return JsonTokenFalse;
}
}
// Null
if (remainingLength >= 4) {
if (json[index] == 'n' && json[index + 1] == 'u' &&
json[index + 2] == 'l' && json[index + 3] == 'l') {
index += 4;
return JsonTokenNull;
}
}
return JsonTokenNone;
}
void setDateTimeFormat(const QString &format) {
dateTimeFormat = format;
}
void setDateFormat(const QString &format) {
dateFormat = format;
}
QString getDateTimeFormat() {
return dateTimeFormat;
}
QString getDateFormat() {
return dateFormat;
}
} //end namespace

134
src/qt-json/json.h Executable file
View File

@ -0,0 +1,134 @@
/**
* QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
* Copyright (C) 2011 Eeli Reilin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file json.h
*/
#ifndef JSON_H
#define JSON_H
#include <QVariant>
#include <QString>
/**
* \namespace QtJson
* \brief A JSON data parser
*
* Json parses a JSON data into a QVariant hierarchy.
*/
namespace QtJson {
typedef QVariantMap JsonObject;
typedef QVariantList JsonArray;
/**
* Clone a JSON object (makes a deep copy)
*
* \param data The JSON object
*/
QVariant clone(const QVariant &data);
/**
* Insert value to JSON object (QVariantMap)
*
* \param v The JSON object
* \param key The key
* \param value The value
*/
void insert(QVariant &v, const QString &key, const QVariant &value);
/**
* Append value to JSON array (QVariantList)
*
* \param v The JSON array
* \param value The value
*/
void append(QVariant &v, const QVariant &value);
/**
* Parse a JSON string
*
* \param json The JSON data
*/
QVariant parse(const QString &json);
/**
* Parse a JSON string
*
* \param json The JSON data
* \param success The success of the parsing
*/
QVariant parse(const QString &json, bool &success);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
*
* \return QByteArray Textual JSON representation in UTF-8
*/
QByteArray serialize(const QVariant &data);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
* \param success The success of the serialization
*
* \return QByteArray Textual JSON representation in UTF-8
*/
QByteArray serialize(const QVariant &data, bool &success);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
*
* \return QString Textual JSON representation
*/
QString serializeStr(const QVariant &data);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
* \param success The success of the serialization
*
* \return QString Textual JSON representation
*/
QString serializeStr(const QVariant &data, bool &success);
/**
* This method sets date(time) format to be used for QDateTime::toString
* If QString is empty, Qt::TextDate is used.
*
* \param format The JSON data generated by the parser.
*/
void setDateTimeFormat(const QString& format);
void setDateFormat(const QString& format);
/**
* This method gets date(time) format to be used for QDateTime::toString
* If QString is empty, Qt::TextDate is used.
*/
QString getDateTimeFormat();
QString getDateFormat();
}
#endif //JSON_H

View File

@ -0,0 +1,26 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/category.h"
Category::Category(RootItem *parent) : RootItem(parent) {
setKind(RootItemKind::Category);
}
Category::~Category() {
}

View File

@ -0,0 +1,32 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef CATEGORY_H
#define CATEGORY_H
#include "services/abstract/rootitem.h"
class Category : public RootItem {
Q_OBJECT
public:
explicit Category(RootItem *parent = NULL);
virtual ~Category();
};
#endif // CATEGORY_H

52
src/services/abstract/feed.cpp Executable file
View File

@ -0,0 +1,52 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/feed.h"
#include "definitions/definitions.h"
Feed::Feed(RootItem *parent) : RootItem(parent) {
m_status = Normal;
m_autoUpdateType = DefaultAutoUpdate;
m_autoUpdateInitialInterval = DEFAULT_AUTO_UPDATE_INTERVAL;
m_autoUpdateRemainingInterval = DEFAULT_AUTO_UPDATE_INTERVAL;
setKind(RootItemKind::Feed);
}
Feed::~Feed() {
}
QVariant Feed::data(int column, int role) const {
switch (role) {
case Qt::ForegroundRole:
switch (status()) {
case NewMessages:
return QColor(Qt::blue);
case Error:
return QColor(Qt::red);
default:
return QVariant();
}
default:
return RootItem::data(column, role);
}
}

114
src/services/abstract/feed.h Executable file
View File

@ -0,0 +1,114 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef FEED_H
#define FEED_H
#include "services/abstract/rootitem.h"
#include "core/message.h"
#include <QVariant>
// Base class for "feed" nodes.
class Feed : public RootItem {
Q_OBJECT
public:
// Specifies the auto-update strategy for the feed.
enum AutoUpdateType {
DontAutoUpdate = 0,
DefaultAutoUpdate = 1,
SpecificAutoUpdate = 2
};
// Specifies the actual "status" of the feed.
// For example if it has new messages, error
// occurred, and so on.
enum Status {
Normal = 0,
NewMessages = 1,
Error = 2,
ParsingError = 3,
OtherError = 4
};
// Constructors.
explicit Feed(RootItem *parent = NULL);
virtual ~Feed();
/////////////////////////////////////////
// /* Members to override.
/////////////////////////////////////////
// Performs synchronous update and returns number of newly updated messages.
// NOTE: This is called from worker thread, not from main UI thread.
// NOTE: This should COMPLETELY download ALL messages from online source
// into locale "Messages" table, INCLUDING contents (or excerpts) of those
// messages.
virtual int update() = 0;
QVariant data(int column, int role) const;
/////////////////////////////////////////
// Members to override. */
/////////////////////////////////////////
inline int autoUpdateInitialInterval() const {
return m_autoUpdateInitialInterval;
}
inline void setAutoUpdateInitialInterval(int auto_update_interval) {
// If new initial auto-update interval is set, then
// we should reset time that remains to the next auto-update.
m_autoUpdateInitialInterval = auto_update_interval;
m_autoUpdateRemainingInterval = auto_update_interval;
}
inline AutoUpdateType autoUpdateType() const {
return m_autoUpdateType;
}
inline void setAutoUpdateType(const AutoUpdateType &autoUpdateType) {
m_autoUpdateType = autoUpdateType;
}
inline int autoUpdateRemainingInterval() const {
return m_autoUpdateRemainingInterval;
}
inline void setAutoUpdateRemainingInterval(int autoUpdateRemainingInterval) {
m_autoUpdateRemainingInterval = autoUpdateRemainingInterval;
}
inline Status status() const {
return m_status;
}
inline void setStatus(const Status &status) {
m_status = status;
}
private:
Status m_status;
AutoUpdateType m_autoUpdateType;
int m_autoUpdateInitialInterval;
int m_autoUpdateRemainingInterval;
};
#endif // FEED_H

View File

@ -0,0 +1,241 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/recyclebin.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/textfactory.h"
#include "services/abstract/serviceroot.h"
#include <QSqlQuery>
RecycleBin::RecycleBin(RootItem *parent_item) : RootItem(parent_item) {
setKind(RootItemKind::Bin);
setIcon(qApp->icons()->fromTheme(QSL("folder-recycle-bin")));
setTitle(tr("Recycle bin"));
setDescription(tr("Recycle bin contains all deleted messages from all feeds."));
setCreationDate(QDateTime::currentDateTime());
}
RecycleBin::~RecycleBin() {
}
int RecycleBin::countOfUnreadMessages() const {
return m_unreadCount;
}
int RecycleBin::countOfAllMessages() const {
return m_totalCount;
}
void RecycleBin::updateCounts(bool update_total_count) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query_all(database);
ServiceRoot *parent_root = getParentServiceRoot();
query_all.setForwardOnly(true);
query_all.prepare("SELECT count(*) FROM Messages "
"WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;");
query_all.bindValue(QSL(":account_id"), parent_root->accountId());
if (query_all.exec() && query_all.next()) {
m_unreadCount = query_all.value(0).toInt();
}
else {
m_unreadCount = 0;
}
if (update_total_count) {
query_all.prepare("SELECT count(*) FROM Messages "
"WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;");
query_all.bindValue(QSL(":account_id"), parent_root->accountId());
if (query_all.exec() && query_all.next()) {
m_totalCount = query_all.value(0).toInt();
}
else {
m_totalCount = 0;
}
}
}
QVariant RecycleBin::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
return tr("Recycle bin\n\n%1").arg(tr("%n deleted message(s).", 0, countOfAllMessages()));
default:
return RootItem::data(column, role);
}
}
QList<Message> RecycleBin::undeletedMessages() const {
QList<Message> messages;
int account_id = const_cast<RecycleBin*>(this)->getParentServiceRoot()->accountId();
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true);
query_read_msg.prepare("SELECT * "
"FROM Messages "
"WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;");
query_read_msg.bindValue(QSL(":account_id"), account_id);
// FIXME: Fix those const functions, this is fucking ugly.
if (query_read_msg.exec()) {
while (query_read_msg.next()) {
bool decoded;
Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded);
if (decoded) {
messages.append(message);
}
messages.append(message);
}
}
return messages;
}
bool RecycleBin::markAsReadUnread(RootItem::ReadStatus status) {
QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for recycle bin read change.");
return false;
}
QSqlQuery query_read_msg(db_handle);
ServiceRoot *parent_root = getParentServiceRoot();
query_read_msg.setForwardOnly(true);
if (!query_read_msg.prepare("UPDATE Messages SET is_read = :read "
"WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;")) {
qWarning("Query preparation failed for recycle bin read change.");
db_handle.rollback();
return false;
}
query_read_msg.bindValue(QSL(":read"), status == RootItem::Read ? 1 : 0);
query_read_msg.bindValue(QSL(":account_id"), parent_root->accountId());
if (!query_read_msg.exec()) {
qDebug("Query execution for recycle bin read change failed.");
db_handle.rollback();
}
// Commit changes.
if (db_handle.commit()) {
updateCounts(false);
parent_root->itemChanged(QList<RootItem*>() << this);
parent_root->requestReloadMessageList(status == RootItem::Read);
return true;
}
else {
return db_handle.rollback();
}
}
bool RecycleBin::cleanMessages(bool clear_only_read) {
QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for recycle bin emptying.");
return false;
}
ServiceRoot *parent_root = getParentServiceRoot();
QSqlQuery query_empty_bin(db_handle);
query_empty_bin.setForwardOnly(true);
if (clear_only_read) {
query_empty_bin.prepare("UPDATE Messages SET is_pdeleted = 1 "
"WHERE is_read = 1 AND is_deleted = 1 AND account_id = :account_id;");
}
else {
query_empty_bin.prepare(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE is_deleted = 1 AND account_id = :account_id;"));
}
query_empty_bin.bindValue(QSL(":account_id"), parent_root->accountId());
if (!query_empty_bin.exec()) {
qWarning("Query execution failed for recycle bin emptying.");
db_handle.rollback();
return false;
}
// Commit changes.
if (db_handle.commit()) {
updateCounts(true);
parent_root->itemChanged(QList<RootItem*>() << this);
parent_root->requestReloadMessageList(true);
return true;
}
else {
return db_handle.rollback();
}
}
bool RecycleBin::empty() {
return cleanMessages(false);
}
bool RecycleBin::restore() {
QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for recycle bin restoring.");
return false;
}
ServiceRoot *parent_root = getParentServiceRoot();
QSqlQuery query_empty_bin(db_handle);
query_empty_bin.setForwardOnly(true);
query_empty_bin.prepare("UPDATE Messages SET is_deleted = 0 "
"WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;");
query_empty_bin.bindValue(QSL(":account_id"), parent_root->accountId());
if (!query_empty_bin.exec()) {
qWarning("Query execution failed for recycle bin restoring.");
db_handle.rollback();
return false;
}
// Commit changes.
if (db_handle.commit()) {
parent_root->updateCounts(true);
parent_root->itemChanged(parent_root->getSubTree());
parent_root->requestReloadMessageList(true);
parent_root->requestFeedReadFilterReload();
return true;
}
else {
return db_handle.rollback();
}
}

View File

@ -1,50 +1,65 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef RECYCLEBIN_H
#define RECYCLEBIN_H
#include "core/rootitem.h"
#include <QCoreApplication>
class RecycleBin : public RootItem {
Q_DECLARE_TR_FUNCTIONS(RecycleBin)
public:
explicit RecycleBin(RootItem *parent = NULL);
virtual ~RecycleBin();
int childCount() const;
void appendChild(RootItem *child);
int countOfUnreadMessages() const;
int countOfAllMessages() const;
QVariant data(int column, int role) const;
bool empty();
bool restore();
public slots:
void updateCounts(bool update_total_count);
private:
int m_totalCount;
int m_unreadCount;
};
#endif // RECYCLEBIN_H
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef RECYCLEBIN_H
#define RECYCLEBIN_H
#include "services/abstract/rootitem.h"
class RecycleBin : public RootItem {
Q_OBJECT
public:
explicit RecycleBin(RootItem *parent_item = NULL);
virtual ~RecycleBin();
QVariant data(int column, int role) const;
QList<Message> undeletedMessages() const;
bool markAsReadUnread(ReadStatus status);
bool cleanMessages(bool clear_only_read);
int countOfUnreadMessages() const;
int countOfAllMessages() const;
void updateCounts(bool update_total_count);
public slots:
/////////////////////////////////////////
// /* Members to override.
/////////////////////////////////////////
// Empties the bin - removes all messages from it (does not remove
// them from DB, just permanently hide them, so that they are not
// re-downloaded).
virtual bool empty();
// Performs complete restoration of all messages contained in the bin
virtual bool restore();
/////////////////////////////////////////
// Members to override. */
/////////////////////////////////////////
private:
int m_totalCount;
int m_unreadCount;
};
#endif // RECYCLEBIN_H

View File

@ -0,0 +1,388 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/rootitem.h"
#include "services/abstract/serviceroot.h"
#include "services/abstract/feed.h"
#include "services/abstract/category.h"
#include "services/abstract/recyclebin.h"
#include "miscellaneous/application.h"
#include <QVariant>
RootItem::RootItem(RootItem *parent_item)
: QObject(NULL),
m_kind(RootItemKind::Root),
m_id(NO_PARENT_CATEGORY),
m_title(QString()),
m_description(QString()),
m_icon(QIcon()),
m_creationDate(QDateTime()),
m_childItems(QList<RootItem*>()),
m_parentItem(parent_item) {
setupFonts();
}
RootItem::~RootItem() {
qDeleteAll(m_childItems);
}
QList<QAction*> RootItem::contextMenu() {
return QList<QAction*>();
}
bool RootItem::canBeEdited() {
return false;
}
bool RootItem::editViaGui() {
return false;
}
bool RootItem::canBeDeleted() {
return false;
}
bool RootItem::deleteViaGui() {
return false;
}
bool RootItem::markAsReadUnread(ReadStatus status) {
bool result = true;
foreach (RootItem *child, m_childItems) {
result &= child->markAsReadUnread(status);
}
return result;
}
QList<Message> RootItem::undeletedMessages() const {
QList<Message> messages;
foreach (RootItem *child, m_childItems) {
messages.append(child->undeletedMessages());
}
return messages;
}
bool RootItem::cleanMessages(bool clear_only_read) {
bool result = true;
RecycleBin *bin = NULL;
foreach (RootItem *child, m_childItems) {
if (child->kind() == RootItemKind::Bin) {
bin = qobject_cast<RecycleBin*>(child);
}
else {
result &= child->cleanMessages(clear_only_read);
}
}
if (bin != NULL) {
result &= bin->cleanMessages(clear_only_read);
}
return result;
}
void RootItem::updateCounts(bool including_total_count) {
foreach (RootItem *child, m_childItems) {
child->updateCounts(including_total_count);
}
}
void RootItem::setupFonts() {
m_normalFont = Application::font("FeedsView");
m_boldFont = m_normalFont;
m_boldFont.setBold(true);
}
int RootItem::row() const {
if (m_parentItem) {
return m_parentItem->m_childItems.indexOf(const_cast<RootItem*>(this));
}
else {
// This item has no parent. Therefore, its row index is 0.
return 0;
}
}
QVariant RootItem::data(int column, int role) const {
Q_UNUSED(column)
Q_UNUSED(role)
switch (role) {
case Qt::ToolTipRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_title;
}
else if (column == FDS_MODEL_COUNTS_INDEX) {
//: Tooltip for "unread" column of feed list.
return tr("%n unread message(s).", 0, countOfUnreadMessages());
}
else {
return QVariant();
}
case Qt::EditRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_title;
}
else if (column == FDS_MODEL_COUNTS_INDEX) {
return countOfUnreadMessages();
}
else {
return QVariant();
}
case Qt::FontRole:
return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont;
case Qt::DisplayRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return m_title;
}
else if (column == FDS_MODEL_COUNTS_INDEX) {
int count_all = countOfAllMessages();
int count_unread = countOfUnreadMessages();
return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString()
.replace(PLACEHOLDER_UNREAD_COUNTS, count_unread < 0 ? QSL("-") : QString::number(count_unread))
.replace(PLACEHOLDER_ALL_COUNTS, count_all < 0 ? QSL("-") : QString::number(count_all));
}
else {
return QVariant();
}
case Qt::DecorationRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return icon();
}
else {
return QVariant();
}
case Qt::TextAlignmentRole:
if (column == FDS_MODEL_COUNTS_INDEX) {
return Qt::AlignCenter;
}
else {
return QVariant();
}
default:
return QVariant();
}
}
Qt::ItemFlags RootItem::additionalFlags() const {
return Qt::NoItemFlags;
}
bool RootItem::performDragDropChange(RootItem *target_item) {
return false;
}
int RootItem::countOfAllMessages() const {
int total_count = 0;
foreach (RootItem *child_item, m_childItems) {
total_count += child_item->countOfAllMessages();
}
return total_count;
}
bool RootItem::isChildOf(RootItem *root) {
if (root == NULL) {
return false;
}
RootItem *this_item = this;
while (this_item->kind() != RootItemKind::Root) {
if (root->childItems().contains(this_item)) {
return true;
}
else {
this_item = this_item->parent();
}
}
return false;
}
bool RootItem::isParentOf(RootItem *child) {
if (child == NULL) {
return false;
}
else {
return child->isChildOf(this);
}
}
QList<RootItem*> RootItem::getSubTree() {
QList<RootItem*> children;
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested items.
while (!traversable_items.isEmpty()) {
RootItem *active_item = traversable_items.takeFirst();
children.append(active_item);
traversable_items.append(active_item->childItems());
}
return children;
}
QList<RootItem*> RootItem::getSubTree(RootItemKind::Kind kind_of_item) {
QList<RootItem*> children;
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested items.
while (!traversable_items.isEmpty()) {
RootItem *active_item = traversable_items.takeFirst();
if ((active_item->kind() & kind_of_item) > 0) {
children.append(active_item);
}
traversable_items.append(active_item->childItems());
}
return children;
}
QList<Category*> RootItem::getSubTreeCategories() {
QList<Category*> children;
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested items.
while (!traversable_items.isEmpty()) {
RootItem *active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Category) {
children.append(active_item->toCategory());
}
traversable_items.append(active_item->childItems());
}
return children;
}
QHash<int,Category*> RootItem::getHashedSubTreeCategories() {
QHash<int,Category*> children;
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested items.
while (!traversable_items.isEmpty()) {
RootItem *active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Category && !children.contains(active_item->id())) {
children.insert(active_item->id(), active_item->toCategory());
}
traversable_items.append(active_item->childItems());
}
return children;
}
QList<Feed*> RootItem::getSubTreeFeeds() {
QList<Feed*> children;
QList<RootItem*> traversable_items;
traversable_items.append(this);
// Iterate all nested items.
while (!traversable_items.isEmpty()) {
RootItem *active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Feed) {
children.append(active_item->toFeed());
}
traversable_items.append(active_item->childItems());
}
return children;
}
ServiceRoot *RootItem::getParentServiceRoot() {
RootItem *working_parent = this;
while (working_parent->kind() != RootItemKind::Root) {
if (working_parent->kind() == RootItemKind::ServiceRoot) {
return working_parent->toServiceRoot();
}
else {
working_parent = working_parent->parent();
}
}
return NULL;
}
bool RootItem::removeChild(RootItem *child) {
return m_childItems.removeOne(child);
}
Category *RootItem::toCategory() {
return static_cast<Category*>(this);
}
Feed *RootItem::toFeed() {
return static_cast<Feed*>(this);
}
ServiceRoot *RootItem::toServiceRoot() {
return static_cast<ServiceRoot*>(this);
}
int RootItem::countOfUnreadMessages() const {
int total_count = 0;
foreach (RootItem *child_item, m_childItems) {
total_count += child_item->countOfUnreadMessages();
}
return total_count;
}
bool RootItem::removeChild(int index) {
if (index >= 0 && index < m_childItems.size()) {
m_childItems.removeAt(index);
return true;
}
else {
return false;
}
}

277
src/services/abstract/rootitem.h Executable file
View File

@ -0,0 +1,277 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef ROOTITEM_H
#define ROOTITEM_H
#include "core/message.h"
#include <QIcon>
#include <QDateTime>
#include <QFont>
class Category;
class Feed;
class ServiceRoot;
class QAction;
namespace RootItemKind {
// Describes the kind of the item.
enum Kind {
Root = 1,
Bin = 2,
Feed = 4,
Category = 8,
ServiceRoot = 16
};
inline Kind operator|(Kind a, Kind b) {
return static_cast<Kind>(static_cast<int>(a) | static_cast<int>(b));
}
}
// Represents ROOT item of FeedsModel.
// NOTE: This class is derived to add functionality for
// all other non-root items of FeedsModel.
class RootItem : public QObject {
Q_OBJECT
public:
// Holds statuses for feeds/messages
// to be marked read/unread.
enum ReadStatus {
Unread = 0,
Read = 1
};
// Holds statuses for messages
// to be switched importance (starred).
enum Importance {
NotImportant = 0,
Important = 1
};
// Constructors and destructors.
explicit RootItem(RootItem *parent_item = NULL);
virtual ~RootItem();
/////////////////////////////////////////
// /* Members to override.
/////////////////////////////////////////
// Returns list of specific actions which can be done with the item.
// Do not include general actions here like actions: Mark as read, Update, ...
// NOTE: Ownership of returned actions is not switched to caller, free them when needed.
virtual QList<QAction*> contextMenu();
// Can properties of this item be edited?
virtual bool canBeEdited();
// Performs editing of properties of this item (probably via dialog)
// and returns result status.
virtual bool editViaGui();
// Can the item be deleted?
virtual bool canBeDeleted();
// Performs deletion of the item, this
// method should NOT display any additional dialogs.
// Returns result status.
virtual bool deleteViaGui();
// Performs all needed steps (DB update, remote server update)
// to mark this item as read/unread.
virtual bool markAsReadUnread(ReadStatus status);
// Get ALL undeleted messages from this item in one single list.
// This is currently used for displaying items in "newspaper mode".
virtual QList<Message> undeletedMessages() const;
// This method should "clean" all messages it contains.
// What "clean" means? It means delete messages -> move them to recycle bin
// or eventually remove them completely if there is no recycle bin functionality.
// If this method is called on "recycle bin" instance of your
// service account, it should "empty" the recycle bin.
virtual bool cleanMessages(bool clear_only_read);
// Updates counts of all/unread messages for this feed.
virtual void updateCounts(bool including_total_count);
virtual int row() const;
virtual QVariant data(int column, int role) const;
virtual Qt::ItemFlags additionalFlags() const;
virtual bool performDragDropChange(RootItem *target_item);
// Each item offers "counts" of messages.
// Returns counts of messages of all child items summed up.
virtual int countOfUnreadMessages() const;
virtual int countOfAllMessages() const;
/////////////////////////////////////////
// Members to override. */
/////////////////////////////////////////
inline RootItem *parent() const {
return m_parentItem;
}
inline void setParent(RootItem *parent_item) {
m_parentItem = parent_item;
}
inline RootItem *child(int row) {
return m_childItems.value(row);
}
inline int childCount() const {
return m_childItems.size();
}
inline void appendChild(RootItem *child) {
m_childItems.append(child);
child->setParent(this);
}
// Access to children.
inline QList<RootItem*> childItems() const {
return m_childItems;
}
// Removes all children from this item.
// NOTE: Children are NOT freed from the memory.
inline void clearChildren() {
m_childItems.clear();
}
inline void setChildItems(QList<RootItem*> child_items) {
m_childItems = child_items;
}
// Removes particular child at given index.
// NOTE: Child is NOT freed from the memory.
bool removeChild(int index);
bool removeChild(RootItem *child);
// Checks whether "this" object is child (direct or indirect)
// of the given root.
bool isChildOf(RootItem *root);
// Is "this" item parent (direct or indirect) if given child?
bool isParentOf(RootItem *child);
// Returns flat list of all items from subtree where this item is a root.
// Returned list includes this item too.
QList<RootItem*> getSubTree();
QList<RootItem*> getSubTree(RootItemKind::Kind kind_of_item);
QList<Category*> getSubTreeCategories();
QHash<int,Category*> getHashedSubTreeCategories();
QList<Feed*> getSubTreeFeeds();
// Returns the service root node which is direct or indirect parent of current item.
ServiceRoot *getParentServiceRoot();
inline RootItemKind::Kind kind() const {
return m_kind;
}
inline void setKind(RootItemKind::Kind kind) {
m_kind = kind;
}
// Each item can have icon.
inline QIcon icon() const {
return m_icon;
}
inline void setIcon(const QIcon &icon) {
m_icon = icon;
}
// Each item has some kind of id. Usually taken from primary key attribute from DB.
inline int id() const {
return m_id;
}
inline void setId(int id) {
m_id = id;
}
// Each item has its title.
inline QString title() const {
return m_title;
}
inline void setTitle(const QString &title) {
m_title = title;
}
inline QDateTime creationDate() const {
return m_creationDate;
}
inline void setCreationDate(const QDateTime &creation_date) {
m_creationDate = creation_date;
}
inline QString description() const {
return m_description;
}
inline void setDescription(const QString &description) {
m_description = description;
}
inline QFont normalFont() const {
return m_normalFont;
}
inline void setNormalFont(const QFont &normal_font) {
m_normalFont = normal_font;
}
inline QFont boldFont() const {
return m_boldFont;
}
inline void setBoldFont(const QFont &bold_font) {
m_boldFont = bold_font;
}
// Converters
Category *toCategory();
Feed *toFeed();
ServiceRoot *toServiceRoot();
private:
void setupFonts();
RootItemKind::Kind m_kind;
int m_id;
QString m_title;
QString m_description;
QIcon m_icon;
QDateTime m_creationDate;
QFont m_normalFont;
QFont m_boldFont;
QList<RootItem*> m_childItems;
RootItem *m_parentItem;
};
#endif // ROOTITEM_H

View File

@ -0,0 +1,22 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/serviceentrypoint.h"
ServiceEntryPoint::~ServiceEntryPoint() {
}

View File

@ -0,0 +1,81 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef SERVICE_H
#define SERVICE_H
#include <QDialog>
#include <QIcon>
#include <QString>
class ServiceRoot;
class FeedsModel;
// TOP LEVEL class which provides basic information about the "service"
class ServiceEntryPoint {
public:
// Constructors.
virtual ~ServiceEntryPoint();
/////////////////////////////////////////
// /* Members to override.
/////////////////////////////////////////
// Creates new service root item, which is ready to be added
// into the model. This method can for example display
// some kind of first-time configuration dialog inside itself
// before returning the root item.
// Returns NULL if initialization of new root cannot be done.
virtual ServiceRoot *createNewRoot() = 0;
// Performs initialization of all service accounts created using this entry
// point from persistent DB.
// Returns list of root nodes which will be afterwards added
// to the global feed model.
virtual QList<ServiceRoot*> initializeSubtree() = 0;
// Can this service account be added just once?
// NOTE: This is true particularly for "standard" service
// which operates with normal RSS/ATOM feeds.
virtual bool isSingleInstanceService() = 0;
// Human readable service name, for example "TT-RSS".
virtual QString name() = 0;
// Some arbitrary string.
// NOTE: Keep in sync with ServiceRoot::code().
virtual QString code() = 0;
// Human readable service description, for example "Services which offers TT-RSS integration.".
virtual QString description() = 0;
// Version of the service, using of semantic versioning is recommended.
virtual QString version() = 0;
// Author of the service.
virtual QString author() = 0;
// Icon of the service.
virtual QIcon icon() = 0;
/////////////////////////////////////////
// Members to override. */
/////////////////////////////////////////
};
#endif // SERVICE_H

View File

@ -0,0 +1,198 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "services/abstract/serviceroot.h"
#include "core/feedsmodel.h"
#include "miscellaneous/application.h"
#include "miscellaneous/textfactory.h"
#include "services/abstract/category.h"
#include <QSqlQuery>
ServiceRoot::ServiceRoot(RootItem *parent) : RootItem(parent), m_accountId(NO_PARENT_CATEGORY) {
setKind(RootItemKind::ServiceRoot);
}
ServiceRoot::~ServiceRoot() {
}
bool ServiceRoot::deleteViaGui() {
QSqlDatabase connection = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
// Remove all messages.
if (!QSqlQuery(connection).exec(QString("DELETE FROM Messages WHERE account_id = %1;").arg(accountId()))) {
return false;
}
// Remove all feeds.
if (!QSqlQuery(connection).exec(QString("DELETE FROM Feeds WHERE account_id = %1;").arg(accountId()))) {
return false;
}
// Remove all categories.
if (!QSqlQuery(connection).exec(QString("DELETE FROM Categories WHERE account_id = %1;").arg(accountId()))) {
return false;
}
// Switch "existence" flag.
bool data_removed = QSqlQuery(connection).exec(QString("DELETE FROM Accounts WHERE id = %1;").arg(accountId()));
if (data_removed) {
requestItemRemoval(this);
}
return data_removed;
}
bool ServiceRoot::markAsReadUnread(RootItem::ReadStatus status) {
QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
if (!db_handle.transaction()) {
qWarning("Starting transaction for feeds read change.");
return false;
}
QSqlQuery query_read_msg(db_handle);
query_read_msg.setForwardOnly(true);
query_read_msg.prepare(QSL("UPDATE Messages SET is_read = :read WHERE is_pdeleted = 0 AND account_id = :account_id;"));
query_read_msg.bindValue(QSL(":account_id"), accountId());
query_read_msg.bindValue(QSL(":read"), status == RootItem::Read ? 1 : 0);
if (!query_read_msg.exec()) {
qDebug("Query execution for feeds read change failed.");
db_handle.rollback();
}
// Commit changes.
if (db_handle.commit()) {
updateCounts(false);
itemChanged(getSubTree());
requestReloadMessageList(status == RootItem::Read);
return true;
}
else {
return db_handle.rollback();
}
}
QList<Message> ServiceRoot::undeletedMessages() const {
QList<Message> messages;
int account_id = accountId();
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true);
query_read_msg.prepare("SELECT * "
"FROM Messages "
"WHERE is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;");
query_read_msg.bindValue(QSL(":account_id"), account_id);
// FIXME: Fix those const functions, this is fucking ugly.
if (query_read_msg.exec()) {
while (query_read_msg.next()) {
bool decoded;
Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded);
if (decoded) {
messages.append(message);
}
messages.append(message);
}
}
return messages;
}
void ServiceRoot::itemChanged(const QList<RootItem *> &items) {
emit dataChanged(items);
}
void ServiceRoot::requestReloadMessageList(bool mark_selected_messages_read) {
emit reloadMessageListRequested(mark_selected_messages_read);
}
void ServiceRoot::requestFeedReadFilterReload() {
emit readFeedsFilterInvalidationRequested();
}
void ServiceRoot::requestItemExpand(const QList<RootItem *> &items, bool expand) {
emit itemExpandRequested(items, expand);
}
void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) {
emit itemReassignmentRequested(item, new_parent);
}
void ServiceRoot::requestItemRemoval(RootItem *item) {
emit itemRemovalRequested(item);
}
int ServiceRoot::accountId() const {
return m_accountId;
}
void ServiceRoot::setAccountId(int account_id) {
m_accountId = account_id;
}
void ServiceRoot::assembleFeeds(Assignment feeds) {
QHash<int,Category*> categories = getHashedSubTreeCategories();
foreach (const AssignmentItem &feed, feeds) {
if (feed.first == NO_PARENT_CATEGORY) {
// This is top-level feed, add it to the root item.
appendChild(feed.second);
feed.second->updateCounts(true);
}
else if (categories.contains(feed.first)) {
// This feed belongs to this category.
categories.value(feed.first)->appendChild(feed.second);
feed.second->updateCounts(true);
}
else {
qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title()));
}
}
}
void ServiceRoot::assembleCategories(Assignment categories) {
QHash<int,RootItem*> assignments;
assignments.insert(NO_PARENT_CATEGORY, this);
// Add top-level categories.
while (!categories.isEmpty()) {
for (int i = 0; i < categories.size(); i++) {
if (assignments.contains(categories.at(i).first)) {
// Parent category of this category is already added.
assignments.value(categories.at(i).first)->appendChild(categories.at(i).second);
// Now, added category can be parent for another categories, add it.
assignments.insert(categories.at(i).second->id(), categories.at(i).second);
// Remove the category from the list, because it was
// added to the final collection.
categories.removeAt(i);
i--;
}
}
}
}

View File

@ -0,0 +1,179 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef SERVICEROOT_H
#define SERVICEROOT_H
#include "services/abstract/rootitem.h"
#include "core/message.h"
#include <QPair>
class FeedsModel;
class RecycleBin;
class QAction;
class QSqlTableModel;
// Car here represents ID of the item.
typedef QList<QPair<int,RootItem*> > Assignment;
typedef QPair<int,RootItem*> AssignmentItem;
// THIS IS the root node of the service.
// NOTE: The root usually contains some core functionality of the
// service like service account username/password etc.
class ServiceRoot : public RootItem {
Q_OBJECT
public:
explicit ServiceRoot(RootItem *parent = NULL);
virtual ~ServiceRoot();
/////////////////////////////////////////
// /* Members to override.
/////////////////////////////////////////
bool deleteViaGui();
bool markAsReadUnread(ReadStatus status);
// Returns list of specific actions for "Add new item" main window menu.
// So typical list of returned actions could look like:
// a) Add new feed
// b) Add new category
// c) ...
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> addItemMenu() = 0;
// Returns list of specific actions to be shown in main window menu
// bar in sections "Services -> 'this service'".
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> serviceMenu() = 0;
// Access to recycle bin of this account if there is any.
virtual RecycleBin *recycleBin() = 0;
QList<Message> undeletedMessages() const;
// Start/stop services.
// Start method is called when feed model gets initialized OR after user adds new service.
// Account should synchronously initialize its children (load them from DB is recommended
// here).
//
// Stop method is called just before application exits OR when
// user explicitly deletes existing service instance.
virtual void start() = 0;
virtual void stop() = 0;
// Returns the UNIQUE code of the given service.
// NOTE: Keep in sync with ServiceEntryRoot::code().
virtual QString code() = 0;
// This method should prepare messages for given "item" (download them maybe?)
// into predefined "Messages" table
// and then use method QSqlTableModel::setFilter(....).
// NOTE: It would be more preferable if all messages are downloaded
// right when feeds are updated.
virtual bool loadMessagesForItem(RootItem *item, QSqlTableModel *model) = 0;
// Called BEFORE this read status update (triggered by user in message list) is stored in DB,
// when false is returned, change is aborted.
// This is the place to make some other changes like updating
// some ONLINE service or something.
//
// "read" is status which is ABOUT TO BE SET.
virtual bool onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read) = 0;
// Called AFTER this read status update (triggered by user in message list) is stored in DB,
// when false is returned, change is aborted.
// Here service root should inform (via signals)
// which items are actually changed.
//
// "read" is status which is ABOUT TO BE SET.
virtual bool onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read) = 0;
// Called BEFORE this importance switch update is stored in DB,
// when false is returned, change is aborted.
// This is the place to make some other changes like updating
// some ONLINE service or something.
//
// "changes" - list of pairs - <message (integer id), new status>
virtual bool onBeforeSwitchMessageImportance(RootItem *selected_item, const QList<QPair<Message,RootItem::Importance> > &changes) = 0;
// Called AFTER this importance switch update is stored in DB,
// when false is returned, change is aborted.
// Here service root should inform (via signals)
// which items are actually changed.
//
// "changes" - list of pairs - <message (integer id), new status>
virtual bool onAfterSwitchMessageImportance(RootItem *selected_item, const QList<QPair<Message,RootItem::Importance> > &changes) = 0;
// Called BEFORE the list of messages is about to be deleted
// by the user from message list.
virtual bool onBeforeMessagesDelete(RootItem *selected_item, const QList<Message> &messages) = 0;
// Called AFTER the list of messages was deleted
// by the user from message list.
virtual bool onAfterMessagesDelete(RootItem *selected_item, const QList<Message> &messages) = 0;
// Called BEFORE the list of messages is about to be restored from recycle bin
// by the user from message list.
// Selected item is naturally recycle bin.
virtual bool onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList<Message> &messages) = 0;
// Called AFTER the list of messages was restored from recycle bin
// by the user from message list.
// Selected item is naturally recycle bin.
virtual bool onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList<Message> &messages) = 0;
/////////////////////////////////////////
// Members to override. */
/////////////////////////////////////////
// Obvious methods to wrap signals.
void itemChanged(const QList<RootItem*> &items);
void requestReloadMessageList(bool mark_selected_messages_read);
void requestFeedReadFilterReload();
void requestItemExpand(const QList<RootItem*> &items, bool expand);
void requestItemReassignment(RootItem *item, RootItem *new_parent);
void requestItemRemoval(RootItem *item);
// Account ID corresponds with DB attribute Accounts (id).
int accountId() const;
void setAccountId(int account_id);
protected:
// Takes lists of feeds/categories and assembles them into the tree structure.
void assembleCategories(Assignment categories);
void assembleFeeds(Assignment feeds);
signals:
// Emitted if data in any item belonging to this root are changed.
void dataChanged(QList<RootItem*> items);
void readFeedsFilterInvalidationRequested();
void reloadMessageListRequested(bool mark_selected_messages_read);
void itemExpandRequested(QList<RootItem*> items, bool expand);
void itemReassignmentRequested(RootItem *item, RootItem *new_parent);
void itemRemovalRequested(RootItem *item);
private:
int m_accountId;
};
#endif // SERVICEROOT_H

View File

@ -15,11 +15,10 @@
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "gui/dialogs/formcategorydetails.h"
#include "services/standard/gui/formstandardcategorydetails.h"
#include "definitions/definitions.h"
#include "core/rootitem.h"
#include "core/category.h"
#include "services/abstract/rootitem.h"
#include "core/feedsmodel.h"
#include "miscellaneous/iconfactory.h"
#include "gui/feedsview.h"
@ -27,6 +26,8 @@
#include "gui/messagebox.h"
#include "gui/systemtrayicon.h"
#include "gui/dialogs/formmain.h"
#include "services/standard/standardcategory.h"
#include "services/standard/standardserviceroot.h"
#include <QLineEdit>
#include <QTextEdit>
@ -38,11 +39,8 @@
#include <QFileDialog>
FormCategoryDetails::FormCategoryDetails(FeedsModel *model,
QWidget *parent)
: QDialog(parent),
m_editableCategory(NULL),
m_feedsModel(model) {
FormStandardCategoryDetails::FormStandardCategoryDetails(StandardServiceRoot *service_root, QWidget *parent)
: QDialog(parent), m_editableCategory(NULL), m_serviceRoot(service_root) {
initialize();
createConnections();
@ -51,11 +49,11 @@ FormCategoryDetails::FormCategoryDetails(FeedsModel *model,
onDescriptionChanged(QString());
}
FormCategoryDetails::~FormCategoryDetails() {
FormStandardCategoryDetails::~FormStandardCategoryDetails() {
qDebug("Destroying FormCategoryDetails instance.");
}
void FormCategoryDetails::createConnections() {
void FormStandardCategoryDetails::createConnections() {
// General connections.
connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(apply()));
connect(m_ui->m_txtTitle->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onTitleChanged(QString)));
@ -67,7 +65,7 @@ void FormCategoryDetails::createConnections() {
connect(m_actionUseDefaultIcon, SIGNAL(triggered()), this, SLOT(onUseDefaultIcon()));
}
void FormCategoryDetails::setEditableCategory(Category *editable_category) {
void FormStandardCategoryDetails::setEditableCategory(StandardCategory *editable_category) {
m_editableCategory = editable_category;
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) editable_category->parent())));
@ -76,9 +74,9 @@ void FormCategoryDetails::setEditableCategory(Category *editable_category) {
m_ui->m_btnIcon->setIcon(editable_category->icon());
}
int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_select) {
int FormStandardCategoryDetails::exec(StandardCategory *input_category, RootItem *parent_to_select) {
// Load categories.
loadCategories(m_feedsModel->allCategories().values(), m_feedsModel->rootItem(), input_category);
loadCategories(m_serviceRoot->allCategories(), m_serviceRoot, input_category);
if (input_category == NULL) {
// User is adding new category.
@ -90,10 +88,10 @@ int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_sele
// Load parent from suggested item.
if (parent_to_select != NULL) {
if (parent_to_select->kind() == RootItem::Cattegory) {
if (parent_to_select->kind() == RootItemKind::Category) {
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select)));
}
else if (parent_to_select->kind() == RootItem::Feeed) {
else if (parent_to_select->kind() == RootItemKind::Feed) {
int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent()));
if (target_item >= 0) {
@ -112,22 +110,23 @@ int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_sele
return QDialog::exec();
}
void FormCategoryDetails::apply() {
void FormStandardCategoryDetails::apply() {
RootItem *parent = static_cast<RootItem*>(m_ui->m_cmbParentCategory->itemData(m_ui->m_cmbParentCategory->currentIndex()).value<void*>());
Category *new_category = new Category();
StandardCategory *new_category = new StandardCategory();
new_category->setTitle(m_ui->m_txtTitle->lineEdit()->text());
new_category->setCreationDate(QDateTime::currentDateTime());
new_category->setDescription(m_ui->m_txtDescription->lineEdit()->text());
new_category->setIcon(m_ui->m_btnIcon->icon());
new_category->setParent(parent);
if (m_editableCategory == NULL) {
// Add the category.
if (m_feedsModel->addCategory(new_category, parent)) {
if (new_category->addItself(parent)) {
m_serviceRoot->requestItemReassignment(new_category, parent);
accept();
}
else {
delete new_category;
qApp->showGuiMessage(tr("Cannot add category"),
tr("Category was not added due to error."),
QSystemTrayIcon::Critical,
@ -135,19 +134,25 @@ void FormCategoryDetails::apply() {
}
}
else {
if (m_feedsModel->editCategory(m_editableCategory, new_category)) {
new_category->setParent(parent);
bool edited = m_editableCategory->editItself(new_category);
if (edited) {
m_serviceRoot->requestItemReassignment(m_editableCategory, new_category->parent());
accept();
}
else {
qApp->showGuiMessage(tr("Cannot edit category"),
tr("Category was not edited due to error."),
QSystemTrayIcon::Critical,
qApp->mainForm(), true);
QSystemTrayIcon::Critical, this, true);
}
delete new_category;
}
}
void FormCategoryDetails::onTitleChanged(const QString &new_title){
void FormStandardCategoryDetails::onTitleChanged(const QString &new_title){
if (new_title.simplified().size() >= MIN_CATEGORY_NAME_LENGTH) {
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
m_ui->m_txtTitle->setStatus(WidgetWithStatus::Ok, tr("Category name is ok."));
@ -158,7 +163,7 @@ void FormCategoryDetails::onTitleChanged(const QString &new_title){
}
}
void FormCategoryDetails::onDescriptionChanged(const QString &new_description) {
void FormStandardCategoryDetails::onDescriptionChanged(const QString &new_description) {
if (new_description.simplified().isEmpty()) {
m_ui->m_txtDescription->setStatus(LineEditWithStatus::Warning, tr("Description is empty."));
}
@ -167,11 +172,11 @@ void FormCategoryDetails::onDescriptionChanged(const QString &new_description) {
}
}
void FormCategoryDetails::onNoIconSelected() {
void FormStandardCategoryDetails::onNoIconSelected() {
m_ui->m_btnIcon->setIcon(QIcon());
}
void FormCategoryDetails::onLoadIconFromFile() {
void FormStandardCategoryDetails::onLoadIconFromFile() {
QFileDialog dialog(this, tr("Select icon file for the category"),
qApp->homeFolderPath(), tr("Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga)"));
dialog.setFileMode(QFileDialog::ExistingFile);
@ -190,12 +195,12 @@ void FormCategoryDetails::onLoadIconFromFile() {
}
}
void FormCategoryDetails::onUseDefaultIcon() {
void FormStandardCategoryDetails::onUseDefaultIcon() {
m_ui->m_btnIcon->setIcon(qApp->icons()->fromTheme(QSL("folder-category")));
}
void FormCategoryDetails::initialize() {
m_ui = new Ui::FormCategoryDetails();
void FormStandardCategoryDetails::initialize() {
m_ui = new Ui::FormStandardCategoryDetails();
m_ui->setupUi(this);
// Set text boxes.
@ -241,14 +246,14 @@ void FormCategoryDetails::initialize() {
m_ui->m_txtTitle->lineEdit()->setFocus(Qt::TabFocusReason);
}
void FormCategoryDetails::loadCategories(const QList<Category*> categories,
RootItem *root_item,
Category *input_category) {
void FormStandardCategoryDetails::loadCategories(const QList<StandardCategory*> categories,
RootItem *root_item,
StandardCategory *input_category) {
m_ui->m_cmbParentCategory->addItem(root_item->icon(),
root_item->title(),
QVariant::fromValue((void*) root_item));
foreach (Category *category, categories) {
foreach (StandardCategory *category, categories) {
if (input_category != NULL && (category == input_category || category->isChildOf(input_category))) {
// This category cannot be selected as the new
// parent for currently edited category, so

View File

@ -18,33 +18,33 @@
#ifndef FORMCATEGORYDETAILS_H
#define FORMCATEGORYDETAILS_H
#include "ui_formcategorydetails.h"
#include "ui_formstandardcategorydetails.h"
#include <QDialog>
namespace Ui {
class FormCategoryDetails;
class FormStandardCategoryDetails;
}
class Category;
class Category;
class StandardCategory;
class StandardServiceRoot;
class FeedsModel;
class RootItem;
class QMenu;
class QAction;
class FormCategoryDetails : public QDialog {
class FormStandardCategoryDetails : public QDialog {
Q_OBJECT
public:
// Constructors and destructors.
explicit FormCategoryDetails(FeedsModel *model, QWidget *parent = 0);
virtual ~FormCategoryDetails();
explicit FormStandardCategoryDetails(StandardServiceRoot *service_root, QWidget *parent = 0);
virtual ~FormStandardCategoryDetails();
public slots:
// Executes add/edit standard category dialog.
int exec(Category *input_category, RootItem *parent_to_select);
int exec(StandardCategory *input_category, RootItem *parent_to_select);
protected slots:
// Applies changes.
@ -64,7 +64,7 @@ class FormCategoryDetails : public QDialog {
void createConnections();
// Sets the category which will be edited.
void setEditableCategory(Category *editable_category);
void setEditableCategory(StandardCategory *editable_category);
// Initializes the dialog.
void initialize();
@ -72,12 +72,12 @@ class FormCategoryDetails : public QDialog {
// Loads categories into the dialog + give root "category"
// and make sure that no childs of input category (including)
// input category are loaded.
void loadCategories(const QList<Category*> categories, RootItem *root_item, Category *input_category);
void loadCategories(const QList<StandardCategory*> categories, RootItem *root_item, StandardCategory *input_category);
private:
Ui::FormCategoryDetails *m_ui;
Category *m_editableCategory;
FeedsModel *m_feedsModel;
Ui::FormStandardCategoryDetails *m_ui;
StandardCategory *m_editableCategory;
StandardServiceRoot *m_serviceRoot;
QMenu *m_iconMenu;
QAction *m_actionLoadIconFromFile;

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FormCategoryDetails</class>
<widget class="QDialog" name="FormCategoryDetails">
<class>FormStandardCategoryDetails</class>
<widget class="QDialog" name="FormStandardCategoryDetails">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>346</width>
<height>180</height>
<width>397</width>
<height>209</height>
</rect>
</property>
<property name="minimumSize">
@ -153,7 +153,7 @@
<connection>
<sender>m_buttonBox</sender>
<signal>rejected()</signal>
<receiver>FormCategoryDetails</receiver>
<receiver>FormStandardCategoryDetails</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">

View File

@ -15,13 +15,14 @@
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "gui/dialogs/formfeeddetails.h"
#include "services/standard/gui/formstandardfeeddetails.h"
#include "definitions/definitions.h"
#include "core/feedsmodel.h"
#include "core/rootitem.h"
#include "core/category.h"
#include "core/feed.h"
#include "services/abstract/rootitem.h"
#include "services/standard/standardserviceroot.h"
#include "services/standard/standardcategory.h"
#include "services/standard/standardfeed.h"
#include "miscellaneous/textfactory.h"
#include "miscellaneous/iconfactory.h"
#include "network-web/networkfactory.h"
@ -39,10 +40,10 @@
#include <QMimeData>
FormFeedDetails::FormFeedDetails(FeedsModel *model, QWidget *parent)
FormStandardFeedDetails::FormStandardFeedDetails(StandardServiceRoot *service_root, QWidget *parent)
: QDialog(parent),
m_editableFeed(NULL),
m_feedsModel(model) {
m_serviceRoot(service_root) {
initialize();
createConnections();
@ -54,13 +55,13 @@ FormFeedDetails::FormFeedDetails(FeedsModel *model, QWidget *parent)
onPasswordChanged(QString());
}
FormFeedDetails::~FormFeedDetails() {
FormStandardFeedDetails::~FormStandardFeedDetails() {
delete m_ui;
}
int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) {
int FormStandardFeedDetails::exec(StandardFeed *input_feed, RootItem *parent_to_select) {
// Load categories.
loadCategories(m_feedsModel->allCategories().values(), m_feedsModel->rootItem());
loadCategories(m_serviceRoot->allCategories(), m_serviceRoot);
if (input_feed == NULL) {
// User is adding new category.
@ -77,10 +78,10 @@ int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) {
}
if (parent_to_select != NULL) {
if (parent_to_select->kind() == RootItem::Cattegory) {
if (parent_to_select->kind() == RootItemKind::Category) {
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select)));
}
else if (parent_to_select->kind() == RootItem::Feeed) {
else if (parent_to_select->kind() == RootItemKind::Feed) {
int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent()));
if (target_item >= 0) {
@ -103,7 +104,7 @@ int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) {
return QDialog::exec();
}
void FormFeedDetails::onTitleChanged(const QString &new_title){
void FormStandardFeedDetails::onTitleChanged(const QString &new_title){
if (new_title.simplified().size() >= MIN_CATEGORY_NAME_LENGTH) {
m_ui->m_txtTitle->setStatus(LineEditWithStatus::Ok, tr("Feed name is ok."));
}
@ -114,7 +115,7 @@ void FormFeedDetails::onTitleChanged(const QString &new_title){
checkOkButtonEnabled();
}
void FormFeedDetails::onDescriptionChanged(const QString &new_description) {
void FormStandardFeedDetails::onDescriptionChanged(const QString &new_description) {
if (new_description.simplified().isEmpty()) {
m_ui->m_txtDescription->setStatus(LineEditWithStatus::Warning, tr("Description is empty."));
}
@ -123,7 +124,7 @@ void FormFeedDetails::onDescriptionChanged(const QString &new_description) {
}
}
void FormFeedDetails::onUrlChanged(const QString &new_url) {
void FormStandardFeedDetails::onUrlChanged(const QString &new_url) {
if (QRegExp(URL_REGEXP).exactMatch(new_url)) {
// New url is well-formed.
m_ui->m_txtUrl->setStatus(LineEditWithStatus::Ok, tr("The url is ok."));
@ -140,7 +141,7 @@ void FormFeedDetails::onUrlChanged(const QString &new_url) {
checkOkButtonEnabled();
}
void FormFeedDetails::onUsernameChanged(const QString &new_username) {
void FormStandardFeedDetails::onUsernameChanged(const QString &new_username) {
bool is_username_ok = !m_ui->m_gbAuthentication->isChecked() || !new_username.simplified().isEmpty();
m_ui->m_txtUsername->setStatus(is_username_ok ?
@ -151,7 +152,7 @@ void FormFeedDetails::onUsernameChanged(const QString &new_username) {
tr("Username is empty."));
}
void FormFeedDetails::onPasswordChanged(const QString &new_password) {
void FormStandardFeedDetails::onPasswordChanged(const QString &new_password) {
bool is_password_ok = !m_ui->m_gbAuthentication->isChecked() || !new_password.simplified().isEmpty();
m_ui->m_txtPassword->setStatus(is_password_ok ?
@ -162,27 +163,27 @@ void FormFeedDetails::onPasswordChanged(const QString &new_password) {
tr("Password is empty."));
}
void FormFeedDetails::onAuthenticationSwitched() {
void FormStandardFeedDetails::onAuthenticationSwitched() {
onUsernameChanged(m_ui->m_txtUsername->lineEdit()->text());
onPasswordChanged(m_ui->m_txtPassword->lineEdit()->text());
}
void FormFeedDetails::onAutoUpdateTypeChanged(int new_index) {
Feed::AutoUpdateType auto_update_type = static_cast<Feed::AutoUpdateType>(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt());
void FormStandardFeedDetails::onAutoUpdateTypeChanged(int new_index) {
StandardFeed::AutoUpdateType auto_update_type = static_cast<StandardFeed::AutoUpdateType>(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt());
switch (auto_update_type) {
case Feed::DontAutoUpdate:
case Feed::DefaultAutoUpdate:
case StandardFeed::DontAutoUpdate:
case StandardFeed::DefaultAutoUpdate:
m_ui->m_spinAutoUpdateInterval->setEnabled(false);
break;
case Feed::SpecificAutoUpdate:
case StandardFeed::SpecificAutoUpdate:
default:
m_ui->m_spinAutoUpdateInterval->setEnabled(true);
}
}
void FormFeedDetails::checkOkButtonEnabled() {
void FormStandardFeedDetails::checkOkButtonEnabled() {
LineEditWithStatus::StatusType title_status = m_ui->m_txtTitle->status();
LineEditWithStatus::StatusType url_status = m_ui->m_txtUrl->status();
@ -191,11 +192,11 @@ void FormFeedDetails::checkOkButtonEnabled() {
url_status == LineEditWithStatus::Warning));
}
void FormFeedDetails::onNoIconSelected() {
void FormStandardFeedDetails::onNoIconSelected() {
m_ui->m_btnIcon->setIcon(QIcon());
}
void FormFeedDetails::onLoadIconFromFile() {
void FormStandardFeedDetails::onLoadIconFromFile() {
QFileDialog dialog(this, tr("Select icon file for the feed"),
qApp->homeFolderPath(), tr("Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga)"));
dialog.setFileMode(QFileDialog::ExistingFile);
@ -214,14 +215,14 @@ void FormFeedDetails::onLoadIconFromFile() {
}
}
void FormFeedDetails::onUseDefaultIcon() {
void FormStandardFeedDetails::onUseDefaultIcon() {
m_ui->m_btnIcon->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
}
void FormFeedDetails::apply() {
void FormStandardFeedDetails::apply() {
RootItem *parent = static_cast<RootItem*>(m_ui->m_cmbParentCategory->itemData(m_ui->m_cmbParentCategory->currentIndex()).value<void*>());
Feed::Type type = static_cast<Feed::Type>(m_ui->m_cmbType->itemData(m_ui->m_cmbType->currentIndex()).value<int>());
Feed *new_feed = new Feed();
StandardFeed::Type type = static_cast<StandardFeed::Type>(m_ui->m_cmbType->itemData(m_ui->m_cmbType->currentIndex()).value<int>());
StandardFeed *new_feed = new StandardFeed();
// Setup data for new_feed.
new_feed->setTitle(m_ui->m_txtTitle->lineEdit()->text());
@ -234,24 +235,30 @@ void FormFeedDetails::apply() {
new_feed->setPasswordProtected(m_ui->m_gbAuthentication->isChecked());
new_feed->setUsername(m_ui->m_txtUsername->lineEdit()->text());
new_feed->setPassword(m_ui->m_txtPassword->lineEdit()->text());
new_feed->setAutoUpdateType(static_cast<Feed::AutoUpdateType>(m_ui->m_cmbAutoUpdateType->itemData(m_ui->m_cmbAutoUpdateType->currentIndex()).toInt()));
new_feed->setAutoUpdateType(static_cast<StandardFeed::AutoUpdateType>(m_ui->m_cmbAutoUpdateType->itemData(m_ui->m_cmbAutoUpdateType->currentIndex()).toInt()));
new_feed->setAutoUpdateInitialInterval(m_ui->m_spinAutoUpdateInterval->value());
new_feed->setParent(parent);
if (m_editableFeed == NULL) {
// Add the feed.
if (m_feedsModel->addFeed(new_feed, parent)) {
if (new_feed->addItself(parent)) {
m_serviceRoot->requestItemReassignment(new_feed, parent);
accept();
}
else {
delete new_feed;
qApp->showGuiMessage(tr("Cannot add feed"),
tr("Feed was not added due to error."),
QSystemTrayIcon::Critical, this, true);
}
}
else {
new_feed->setParent(parent);
// Edit the feed.
if (m_feedsModel->editFeed(m_editableFeed, new_feed)) {
bool edited = m_editableFeed->editItself(new_feed);
if (edited) {
m_serviceRoot->requestItemReassignment(m_editableFeed, new_feed->parent());
accept();
}
else {
@ -259,13 +266,15 @@ void FormFeedDetails::apply() {
tr("Feed was not edited due to error."),
QSystemTrayIcon::Critical, this, true);
}
delete new_feed;
}
}
void FormFeedDetails::guessFeed() {
QPair<Feed*, QNetworkReply::NetworkError> result = Feed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(),
m_ui->m_txtUsername->lineEdit()->text(),
m_ui->m_txtPassword->lineEdit()->text());
void FormStandardFeedDetails::guessFeed() {
QPair<StandardFeed*, QNetworkReply::NetworkError> result = StandardFeed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(),
m_ui->m_txtUsername->lineEdit()->text(),
m_ui->m_txtPassword->lineEdit()->text());
if (result.first != NULL) {
// Icon or whole feed was guessed.
@ -306,10 +315,10 @@ void FormFeedDetails::guessFeed() {
}
}
void FormFeedDetails::guessIconOnly() {
QPair<Feed*, QNetworkReply::NetworkError> result = Feed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(),
m_ui->m_txtUsername->lineEdit()->text(),
m_ui->m_txtPassword->lineEdit()->text());
void FormStandardFeedDetails::guessIconOnly() {
QPair<StandardFeed*, QNetworkReply::NetworkError> result = StandardFeed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(),
m_ui->m_txtUsername->lineEdit()->text(),
m_ui->m_txtPassword->lineEdit()->text());
if (result.first != NULL) {
// Icon or whole feed was guessed.
@ -337,7 +346,7 @@ void FormFeedDetails::guessIconOnly() {
}
}
void FormFeedDetails::createConnections() {
void FormStandardFeedDetails::createConnections() {
// General connections.
connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(apply()));
connect(m_ui->m_txtTitle->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onTitleChanged(QString)));
@ -356,7 +365,7 @@ void FormFeedDetails::createConnections() {
connect(m_actionUseDefaultIcon, SIGNAL(triggered()), this, SLOT(onUseDefaultIcon()));
}
void FormFeedDetails::setEditableFeed(Feed *editable_feed) {
void FormStandardFeedDetails::setEditableFeed(StandardFeed *editable_feed) {
m_editableFeed = editable_feed;
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) editable_feed->parent())));
@ -364,7 +373,7 @@ void FormFeedDetails::setEditableFeed(Feed *editable_feed) {
m_ui->m_txtDescription->lineEdit()->setText(editable_feed->description());
m_ui->m_btnIcon->setIcon(editable_feed->icon());
m_ui->m_cmbType->setCurrentIndex(m_ui->m_cmbType->findData(QVariant::fromValue((int) editable_feed->type())));
m_ui->m_cmbEncoding->setCurrentIndex(m_ui->m_cmbEncoding->findData(editable_feed->encoding(), Qt::DisplayRole));
m_ui->m_cmbEncoding->setCurrentIndex(m_ui->m_cmbEncoding->findData(editable_feed->encoding(), Qt::DisplayRole, Qt::MatchFixedString));
m_ui->m_gbAuthentication->setChecked(editable_feed->passwordProtected());
m_ui->m_txtUsername->lineEdit()->setText(editable_feed->username());
m_ui->m_txtPassword->lineEdit()->setText(editable_feed->password());
@ -373,8 +382,8 @@ void FormFeedDetails::setEditableFeed(Feed *editable_feed) {
m_ui->m_spinAutoUpdateInterval->setValue(editable_feed->autoUpdateInitialInterval());
}
void FormFeedDetails::initialize() {
m_ui = new Ui::FormFeedDetails();
void FormStandardFeedDetails::initialize() {
m_ui = new Ui::FormStandardFeedDetails();
m_ui->setupUi(this);
// Set flags and attributes.
@ -405,10 +414,10 @@ void FormFeedDetails::initialize() {
#endif
// Add standard feed types.
m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Atom10), QVariant::fromValue((int) Feed::Atom10));
m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rdf), QVariant::fromValue((int) Feed::Rdf));
m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rss0X), QVariant::fromValue((int) Feed::Rss0X));
m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rss2X), QVariant::fromValue((int) Feed::Rss2X));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Atom10), QVariant::fromValue((int) StandardFeed::Atom10));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rdf), QVariant::fromValue((int) StandardFeed::Rdf));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss0X), QVariant::fromValue((int) StandardFeed::Rss0X));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss2X), QVariant::fromValue((int) StandardFeed::Rss2X));
// Load available encodings.
QList<QByteArray> encodings = QTextCodec::availableCodecs();
@ -449,9 +458,9 @@ void FormFeedDetails::initialize() {
// Setup auto-update options.
m_ui->m_spinAutoUpdateInterval->setValue(DEFAULT_AUTO_UPDATE_INTERVAL);
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"), QVariant::fromValue((int) Feed::DefaultAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"), QVariant::fromValue((int) Feed::SpecificAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"), QVariant::fromValue((int) Feed::DontAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"), QVariant::fromValue((int) StandardFeed::DefaultAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"), QVariant::fromValue((int) StandardFeed::SpecificAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"), QVariant::fromValue((int) StandardFeed::DontAutoUpdate));
// Set tab order.
setTabOrder(m_ui->m_cmbParentCategory, m_ui->m_cmbType);
@ -475,15 +484,14 @@ void FormFeedDetails::initialize() {
m_ui->m_txtUrl->lineEdit()->setFocus(Qt::TabFocusReason);
}
void FormFeedDetails::loadCategories(const QList<Category*> categories,
RootItem *root_item) {
void FormStandardFeedDetails::loadCategories(const QList<StandardCategory*> categories,
RootItem *root_item) {
m_ui->m_cmbParentCategory->addItem(root_item->icon(),
root_item->title(),
QVariant::fromValue((void*) root_item));
foreach (Category *category, categories) {
m_ui->m_cmbParentCategory->addItem(category->data(FDS_MODEL_TITLE_INDEX,
Qt::DecorationRole).value<QIcon>(),
foreach (StandardCategory *category, categories) {
m_ui->m_cmbParentCategory->addItem(category->icon(),
category->title(),
QVariant::fromValue((void*) category));
}

View File

@ -18,31 +18,31 @@
#ifndef FORMSTANDARDFEEDDETAILS_H
#define FORMSTANDARDFEEDDETAILS_H
#include "ui_formfeeddetails.h"
#include <QDialog>
#include "ui_formstandardfeeddetails.h"
namespace Ui {
class FormFeedDetails;
class FormStandardFeedDetails;
}
class FeedsModel;
class Feed;
class Category;
class StandardServiceRoot;
class StandardFeed;
class StandardCategory;
class RootItem;
class FormFeedDetails : public QDialog {
class FormStandardFeedDetails : public QDialog {
Q_OBJECT
public:
// Constructors and destructors.
explicit FormFeedDetails(FeedsModel *model, QWidget *parent = 0);
virtual ~FormFeedDetails();
explicit FormStandardFeedDetails(StandardServiceRoot *service_root, QWidget *parent = 0);
virtual ~FormStandardFeedDetails();
public slots:
// Executes add/edit standard feed dialog.
int exec(Feed *input_feed, RootItem *parent_to_select);
int exec(StandardFeed *input_feed, RootItem *parent_to_select);
protected slots:
// Applies changes.
@ -72,19 +72,19 @@ class FormFeedDetails : public QDialog {
void createConnections();
// Sets the feed which will be edited.
void setEditableFeed(Feed *editable_feed);
void setEditableFeed(StandardFeed *editable_feed);
// Initializes the dialog.
void initialize();
// Loads categories into the dialog from the model.
void loadCategories(const QList<Category*> categories,
void loadCategories(const QList<StandardCategory*> categories,
RootItem *root_item);
private:
Ui::FormFeedDetails *m_ui;
Feed *m_editableFeed;
FeedsModel *m_feedsModel;
Ui::FormStandardFeedDetails *m_ui;
StandardFeed *m_editableFeed;
StandardServiceRoot *m_serviceRoot;
QMenu *m_iconMenu;
QAction *m_actionLoadIconFromFile;

Some files were not shown because too many files have changed in this diff Show More