diff --git a/Larss.pro b/Larss.pro index 74bcf7e..006626b 100644 --- a/Larss.pro +++ b/Larss.pro @@ -10,15 +10,15 @@ TARGET = Larss TEMPLATE = app -SOURCES += main.cpp\ - mainwindow.cpp \ - feedmodel.cpp \ - rssparser.cpp \ - feedpoller.cpp +SOURCES += larss/main.cpp\ + larss/mainwindow.cpp \ + larss/feedmodel.cpp \ + larss/rssparser.cpp \ + larss/feedpoller.cpp -HEADERS += mainwindow.h \ - feedmodel.h \ - rssparser.h \ - feedpoller.h +HEADERS += include/mainwindow.h \ + include/feedmodel.h \ + include/rssparser.h \ + include/feedpoller.h -FORMS += mainwindow.ui +FORMS += ui/mainwindow.ui diff --git a/feedmodel.cpp b/feedmodel.cpp deleted file mode 100644 index 7382f1e..0000000 --- a/feedmodel.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include "feedmodel.h" -#include <QAbstractItemModel> -#include <QSqlDatabase> -#include <QSqlQuery> -#include <QSqlError> -#include <QDebug> -#include <QStringList> -#include <QLabel> - -using namespace Larss; - -FeedModel::FeedModel(QSqlDatabase db, QObject *parent) : QAbstractItemModel (parent) -{ - this->db = db; - - // Check that the right tables are present in the db - if (!db.tables().contains("categories")) - { - QSqlQuery query(db); - query.prepare("CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT);"); - if (!query.exec()) - qDebug() << "Error while creating the categories table in the db"; - } - - if (!db.tables().contains("feeds")) - { - QSqlQuery query(db); - query.prepare("CREATE TABLE feeds (id INTEGER PRIMARY KEY, category INTEGER, name TEXT, url TEXT);"); - if (!query.exec()) - qDebug() << "Error while creating the feeds table in the db"; - } -} - -FeedModel::~FeedModel() -{ -} - - -QModelIndex -FeedModel::index(int row, int column, const QModelIndex &parent) const -{ - if (parent.internalId() == 0) - { - QSqlQuery query (db); - query.prepare("SELECT id from categories ORDER by id;"); - if (query.exec()) - { - if (!query.first()) - return QModelIndex(); - for (int i = 0; i < row; i++) - { - if (!query.next ()) - return QModelIndex(); - } - return createIndex(row, column, query.value(0).toInt()); - } - else - return QModelIndex(); - } - else - { - QSqlQuery query(db); - query.prepare ("SELECT id from feeds WHERE category=:category ORDER BY id;"); - query.bindValue("category", parent.internalId()); - if (query.exec()) - { - if (!query.first()) - return QModelIndex(); - else - { - for(int i = 0; i < row; i++) - { - if (!query.next()) - return QModelIndex(); - } - return createIndex(row, column, query.value(0).toInt() + FEEDMODEL_MAX_CATEGORIES); - } - } - else - return QModelIndex(); - } -} - -bool -FeedModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (role != Qt::EditRole) - return false; - - QSqlQuery query(db); - - if (index.internalId() < FEEDMODEL_MAX_CATEGORIES && index.internalId() != 0) - { - // We are trying to modify a category - query.prepare("UPDATE categories SET name=:value WHERE id=:id;"); - query.bindValue("value", value.toString()); - query.bindValue("id", index.internalId()); - } - else - { - // We are trying to modify a feed - query.prepare("UPDATE feeds SET name=:value WHERE id=:id;"); - query.bindValue("value", value.toString()); - query.bindValue("id", index.internalId() - FEEDMODEL_MAX_CATEGORIES); - } - - if (!query.exec()) - { - qDebug() << "Query failed" << query.lastError() << query.executedQuery(); - return false; - } - else - { - // Emit the datachanged signal - dataChanged(index, index); - return true; - } -} - -Qt::ItemFlags -FeedModel::flags(const QModelIndex &index) const -{ - Q_UNUSED(index); - return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); -} - -int -FeedModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) - { - // Categories count - QSqlQuery query(db); - query.prepare ("SELECT id from categories ORDER by id;"); - if (query.exec()) - { - int row_count = 1; - if (!query.first()) - return 0; - else - while (query.next()) - row_count++; - return row_count; - } - else - return 0; - } - else - { - int category_id = parent.internalId(); - QSqlQuery query(db); - query.prepare("SELECT id from feeds where category=:category;"); - query.bindValue("category", category_id); - if (query.exec()) - { - int row_count = 1; - if (!query.first()) - return 0; - else - while (query.next()) - row_count++; - return row_count; - } - else - return 0; - } -} - -bool -FeedModel::addCategory(QString name) -{ - QSqlQuery query(db); - query.prepare("INSERT INTO categories VALUES (NULL, :name);"); - query.bindValue("name", name); - - bool successful = query.exec(); - if (successful) - reset(); - return successful; -} - -bool -FeedModel::addFeed(QString name, QString url, quint32 category_id) -{ - QSqlQuery query(db); - query.prepare("INSERT INTO feeds VALUES (NULL, :category, :name, :url);"); - query.bindValue("category", category_id); - query.bindValue("name", name); - query.bindValue("url", url); - - bool successful = query.exec(); - if (successful) - reset(); - return successful; -} - -int -FeedModel::columnCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return 1; -} - -QVariant -FeedModel::data(const QModelIndex &index, int role) const -{ - if (role == Qt::DisplayRole) - { - if (index.internalId() == 0) - return QString ("Root"); - if (index.internalId() < FEEDMODEL_MAX_CATEGORIES) - { - QSqlQuery query(db); - query.prepare ("SELECT id, name from categories WHERE id=:category;"); - query.bindValue("category", index.internalId()); - if (query.exec()) - { - if (!query.first()) - return QVariant(QVariant::Invalid); - else - { - return query.value(1).toString(); - } - } - else - return QVariant(QVariant::Invalid); - } - else - { - QSqlQuery query(db); - query.prepare ("SELECT id, category, name, url from feeds WHERE id=:feed;"); - query.bindValue("feed", index.internalId() - FEEDMODEL_MAX_CATEGORIES); - if (query.exec()) - { - if (query.first()) - return query.value(2).toString(); - else - return QVariant(QVariant::Invalid); - } - else - return QVariant(QVariant::Invalid); - } - } - else - return QVariant (QVariant::Invalid); -} - -QModelIndex -FeedModel::parent(const QModelIndex &child) const -{ - if (!child.isValid()) - return QModelIndex (); - - quint32 row; - quint32 id = child.internalId(); - - if (id == 0) - return QModelIndex(); - else if (id < FEEDMODEL_MAX_CATEGORIES) - { - // Get the position of the category - QSqlQuery query (db); - query.prepare ("SELECT id from category;"); - if (query.exec ()) - { - if (query.first ()) - row = 1; - else - return QModelIndex (); - while (query.next ()) - { - row++; - if ((quint64) query.value(0).toInt() == id) - break; - } - - return createIndex (row, 1, 0); - } - else - return QModelIndex(); - } - else - { - quint32 category_id; - // We have a feed here, that actually has a real parent. - // We need to get the ID of the category - id -= FEEDMODEL_MAX_CATEGORIES; - QSqlQuery query (db); - query.prepare ("SELECT category from feeds WHERE id=:id;"); - query.bindValue("id", id); - if (query.exec()) - { - if (!query.first ()) - return QModelIndex(); - else - { - category_id = query.value(0).toInt(); - - // We need to get the position of the feed in the category - query.prepare("SELECT id from feeds WHERE category=:category;"); - query.bindValue("category", category_id); - if (query.exec()) - { - row = 1; - if (!query.first()) - return QModelIndex(); - else - { - while (query.next()) - row++; - return createIndex(row, 1, category_id); - } - } - else - return QModelIndex(); - } - } - else - return QModelIndex(); - } -} - -QVariant -FeedModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - // We have a header data only for the first column, horizontal mode. - if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0) - return tr("Feed"); - else - return QVariant (); -} - -QString -FeedModel::getUrl(const QModelIndex &index) -{ - quint64 id = index.internalId(); - if (id < FEEDMODEL_MAX_CATEGORIES) - return ""; - else - { - QSqlQuery query(db); - query.prepare("SELECT url from feeds WHERE id=:id;"); - query.bindValue("id", id - FEEDMODEL_MAX_CATEGORIES); - if (query.exec()) - { - if (query.first()) - return query.value(0).toString(); - else - return ""; - } - else - return ""; - } -} - diff --git a/feedmodel.h b/feedmodel.h deleted file mode 100644 index 6ead440..0000000 --- a/feedmodel.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef FEEDMODEL_H -#define FEEDMODEL_H - -#include <QObject> -#include <QAbstractItemModel> -#include <QSqlDatabase> -#include <QtXml> - -namespace Larss { - -#define FEEDMODEL_MAX_CATEGORIES 1024 - -class FeedModel : public QAbstractItemModel { - Q_OBJECT - -public: - explicit FeedModel(QSqlDatabase db, QObject *parent = 0); - - /** - * @brief Destructor for the FeedModel - */ - ~FeedModel(); - - /** - * @brief Get the ModelIndex associated with a given row and column. - */ - QModelIndex index(int row, int column, const QModelIndex &parent) const; - - /** - * @brief Get the number of rows in the list. - */ - int rowCount(const QModelIndex &parent) const; - - /** - * @brief Get the number of column - */ - int columnCount(const QModelIndex &parent) const; - - /** - * @brief Get the data associated to a given node. - */ - QVariant data(const QModelIndex &index, int role) const; - - /** - * @brief Get the parent of a given node. - */ - QModelIndex parent(const QModelIndex &child) const; - - /** - * @brief Return the flags for the given item. - */ - Qt::ItemFlags flags(const QModelIndex &index) const; - - /** - * @brief Return the data to be inserted in the column header of the treeview. - */ - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - /** - * @brief Return the URL associated with a given ModelIndex - */ - QString getUrl (const QModelIndex& index); - - /** - * @brief Method used to change the data in the database - */ - bool setData(const QModelIndex &index, const QVariant &value, int role); - - /** - * @brief Add a category. - */ - bool addCategory (QString name); - - /** - * @brief Add a new feed in the specified category. - */ - bool addFeed (QString name, QString url, quint32 category_id); - -signals: - -public slots: - -private: - /** - * @brief Database containing the data of the feeds. - */ - QSqlDatabase db; - - -}; - -} - -#endif // FEEDMODEL_H diff --git a/feedpoller.cpp b/feedpoller.cpp deleted file mode 100644 index 11a6834..0000000 --- a/feedpoller.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "feedpoller.h" -#include <QtXml> -#include "feedmodel.h" - -using namespace Larss; - -FeedPoller::FeedPoller(QObject *parent, RssParser *parser, FeedModel *model) : - QThread(parent) -{ - this->parser = parser; - this->model = model; - - workQueue = new QList<QModelIndex> (); - nowLoading = 0; - - rssContent = new QHash<quint32, QString>(); - poll_active = true; - - // Create the QNetworkAccessManager and connect the loaded signal - // with our handler. - manager = new QNetworkAccessManager (); - manager->connect(manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(networkManagerReplyFinished(QNetworkReply*))); -} - -void -FeedPoller::run() -{ - - // Create the timer that will call the function every second. - QTimer *timer = new QTimer (); - timer->setInterval(1000); - timer->connect(timer, SIGNAL(timeout()), - this, SLOT(poll())); - timer->start(); - - QThread::exec(); -} - -bool -FeedPoller::poll() -{ - // Poll indefinitely until we are requested to exit. - if (nowLoading == 0) - { - if (workQueue->isEmpty()) - return false; - else - { - QModelIndex next_item = workQueue->takeFirst(); - nowLoading = next_item.internalId(); - manager->get(QNetworkRequest(QUrl(model->getUrl(next_item)))); - return true; - } - } -} - -void -FeedPoller::stopPolling () -{ - poll_active = false; - QThread::exit(); -} - -void -FeedPoller::queueWork(const QModelIndex &index) -{ - if (index.internalId() < FEEDMODEL_MAX_CATEGORIES) - return; - workQueue->append(index); -} - -void -FeedPoller::networkManagerReplyFinished(QNetworkReply *reply) -{ - // Assume that the string is UTF-8 encoded. This is likely to be - // true, but I should check it in some way. - rssContent->insert (nowLoading, QString::fromUtf8(reply->readAll())); - - // Now update the database with the new data obtained. - QDomDocument doc; - if (doc.setContent(rssContent->value(nowLoading))) - { - // Try to catch other news_feed with the same link, so preload all of them. - QSqlQuery query(parser->db); - query.prepare ("SELECT link from news WHERE feed=:feed"); - query.bindValue("feed", nowLoading - FEEDMODEL_MAX_CATEGORIES); - if (!query.exec ()) - return; - - QStringList links; - if (query.first()) - { - links.append(query.value(0).toString()); - while (query.next()) - links.append(query.value(0).toString()); - } - - QDomElement doc_el = doc.documentElement(); - QDomNodeList items = doc_el.elementsByTagName("item"); - - for (quint32 i = 0; i < items.length(); i++) - { - // Get the i-th news - QDomNode item = items.item(i); - QDomElement element = item.toElement(); - - // Get the data in it - QString link = element.elementsByTagName("link").item(0).firstChild().nodeValue(); - QString title = element.elementsByTagName("title").item(0).firstChild().nodeValue(); - QString description = element.elementsByTagName("description").item(0).firstChild().nodeValue(); - QString content = element.elementsByTagName("content:encoded").item(0).firstChild().nodeValue(); - - // We should enable this for RSS 2.0 - // QString guid = element.elementsByTagName("guid").item(0).firstChild().nodeValue(); - // QString pubDate = element.elementsByTagName("pubDate").item(0).firstChild().nodeValue(); - - if (!links.contains(link)) - { - // That means that no results were found, so let's insert this one. - QSqlRecord record = parser->record(); - record.setValue("time", 0); - record.setValue("read", 0); - record.setValue("title", title); - record.setValue("link", link); - record.setValue("description", description); - record.setValue("content", content); - record.setValue("feed", nowLoading - FEEDMODEL_MAX_CATEGORIES); - - if (!parser->insertRecord(-1, record)) - qDebug () << "Error inserting record"; - } - } - - if (!parser->submitAll()) - qDebug() << "Error submitting new data"; - } - else - qDebug () << "Error parsing the document"; - - nowLoading = 0; - return; - - // Check if there is work in the queue - if (!workQueue->isEmpty()) - { - QModelIndex next_item = workQueue->takeFirst(); - nowLoading = next_item.internalId(); - manager->get(QNetworkRequest(QUrl(model->getUrl(next_item)))); - } -} diff --git a/feedpoller.h b/feedpoller.h deleted file mode 100644 index 4494249..0000000 --- a/feedpoller.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef FEEDPOLLER_H -#define FEEDPOLLER_H - -#include <QObject> -#include <QThread> -#include <QtSql> -#include <QtNetwork> -#include "rssparser.h" -#include "feedmodel.h" - -namespace Larss { - - class FeedPoller : public QThread - { - Q_OBJECT - public: - explicit FeedPoller(QObject *parent, RssParser* parser, FeedModel *model); - void queueWork (const QModelIndex& index); - void stopPolling (); - - signals: - - public slots: - void networkManagerReplyFinished(QNetworkReply* reply); - bool poll(); - - private: - /** - * @brief The parser of this instance of Larss. - */ - RssParser *parser; - - /** - * @brief The FeedModel of this instance of Larss. - */ - FeedModel *model; - - /** - * @brief The content of the rss loaded from the various - * items in the feedmodel. - */ - QHash<quint32, QString> *rssContent; - - /** - * @brief The NetworkAccessManager that will be used to retrieve - * the data from sourceUrl. - */ - QNetworkAccessManager *manager; - - /** - * @brief An index of the RSS that is currently being loaded, or 0 - * if there is nothing in the queue. - */ - quint32 nowLoading; - - /** - * @brief Queue of item that needs to be refreshed. - */ - QList<QModelIndex> *workQueue; - - /** - * @brief If the thread is requested to polling or not. - */ - bool poll_active; - - /** - * @brief Real work of the thread. - */ - void run(); - - }; -} - -#endif // FEEDPOLLER_H diff --git a/include/feedmodel.h b/include/feedmodel.h new file mode 100644 index 0000000..6ead440 --- /dev/null +++ b/include/feedmodel.h @@ -0,0 +1,94 @@ +#ifndef FEEDMODEL_H +#define FEEDMODEL_H + +#include <QObject> +#include <QAbstractItemModel> +#include <QSqlDatabase> +#include <QtXml> + +namespace Larss { + +#define FEEDMODEL_MAX_CATEGORIES 1024 + +class FeedModel : public QAbstractItemModel { + Q_OBJECT + +public: + explicit FeedModel(QSqlDatabase db, QObject *parent = 0); + + /** + * @brief Destructor for the FeedModel + */ + ~FeedModel(); + + /** + * @brief Get the ModelIndex associated with a given row and column. + */ + QModelIndex index(int row, int column, const QModelIndex &parent) const; + + /** + * @brief Get the number of rows in the list. + */ + int rowCount(const QModelIndex &parent) const; + + /** + * @brief Get the number of column + */ + int columnCount(const QModelIndex &parent) const; + + /** + * @brief Get the data associated to a given node. + */ + QVariant data(const QModelIndex &index, int role) const; + + /** + * @brief Get the parent of a given node. + */ + QModelIndex parent(const QModelIndex &child) const; + + /** + * @brief Return the flags for the given item. + */ + Qt::ItemFlags flags(const QModelIndex &index) const; + + /** + * @brief Return the data to be inserted in the column header of the treeview. + */ + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + /** + * @brief Return the URL associated with a given ModelIndex + */ + QString getUrl (const QModelIndex& index); + + /** + * @brief Method used to change the data in the database + */ + bool setData(const QModelIndex &index, const QVariant &value, int role); + + /** + * @brief Add a category. + */ + bool addCategory (QString name); + + /** + * @brief Add a new feed in the specified category. + */ + bool addFeed (QString name, QString url, quint32 category_id); + +signals: + +public slots: + +private: + /** + * @brief Database containing the data of the feeds. + */ + QSqlDatabase db; + + +}; + +} + +#endif // FEEDMODEL_H diff --git a/include/feedpoller.h b/include/feedpoller.h new file mode 100644 index 0000000..4494249 --- /dev/null +++ b/include/feedpoller.h @@ -0,0 +1,74 @@ +#ifndef FEEDPOLLER_H +#define FEEDPOLLER_H + +#include <QObject> +#include <QThread> +#include <QtSql> +#include <QtNetwork> +#include "rssparser.h" +#include "feedmodel.h" + +namespace Larss { + + class FeedPoller : public QThread + { + Q_OBJECT + public: + explicit FeedPoller(QObject *parent, RssParser* parser, FeedModel *model); + void queueWork (const QModelIndex& index); + void stopPolling (); + + signals: + + public slots: + void networkManagerReplyFinished(QNetworkReply* reply); + bool poll(); + + private: + /** + * @brief The parser of this instance of Larss. + */ + RssParser *parser; + + /** + * @brief The FeedModel of this instance of Larss. + */ + FeedModel *model; + + /** + * @brief The content of the rss loaded from the various + * items in the feedmodel. + */ + QHash<quint32, QString> *rssContent; + + /** + * @brief The NetworkAccessManager that will be used to retrieve + * the data from sourceUrl. + */ + QNetworkAccessManager *manager; + + /** + * @brief An index of the RSS that is currently being loaded, or 0 + * if there is nothing in the queue. + */ + quint32 nowLoading; + + /** + * @brief Queue of item that needs to be refreshed. + */ + QList<QModelIndex> *workQueue; + + /** + * @brief If the thread is requested to polling or not. + */ + bool poll_active; + + /** + * @brief Real work of the thread. + */ + void run(); + + }; +} + +#endif // FEEDPOLLER_H diff --git a/include/mainwindow.h b/include/mainwindow.h new file mode 100644 index 0000000..6c71c04 --- /dev/null +++ b/include/mainwindow.h @@ -0,0 +1,45 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include "rssparser.h" +#include "feedmodel.h" +#include "feedpoller.h" +#include <QSqlDatabase> + +namespace Ui { + class MainWindow; +} + +namespace Larss { + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void on_actionExit_activated(); + + void on_feedTreeView_clicked(const QModelIndex &index); + + void on_newsTableView_clicked(const QModelIndex &index); + + void on_newsTableView_activated(const QModelIndex &index); + +private: + Ui::MainWindow *ui; + void do_exit(); + + QSqlDatabase db; + FeedModel *feedModel; + RssParser *rssParser; + FeedPoller *poller; +}; + +} + +#endif // MAINWINDOW_H diff --git a/include/rssparser.h b/include/rssparser.h new file mode 100644 index 0000000..1070b43 --- /dev/null +++ b/include/rssparser.h @@ -0,0 +1,88 @@ +#ifndef RSSPARSER_H +#define RSSPARSER_H + +#include <QObject> +#include <QtNetwork/QNetworkAccessManager> +#include <QList> +#include <QSqlTableModel> +#include "feedmodel.h" + +namespace Larss { + + class RssParser : public QSqlTableModel + { + Q_OBJECT + public: + /** + * @brief RssParser constructor. + */ + explicit RssParser(QSqlDatabase db, FeedModel * model = NULL, QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~RssParser(); + + /** + * @brief Function that tells the views that use this model + * what to display in the title of the columns. + */ + QVariant headerData (int section, Qt::Orientation orientation, int role) const; + + /** + * @brief Get the link associated with a given ModelIndex. + */ + QString getLink (const QModelIndex& index); + + /** + * @brief Get the id of the feed pointed by the QModelIndex or + * 0 if there is no feed pointed (i.e. is a category or + * the root of the tree). + */ + quint64 getFeed (const QModelIndex& index); + + /** + * @brief Get the content associated with the feed. + */ + QString getContent (const QModelIndex& index); + + /** + * @brief Get the title of a news pointed by index + */ + QString getTitle (const QModelIndex& index); + + /** + * @brief Set the read status on a news. + */ + void setReadStatus (const QModelIndex& index, bool read); + + /** + * @brief Reimplement data to make unread post bold. + */ + QVariant data(const QModelIndex &idx, int role) const; + + /** + * @brief Set the active category to display. + */ + void selectActiveFeed (quint64 feed_id); + + /** + * @brief Database where all the news will be loaded and saved. + */ + QSqlDatabase db; + + signals: + + public slots: + + private: + + /** + * @brief The FeedModel + */ + FeedModel *model; + }; + +} + +#endif // RSSPARSER_H diff --git a/larss/feedmodel.cpp b/larss/feedmodel.cpp new file mode 100644 index 0000000..7382f1e --- /dev/null +++ b/larss/feedmodel.cpp @@ -0,0 +1,355 @@ +#include "feedmodel.h" +#include <QAbstractItemModel> +#include <QSqlDatabase> +#include <QSqlQuery> +#include <QSqlError> +#include <QDebug> +#include <QStringList> +#include <QLabel> + +using namespace Larss; + +FeedModel::FeedModel(QSqlDatabase db, QObject *parent) : QAbstractItemModel (parent) +{ + this->db = db; + + // Check that the right tables are present in the db + if (!db.tables().contains("categories")) + { + QSqlQuery query(db); + query.prepare("CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT);"); + if (!query.exec()) + qDebug() << "Error while creating the categories table in the db"; + } + + if (!db.tables().contains("feeds")) + { + QSqlQuery query(db); + query.prepare("CREATE TABLE feeds (id INTEGER PRIMARY KEY, category INTEGER, name TEXT, url TEXT);"); + if (!query.exec()) + qDebug() << "Error while creating the feeds table in the db"; + } +} + +FeedModel::~FeedModel() +{ +} + + +QModelIndex +FeedModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.internalId() == 0) + { + QSqlQuery query (db); + query.prepare("SELECT id from categories ORDER by id;"); + if (query.exec()) + { + if (!query.first()) + return QModelIndex(); + for (int i = 0; i < row; i++) + { + if (!query.next ()) + return QModelIndex(); + } + return createIndex(row, column, query.value(0).toInt()); + } + else + return QModelIndex(); + } + else + { + QSqlQuery query(db); + query.prepare ("SELECT id from feeds WHERE category=:category ORDER BY id;"); + query.bindValue("category", parent.internalId()); + if (query.exec()) + { + if (!query.first()) + return QModelIndex(); + else + { + for(int i = 0; i < row; i++) + { + if (!query.next()) + return QModelIndex(); + } + return createIndex(row, column, query.value(0).toInt() + FEEDMODEL_MAX_CATEGORIES); + } + } + else + return QModelIndex(); + } +} + +bool +FeedModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role != Qt::EditRole) + return false; + + QSqlQuery query(db); + + if (index.internalId() < FEEDMODEL_MAX_CATEGORIES && index.internalId() != 0) + { + // We are trying to modify a category + query.prepare("UPDATE categories SET name=:value WHERE id=:id;"); + query.bindValue("value", value.toString()); + query.bindValue("id", index.internalId()); + } + else + { + // We are trying to modify a feed + query.prepare("UPDATE feeds SET name=:value WHERE id=:id;"); + query.bindValue("value", value.toString()); + query.bindValue("id", index.internalId() - FEEDMODEL_MAX_CATEGORIES); + } + + if (!query.exec()) + { + qDebug() << "Query failed" << query.lastError() << query.executedQuery(); + return false; + } + else + { + // Emit the datachanged signal + dataChanged(index, index); + return true; + } +} + +Qt::ItemFlags +FeedModel::flags(const QModelIndex &index) const +{ + Q_UNUSED(index); + return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); +} + +int +FeedModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + { + // Categories count + QSqlQuery query(db); + query.prepare ("SELECT id from categories ORDER by id;"); + if (query.exec()) + { + int row_count = 1; + if (!query.first()) + return 0; + else + while (query.next()) + row_count++; + return row_count; + } + else + return 0; + } + else + { + int category_id = parent.internalId(); + QSqlQuery query(db); + query.prepare("SELECT id from feeds where category=:category;"); + query.bindValue("category", category_id); + if (query.exec()) + { + int row_count = 1; + if (!query.first()) + return 0; + else + while (query.next()) + row_count++; + return row_count; + } + else + return 0; + } +} + +bool +FeedModel::addCategory(QString name) +{ + QSqlQuery query(db); + query.prepare("INSERT INTO categories VALUES (NULL, :name);"); + query.bindValue("name", name); + + bool successful = query.exec(); + if (successful) + reset(); + return successful; +} + +bool +FeedModel::addFeed(QString name, QString url, quint32 category_id) +{ + QSqlQuery query(db); + query.prepare("INSERT INTO feeds VALUES (NULL, :category, :name, :url);"); + query.bindValue("category", category_id); + query.bindValue("name", name); + query.bindValue("url", url); + + bool successful = query.exec(); + if (successful) + reset(); + return successful; +} + +int +FeedModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant +FeedModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) + { + if (index.internalId() == 0) + return QString ("Root"); + if (index.internalId() < FEEDMODEL_MAX_CATEGORIES) + { + QSqlQuery query(db); + query.prepare ("SELECT id, name from categories WHERE id=:category;"); + query.bindValue("category", index.internalId()); + if (query.exec()) + { + if (!query.first()) + return QVariant(QVariant::Invalid); + else + { + return query.value(1).toString(); + } + } + else + return QVariant(QVariant::Invalid); + } + else + { + QSqlQuery query(db); + query.prepare ("SELECT id, category, name, url from feeds WHERE id=:feed;"); + query.bindValue("feed", index.internalId() - FEEDMODEL_MAX_CATEGORIES); + if (query.exec()) + { + if (query.first()) + return query.value(2).toString(); + else + return QVariant(QVariant::Invalid); + } + else + return QVariant(QVariant::Invalid); + } + } + else + return QVariant (QVariant::Invalid); +} + +QModelIndex +FeedModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex (); + + quint32 row; + quint32 id = child.internalId(); + + if (id == 0) + return QModelIndex(); + else if (id < FEEDMODEL_MAX_CATEGORIES) + { + // Get the position of the category + QSqlQuery query (db); + query.prepare ("SELECT id from category;"); + if (query.exec ()) + { + if (query.first ()) + row = 1; + else + return QModelIndex (); + while (query.next ()) + { + row++; + if ((quint64) query.value(0).toInt() == id) + break; + } + + return createIndex (row, 1, 0); + } + else + return QModelIndex(); + } + else + { + quint32 category_id; + // We have a feed here, that actually has a real parent. + // We need to get the ID of the category + id -= FEEDMODEL_MAX_CATEGORIES; + QSqlQuery query (db); + query.prepare ("SELECT category from feeds WHERE id=:id;"); + query.bindValue("id", id); + if (query.exec()) + { + if (!query.first ()) + return QModelIndex(); + else + { + category_id = query.value(0).toInt(); + + // We need to get the position of the feed in the category + query.prepare("SELECT id from feeds WHERE category=:category;"); + query.bindValue("category", category_id); + if (query.exec()) + { + row = 1; + if (!query.first()) + return QModelIndex(); + else + { + while (query.next()) + row++; + return createIndex(row, 1, category_id); + } + } + else + return QModelIndex(); + } + } + else + return QModelIndex(); + } +} + +QVariant +FeedModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + // We have a header data only for the first column, horizontal mode. + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section == 0) + return tr("Feed"); + else + return QVariant (); +} + +QString +FeedModel::getUrl(const QModelIndex &index) +{ + quint64 id = index.internalId(); + if (id < FEEDMODEL_MAX_CATEGORIES) + return ""; + else + { + QSqlQuery query(db); + query.prepare("SELECT url from feeds WHERE id=:id;"); + query.bindValue("id", id - FEEDMODEL_MAX_CATEGORIES); + if (query.exec()) + { + if (query.first()) + return query.value(0).toString(); + else + return ""; + } + else + return ""; + } +} + diff --git a/larss/feedpoller.cpp b/larss/feedpoller.cpp new file mode 100644 index 0000000..11a6834 --- /dev/null +++ b/larss/feedpoller.cpp @@ -0,0 +1,151 @@ +#include "feedpoller.h" +#include <QtXml> +#include "feedmodel.h" + +using namespace Larss; + +FeedPoller::FeedPoller(QObject *parent, RssParser *parser, FeedModel *model) : + QThread(parent) +{ + this->parser = parser; + this->model = model; + + workQueue = new QList<QModelIndex> (); + nowLoading = 0; + + rssContent = new QHash<quint32, QString>(); + poll_active = true; + + // Create the QNetworkAccessManager and connect the loaded signal + // with our handler. + manager = new QNetworkAccessManager (); + manager->connect(manager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(networkManagerReplyFinished(QNetworkReply*))); +} + +void +FeedPoller::run() +{ + + // Create the timer that will call the function every second. + QTimer *timer = new QTimer (); + timer->setInterval(1000); + timer->connect(timer, SIGNAL(timeout()), + this, SLOT(poll())); + timer->start(); + + QThread::exec(); +} + +bool +FeedPoller::poll() +{ + // Poll indefinitely until we are requested to exit. + if (nowLoading == 0) + { + if (workQueue->isEmpty()) + return false; + else + { + QModelIndex next_item = workQueue->takeFirst(); + nowLoading = next_item.internalId(); + manager->get(QNetworkRequest(QUrl(model->getUrl(next_item)))); + return true; + } + } +} + +void +FeedPoller::stopPolling () +{ + poll_active = false; + QThread::exit(); +} + +void +FeedPoller::queueWork(const QModelIndex &index) +{ + if (index.internalId() < FEEDMODEL_MAX_CATEGORIES) + return; + workQueue->append(index); +} + +void +FeedPoller::networkManagerReplyFinished(QNetworkReply *reply) +{ + // Assume that the string is UTF-8 encoded. This is likely to be + // true, but I should check it in some way. + rssContent->insert (nowLoading, QString::fromUtf8(reply->readAll())); + + // Now update the database with the new data obtained. + QDomDocument doc; + if (doc.setContent(rssContent->value(nowLoading))) + { + // Try to catch other news_feed with the same link, so preload all of them. + QSqlQuery query(parser->db); + query.prepare ("SELECT link from news WHERE feed=:feed"); + query.bindValue("feed", nowLoading - FEEDMODEL_MAX_CATEGORIES); + if (!query.exec ()) + return; + + QStringList links; + if (query.first()) + { + links.append(query.value(0).toString()); + while (query.next()) + links.append(query.value(0).toString()); + } + + QDomElement doc_el = doc.documentElement(); + QDomNodeList items = doc_el.elementsByTagName("item"); + + for (quint32 i = 0; i < items.length(); i++) + { + // Get the i-th news + QDomNode item = items.item(i); + QDomElement element = item.toElement(); + + // Get the data in it + QString link = element.elementsByTagName("link").item(0).firstChild().nodeValue(); + QString title = element.elementsByTagName("title").item(0).firstChild().nodeValue(); + QString description = element.elementsByTagName("description").item(0).firstChild().nodeValue(); + QString content = element.elementsByTagName("content:encoded").item(0).firstChild().nodeValue(); + + // We should enable this for RSS 2.0 + // QString guid = element.elementsByTagName("guid").item(0).firstChild().nodeValue(); + // QString pubDate = element.elementsByTagName("pubDate").item(0).firstChild().nodeValue(); + + if (!links.contains(link)) + { + // That means that no results were found, so let's insert this one. + QSqlRecord record = parser->record(); + record.setValue("time", 0); + record.setValue("read", 0); + record.setValue("title", title); + record.setValue("link", link); + record.setValue("description", description); + record.setValue("content", content); + record.setValue("feed", nowLoading - FEEDMODEL_MAX_CATEGORIES); + + if (!parser->insertRecord(-1, record)) + qDebug () << "Error inserting record"; + } + } + + if (!parser->submitAll()) + qDebug() << "Error submitting new data"; + } + else + qDebug () << "Error parsing the document"; + + nowLoading = 0; + return; + + // Check if there is work in the queue + if (!workQueue->isEmpty()) + { + QModelIndex next_item = workQueue->takeFirst(); + nowLoading = next_item.internalId(); + manager->get(QNetworkRequest(QUrl(model->getUrl(next_item)))); + } +} diff --git a/larss/main.cpp b/larss/main.cpp new file mode 100644 index 0000000..1eea1f3 --- /dev/null +++ b/larss/main.cpp @@ -0,0 +1,10 @@ +#include <QtGui/QApplication> +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication lars_application(argc, argv); + Larss::MainWindow main_window; + main_window.show(); + return lars_application.exec(); +} diff --git a/larss/mainwindow.cpp b/larss/mainwindow.cpp new file mode 100644 index 0000000..626ec1a --- /dev/null +++ b/larss/mainwindow.cpp @@ -0,0 +1,104 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include <QDebug> +#include <QtGui> + +using namespace Larss; + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // Open the database + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("/home/leonardo/larss.db"); + db.open(); + + // Load feedModel that will wrap the SQLite database + feedModel = new FeedModel(db, this); + ui->feedTreeView->setModel(feedModel); + ui->feedTreeView->setEditTriggers(QTreeView::DoubleClicked); + + // Load the RSSParser, hiding the unnecessary columns + rssParser = new RssParser(db, feedModel, this); + ui->newsTableView->setModel(rssParser); + ui->newsTableView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->newsTableView->setColumnHidden(0, true); // ID + ui->newsTableView->setColumnHidden(1, true); // Feed ID + ui->newsTableView->setColumnHidden(3, true); // Link + ui->newsTableView->setColumnHidden(4, true); // Description + ui->newsTableView->setColumnHidden(5, true); // Content + ui->newsTableView->setColumnHidden(6, true); // Time + ui->newsTableView->setColumnHidden(7, true); // Read state + ui->newsTableView->setEditTriggers(QTableView::NoEditTriggers); + ui->newsTableView->verticalHeader()->setHidden(true); + ui->newsTableView->horizontalHeader()->setHidden(true); + ui->newsTableView->horizontalHeader()->setStretchLastSection(false); + ui->newsTableView->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch); + + // Show only unread elements + rssParser->setFilter("read=0"); + + poller = new FeedPoller (this, rssParser, feedModel); + poller->start(); +} + +MainWindow::~MainWindow() +{ + db.close(); + delete ui; +} + +void MainWindow::do_exit() +{ + poller->stopPolling(); + poller->wait(); + QApplication::exit(); +} + +void MainWindow::on_actionExit_activated() +{ + // Exit the application + do_exit(); +} + +void Larss::MainWindow::on_feedTreeView_clicked(const QModelIndex &index) +{ + // Trigger refresh of selected item + poller->queueWork(index); + + // Set the active filter + quint64 feed_id; + if ((feed_id = rssParser->getFeed (index))) + { + rssParser->selectActiveFeed(feed_id); + + // Reset the title + ui->webViewTitleLabel->setText(""); + } + +} + +void Larss::MainWindow::on_newsTableView_clicked(const QModelIndex &index) +{ + // Get the number of the row, since index.row () is likely to change + // while we set the read status on the post. + quint32 row_number = index.row(); + + // A row got activated, so open it in the webview. + ui->webView->setHtml(rssParser->getContent(index)); + + // Select the right title + ui->webViewTitleLabel->setText(QString("<b>%1</b>").arg(rssParser->getTitle (index))); + + // And then mark it as read + rssParser->setReadStatus(index, true); + ui->newsTableView->selectRow(row_number); +} + +void Larss::MainWindow::on_newsTableView_activated(const QModelIndex &index) +{ + on_newsTableView_clicked(index); +} diff --git a/larss/rssparser.cpp b/larss/rssparser.cpp new file mode 100644 index 0000000..03e1ac1 --- /dev/null +++ b/larss/rssparser.cpp @@ -0,0 +1,139 @@ +#include "rssparser.h" +#include <QDebug> +#include <QtXml> +#include <QtSql> +#include <QtNetwork> +#include <QtGui> +#include <QtCore> + +using namespace Larss; + +Larss::RssParser::RssParser(QSqlDatabase db, FeedModel *model, QObject *parent) : + QSqlTableModel (parent, db) +{ + // Create the database if it does not exists. + if (!db.tables().contains ("news")) + { + qDebug () << "Creating table news that is not in database..."; + QSqlQuery query(db); + query.prepare("CREATE TABLE news (id INTEGER PRIMARY KEY, feed INTEGER, title TEXT, link TEXT, description TEXT, content TEXT, time INTEGER, read INTEGER);"); + if (!query.exec()) + qDebug () << "Error occurred while creating the database:" << query.lastError(); + } + + // Set init parameters for the QSqlDataTable + setTable("news"); + + // Select manual submit so user cannot modify content directly + setEditStrategy(QSqlTableModel::OnManualSubmit); + this->model = model; + select(); +} + +QVariant +Larss::RssParser::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case 0: + return tr("ID"); + break; + case 1: + return tr("Feed"); + break; + case 2: + return tr("Title"); + break; + case 3: + return tr("Link"); + break; + case 4: + return tr("Description"); + break; + case 5: + return tr("Content"); + break; + case 6: + return tr("Time"); + break; + case 7: + return tr("Read"); + } + } + } + + return QVariant (QVariant::Invalid); +} + +Larss::RssParser::~RssParser() +{ +} + +QVariant +Larss::RssParser::data(const QModelIndex &idx, int role) const +{ + if (role == Qt::FontRole) + { + // Get default font + QFont default_font = QSqlTableModel::data(idx, role).toString(); + + // Check if this news is read or not + QSqlRecord record = this->record(idx.row()); + if (record.value("read") == 0) + default_font.setBold(true); + return default_font; + } + // Call the default implementaton in almost every case + return QSqlTableModel::data(idx, role); +} + +QString +Larss::RssParser::getLink(const QModelIndex &index) +{ + QSqlRecord record = this->record(index.row()); + return record.value("link").toString(); +} + +void +Larss::RssParser::setReadStatus(const QModelIndex& index, bool read) +{ + QModelIndex read_index = createIndex(index.row(), 7, index.internalPointer()); + setData(read_index, read ? 1 : 0); + if (!submitAll()) + qDebug() << "Error while setting the read flag"; +} + +quint64 +Larss::RssParser::getFeed(const QModelIndex &index) +{ + quint64 id = index.internalId(); + if (id < FEEDMODEL_MAX_CATEGORIES) + return 0; + else + return (id - FEEDMODEL_MAX_CATEGORIES); +} + +QString +Larss::RssParser::getContent(const QModelIndex &index) +{ + QModelIndex description_index = createIndex(index.row(), 5, index.internalPointer()); + return data(description_index, Qt::DisplayRole).toString(); +} + +QString +Larss::RssParser::getTitle(const QModelIndex &index) +{ + QModelIndex title_index = createIndex(index.row(), 2, index.internalPointer()); + return data(title_index, Qt::DisplayRole).toString(); +} + +void +Larss::RssParser::selectActiveFeed(quint64 feed_id) +{ + // Show only the news from the given feed + setFilter(QString("feed='%1'").arg(feed_id)); +} diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 1eea1f3..0000000 --- a/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include <QtGui/QApplication> -#include "mainwindow.h" - -int main(int argc, char *argv[]) -{ - QApplication lars_application(argc, argv); - Larss::MainWindow main_window; - main_window.show(); - return lars_application.exec(); -} diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index 626ec1a..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include <QDebug> -#include <QtGui> - -using namespace Larss; - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - // Open the database - db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName("/home/leonardo/larss.db"); - db.open(); - - // Load feedModel that will wrap the SQLite database - feedModel = new FeedModel(db, this); - ui->feedTreeView->setModel(feedModel); - ui->feedTreeView->setEditTriggers(QTreeView::DoubleClicked); - - // Load the RSSParser, hiding the unnecessary columns - rssParser = new RssParser(db, feedModel, this); - ui->newsTableView->setModel(rssParser); - ui->newsTableView->setSelectionBehavior(QAbstractItemView::SelectRows); - ui->newsTableView->setColumnHidden(0, true); // ID - ui->newsTableView->setColumnHidden(1, true); // Feed ID - ui->newsTableView->setColumnHidden(3, true); // Link - ui->newsTableView->setColumnHidden(4, true); // Description - ui->newsTableView->setColumnHidden(5, true); // Content - ui->newsTableView->setColumnHidden(6, true); // Time - ui->newsTableView->setColumnHidden(7, true); // Read state - ui->newsTableView->setEditTriggers(QTableView::NoEditTriggers); - ui->newsTableView->verticalHeader()->setHidden(true); - ui->newsTableView->horizontalHeader()->setHidden(true); - ui->newsTableView->horizontalHeader()->setStretchLastSection(false); - ui->newsTableView->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch); - - // Show only unread elements - rssParser->setFilter("read=0"); - - poller = new FeedPoller (this, rssParser, feedModel); - poller->start(); -} - -MainWindow::~MainWindow() -{ - db.close(); - delete ui; -} - -void MainWindow::do_exit() -{ - poller->stopPolling(); - poller->wait(); - QApplication::exit(); -} - -void MainWindow::on_actionExit_activated() -{ - // Exit the application - do_exit(); -} - -void Larss::MainWindow::on_feedTreeView_clicked(const QModelIndex &index) -{ - // Trigger refresh of selected item - poller->queueWork(index); - - // Set the active filter - quint64 feed_id; - if ((feed_id = rssParser->getFeed (index))) - { - rssParser->selectActiveFeed(feed_id); - - // Reset the title - ui->webViewTitleLabel->setText(""); - } - -} - -void Larss::MainWindow::on_newsTableView_clicked(const QModelIndex &index) -{ - // Get the number of the row, since index.row () is likely to change - // while we set the read status on the post. - quint32 row_number = index.row(); - - // A row got activated, so open it in the webview. - ui->webView->setHtml(rssParser->getContent(index)); - - // Select the right title - ui->webViewTitleLabel->setText(QString("<b>%1</b>").arg(rssParser->getTitle (index))); - - // And then mark it as read - rssParser->setReadStatus(index, true); - ui->newsTableView->selectRow(row_number); -} - -void Larss::MainWindow::on_newsTableView_activated(const QModelIndex &index) -{ - on_newsTableView_clicked(index); -} diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index 6c71c04..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include <QMainWindow> -#include "rssparser.h" -#include "feedmodel.h" -#include "feedpoller.h" -#include <QSqlDatabase> - -namespace Ui { - class MainWindow; -} - -namespace Larss { - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: - void on_actionExit_activated(); - - void on_feedTreeView_clicked(const QModelIndex &index); - - void on_newsTableView_clicked(const QModelIndex &index); - - void on_newsTableView_activated(const QModelIndex &index); - -private: - Ui::MainWindow *ui; - void do_exit(); - - QSqlDatabase db; - FeedModel *feedModel; - RssParser *rssParser; - FeedPoller *poller; -}; - -} - -#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui deleted file mode 100644 index f297349..0000000 --- a/mainwindow.ui +++ /dev/null @@ -1,122 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>720</width> - <height>480</height> - </rect> - </property> - <property name="windowTitle"> - <string>Larss 0.1</string> - </property> - <widget class="QWidget" name="centralWidget"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QSplitter" name="splitter_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <widget class="QTreeView" name="feedTreeView"/> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <widget class="QTableView" name="newsTableView"> - <property name="showGrid"> - <bool>false</bool> - </property> - <property name="gridStyle"> - <enum>Qt::DashLine</enum> - </property> - <attribute name="verticalHeaderStretchLastSection"> - <bool>false</bool> - </attribute> - </widget> - <widget class="QWidget" name=""> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLabel" name="webViewTitleLabel"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QWebView" name="webView"> - <property name="url"> - <url> - <string>about:blank</string> - </url> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - </widget> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menuBar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>720</width> - <height>24</height> - </rect> - </property> - <widget class="QMenu" name="menuFile"> - <property name="title"> - <string>File</string> - </property> - <addaction name="actionExit"/> - </widget> - <addaction name="menuFile"/> - </widget> - <widget class="QStatusBar" name="statusBar"/> - <action name="actionExit"> - <property name="text"> - <string>Exit</string> - </property> - </action> - </widget> - <layoutdefault spacing="6" margin="11"/> - <customwidgets> - <customwidget> - <class>QWebView</class> - <extends>QWidget</extends> - <header>QtWebKit/QWebView</header> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/rssparser.cpp b/rssparser.cpp deleted file mode 100644 index 03e1ac1..0000000 --- a/rssparser.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "rssparser.h" -#include <QDebug> -#include <QtXml> -#include <QtSql> -#include <QtNetwork> -#include <QtGui> -#include <QtCore> - -using namespace Larss; - -Larss::RssParser::RssParser(QSqlDatabase db, FeedModel *model, QObject *parent) : - QSqlTableModel (parent, db) -{ - // Create the database if it does not exists. - if (!db.tables().contains ("news")) - { - qDebug () << "Creating table news that is not in database..."; - QSqlQuery query(db); - query.prepare("CREATE TABLE news (id INTEGER PRIMARY KEY, feed INTEGER, title TEXT, link TEXT, description TEXT, content TEXT, time INTEGER, read INTEGER);"); - if (!query.exec()) - qDebug () << "Error occurred while creating the database:" << query.lastError(); - } - - // Set init parameters for the QSqlDataTable - setTable("news"); - - // Select manual submit so user cannot modify content directly - setEditStrategy(QSqlTableModel::OnManualSubmit); - this->model = model; - select(); -} - -QVariant -Larss::RssParser::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role == Qt::DisplayRole) - { - if (orientation == Qt::Horizontal) - { - switch (section) - { - case 0: - return tr("ID"); - break; - case 1: - return tr("Feed"); - break; - case 2: - return tr("Title"); - break; - case 3: - return tr("Link"); - break; - case 4: - return tr("Description"); - break; - case 5: - return tr("Content"); - break; - case 6: - return tr("Time"); - break; - case 7: - return tr("Read"); - } - } - } - - return QVariant (QVariant::Invalid); -} - -Larss::RssParser::~RssParser() -{ -} - -QVariant -Larss::RssParser::data(const QModelIndex &idx, int role) const -{ - if (role == Qt::FontRole) - { - // Get default font - QFont default_font = QSqlTableModel::data(idx, role).toString(); - - // Check if this news is read or not - QSqlRecord record = this->record(idx.row()); - if (record.value("read") == 0) - default_font.setBold(true); - return default_font; - } - // Call the default implementaton in almost every case - return QSqlTableModel::data(idx, role); -} - -QString -Larss::RssParser::getLink(const QModelIndex &index) -{ - QSqlRecord record = this->record(index.row()); - return record.value("link").toString(); -} - -void -Larss::RssParser::setReadStatus(const QModelIndex& index, bool read) -{ - QModelIndex read_index = createIndex(index.row(), 7, index.internalPointer()); - setData(read_index, read ? 1 : 0); - if (!submitAll()) - qDebug() << "Error while setting the read flag"; -} - -quint64 -Larss::RssParser::getFeed(const QModelIndex &index) -{ - quint64 id = index.internalId(); - if (id < FEEDMODEL_MAX_CATEGORIES) - return 0; - else - return (id - FEEDMODEL_MAX_CATEGORIES); -} - -QString -Larss::RssParser::getContent(const QModelIndex &index) -{ - QModelIndex description_index = createIndex(index.row(), 5, index.internalPointer()); - return data(description_index, Qt::DisplayRole).toString(); -} - -QString -Larss::RssParser::getTitle(const QModelIndex &index) -{ - QModelIndex title_index = createIndex(index.row(), 2, index.internalPointer()); - return data(title_index, Qt::DisplayRole).toString(); -} - -void -Larss::RssParser::selectActiveFeed(quint64 feed_id) -{ - // Show only the news from the given feed - setFilter(QString("feed='%1'").arg(feed_id)); -} diff --git a/rssparser.h b/rssparser.h deleted file mode 100644 index 1070b43..0000000 --- a/rssparser.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef RSSPARSER_H -#define RSSPARSER_H - -#include <QObject> -#include <QtNetwork/QNetworkAccessManager> -#include <QList> -#include <QSqlTableModel> -#include "feedmodel.h" - -namespace Larss { - - class RssParser : public QSqlTableModel - { - Q_OBJECT - public: - /** - * @brief RssParser constructor. - */ - explicit RssParser(QSqlDatabase db, FeedModel * model = NULL, QObject *parent = 0); - - /** - * @brief Default destructor. - */ - ~RssParser(); - - /** - * @brief Function that tells the views that use this model - * what to display in the title of the columns. - */ - QVariant headerData (int section, Qt::Orientation orientation, int role) const; - - /** - * @brief Get the link associated with a given ModelIndex. - */ - QString getLink (const QModelIndex& index); - - /** - * @brief Get the id of the feed pointed by the QModelIndex or - * 0 if there is no feed pointed (i.e. is a category or - * the root of the tree). - */ - quint64 getFeed (const QModelIndex& index); - - /** - * @brief Get the content associated with the feed. - */ - QString getContent (const QModelIndex& index); - - /** - * @brief Get the title of a news pointed by index - */ - QString getTitle (const QModelIndex& index); - - /** - * @brief Set the read status on a news. - */ - void setReadStatus (const QModelIndex& index, bool read); - - /** - * @brief Reimplement data to make unread post bold. - */ - QVariant data(const QModelIndex &idx, int role) const; - - /** - * @brief Set the active category to display. - */ - void selectActiveFeed (quint64 feed_id); - - /** - * @brief Database where all the news will be loaded and saved. - */ - QSqlDatabase db; - - signals: - - public slots: - - private: - - /** - * @brief The FeedModel - */ - FeedModel *model; - }; - -} - -#endif // RSSPARSER_H diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui new file mode 100644 index 0000000..f297349 --- /dev/null +++ b/ui/mainwindow.ui @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>720</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>Larss 0.1</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QSplitter" name="splitter_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTreeView" name="feedTreeView"/> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QTableView" name="newsTableView"> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="gridStyle"> + <enum>Qt::DashLine</enum> + </property> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + <widget class="QWidget" name=""> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="webViewTitleLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QWebView" name="webView"> + <property name="url"> + <url> + <string>about:blank</string> + </url> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>720</width> + <height>24</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionExit"/> + </widget> + <addaction name="menuFile"/> + </widget> + <widget class="QStatusBar" name="statusBar"/> + <action name="actionExit"> + <property name="text"> + <string>Exit</string> + </property> + </action> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QWebView</class> + <extends>QWidget</extends> + <header>QtWebKit/QWebView</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>