Initial import of the code.
authorJan Dumon <j.dumon@option.com>
Mon, 1 Mar 2010 21:52:47 +0000 (22:52 +0100)
committerJan Dumon <j.dumon@option.com>
Mon, 1 Mar 2010 21:52:47 +0000 (22:52 +0100)
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.

25 files changed:
debian/README [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/dirs [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/menu.ex [new file with mode: 0644]
debian/rules [new file with mode: 0755]
grr.pro [new file with mode: 0644]
src/contentwindow.cpp [new file with mode: 0644]
src/contentwindow.h [new file with mode: 0644]
src/entrieswindow.cpp [new file with mode: 0644]
src/entrieswindow.h [new file with mode: 0644]
src/feedswindow.cpp [new file with mode: 0644]
src/feedswindow.h [new file with mode: 0644]
src/googlereader.cpp [new file with mode: 0644]
src/googlereader.h [new file with mode: 0644]
src/grr.desktop [new file with mode: 0644]
src/grr.png [new file with mode: 0644]
src/grr.qrc [new file with mode: 0644]
src/images/star-0.png [new file with mode: 0644]
src/images/star-1.png [new file with mode: 0644]
src/qtmain.cpp [new file with mode: 0644]
welcome [deleted file]

diff --git a/debian/README b/debian/README
new file mode 100644 (file)
index 0000000..c1dc10b
--- /dev/null
@@ -0,0 +1,6 @@
+The Debian Package grr
+----------------------------
+
+Comments regarding the Package
+
+ -- unknown <jan@unknown>  Tue, 23 Feb 2010 22:26:11 +0100
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..6a92dee
--- /dev/null
@@ -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 <jan@crossbar.net>  Fri, 26 Feb 2010 23:12:00 +0100
+
+grr (0.0.1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Jan Dumon <jan@crossbar.net>  Tue, 23 Feb 2010 22:26:11 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..7da0518
--- /dev/null
@@ -0,0 +1,73 @@
+Source: grr
+Section: user/network
+Priority: optional
+Maintainer: Jan Dumon <jan@crossbar.net>
+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 (file)
index 0000000..9688426
--- /dev/null
@@ -0,0 +1,40 @@
+This package was debianized by Jan Dumon <jan@crossbar.net> on
+Tue, 23 Feb 2010 22:26:11 +0100.
+
+It was downloaded from http://dufo.be
+
+Upstream Author(s):
+
+    Jan Dumon <jan@crossbar.net>
+
+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 <jan@crossbar.net> 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 (file)
index 0000000..7c826f6
--- /dev/null
@@ -0,0 +1 @@
+opt/grr
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/debian/menu.ex b/debian/menu.ex
new file mode 100644 (file)
index 0000000..8a5f92e
--- /dev/null
@@ -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 (executable)
index 0000000..414ea49
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..a41aa92
--- /dev/null
@@ -0,0 +1,214 @@
+#include <QMenuBar>
+#include <QDebug>
+#include <QDesktopServices>
+#include <QtGui>
+#include <QtWebKit>
+#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<QGraphicsWebView *>(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<QGraphicsSceneMouseEvent *>(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<QGraphicsWebView *>(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 (file)
index 0000000..5b07254
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef _CONTENT_WINDOW_H
+#define _CONTENT_WINDOW_H
+
+#include <QMainWindow>
+#include <QWebView>
+#include <QtGui>
+#include <QtWebKit>
+#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 (file)
index 0000000..18f575a
--- /dev/null
@@ -0,0 +1,153 @@
+#include <QMenuBar>
+#include <QDebug>
+#include <QPainter>
+#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() {
+       QList<Entry *>entries = 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<Entry *>(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<Entry *>(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 (file)
index 0000000..873c054
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef _ENTRIES_WINDOW_H
+#define _ENTRIES_WINDOW_H
+
+#include <QMainWindow>
+#include <QListView>
+#include <QStyledItemDelegate>
+#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, QList<Entry *>list = QList<Entry *>(), 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 *> 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 (file)
index 0000000..fb9afb0
--- /dev/null
@@ -0,0 +1,249 @@
+#include <QMenuBar>
+#include <QDebug>
+#include <QPainter>
+#include <QHBoxLayout>
+#include <QMaemo5InformationBox>
+#include <QMessageBox>
+#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() {
+       QList<Feed *>feeds = 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<Feed *>(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<Feed *>(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 (file)
index 0000000..4c0fb0c
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef _WINDOW_H
+#define _WINDOW_H
+
+#include <QMainWindow>
+#include <QListView>
+#include <QStyledItemDelegate>
+#include <QLabel>
+#include <QLineEdit>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QDialog>
+#include <QSettings>
+#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, QList<Feed *>list = QList<Feed *>(), 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 *> 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 (file)
index 0000000..5919ca4
--- /dev/null
@@ -0,0 +1,552 @@
+#include <QtWebKit>
+#include <QApplication>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QNetworkCookie>
+#include <QBuffer>
+#include <QTimer>
+#include <QDateTime>
+#include <QDomDocument>
+#include <QDebug>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#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>() << 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<QString, Feed *>::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<Feed *> GoogleReader::getFeeds() {
+       QList<Feed *> 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<QString, Entry *>::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<Entry *> Feed::getEntries() {
+       QList<Entry *> 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 (file)
index 0000000..24cad09
--- /dev/null
@@ -0,0 +1,173 @@
+#ifndef _QTMAIN_H
+#define _QTMAIN_H
+
+#include <QObject>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QDomDocument>
+#include <QBuffer>
+#include <QDateTime>
+
+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<Entry *> getEntries();
+               int getEntriesSize() { return entries.size(); }
+               void markRead();
+               void updateUnread(int);
+
+       signals:
+               void updateFeedComplete();
+               void allReadChanged();
+
+       private:
+               QHash<QString, Entry *> 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<Feed *> getFeeds();
+               QNetworkAccessManager *getManager() {
+                       return &manager;
+               }
+               void getToken();
+               
+               QUrl edittag_url;
+               QUrl markallread_url;
+               QByteArray token;
+               QNetworkAccessManager manager;
+               QDateTime lastUpdated;
+               QHash<QString, Feed *> 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 (file)
index 0000000..e6131d3
--- /dev/null
@@ -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 (file)
index 0000000..297f147
Binary files /dev/null and b/src/grr.png differ
diff --git a/src/grr.qrc b/src/grr.qrc
new file mode 100644 (file)
index 0000000..cd8145e
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+  <qresource>
+    <file>images/star-0.png</file>
+    <file>images/star-1.png</file>
+  </qresource>
+</RCC>
+
diff --git a/src/images/star-0.png b/src/images/star-0.png
new file mode 100644 (file)
index 0000000..f3d9a16
Binary files /dev/null and b/src/images/star-0.png differ
diff --git a/src/images/star-1.png b/src/images/star-1.png
new file mode 100644 (file)
index 0000000..f6f5555
Binary files /dev/null and b/src/images/star-1.png differ
diff --git a/src/qtmain.cpp b/src/qtmain.cpp
new file mode 100644 (file)
index 0000000..d98c0c8
--- /dev/null
@@ -0,0 +1,13 @@
+#include <QApplication>
+
+#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 (file)
index 9daeafb..0000000
--- a/welcome
+++ /dev/null
@@ -1 +0,0 @@
-test