From 7f1f955b572af3ed84cf079fdd9cee4626db010a Mon Sep 17 00:00:00 2001 From: Jan Dumon Date: Mon, 1 Mar 2010 22:52:47 +0100 Subject: [PATCH] Initial import of the code. It's not very high quality code and riddled with memory leaks. It was put together rather quickly to see how hard it would be to make a simple google reader client. --- debian/README | 6 + debian/changelog | 13 ++ debian/compat | 1 + debian/control | 73 +++++++ debian/copyright | 40 ++++ debian/dirs | 1 + debian/menu.ex | 2 + debian/rules | 87 ++++++++ grr.pro | 58 ++++++ src/contentwindow.cpp | 214 +++++++++++++++++++ src/contentwindow.h | 31 +++ src/entrieswindow.cpp | 153 ++++++++++++++ src/entrieswindow.h | 63 ++++++ src/feedswindow.cpp | 249 ++++++++++++++++++++++ src/feedswindow.h | 94 +++++++++ src/googlereader.cpp | 552 +++++++++++++++++++++++++++++++++++++++++++++++++ src/googlereader.h | 173 ++++++++++++++++ src/grr.desktop | 10 + src/grr.png | Bin 0 -> 3752 bytes src/grr.qrc | 7 + src/images/star-0.png | Bin 0 -> 730 bytes src/images/star-1.png | Bin 0 -> 1123 bytes src/qtmain.cpp | 13 ++ welcome | 1 - 24 files changed, 1840 insertions(+), 1 deletion(-) create mode 100644 debian/README create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/docs create mode 100644 debian/menu.ex create mode 100755 debian/rules create mode 100644 grr.pro create mode 100644 src/contentwindow.cpp create mode 100644 src/contentwindow.h create mode 100644 src/entrieswindow.cpp create mode 100644 src/entrieswindow.h create mode 100644 src/feedswindow.cpp create mode 100644 src/feedswindow.h create mode 100644 src/googlereader.cpp create mode 100644 src/googlereader.h create mode 100644 src/grr.desktop create mode 100644 src/grr.png create mode 100644 src/grr.qrc create mode 100644 src/images/star-0.png create mode 100644 src/images/star-1.png create mode 100644 src/qtmain.cpp delete mode 100644 welcome diff --git a/debian/README b/debian/README new file mode 100644 index 0000000..c1dc10b --- /dev/null +++ b/debian/README @@ -0,0 +1,6 @@ +The Debian Package grr +---------------------------- + +Comments regarding the Package + + -- unknown Tue, 23 Feb 2010 22:26:11 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..6a92dee --- /dev/null +++ b/debian/changelog @@ -0,0 +1,13 @@ +grr (0.0.2) unstable; urgency=low + + * Added 'Starred' tags + + * Fixed race condition at startup which causes an empty feed list. + + -- Jan Dumon Fri, 26 Feb 2010 23:12:00 +0100 + +grr (0.0.1) unstable; urgency=low + + * Initial Release. + + -- Jan Dumon Tue, 23 Feb 2010 22:26:11 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..7da0518 --- /dev/null +++ b/debian/control @@ -0,0 +1,73 @@ +Source: grr +Section: user/network +Priority: optional +Maintainer: Jan Dumon +Build-Depends: debhelper (>= 4.0.0), libqt4-maemo5-dev (>= 4.6.2~git20100212), libgconf2-dev (>= 2.13.5) +Standards-Version: 3.6.0 + +Package: grr +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Grr is a simple application for accessing Google Reader. +XSBC-Bugtracker: http://garage.maemo.org/projects/grr +XB-Maemo-Icon-26: + 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 + YII= diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..9688426 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,40 @@ +This package was debianized by Jan Dumon on +Tue, 23 Feb 2010 22:26:11 +0100. + +It was downloaded from http://dufo.be + +Upstream Author(s): + + Jan Dumon + +Copyright: + + Copyright (C) 2010 Jan Dumon + +License: + + This package is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This package 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU Lesser General +Public License can be found in `/usr/share/common-licenses/LGPL'. + +The Debian packaging is (C) 2010, Jan Dumon and +is licensed under the GPL, see `/usr/share/common-licenses/GPL'. + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +src/grr.png: + License: http://creativecommons.org/licenses/by-sa/3.0/ + Author: http://lopagof.deviantart.com/ diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..7c826f6 --- /dev/null +++ b/debian/dirs @@ -0,0 +1 @@ +opt/grr diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e69de29 diff --git a/debian/menu.ex b/debian/menu.ex new file mode 100644 index 0000000..8a5f92e --- /dev/null +++ b/debian/menu.ex @@ -0,0 +1,2 @@ +?package(grr):needs="X11|text|vc|wm" section="Applications/see-menu-manual"\ + title="grr" command="/opt/grr/grr" diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..414ea49 --- /dev/null +++ b/debian/rules @@ -0,0 +1,87 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + + + + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + /opt/qt4-maemo5/bin/qmake + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + $(MAKE) + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + $(MAKE) DESTDIR="$(CURDIR)"/debian/grr install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs +# dh_installdocs +# dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo +# dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/grr.pro b/grr.pro new file mode 100644 index 0000000..0afe2e3 --- /dev/null +++ b/grr.pro @@ -0,0 +1,58 @@ +TARGET = grr +HEADERS += googlereader.h feedswindow.h entrieswindow.h contentwindow.h +SOURCES += qtmain.cpp googlereader.cpp feedswindow.cpp entrieswindow.cpp contentwindow.cpp +RESOURCES += grr.qrc +FORMS += +LEXSOURCES += #LEXS# +YACCSOURCES += #YACCS# + +INCLUDEPATH += +LIBS += +DEFINES += + +# All generated files goes same directory +OBJECTS_DIR = build +MOC_DIR = build +UI_DIR = build + +DESTDIR = build +TEMPLATE = app +DEPENDPATH += +VPATH += src +CONFIG -= +CONFIG += debug +QT = core gui network xml webkit maemo5 + +# +# Targets for debian source and binary package creation +# +#debian-src.commands = dpkg-buildpackage -S -r -us -uc -d +debian-src.commands = dpkg-buildpackage -sa -S +debian-bin.commands = dpkg-buildpackage -b -r -uc -d +debian-all.depends = debian-src debian-bin + +# +# Clean all but Makefile +# +compiler_clean.commands = -$(DEL_FILE) $(TARGET) + +QMAKE_EXTRA_TARGETS += debian-all debian-src debian-bin install compiler_clean + + +unix { + PREFIX = debian/grr + BINDIR = $$PREFIX/opt/grr + DATADIR = $$PREFIX/usr/share + + DEFINES += DATADIR=\"$$DATADIR\" PKGDATADIR=\"$$PKGDATADIR\" + + INSTALLS += target desktop icon64 + + target.path = $$BINDIR + + desktop.path = $$DATADIR/applications/hildon + desktop.files += grr.desktop + + icon64.path = $$DATADIR/icons/hicolor/64x64/apps + icon64.files += grr.png +} diff --git a/src/contentwindow.cpp b/src/contentwindow.cpp new file mode 100644 index 0000000..a41aa92 --- /dev/null +++ b/src/contentwindow.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include "contentwindow.h" + +/* Got ViewportItem and GraphicsView from maemobrowser in the qt examples. The + * whole point of this is to get a finger friendly web widget */ + +class ViewportItem : public QGraphicsWidget, public QAbstractKineticScroller { + Q_OBJECT + + public: + ViewportItem() : QGraphicsWidget(), QAbstractKineticScroller(), m_widget(0), m_ignoreEvents(false) { + setFlag(QGraphicsItem::ItemHasNoContents, true); + setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); + setFlag(QGraphicsItem::ItemClipsToShape, true); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setFiltersChildEvents(true); + } + + void setWidget(QGraphicsWidget *widget) { + if (m_widget) { + m_widget->setParentItem(0); + delete m_widget; + } + m_widget = widget; + if (m_widget) { + m_widget->setParentItem(this); + m_widget->setAttribute(Qt::WA_OpaquePaintEvent, true); + + if (qgraphicsitem_cast(m_widget)) { + connect(m_widget, SIGNAL(loadProgress(int)), this, SLOT(resizeWebViewToFrame())); + connect(m_widget, SIGNAL(loadFinished(bool)), this, SLOT(resizeWebViewToFrame())); + resizeWebViewToFrame(); + } + } + } + + protected: + bool sceneEventFilter(QGraphicsItem *i, QEvent *e) { + bool res = false; + if (i && (i == m_widget) && !m_ignoreEvents && m_widget->isEnabled()) { + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + /*case QEvent::GraphicsSceneMouseDoubleClick:*/ { + res = handleMouseEvent(static_cast(e)); + break; + } + default: + break; + } + } + // prevent text selection and image dragging + if (e->type() == QEvent::GraphicsSceneMouseMove) + return true; + return res ? true : QGraphicsWidget::sceneEventFilter(i, e); + } + + QSize viewportSize() const { + return size().toSize(); + } + + QPoint maximumScrollPosition() const { + QSizeF s = m_widget ? m_widget->size() - size() : QSize(0, 0); + return QPoint(qMax(0, int(s.width())), qMax(0, int(s.height()))); + } + + QPoint scrollPosition() const { + return m_widget ? -m_widget->pos().toPoint() + m_overShoot : QPoint(); + } + + void setScrollPosition(const QPoint &p, const QPoint &overShoot) { + m_overShoot = overShoot; + if (m_widget) + m_widget->setPos(-p + m_overShoot); + } + + void cancelLeftMouseButtonPress(const QPoint & /*globalPressPos*/) { + } + + void sendEvent(QGraphicsItem *i, QEvent *e) { + m_ignoreEvents = true; + scene()->sendEvent(i, e); + m_ignoreEvents = false; + } + + private slots: + void resizeWebViewToFrame() { + if (QGraphicsWebView *view = qgraphicsitem_cast(m_widget)) { + if (view->page() && view->page()->mainFrame()) { + QSizeF s = view->page()->mainFrame()->contentsSize(); + /* TODO: Figure out the proper way to + * get this 800 pixels. */ + QSizeF s2 = size(); + s2.setWidth(800); + s = s.expandedTo(s2); + view->setGeometry(QRectF(view->geometry().topLeft(), s)); + } + } + } + + private: + QGraphicsWidget *m_widget; + bool m_ignoreEvents; + QPoint m_overShoot; +}; + +class GraphicsView : public QGraphicsView { + Q_OBJECT + + public: + GraphicsView() : QGraphicsView(new QGraphicsScene()), viewport(0) { + setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + setOptimizationFlags(QGraphicsView::DontSavePainterState); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setFrameShape(QFrame::NoFrame); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + viewport = new ViewportItem(); + scene()->addItem(viewport); + } + + ViewportItem *viewportItem() const { + return viewport; + } + + protected: + void resizeEvent(QResizeEvent *e) { + QGraphicsView::resizeEvent(e); + setUpdatesEnabled(false); + + if (!viewport) + return; + + QRectF rect(QPointF(0, 0), size()); + scene()->setSceneRect(rect); + + viewport->setGeometry(rect); + setUpdatesEnabled(true); + update(); + } + + private: + ViewportItem *viewport; +}; + +ContentWindow::ContentWindow(QWidget *parent, Entry *e) : QMainWindow(parent) { + setAttribute(Qt::WA_Maemo5StackedWindow); + + QWebSettings::globalSettings()->setAttribute(QWebSettings::PluginsEnabled, true); + + entry = e; + + starred = new QAction(tr("Starred"), this); + starred->setCheckable(true); + starred->setChecked((entry->flags & ENTRY_FLAG_STARRED)); + menuBar()->addAction(starred); + + keepUnread = new QAction(tr("Keep unread"), this); + keepUnread->setCheckable(true); + keepUnread->setEnabled((entry->flags & ENTRY_FLAG_LOCKED) == 0); + menuBar()->addAction(keepUnread); + + menuBar()->addAction(tr("See original"), this, SLOT(seeOriginal())); + + setWindowTitle(entry->title); + + GraphicsView *gv = new GraphicsView(); + webview = new QGraphicsWebView(); + gv->viewportItem()->setWidget(webview); + + /* TODO: Configurable text size ?? */ + webview->settings()->setFontSize(QWebSettings::MinimumFontSize, 22); + + connect(webview, SIGNAL(loadFinished(bool)), SLOT(loadFinished(bool))); + connect(webview, SIGNAL(loadStarted()), SLOT(loadStarted())); + + webview->setHtml(entry->content); + + setCentralWidget(gv); +} + +ContentWindow::~ContentWindow() { + delete(webview); +} + +void ContentWindow::loadStarted() { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); +} + +void ContentWindow::loadFinished(bool) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false); +} + +void ContentWindow::seeOriginal() { + /* Attempt to launch external browser */ + if(!QDesktopServices::openUrl(entry->link)) + webview->setUrl(entry->link); /* Failed... Show inline */ +} + +void ContentWindow::closeEvent(QCloseEvent *event) { + entry->markRead(!keepUnread->isChecked()); + entry->markStar(starred->isChecked()); + QMainWindow::closeEvent(event); +} + +#include "contentwindow.moc" diff --git a/src/contentwindow.h b/src/contentwindow.h new file mode 100644 index 0000000..5b07254 --- /dev/null +++ b/src/contentwindow.h @@ -0,0 +1,31 @@ +#ifndef _CONTENT_WINDOW_H +#define _CONTENT_WINDOW_H + +#include +#include +#include +#include +#include "googlereader.h" + +class ContentWindow : public QMainWindow { + Q_OBJECT + + public: + ContentWindow(QWidget *parent = 0, Entry *e = 0); + virtual ~ContentWindow(); + void closeEvent(QCloseEvent *event); + + public slots: + void loadFinished(bool); + void loadStarted(); + void seeOriginal(); + + private: + Entry *entry; + QGraphicsWebView *webview; + QAction *starred; + QAction *keepUnread; +}; + +#endif + diff --git a/src/entrieswindow.cpp b/src/entrieswindow.cpp new file mode 100644 index 0000000..18f575a --- /dev/null +++ b/src/entrieswindow.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include "entrieswindow.h" +#include "contentwindow.h" + +EntriesWindow::EntriesWindow(QWidget *parent, Feed *f) : QMainWindow(parent) { + setAttribute(Qt::WA_Maemo5StackedWindow); + + feed = f; + + menuBar()->addAction(tr("Fetch more"), this, SLOT(sync())); + menuBar()->addAction(tr("Mark all as read"), this, SLOT(markRead())); + + QActionGroup *filter_group = new QActionGroup(this); + show_all = new QAction(tr("Show All"), filter_group); + show_all->setCheckable(true); + show_all->setChecked(true); + show_updated = new QAction(tr("Show Updated"), filter_group); + show_updated->setCheckable(true); + menuBar()->addActions(filter_group->actions()); + + setWindowTitle(f->title); + + list = new QListView(); + setCentralWidget(list); + list->setItemDelegate(new EntryListDelegate(this)); + + connect(list, SIGNAL(activated(const QModelIndex &)), + SLOT(entrySelected(const QModelIndex &))); + + connect(feed, SIGNAL(updateFeedComplete()), + SLOT(entriesUpdated())); + + if(feed->getEntriesSize() == 0 || feed->lastUpdated < feed->reader->lastUpdated) + sync(); + else + entriesUpdated(); +} + +EntriesWindow::~EntriesWindow() { +} + +void EntriesWindow::sync() { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + feed->fetch(feed->lastUpdated > feed->reader->lastUpdated); +} + +void EntriesWindow::markRead() { + feed->markRead(); +} + +void EntriesWindow::entriesUpdated() { + QListentries = feed->getEntries(); + QAbstractItemModel *old_model = list->model(); + EntryListModel *new_model = new EntryListModel(this, entries, show_updated->isChecked()); + list->setModel(new_model); + connect(show_updated, SIGNAL(toggled(bool)), new_model, SLOT(showUpdated(bool))); + delete(old_model); + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false); +} + +void EntriesWindow::entrySelected(const QModelIndex &index) { + Entry *e = qVariantValue(index.data()); + + ContentWindow *w = new ContentWindow(this, e); + w->show(); +} + +int EntryListModel::rowCount(const QModelIndex &) const { + int size = 0; + + if(show_updated) { + for(int i = 0; i < entry_list.size(); i++) { + if((entry_list.at(i)->flags & ENTRY_FLAG_READ) == 0) + size++; + } + } + else + size = entry_list.size(); + + return size;; +} + +QVariant EntryListModel::data(const QModelIndex &index, int role) const { + if(!index.isValid()) + return QVariant(); + + if(index.row() >= rowCount() || index.row() < 0) { + return QVariant(); + } + + if(role == Qt::DisplayRole) { + if(show_updated) { + int i, j; + for(i = 0, j = 0; i < entry_list.size(); i++) { + if((entry_list.at(i)->flags & ENTRY_FLAG_READ) == 0) j++; + if(j > index.row()) + return qVariantFromValue(entry_list.at(i)); + } + } + else + return qVariantFromValue(entry_list.at(index.row())); + } + + return QVariant(); +} + +void EntryListModel::showUpdated(bool updated) { + beginResetModel(); + show_updated = updated; + endResetModel(); +} + +void EntryListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const { + + QStyledItemDelegate::paint(painter, option, index); + + Entry *e = qVariantValue(index.data()); + + QFont font = option.font; + QRect rect = option.rect; + rect.adjust(20, 8, -20, -8); + QPoint topleft = rect.topLeft(); + topleft.ry() += 2; + rect.adjust(36, 0, 0, 0); + + painter->save(); + + if(((e->flags & ENTRY_FLAG_READ) == 0) && + !(option.state & QStyle::State_Selected)) { + painter->setPen(option.palette.highlight().color()); + } + + painter->drawText(rect, Qt::AlignTop | Qt::AlignLeft, e->title); + + painter->setPen(option.palette.mid().color()); + font.setPointSizeF(font.pointSizeF() * 0.70); + painter->setFont(font); + + painter->drawText(rect, Qt::AlignBottom | Qt::AlignLeft, e->author); + + painter->drawText(rect, Qt::AlignBottom | Qt::AlignRight, e->published.toString()); + + if(e->flags & ENTRY_FLAG_STARRED) { + QImage img = QImage(QLatin1String(":/images/star-1")); + painter->drawImage(topleft, img); + } + + painter->restore(); +} + diff --git a/src/entrieswindow.h b/src/entrieswindow.h new file mode 100644 index 0000000..873c054 --- /dev/null +++ b/src/entrieswindow.h @@ -0,0 +1,63 @@ +#ifndef _ENTRIES_WINDOW_H +#define _ENTRIES_WINDOW_H + +#include +#include +#include +#include "googlereader.h" + +class EntriesWindow : public QMainWindow { + Q_OBJECT + + public: + EntriesWindow(QWidget *parent = 0, Feed *f = 0); + virtual ~EntriesWindow(); + + private slots: + void sync(); + void markRead(); + void entriesUpdated(); + void entrySelected(const QModelIndex &); + + private: + QListView *list; + QAction *show_all; + QAction *show_updated; + Feed *feed; +}; + +class EntryListModel : public QAbstractListModel { + Q_OBJECT + + public: + EntryListModel(QObject *parent = 0, QListlist = QList(), bool updated = false) + : QAbstractListModel(parent) { + entry_list = list; + show_updated = updated; + } + + int rowCount(const QModelIndex &model = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + public slots: + void showUpdated(bool); + + private: + QList entry_list; + bool show_updated; +}; + +class EntryListDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + EntryListDelegate(QObject *parent = 0) + : QStyledItemDelegate(parent) {}; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; + +#endif + diff --git a/src/feedswindow.cpp b/src/feedswindow.cpp new file mode 100644 index 0000000..fb9afb0 --- /dev/null +++ b/src/feedswindow.cpp @@ -0,0 +1,249 @@ +#include +#include +#include +#include +#include +#include +#include "feedswindow.h" +#include "entrieswindow.h" + +#define APPNAME "grr" +#define APPVERSION "0.0.2" + +FeedsWindow::FeedsWindow(QWidget *) : QMainWindow(0) { + setAttribute(Qt::WA_Maemo5StackedWindow); + + setWindowIcon(QIcon("/usr/share/icons/hicolor/64x64/apps/grr.png")); + + settings = new QSettings("dufo.be", "grr"); + + menuBar()->addAction(tr("Settings"), this, SLOT(showSettings())); + menuBar()->addAction(tr("Sync now"), this, SLOT(sync())); + menuBar()->addAction(tr("About"), this, SLOT(about())); + + QActionGroup *filter_group = new QActionGroup(this); + show_all = new QAction(tr("Show All"), filter_group); + show_all->setCheckable(true); + show_updated= new QAction(tr("Show Updated"), filter_group); + show_updated->setCheckable(true); + + if(settings->value("show_updated", false).toBool()) + show_updated->setChecked(true); + else + show_all->setChecked(true); + + menuBar()->addActions(filter_group->actions()); + + setWindowTitle(APPNAME); + + list = new QListView(); + setCentralWidget(list); + list->setItemDelegate(new FeedListDelegate(this)); + + connect(list, SIGNAL(activated(const QModelIndex &)), + SLOT(feedSelected(const QModelIndex &))); + + reader = new GoogleReader(); + + connect(reader, SIGNAL(updateUnreadComplete()), SLOT(feedsUpdated())); + connect(reader, SIGNAL(allReadChanged()), SLOT(refreshModel())); + connect(reader, SIGNAL(loginFailed(QString)), SLOT(loginFailed(QString))); + + connect(show_updated, SIGNAL(toggled(bool)), SLOT(showUpdated(bool))); + + if((settings->value("login", "").toString() == "") || + (settings->value("passwd", "").toString() == "")) { + emit loginFailed("Incomplete username or password"); + } + else { + reader->setLogin(settings->value("login", "").toString()); + reader->setPasswd(settings->value("passwd", "").toString()); + sync(); + } +} + +FeedsWindow::~FeedsWindow() { +} + +void FeedsWindow::showSettings() { + SettingsDialog settingsDialog(this, settings); + if(settingsDialog.exec() == QDialog::Accepted) { + reader->logOut(); + reader->setLogin(settings->value("login", "").toString()); + reader->setPasswd(settings->value("passwd", "").toString()); + sync(); + } +} + +void FeedsWindow::sync() { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true); + reader->updateSubscriptions(); + reader->updateUnread(); +} + +void FeedsWindow::refreshModel() { + QListfeeds = reader->getFeeds(); + QAbstractItemModel *old_model = list->model(); + FeedListModel *new_model = new FeedListModel(this, feeds, show_updated->isChecked()); + list->setModel(new_model); + connect(show_updated, SIGNAL(toggled(bool)), new_model, SLOT(showUpdated(bool))); + delete(old_model); +} + +void FeedsWindow::feedsUpdated() { + refreshModel(); + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false); +} + +void FeedsWindow::feedSelected(const QModelIndex &index) { + Feed *f = qVariantValue(index.data()); + EntriesWindow *w = new EntriesWindow(this, f); + w->show(); +} + +void FeedsWindow::showUpdated(bool updated) { + settings->setValue("show_updated", updated); +} + +void FeedsWindow::loginFailed(QString message) { + setAttribute(Qt::WA_Maemo5ShowProgressIndicator, false); + QMaemo5InformationBox::information(this, message, QMaemo5InformationBox::DefaultTimeout); + showSettings(); +} + +void FeedsWindow::about() { + QMessageBox::about(this, APPNAME " " APPVERSION, ""); +} + +int FeedListModel::rowCount(const QModelIndex &) const { + int size = 0; + + if(show_updated) { + for(int i = 0; i < feed_list.size(); i++) { + if(feed_list.at(i)->unread || feed_list.at(i)->special) + size++; + } + } + else + size = feed_list.size(); + + return size;; +} + +QVariant FeedListModel::data(const QModelIndex &index, int role) const { + if(!index.isValid()) + return QVariant(); + + if(index.row() >= rowCount() || index.row() < 0) { + return QVariant(); + } + + if(role == Qt::DisplayRole) { + if(show_updated) { + int i, j; + for(i = 0, j = 0; i < feed_list.size(); i++) { + if(feed_list.at(i)->unread || feed_list.at(i)->special) j++; + if(j > index.row()) + return qVariantFromValue(feed_list.at(i)); + } + } + else + return qVariantFromValue(feed_list.at(index.row())); + } + + return QVariant(); +} + +void FeedListModel::showUpdated(bool updated) { + beginResetModel(); + show_updated = updated; + endResetModel(); +} + +void FeedListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const { + + QStyledItemDelegate::paint(painter, option, index); + + Feed *f = qVariantValue(index.data()); + + QRect rect = option.rect; + rect.adjust(20, 8, -20, -8); + QFont font = option.font; + int flags = Qt::AlignVCenter; + + painter->save(); + + if(f->cat_label != "") + flags = Qt::AlignTop; + + if(f->unread) { + if(!(option.state & QStyle::State_Selected)) + painter->setPen(option.palette.highlight().color()); + + painter->drawText(rect, flags | Qt::AlignRight, QString("%1").arg(f->unread)); + } + + painter->drawText(rect, flags | Qt::AlignLeft, f->title); + + if(f->cat_label != "") { + painter->setPen(option.palette.mid().color()); + font.setPointSizeF(font.pointSizeF() * 0.70); + painter->setFont(font); + painter->drawText(rect, Qt::AlignBottom | Qt::AlignLeft, f->cat_label); + } + + painter->restore(); +} + +SettingsDialog::SettingsDialog(QWidget *parent, QSettings *s) : QDialog(parent) { + settings = s; + + loginLabel = new QLabel(tr("Login:")); + loginEdit = new QLineEdit(settings->value("login", "").toString()); + loginLabel->setBuddy(loginEdit); + + passwdLabel = new QLabel(tr("Password:")); + passwdEdit = new QLineEdit(settings->value("passwd", "").toString()); + passwdEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit); + passwdLabel->setBuddy(passwdEdit); + + saveButton = new QPushButton(tr("Save")); + saveButton->setDefault(true); + cancelButton = new QPushButton(tr("Cancel")); + cancelButton->setDefault(false); + + buttonBox = new QDialogButtonBox(Qt::Vertical); + buttonBox->addButton(cancelButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(saveButton, QDialogButtonBox::ActionRole); + + QHBoxLayout *loginLayout = new QHBoxLayout; + loginLayout->addWidget(loginLabel); + loginLayout->addWidget(loginEdit); + + QHBoxLayout *passwdLayout = new QHBoxLayout; + passwdLayout->addWidget(passwdLabel); + passwdLayout->addWidget(passwdEdit); + + QVBoxLayout *leftLayout = new QVBoxLayout; + leftLayout->addLayout(loginLayout); + leftLayout->addLayout(passwdLayout); + leftLayout->addStretch(1); + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->setSizeConstraint(QLayout::SetFixedSize); + mainLayout->addLayout(leftLayout, 0, 0); + mainLayout->addWidget(buttonBox, 0, 1); + setLayout(mainLayout); + + setWindowTitle(tr("Settings")); + + connect(saveButton, SIGNAL(clicked()), SLOT(accept())); + connect(cancelButton, SIGNAL(clicked()), SLOT(reject())); +} + +void SettingsDialog::accept() { + settings->setValue("login", loginEdit->text()); + settings->setValue("passwd", passwdEdit->text()); + QDialog::accept(); +} diff --git a/src/feedswindow.h b/src/feedswindow.h new file mode 100644 index 0000000..4c0fb0c --- /dev/null +++ b/src/feedswindow.h @@ -0,0 +1,94 @@ +#ifndef _WINDOW_H +#define _WINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "googlereader.h" + +class FeedsWindow : public QMainWindow { + Q_OBJECT + + public: + FeedsWindow(QWidget *parent = 0); + virtual ~FeedsWindow(); + + private slots: + void showUpdated(bool); + void showSettings(); + void sync(); + void feedsUpdated(); + void feedSelected(const QModelIndex &); + void refreshModel(); + void loginFailed(QString); + void about(); + + private: + QListView *list; + GoogleReader *reader; + QAction *show_all; + QAction *show_updated; + QSettings *settings; +}; + +class FeedListModel : public QAbstractListModel { + Q_OBJECT + + public: + FeedListModel(QObject *parent = 0, QListlist = QList(), bool updated = false) + : QAbstractListModel(parent) { + feed_list = list; + show_updated = updated; + } + + int rowCount(const QModelIndex &model = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + public slots: + void showUpdated(bool); + + private: + QList feed_list; + bool show_updated; +}; + +class FeedListDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + FeedListDelegate(QObject *parent = 0) + : QStyledItemDelegate(parent) {}; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; + +class SettingsDialog : public QDialog { + Q_OBJECT + + public: + SettingsDialog(QWidget *parent = 0, QSettings *s = 0); + + public slots: + void accept(); + + private: + QLabel *loginLabel; + QLineEdit *loginEdit; + QLabel *passwdLabel; + QLineEdit *passwdEdit; + QDialogButtonBox *buttonBox; + QPushButton *saveButton; + QPushButton *cancelButton; + QSettings *settings; +}; + +#endif + diff --git a/src/googlereader.cpp b/src/googlereader.cpp new file mode 100644 index 0000000..5919ca4 --- /dev/null +++ b/src/googlereader.cpp @@ -0,0 +1,552 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "googlereader.h" + +void Feed::updateSubscription(Feed *feed) { + title = feed->title; + sortid = feed->sortid; + firstitemmsec = feed->firstitemmsec; + cat_id = feed->cat_id; + cat_label = feed->cat_label; + subscription_updated = true; +} + +void Feed::fetch(bool cont) { + QNetworkRequest request; + QByteArray ba = "http://www.google.com/reader/atom/"; + + ba.append(QUrl::toPercentEncoding(id)); + QUrl url = QUrl::fromEncoded(ba); + + if(continuation != "" && cont) + url.addEncodedQueryItem("c", continuation.toUtf8()); + + request.setUrl(url); + reader->getManager()->get(request); +} + +GoogleReader::GoogleReader() { + connect(&manager, SIGNAL(finished(QNetworkReply*)), + SLOT(downloadFinished(QNetworkReply*))); + + SID = NULL; + updateSubscriptionsPending = false; + updateUnreadPending = false; + SIDPending = false; + + login_url.setUrl("https://www.google.com/accounts/ClientLogin"); + subscriptions_url.setUrl("http://www.google.com/reader/api/0/subscription/list"); + unread_url.setUrl("http://www.google.com/reader/api/0/unread-count"); + edittag_url.setUrl("http://www.google.com/reader/api/0/edit-tag?client=-"); + token_url.setUrl("http://www.google.com/reader/api/0/token"); + markallread_url.setUrl("http://www.google.com/reader/api/0/mark-all-as-read?client=-"); + + /* Add the virtual 'Starred items' feed */ + Feed *feed = new Feed(this); + feed->id = "user/-/state/com.google/starred"; + feed->title = "Starred items"; + feed->special = true; + feeds.insert(feed->id, feed); + connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged())); +} + +void GoogleReader::downloadFinished(QNetworkReply *reply) { + QUrl url = reply->url(); + + /* TODO: Instead of comparing against the url, use the signal from the + * QNetworkReply... */ + + if (reply->error()) { + qDebug() << "Download of" << url << "failed:" << qPrintable(reply->errorString()); + if(url == login_url) { + SIDPending = false; + emit loginFailed("Incorrect username or password"); + } + else if(url == edittag_url) + getToken(); + return; + } + else if(url == login_url) { + QByteArray data = reply->readAll(); + data.remove(0, data.indexOf("SID=", 0) + 4); + data.remove(data.indexOf("\n", 0), 1024); + SID = strdup(data.data()); + + qDebug() << "SID:" << SID; + + manager.cookieJar()->setCookiesFromUrl( + QList() << QNetworkCookie("SID", SID), + QUrl("http://www.google.com")); + + SIDPending = false; + + getToken(); + + /* TODO: Replace this with a proper state machine */ + if(updateSubscriptionsPending) { + updateSubscriptionsPending = false; + updateSubscriptions(); + } + } + else if(url == token_url) { + token = reply->readAll(); + qDebug() << "token:" << token; + } + else if(url == subscriptions_url) { + QByteArray data = reply->readAll(); + QDomDocument dom; + dom.setContent(data); + parseSubscriptions(dom); + emit updateSubscriptionsComplete(); + + /* TODO: Replace this with a proper state machine */ + if(updateUnreadPending) { + updateUnreadPending = false; + updateUnread(); + } + } + else if(url == unread_url) { + QByteArray data = reply->readAll(); + QDomDocument dom; + dom.setContent(data); + parseUnread(dom); + emit updateUnreadComplete(); + } + else if(url == edittag_url) { + QByteArray data = reply->readAll(); + //qDebug() << "Result:" << data; + } + else if(url == markallread_url) { + QByteArray data = reply->readAll(); + //qDebug() << "Result:" << data; + } + else { + QByteArray data = reply->readAll(); + QDomDocument dom; + dom.setContent(data); + parseFeed(dom); + } + + reply->deleteLater(); +} + +void GoogleReader::parseFeed(QDomDocument dom) { + QDomElement set, e; + QDomNode n, c; + QString continuation, feedsource; + Feed *feed = NULL; + + set = dom.firstChildElement(); + + for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) { + e = n.toElement(); + QString name = e.tagName(); + if(name == "entry") { + Entry entry; + QString content, summary; + + QString locked = e.attribute("gr:is-read-state-locked", "false"); + if(locked == "true") + entry.flags |= ENTRY_FLAG_LOCKED | ENTRY_FLAG_READ; + + entry.crawled = e.attribute("gr:crawl-timestamp-msec", "0").toULongLong(); + + for(c = n.firstChild(); !c.isNull(); c = c.nextSibling()) { + e = c.toElement(); + name = e.tagName(); + if(name == "id") + entry.id = e.text(); + else if(name == "title") { + QWebPage p; + p.mainFrame()->setHtml(e.text()); + entry.title = p.mainFrame()->toPlainText(); + } + else if(name == "published") + entry.published = QDateTime::fromString(e.text(), "yyyy-MM-dd'T'HH:mm:ss'Z'"); + else if(name == "link") + entry.link = QUrl(e.attribute("href", "")); + else if(name == "source") + entry.source = e.attribute("gr:stream-id", ""); + else if(name == "content") + content = e.text(); + else if(name == "summary") + summary = e.text(); + else if(name == "author") { + e = c.firstChild().toElement(); + entry.author = e.text(); + if(entry.author == "(author unknown)") + entry.author = ""; + } + else if(name == "category") { + QString label = e.attribute("label", ""); + if(label == "read") + entry.flags |= ENTRY_FLAG_READ; + else if(label == "starred") + entry.flags |= ENTRY_FLAG_STARRED; + } + } + + if(content != "") + entry.content = content; + else if(summary != "") + entry.content = summary; + + if(!feed) + feed = feeds.value(feedsource == "" ? entry.source : feedsource); + + if(feed) { + entry.feed = feed; + feed->addEntry(new Entry(entry)); + } + } + else if(name == "gr:continuation") { + continuation = e.text(); + } + else if(name == "id") { + if(e.text().endsWith("/state/com.google/starred")) + feedsource = "user/-/state/com.google/starred"; + } + } + + if(feed) { + feed->lastUpdated = QDateTime::currentDateTime(); + feed->continuation = continuation; + feed->signalUpdated(); + } +} + +void GoogleReader::parseSubscriptions(QDomDocument dom) { + QDomElement set, e; + QDomNode n, c; + + /* Clear the subscription updated flag */ + QHash::iterator i; + for(i = feeds.begin(); i != feeds.end(); ++i) + i.value()->subscription_updated = false; + + set = dom.firstChildElement(); + set = set.firstChildElement("list"); + set = set.firstChildElement("object"); + + for (; !set.isNull(); set = set.nextSiblingElement("object")) { + Feed *feed = new Feed(this); + Feed *existing_feed; + + for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) { + e = n.toElement(); + QString name = e.attribute("name"); + if(name == "id") + feed->id = e.text(); + else if(name == "title") + feed->title = e.text(); + else if(name == "sortid") + feed->sortid = e.text(); + else if(name == "firstitemmsec") + feed->firstitemmsec = e.text(); + else if(name == "categories") { + for(c = n.firstChild().firstChild(); !c.isNull(); c = c.nextSibling()) { + e = c.toElement(); + QString name = e.attribute("name"); + if(name == "id") + feed->cat_id = e.text(); + else if(name == "label") + feed->cat_label = e.text(); + } + } + } + + existing_feed = feeds.value(feed->id); + if(existing_feed) { + existing_feed->updateSubscription(feed); + delete(feed); + feed = existing_feed; + + } + else { + feed->subscription_updated = true; + feeds.insert(feed->id, feed); + connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged())); + } + } + + /* Delete feeds no longer subscribed to */ + for(i = feeds.begin(); i != feeds.end(); ++i) { + if(i.value()->subscription_updated == false && i.value()->special == false) { + printf("DELETED: %s\n", i.value()->title.toLatin1().data()); + i = feeds.erase(i); + } + } + + lastUpdated = QDateTime::currentDateTime(); +} + +void GoogleReader::parseUnread(QDomDocument dom) { + QDomElement set, e; + QDomNode n, c; + + set = dom.firstChildElement(); + set = set.firstChildElement("list"); + set = set.firstChildElement("object"); + + for (; !set.isNull(); set = set.nextSiblingElement("object")) { + QString id; + int count = 0; + ulong newestitem = 0; + + for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) { + e = n.toElement(); + QString name = e.attribute("name"); + if(name == "id") + id = e.text(); + else if(name == "count") + count = e.text().toInt(); + else if(name == "newestItemTimestampUsec") + newestitem = e.text().toULong(); + } + + Feed *f = feeds.value(id); + if(f) { + f->unread = count; + f->newestitem = newestitem; + //qDebug() << f->title << "->" << count; + } + else { + //printf("%s not found\n", id.toLatin1().data()); + } + } + + lastUpdated = QDateTime::currentDateTime(); +} + +void GoogleReader::getSID() { + + if(SIDPending) + return; + + SIDPending = true; + + QNetworkRequest request; + request.setUrl(login_url); + + buffer.open(QBuffer::ReadWrite | QBuffer::Truncate); + buffer.write("Email="); + buffer.write(QUrl::toPercentEncoding(login)); + buffer.write("&Passwd="); + buffer.write(QUrl::toPercentEncoding(passwd)); + buffer.write("&service=reader&source=grms&continue=http://www.google.com"); + + //buffer.seek(0); + //qDebug() << buffer.readAll(); + + buffer.seek(0); + manager.post(request, &buffer); +} + +void GoogleReader::getToken() { + QNetworkRequest request; + request.setUrl(token_url); + manager.get(request); +} + +void GoogleReader::updateSubscriptions() { + QNetworkRequest request; + + if(updateSubscriptionsPending) + return; + + if(!SID) { + updateSubscriptionsPending = true; + getSID(); + return; + } + + request.setUrl(subscriptions_url); + manager.get(request); +} + +void GoogleReader::updateUnread() { + QNetworkRequest request; + + if(updateUnreadPending) + return; + + if(!SID) { + updateUnreadPending = true; + getSID(); + return; + } + + request.setUrl(unread_url); + manager.get(request); +} + +static bool compareFeedItems(const Feed *f1, const Feed *f2) { + if(f1->special && !f2->special) + return true; + + if(f2->special && !f1->special) + return false; + + if(f1->cat_label == f2->cat_label) + return f1->title.toLower() < f2->title.toLower(); + + if(f1->cat_label.length() == 0) + return false; + + if(f2->cat_label.length() == 0) + return true; + + return f1->cat_label.toLower() < f2->cat_label.toLower(); +} + +QList GoogleReader::getFeeds() { + QList list = feeds.values(); + qSort(list.begin(), list.end(), compareFeedItems); + return list; +} + +void Feed::addEntry(Entry *entry) { + entries.insert(entry->id, entry); +} + +void Feed::delEntry(Entry *entry) { + entries.remove(entry->id); +} + +void Feed::signalUpdated() { + // TODO: Clean this up + emit updateFeedComplete(); +} + +void Feed::updateUnread(int i) { + bool allRead = (unread == 0); + + unread += i; + if(unread <= 0) unread = 0; + + if(allRead != (unread == 0)) + emit allReadChanged(); +} + +void Feed::markRead() { + if(unread > 0) { + /* Mark all the remaining items read */ + + QNetworkRequest request; + request.setUrl(reader->markallread_url); + + buffer.open(QBuffer::ReadWrite | QBuffer::Truncate); + buffer.write("s="); + buffer.write(QUrl::toPercentEncoding(id)); + //buffer.write("&ts="); + //buffer.write(QByteArray::number(oldest)); + buffer.write("&T="); + buffer.write(QUrl::toPercentEncoding(reader->token)); + + //buffer.seek(0); + //qDebug() << buffer.readAll(); + + buffer.seek(0); + reader->manager.post(request, &buffer); + + unread = 0; + + /* Go over all the entries and mark them read */ + QHash::iterator i; + for(i = entries.begin(); i != entries.end(); ++i) + i.value()->flags |= ENTRY_FLAG_READ | ENTRY_FLAG_LOCKED; + } + + emit allReadChanged(); +} + +static bool compareEntryItems(const Entry *e1, const Entry *e2) { + return e1->published > e2->published; +} + +QList Feed::getEntries() { + QList list = entries.values(); + qSort(list.begin(), list.end(), compareEntryItems); + return list; +} + +/* TODO: Remove the duplicate code in changing stated */ + +void Entry::markRead(bool mark_read) { + /* Check if the read flag differs from the requested state */ + if(((flags & ENTRY_FLAG_READ) != 0) == mark_read) + return; + + /* Cannot mark an item unread if it's locked */ + if((flags & ENTRY_FLAG_LOCKED) && !mark_read) + return; + + QNetworkRequest request; + request.setUrl(feed->reader->edittag_url); + + postread.open(QBuffer::ReadWrite | QBuffer::Truncate); + postread.write("i="); + postread.write(QUrl::toPercentEncoding(id)); + if(mark_read) + postread.write("&a="); + else + postread.write("&r="); + postread.write(QUrl::toPercentEncoding("user/-/state/com.google/read")); + postread.write("&ac=edit-tags&T="); + postread.write(QUrl::toPercentEncoding(feed->reader->token)); + postread.seek(0); + feed->reader->manager.post(request, &postread); + + feed->updateUnread(mark_read ? -1 : 1); + + if(mark_read) + flags |= ENTRY_FLAG_READ; + else + flags &= ~ENTRY_FLAG_READ; +} + +void Entry::markStar(bool mark_star) { + /* Check if the starred flag differs from the requested state */ + if(((flags & ENTRY_FLAG_STARRED) != 0) == mark_star) + return; + + QNetworkRequest request; + request.setUrl(feed->reader->edittag_url); + + poststar.open(QBuffer::ReadWrite | QBuffer::Truncate); + poststar.write("i="); + poststar.write(QUrl::toPercentEncoding(id)); + if(mark_star) + poststar.write("&a="); + else + poststar.write("&r="); + poststar.write(QUrl::toPercentEncoding("user/-/state/com.google/starred")); + poststar.write("&ac=edit-tags&T="); + poststar.write(QUrl::toPercentEncoding(feed->reader->token)); + poststar.seek(0); + feed->reader->manager.post(request, &poststar); + + Feed *starred = feed->reader->feeds.value("user/-/state/com.google/starred"); + + if(mark_star) { + starred->addEntry(this); + flags |= ENTRY_FLAG_STARRED; + } + else { + starred->delEntry(this); + flags &= ~ENTRY_FLAG_STARRED; + } +} diff --git a/src/googlereader.h b/src/googlereader.h new file mode 100644 index 0000000..24cad09 --- /dev/null +++ b/src/googlereader.h @@ -0,0 +1,173 @@ +#ifndef _QTMAIN_H +#define _QTMAIN_H + +#include +#include +#include +#include +#include +#include + +class GoogleReader; +class Entry; + +class Feed : public QObject { + Q_OBJECT + + public: + bool subscription_updated; + + QString id; + QString title; + QString sortid; + QString firstitemmsec; + QString cat_id; + QString cat_label; + + int unread; + uint newestitem; + + QString continuation; + + QDateTime lastUpdated; + + GoogleReader *reader; + + bool special; + + void updateSubscription(Feed *feed); + + Feed(GoogleReader *gr = NULL) : QObject() { + unread = 0; + reader = gr; + special = false; + } + + void fetch(bool); + void addEntry(Entry *); + void delEntry(Entry *); + void signalUpdated(); // TODO: Clean this up... + QList getEntries(); + int getEntriesSize() { return entries.size(); } + void markRead(); + void updateUnread(int); + + signals: + void updateFeedComplete(); + void allReadChanged(); + + private: + QHash entries; + QBuffer buffer; +}; + +Q_DECLARE_METATYPE(Feed *) + +#define ENTRY_FLAG_READ 0x00000001 +#define ENTRY_FLAG_STARRED 0x00000002 +#define ENTRY_FLAG_LOCKED 0x00000004 + +class Entry : public QObject { + Q_OBJECT + + public: + QString id; + QString title; + QString author; + QDateTime published; + qulonglong crawled; + QUrl link; + QString source; + QString content; + unsigned int flags; + Feed *feed; + + Entry(Feed *f = NULL) : QObject() { + feed = f; + flags = 0; + } + + Entry(Entry &e) : QObject() { + id = e.id; + title = e.title; + author = e.author; + published = e.published; + link = e.link; + source = e.source; + content = e.content; + flags = e.flags; + feed = e.feed; + } + + void markRead(bool); + void markStar(bool); + + private: + QBuffer postread; + QBuffer poststar; +}; + +Q_DECLARE_METATYPE(Entry *) + +class GoogleReader: public QObject { + Q_OBJECT + + public: + GoogleReader(); + void updateSubscriptions(); + void updateUnread(); + QList getFeeds(); + QNetworkAccessManager *getManager() { + return &manager; + } + void getToken(); + + QUrl edittag_url; + QUrl markallread_url; + QByteArray token; + QNetworkAccessManager manager; + QDateTime lastUpdated; + QHash feeds; + + void setLogin(QString l) { login = l; } + void setPasswd(QString p) { passwd = p; } + void logOut() { + SID = NULL; + token = NULL; + updateSubscriptionsPending = false; + updateUnreadPending = false; + SIDPending = false; + } + + private slots: + void downloadFinished(QNetworkReply *reply); + + private: + char *SID; + QBuffer buffer; + bool updateSubscriptionsPending; + bool updateUnreadPending; + bool SIDPending; + + void getSID(); + void parseSubscriptions(QDomDocument dom); + void parseUnread(QDomDocument dom); + void parseFeed(QDomDocument dom); + + QUrl login_url; + QUrl subscriptions_url; + QUrl unread_url; + QUrl token_url; + + QString login; + QString passwd; + + signals: + void updateSubscriptionsComplete(); + void updateUnreadComplete(); + void allReadChanged(); + void loginFailed(QString); +}; + +#endif + diff --git a/src/grr.desktop b/src/grr.desktop new file mode 100644 index 0000000..e6131d3 --- /dev/null +++ b/src/grr.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=0.0.2 +Type=Application +Name=grr +Exec=/opt/grr/grr +Icon=grr +X-HildonDesk-ShowInToolbar=true +X-Osso-Type=application/x-executable + diff --git a/src/grr.png b/src/grr.png new file mode 100644 index 0000000000000000000000000000000000000000..297f14789ef6ae179500e94337d3e75b9d867b79 GIT binary patch literal 3752 zcmV;Z4p;GsP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L0B*Vf0B*Vg*50uf00007bV*G`2igMx z6&xG(l6`gn01hZgL_t(|+U;9;bd*(=|J`q?C6!bb5)uN0gdLL50s;x(h78~!+ASyw zf<0$!TU(?DZJchq!5(NF$E`Qo1LB+>0po_#jkHRRNTYxOAs{3nVF?gIASkR%giQ3AjboiCkrc)cE|nhHfx z-X@}FK3sjK#(zR027sUP(*;4;J8Jk?P8*a?s-~jJ;ec21Mic;MK$0ZHMB8C8ThQWa z;e&0+ z&!4@!J8g*FFn^OINkz9$pV_WF5#2E5{eiEot-*+4qu^+AjLgWj6lTuock0C7uXL#m zp{t!~nzm-d&{3$Zts$@18=$zVsn9ePx~>Jd0t3(lKotR1irl5igyQm2Oqe{C6@fQy zTXh--#$roWwOiC6!h`66V1v+>LC_e`h2;V8$ z_$Fc>S;8dCzbzP@W%rK|LT`PJ1p>}hK^`$2bhr>CbZAnN08#W^f&dVKncJ^E5mf~p zI!rM?xR;^p5BJ;!3}n!G1u=8)27U1*Sl0sqkVK^a=!XZS-@12O#2_WBZ;r5G7i^^q%lDN2bgK3rC9+kyF?_ICH!ETgo4s+{L2Lk-jw&j z-4ptv;K>V$0+!jE%?#{caC<+#;4;&BrvwH;AoQv7*IXAO0gWEQk$Mv*kBP?Vfy41z zJ=P^jDx5N=jgP~#+T^fULAxWlb}oT=*5>`JoiZ^~001|o$KubAjc@d?W`p{W-7x{iWOOxFXu8kTIsuw)zNPa4p!aB*c5e)HE;D6egSSJfi| zgsKx15}`>F1ZHTx>>~gGL7;5Chh-@M$Qo?NyHDK=0N7Prho9w@01PlVY+w)ohev=t z)(n$96|Gg5d)2nkp>bQdij_07l5lk0ZOB_T5t<%ej!@Ybm6A8U9=!JGZ4?M+u@V5r z4vfLE|GWdgzHby1RS!EcroFqhSK+zK00;*0@OrDcHXsB7zJK!oe6eae5^OT`Fs=fH zs3^oOeh92-z2|_=`%h>(P zX;`=Y6t?cKgjo{90z%xv`{DfCC(s%jBL{%az||JzTKx++UEPAcCmZm_?(>K;iMVxi zKP;G%is>W!b!rjJz%TC|iBSWh@v9F?VUfkKa8B}?=TY^Fks%W@aVf4?i3SkD`ag3KEwu1AD!Z0pNQ>V)6Bw zTd{q`WUPB&4DOwfg7m~Fm?gn4CBC+l6$s644G*k8jA!3D;VYem0wFUAYwsToRnvQK z1?fXz88s?093T*2x5}_vWsDpUjqguPX)i>7UR;M)KP^Yq6;}Z1nI!@F$7`^=qz-$2 zH4V`g$&e@)Oism-bB*};o0?uFHO!3YyJn-|kADJ5Qe-*6f2ZTltR#H0G6(xsPe*!U z6xc6ACnBgiBlp*P;b>LbiLm3q*DV|ilPLDu3he(pC%6RHSBX%JRmS#breN#i6XES( z(gFdutl5XFEeeP@Xy61O|K~Yj$r_`k=H9qWkeQL4X2-F0w_`}6H9$#0Al$Zg9~>^# zkP{N3P58;o^e!@a=w*0R&J?h&hZg{$gKvf!WSb95B4PW}Q!pblF({+F=hd&TxdI2Z z`Phtfbj=6|&bRZ73%c2vU5*A_2H@BQ2X0xj53^n@z=P`#ma}e>e_FF{bwCM1=p@TZz(3PD3l0J+41Y()EX?yWKztQ#W@p z9QK$4M1f#2i72_~#KJ!w#?xDiLxKPRpFW!t5ILeuB39;=7+L`{W6{(>*IzX0zMUo` zD=-bs=sOa$nndhAQIA{K?*DeZ&@tf<1-!gyY=BbbwXOKPxXuu?6?YATbiIl!GqkH$ z4RZ&1V5;H!5sd;)w~9Mo_zI3z#n7~8W%oyFyrun_SrYK4UFSk3KdJH7>vDm&;({^Y z2?@d9n%awq++zUR8cndth>en<>Kt@1YX0vJ1+jcv0`$9wv*Dj26jwTNp}}Pc*h5nX zUN<6yS^|^R61)mC$$+nJ;OcfN+%~Am!{V{&(VN=uxx5bwBPEY$x)-`#sGOq+L zFUkxW!|2p#q$OH$>8c9=!D?FdYL zLxAZs5gJhGT8GDQX6H>vZnpv= z!qEyx$bM5|EuFi-`3kh|+aGj!%E|&=Ytlrx3J!u5<+&blfXl1ng$In++suqt7G*;5 zI|^r(1U!>hY{>FY+?fHz&kris^~$R@@k|DA3V3 z6ir8dQLW*-LlXS}Ai|+@4nyn89At;qgCZ(3`+WY#KZ5JXQM7)!w=%b^cy-4Uq+JG} zxUvbl&W77@%#6cl8{zFx_7(-gwu6@pd0=S&Z)cVWIC;rwXk8geQHFgI;5+c(e|f!t z+YQ9U0(aj9UcQVS@4knox3(P5Jz0_lp!0Kr4732S8>W`BfS=`+;I)Ud0*V@Kf3JL} z1ST=q-!EQq8S+3zQWP|12=E=a_Jr6oNQt!sWrPwjG>t(kmIofm5d^R#LA`W|UHc2j zwP6#n%k@<5nX>g=Iw`8_Tui`J-^M5hq^?AS{NuG){#FrwanA@0i1)E`QKb{lm4vOrFj~H8X(R9@#8g95SGg`eGWRc)i8InjaOP!zc>S!5&Z{m~= z8>mL_F%YoPDu8iQyqmXvR^Oz2wlpqOpP^v+R5drQ&0X$e`4Y(|Ys4044Vr#!lh!HI_#>Of9b*Yn1UT}Tl&R;q=Ts;W}%EPsD zNjaF)-rrr%jSVe4CCP))aXyO>i6Iaod=a8V9fPA)Oz7vu5Q~Sm?Ww1e7hJEo^Ow#I z*X&=H;jDAEn01|hVUp>2yG_F2{$`Ge68q#ZxJO~GZtzm|6))Ug{Tl%D-1$o{ww++@ zD*)iyxdhfZTTHsnLv=<5fWhd-ug!WPO$3yQB2~C{E~&raEF4a=`-gmoyAl S`HHsy0000 + + images/star-0.png + images/star-1.png + + + diff --git a/src/images/star-0.png b/src/images/star-0.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d9a1616407dd667f121ea23a43b64f972d3478 GIT binary patch literal 730 zcmV<00ww*4P)Px#24YJ`L;(K)>i__jkVCQn000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igJ{ z78Wi@`Q9S{00LS`L_t(Y$JN%)i`PXQ$MJ_hgEZ=(Ad(WAMMg=GL5J{AhmeRg4G$d_ z^^oB-gNOAfrg$~l%DId?<&UeZ;0u~xovVSjF-;Trpv}>a1y8ST8QatAF#KuAMc}tnEsnE zOM4vh6;_3qmiJh<1)t&V5Yyp1sl$PmX}xSA4%`mbFYvimGQDgR8}CM^iLJ124&j`e zrhdkpRti%C){^JpMf}h)x4f1-hJR|wn>xzsGEOuHr>yWDoDDJk(O?7ku4Ur$_^6hA ztd@K|`G)XnEqOt=1LyVu9IhqL$DY=_-wZL0wC_u5$s?^%yb@yizLxxFdwQJf+Pu4j zb7@oaB)-7~Yz{G9oM7!{9Kh_$zabwDrRDSUwywd7TkRrmqUbo^}f0lkF5TJo9pGPw744QB4jNVc?-X8-^I M07*qoM6N<$f;ci*rvLx| literal 0 HcmV?d00001 diff --git a/src/images/star-1.png b/src/images/star-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f5555a8b6b4de7a36fee1049822748ae75f34b GIT binary patch literal 1123 zcmV-p1f2VcP)Px#24YJ`L;(K)>i__jkVCQn000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igJ{ z78V;GN*VwF00ZMmL_t(Y$JLfwOdM4h$Ny(`VQ;wX1?aLXiwLQxEksgn)ud@8ec*xO zE~SK2W15Oqc`+u&7>!S+@zDn#e5hEWB9z^o9k9m6rg(!yQ)ARxFV$9)y1R`9>FnHg z&deMiC~_;{uEFS+d~;?_&VPR2cg~zMAMk%MflqENMaPvQ8XJ9QFq_SNHzDx$e+8J& zOZ!dJ4sr+Y&y`gtUZ3eliuAHRApIuqF8;=YoIFfGi|Lm zX%!V_Wj-%&{zr37%r}I$SZ=qofC`V@92*@uzG=YZ*yu}+a?bL2bUT=e)@?S3qS&)( zz0&5dw2igBxI%f zuXx##r*$oR#a>9iA8`2nfzSI6KD-kvh2!Z|L=?qORrOjl-tik3@av0{vJe5gej#MO zWn_6kU<@GS^v5-YA1}Hc;c&P=*4=Y#W!%ebWccmLnVGA59=JavH#g0B7LXex!~hUU zwj+J2(bTC&udc3c-1lHSd!vb3W$9ERp3CLNssl2Ob)0h$vN80+B|lDo(uiQN`dzo% z)!W$>Q?SO$tZBywkIfo}=sSF1YRTNORpY+@Ii#MMzReX1ZF#x7r}w#%_o3hK@1T?; zfP#6Kv3S4e1s!@IQ2EVzu)hT~45KqzJEMWASkI%izc})uc;`PpX(VZGpr!dM9Z(pc z0BpXsEOl9iUeAmZFbJOIOQpb$V| zJ6k6ml9aEX5crys{)eI{cHY}EX*a2csV_U^Z+~iaheDy}YSnIuO?!#2##Iv(AzaG_fI~S~~In&#BuziDC=e}I-%17m8 zbDUvTTK$g?logesBuP(E!$rc8x_ukL0H7<@Jsl3$);e8vXElx51wOgAs8WJYCLWp) p`0`T9$i#_}SI5(-hW|vx{wJV1#%?$0O=kcA002ovPDHLkV1kA12^atX literal 0 HcmV?d00001 diff --git a/src/qtmain.cpp b/src/qtmain.cpp new file mode 100644 index 0000000..d98c0c8 --- /dev/null +++ b/src/qtmain.cpp @@ -0,0 +1,13 @@ +#include + +#include "feedswindow.h" + +int main(int argc, char **argv) { + + QApplication app(argc, argv); + FeedsWindow feed_window; + feed_window.show(); + + return app.exec(); +} + diff --git a/welcome b/welcome deleted file mode 100644 index 9daeafb..0000000 --- a/welcome +++ /dev/null @@ -1 +0,0 @@ -test -- 1.7.9.5