0.9beta
authorYves Marcoz <yves@marcoz.org>
Fri, 5 Nov 2010 15:02:27 +0000 (08:02 -0700)
committerYves Marcoz <yves@marcoz.org>
Fri, 5 Nov 2010 15:02:27 +0000 (08:02 -0700)
* bug fixes
* 1st iteration of QML interface

28 files changed:
Makefile
debian/changelog
src/FeedingIt
src/FeedingIt-Web.py [new file with mode: 0644]
src/FeedingIt.py
src/qml/ArticleDisplay.qml [new file with mode: 0644]
src/qml/Articles.qml [new file with mode: 0644]
src/qml/Categories.qml [new file with mode: 0644]
src/qml/FeedingIt.qml [new file with mode: 0644]
src/qml/FeedingitUI.qmlproject [new file with mode: 0644]
src/qml/FeedingitUI.qmlproject.user [new file with mode: 0644]
src/qml/Feeds.qml [new file with mode: 0644]
src/qml/TestWebview.qml [new file with mode: 0644]
src/qml/common/Button.qml [new file with mode: 0644]
src/qml/common/Config.qml [new file with mode: 0644]
src/qml/common/ConfirmationMessage.qml [new file with mode: 0644]
src/qml/common/Menu.qml [new file with mode: 0644]
src/qml/common/Slider.qml [new file with mode: 0644]
src/qml/common/Switch.qml [new file with mode: 0644]
src/qml/common/ToolBar.qml [new file with mode: 0644]
src/qml/common/images/toolbutton.png [new file with mode: 0644]
src/qml/common/images/toolbutton.sci [new file with mode: 0644]
src/qml/debug.log [new file with mode: 0644]
src/qml/i18n/FeedingIt.ts [new file with mode: 0644]
src/qml/i18n/qml_en.qm [new file with mode: 0644]
src/qml/i18n/qml_en.ts [new file with mode: 0644]
src/qml/old-FeedingitUI.qml [new file with mode: 0644]
src/rss_sqlite.py

index 1302b7d..51337b9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,12 +8,21 @@ install:
        install -d ${DESTDIR}/usr/bin
        install src/FeedingIt ${DESTDIR}/usr/bin
        install -d ${DESTDIR}/opt/FeedingIt
+       install -d ${DESTDIR}/opt/FeedingIt/qml/i18n
+       install -d ${DESTDIR}/opt/FeedingIt/qml/common
+       install -d ${DESTDIR}/opt/FeedingIt/qml/common/images
+       install -d ${DESTDIR}/opt/FeedingIt/qml
+       install src/qml/*.qml ${DESTDIR}/opt/FeedingIt/qml
+       install src/qml/i18n/*.qm ${DESTDIR}/opt/FeedingIt/qml/i18n
+       install src/qml/common/*.qml ${DESTDIR}/opt/FeedingIt/qml/common
+       install src/qml/common/images/*.{sci,png} ${DESTDIR}/opt/FeedingIt/qml/common/images
        install src/FeedingIt.py ${DESTDIR}/opt/FeedingIt
        install src/feedparser.py ${DESTDIR}/opt/FeedingIt
        install src/portrait.py ${DESTDIR}/opt/FeedingIt
        install src/rss.py ${DESTDIR}/opt/FeedingIt
        install src/opml.py ${DESTDIR}/opt/FeedingIt
        install src/config.py ${DESTDIR}/opt/FeedingIt
+       install src/FeedingIt-Web.py ${DESTDIR}/opt/FeedingIt
        #install src/feedingit_status.desktop ${DESTDIR}/opt/FeedingIt
        install src/update_feeds.py ${DESTDIR}/opt/FeedingIt
        install src/updatedbus.py ${DESTDIR}/opt/FeedingIt
index 64d9319..6516e49 100644 (file)
@@ -1,6 +1,14 @@
+feedingit (0.9.0-0) unstable; urgency=low
+
+  * Added QML interface
+  * Fixed the use of id as article identifier (#6473)
+  * Added categories
+
+ -- Yves <yves@marcoz.org>  Sun, 31 Oct 2010 28:03:19 -0800
+
 feedingit (0.8.0-9) unstable; urgency=low
 
-  * Fixed date for articles without a date field
+  * Fixed date in feed shows as 1/1/1970 if no date field in rss feed is present (#6398)
 
  -- Yves <yves@marcoz.org>  Fri, 12 Sep 2010 10:54:19 -0800
 
index b6762e3..ebd4991 100644 (file)
@@ -11,6 +11,13 @@ dbus)
        #cp feedingit_status.desktop /usr/share/applications/hildon-status-menu/
        nice python2.5 update_feeds.py
        ;;
+qml)
+    cd /opt/FeedingIt
+    python2.5 FeedingIt-Web.py 2>&1 >/dev/null &
+    pid=`pidof python2.5 FeedingIt-Web.py`
+    qmlviewer -fullscreen qml/FeedingIt.qml
+    kill $pid
+    ;;
 *)
     cd /opt/FeedingIt
     python2.5 FeedingIt.py
diff --git a/src/FeedingIt-Web.py b/src/FeedingIt-Web.py
new file mode 100644 (file)
index 0000000..9c79ad2
--- /dev/null
@@ -0,0 +1,155 @@
+import BaseHTTPServer
+import sys
+from rss_sqlite import Listing
+from xml import sax
+from cgi import escape
+from re import sub
+from htmlentitydefs import name2codepoint
+
+CONFIGDIR = "/home/user/.feedingit/"
+
+def unescape(text):
+    def fixup(m):
+        text = m.group(0)
+        if text[:2] == "&#":
+            # character reference
+            try:
+                if text[:3] == "&#x":
+                    return unichr(int(text[3:-1], 16))
+                else:
+                    return unichr(int(text[2:-1]))
+            except ValueError:
+                pass
+        else:
+            # named entity
+            try:
+                text = unichr(name2codepoint[text[1:-1]])
+            except KeyError:
+                pass
+        return text # leave as is
+    return sub("&#?\w+;", fixup, text)
+
+class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
+    
+    def openTaskSwitch(self):
+        import subprocess
+        subprocess.Popen("dbus-send /com/nokia/hildon_desktop com.nokia.hildon_desktop.exit_app_view", shell=True)
+    
+    def getConfigXml(self):
+        xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
+        xml += "<hideReadFeed>True</hideReadFeed>"
+        xml += "<hideReadArticles>True</hideReadArticles>"
+        xml += "</xml>"
+        return xml
+    
+    def generateCategoryXml(self):
+        xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
+        for cat in listing.getListOfCategories():
+            xml += "<category>"
+            xml += "<catname>%s</catname>" %listing.getCategoryTitle(cat)
+            xml += "<catid>%s</catid>" % cat
+            xml += "</category>"
+        xml += "</xml>"
+        return xml
+
+    def fix_title(self, title):
+        return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>","").replace("&mdash;","-"))
+    
+    def generateFeedsXml(self, catid):
+        xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
+        for key in listing.getSortedListOfKeys("Manual", category=catid):
+            xml += "<feed>"
+            xml += "<feedname>%s</feedname>" %listing.getFeedTitle(key)
+            xml += "<feedid>%s</feedid>" %key
+            xml += "<unread>%s</unread>" %listing.getFeedNumberOfUnreadItems(key)
+            xml += "<updatedDate>%s</updatedDate>" %listing.getFeedUpdateTime(key)
+            xml += "<icon>%s</icon>" %listing.getFavicon(key)
+            xml += "</feed>"
+        xml += "</xml>"
+        return xml
+    
+    def generateArticlesXml(self, key, onlyUnread, markAllAsRead):
+        feed = listing.getFeed(key)
+        if markAllAsRead=="True":
+            feed.markAllAsRead()
+            listing.updateUnread(key)
+        xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
+        if onlyUnread == "False":
+            onlyUnread = False
+        for id in feed.getIds(onlyUnread):
+            xml += "<article>"
+            xml += "<title>%s</title>" %self.fix_title(feed.getTitle(id))
+            xml += "<articleid>%s</articleid>" %id
+            xml += "<unread>%s</unread>" %str(feed.isEntryRead(id))
+            xml += "<updatedDate>%s</updatedDate>" %feed.getDateStamp(id)
+            xml += "<path>%s</path>" %feed.getContentLink(id)
+            xml += "</article>"
+        xml += "</xml>"
+        return xml
+
+    def do_GET(self):
+        (req, sep, arg) = self.path.partition("?")
+        request = req.split("/")
+        arguments = {}
+        if arg != "":
+            args = arg.split("&")
+            print args
+            for arg in args:
+                ele = arg.split("=")
+                print ele
+                #try:
+                arguments[ele[0]] = ele[1]
+                #except:
+                #    pass
+        if request[1] == "categories":
+            xml = self.generateCategoryXml()
+        elif request[1] == "feeds":
+            catid = request[2]
+            xml = self.generateFeedsXml(catid)
+        elif request[1] == "articles":
+            key = request[2]
+            onlyUnread = arguments.get("onlyUnread","False")
+            markAllAsRead = arguments.get("markAllAsRead", "False")
+            xml = self.generateArticlesXml(key, onlyUnread, markAllAsRead)
+        elif request[1] == "html":
+            key = request[2]
+            article = request[3]
+            feed = listing.getFeed(key)
+            file = open(feed.getContentLink(article))
+            feed.setEntryRead(article)
+            html = file.read().replace("body", "body bgcolor='#ffffff'", 1)
+            file.close()
+            self.send_response(200)
+            self.send_header("Content-type", "text/html")
+            self.end_headers()
+            self.wfile.write(html)
+            listing.updateUnread(key)
+            return
+        elif request[1] == "config":
+            xml = self.getConfigXml()
+        elif request[1] == "home":
+            file = open(self.path)
+            self.send_response(200)
+            self.send_header("Content-type", "text/html")
+            self.end_headers()
+            self.wfile.write(file.read())
+            file.close()
+            return
+        elif request[1] == "task":
+            self.openTaskSwitch()
+            xml = "<xml>OK</xml>"
+        else:
+            self.send_error(404, "File not found")
+            return
+        self.send_response(200)
+        self.send_header("Content-type", "text/xml")
+        self.end_headers()
+        self.wfile.write(xml.encode("utf-8"))
+
+PORT = 8000
+
+listing = Listing(CONFIGDIR)
+
+httpd = BaseHTTPServer.HTTPServer(("127.0.0.1", PORT), Handler)
+print "serving at port", PORT
+httpd.serve_forever()
index e57689d..9ba9469 100644 (file)
@@ -1130,9 +1130,9 @@ class FeedingIt:
         wizard = AddWidgetWizard(self.window, self.listing, urlIn, self.listing.getListOfCategories())
         ret = wizard.run()
         if ret == 2:
-            (title, url) = wizard.getData()
+            (title, url, category) = wizard.getData()
             if (not title == '') and (not url == ''): 
-               self.listing.addFeed(title, url)
+               self.listing.addFeed(title, url, category=category)
         wizard.destroy()
         self.displayListing()
 
diff --git a/src/qml/ArticleDisplay.qml b/src/qml/ArticleDisplay.qml
new file mode 100644 (file)
index 0000000..963f150
--- /dev/null
@@ -0,0 +1,60 @@
+import Qt 4.7
+import QtWebKit 1.0
+import "common" as Common
+
+Rectangle {
+    x: parent.width; /*height: parent.height;*/
+    width: parent.width;
+    height: parent.height
+    property alias zoomEnabled: slider.visible;
+    property alias value: slider.value;
+    //anchors.top: parent.top; anchors.bottom: parent.bottom;
+    color: "white";
+
+    Flickable {
+        id: flickable
+        //anchors.fill: screen;
+        height: parent.height;
+        width: parent.width;
+        contentWidth: webView.width*webView.scale; //Math.max(screen.width,webView.width*webView.scale)
+        contentHeight: Math.max(screen.height,webView.height*webView.scale)
+        //contentWidth: childrenRect.width; contentHeight: childrenRect.height
+
+        flickDeceleration: 1000;
+
+        WebView {
+            id: webView
+            url: articleDisplay.url;
+            preferredWidth: flickable.width
+            preferredHeight: flickable.height
+            //scale: 1.25;
+            transformOrigin: Item.TopLeft
+            scale: slider.value;
+            settings.defaultFontSize: 24
+        }
+    }
+
+    Common.Slider {
+        id: slider; visible: false
+        minimum: 0.2;
+        maximum: 2;
+        property real prevScale: 1
+        anchors {
+            bottom: parent.bottom; bottomMargin: 65
+            left: parent.left; leftMargin: 25
+            right: parent.right; rightMargin: 25
+        }
+        onValueChanged: {
+            if (webView.width * value > flickable.width) {
+                var xoff = (flickable.width/2 + flickable.contentX) * value / prevScale;
+                flickable.contentX = xoff - flickable.width/2;
+            }
+            if (webView.height * value > flickable.height) {
+                var yoff = (flickable.height/2 + flickable.contentY) * value / prevScale;
+                flickable.contentY = yoff - flickable.height/2;
+            }
+            prevScale = value;
+        }
+        Component.onCompleted: {value=0; value=1; }
+    }
+}
diff --git a/src/qml/Articles.qml b/src/qml/Articles.qml
new file mode 100644 (file)
index 0000000..9c2650d
--- /dev/null
@@ -0,0 +1,73 @@
+import Qt 4.7
+
+Item {
+    //anchors.fill: parent;
+    width: parent.width;
+    property string feedid : ""
+    property alias count: articles.count
+    property alias url: articles.source
+    x: parent.width; height: parent.height;
+    anchors.top: parent.top; anchors.bottom: parent.bottom
+
+    function getArticleid(index) {
+        return articles.get(index).articleid
+    }
+
+    function reload() {
+        articles.reload()
+    }
+
+    ListView {
+        id: articleList; model: articles; delegate: articleDelegate; z: 6
+        width: parent.width; height: parent.height; /*x: 0;*/
+        cacheBuffer: 100;
+        flickDeceleration: 1500
+    }
+
+    XmlListModel {
+        id: articles
+
+        source: feedid == "" ? "" : "http://localhost:8000/articles/" + feedid + "?onlyUnread=" + hideReadArticles
+        query: "/xml/article"
+
+        XmlRole { name: "title"; query: "title/string()" }
+        XmlRole { name: "articleid"; query: "articleid/string()"; isKey: true }
+        XmlRole { name: "path"; query: "path/string()" }
+        XmlRole { name: "unread"; query: "unread/string()"; isKey: true}
+    }
+
+    Component {
+        id: articleDelegate
+
+        Item {
+            id: wrapper; width: wrapper.ListView.view.width; height: 86
+            Item {
+                id: moveMe
+                Rectangle { id: backRect; color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+                Text {
+                    anchors.fill: backRect
+                    anchors.margins: 5
+                    verticalAlignment: Text.AlignVCenter; text: title; color: (unread=="True") ? "white" : "#7b97fd";
+                    width: wrapper.width; wrapMode: Text.WordWrap; font.bold: false;
+                }
+//                Rectangle {
+//                    x: 3; y: 4; width: 77; height: 77; color: "#ff0000"; smooth: true
+
+//                }
+
+//                Column {
+//                    x: 3;
+
+//                    width: wrapper.width - 3; y: 5; spacing: 2
+//                    height: parent.height;
+//                    Text { Rectangle {anchors.fill: parent; color: "white"; opacity: 0.5;}
+//                         verticalAlignment: Text.AlignVCenter; text: title; color: (unread=="True") ? "white" : "#7b97fd"; width: parent.width; wrapMode: Text.WordWrap; font.bold: false; /*elide: Text.ElideRight;*/ /*style: Text.Raised;*/ styleColor: "black"; }
+//                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+//                }
+            }
+            MouseArea { anchors.fill: wrapper; onClicked: { container.articleClicked(articleid, index) } }
+        }
+
+    }
+
+}
diff --git a/src/qml/Categories.qml b/src/qml/Categories.qml
new file mode 100644 (file)
index 0000000..bce5f7b
--- /dev/null
@@ -0,0 +1,51 @@
+import Qt 4.7
+
+Item {
+//    anchors.fill: parent;
+    width: parent.width; height: parent.height;
+    //anchors.top: parent.top; anchors.bottom: parent.bottom
+
+    ListView {
+        id: categoryList; model: categories; delegate: categoryDelegate; z: 6;
+        cacheBuffer: 100; width: parent.width; height: parent.height;
+    }
+
+    XmlListModel {
+
+        id: categories
+
+        //source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+tags+"&" : "")+"format=rss2"
+        //source: "/home/ymarcoz/feedlist.xml"
+        source: "http://localhost:8000/categories"
+        query: "/xml/category"
+        //namespaceDeclarations: "declare namespace media=\"http://search.yahoo.com/mrss/\";"
+
+        XmlRole { name: "title"; query: "catname/string()" }
+        XmlRole { name: "catid"; query: "catid/string()"; isKey: true }
+
+    }
+
+    Component {
+        id: categoryDelegate
+
+        Item {
+
+            id: wrapper; width: wrapper.ListView.view.width; height: 86
+            Item {
+                id: moveMe
+                height: parent.height
+                Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+                Rectangle {
+                    x: 6; y: 4; width: 77; height: parent.height - 9; color: "white"; smooth: true
+
+                }
+                Column {
+                    x: 92; width: wrapper.ListView.view.width - 95; y: 15; spacing: 2
+                    Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+                }
+            }
+            MouseArea { anchors.fill: wrapper; onClicked: { container.categoryClicked(catid); } }
+        }
+    }
+}
diff --git a/src/qml/FeedingIt.qml b/src/qml/FeedingIt.qml
new file mode 100644 (file)
index 0000000..bb2156e
--- /dev/null
@@ -0,0 +1,240 @@
+import Qt 4.7
+import "common" as Common
+// Depends on qt4-declarative-qmlviewer
+
+Item {
+    width: 640
+    height: 480
+    id: screen
+
+    Rectangle {
+        id: container
+        anchors.fill: parent; color: "#343434";
+
+        function modulo(x,y) {
+            // Fixes modulo for negative numbers
+            return ((x%y)+y)%y;
+        }
+
+        function categoryClicked(catid) {
+            feedsItem.catid = catid;
+            categoriesItem.isShown = false;
+            feedsItem.visible = true;
+        }
+
+        function feedClicked(feedid) {
+            articlesItem.feedid = feedid;
+            articlesItem.visible = true;
+        }
+
+        function articleClicked(articleid, index) {
+            // Assign the articleId for the current, next and previous article to the associated variables
+            // Note the modulo, so it goes around
+            articleDisplay.articleindex = modulo(index,articlesItem.count)
+            articleDisplay.nextArticle = articlesItem.getArticleid(modulo(index+1,articlesItem.count))
+            articleDisplay.prevArticle = articlesItem.getArticleid(modulo(index-1,articlesItem.count))
+            articleDisplay.articleid = articleid
+            articleDisplay.visible = true;
+        }
+
+        function backClicked() {
+            if (articleDisplay.visible) {
+                // We're viewing an article, and going back to article listing
+                articleDisplay.visible = false;
+                articleDisplay.articleid = "";
+                articleDisplay.value = 1;
+                articlesItem.reload()
+                return;
+            }
+            if (articlesItem.visible) {
+                // Viewing articles, going back to feeds
+                //articlesItem.feedid = "";
+                feedsItem.reload();
+                articlesItem.visible = false;
+                //articlesItem.reload();
+                return;
+            }
+            if (feedsItem.visible) {
+                // Viewing feeds, going back to categories
+                //feedsItem.catid = "";
+                feedsItem.visible = false;
+                //feedsItem.reload();
+                categoriesItem.isShown = true;
+                return;
+            }
+            if (!feedsItem.visible) {
+                // Viewing categories, quitting
+                Qt.quit();
+            }
+        }
+
+        Common.Menu {
+            id: config
+            z: 5
+            property string hideReadFeeds : "False"
+            property string hideReadArticles : "False"
+
+            property bool isShown: false;
+
+            //width: parent.width; height: parent.height;
+
+            //height: 0
+            states: State {
+                name: "shown"; when: config.isShown == true
+                PropertyChanges { target: config; y: 66 }
+            }
+
+            transitions: Transition {
+                NumberAnimation { properties: "y"; duration: 300; easing.type: "InOutQuad" }
+            }
+
+        }
+
+        Common.ConfirmationMessage {
+            id: confirmationMessage;
+            visible: false
+            onOkClicked: { var doc = new XMLHttpRequest();
+                console.log(articlesItem.url+"&markAllAsRead=True")
+                var url = articlesItem.url+"&markAllAsRead=True"
+                console.log(url)
+                doc.open("GET", url);
+                doc.send();
+                var xmlDoc=doc.responseXML;
+                articlesItem.reload();
+                feedsItem.reload()
+                visible=false
+            }
+            onCancelClicked: visible=false
+        }
+
+        Common.ToolBar {
+            id: toolBar; z: 7
+            height: 66; anchors.top: parent.top; width: parent.width; opacity: 0.9
+            button1Label: qsTr("Config"); button2Label: qsTr("Back")
+            nextLabel: qsTr("Next"); prevLabel: qsTr("Previous")
+            markAllLabel: qsTr("Mark All As Read"); zoomLabel: qsTr("Zoom")
+            taskSwitcherLabel: qsTr("Task Switch")
+            onButton1Clicked: config.isShown = !config.isShown;
+            onButton2Clicked: container.backClicked()
+            onPrevClicked: container.articleClicked(articleDisplay.prevArticle, articleDisplay.articleindex-1)
+            onNextClicked: container.articleClicked(articleDisplay.nextArticle, articleDisplay.articleindex+1)
+            onMarkAllClicked: {
+                confirmationMessage.text = qsTr("Do you want to mark all items as read?");
+                confirmationMessage.visible = true;
+            }
+            onZoomClicked: { articleDisplay.zoomEnabled = !articleDisplay.zoomEnabled; }
+            onTaskSwitcherClicked: {
+                var doc = new XMLHttpRequest();
+                var url = "http://localhost:8000/task"
+                doc.open("GET", url);
+                doc.send();
+                //var xmlDoc=doc.responseXML;
+            }
+
+            states: [ State {
+                name: "navButtons"; when: articleDisplay.articleid != ""
+                PropertyChanges { target: toolBar; nextVisible: true; }
+                PropertyChanges { target: toolBar; prevVisible: true; }
+                PropertyChanges { target: toolBar; zoomVisible: true; }
+            },
+                State {
+                    name: "feedButtons"; when: (articleDisplay.articleid == "")&&(articlesItem.feedid!="")
+                    PropertyChanges { target: toolBar; markAllVisible: true; }
+                }
+            ]
+        }
+
+        Item {
+            id: views
+            //x: 2;
+            //y:66;
+            width: parent.width // - 4
+            anchors.top: toolBar.bottom; anchors.bottom: parent.bottom
+
+            Categories {
+                // Loads the categoryList view and delegate
+                id: categoriesItem
+                property bool isShown: true;
+
+                states: State {
+                    name: "shown"; when: categoriesItem.isShown == false
+                    PropertyChanges { target: categoriesItem; x: -screen.width }
+                }
+
+                transitions: Transition {
+                    NumberAnimation { properties: "x"; duration: 300; easing.type: "InOutQuad" }
+                }
+
+            }
+
+            Feeds {
+
+                // Loads the feedList view and delegate
+                id: feedsItem;
+                property string hideReadFeeds: config.hideReadFeeds
+                visible: false;
+
+                states: [
+                    State { name: "articlesShown"; when: articlesItem.visible; PropertyChanges { target: feedsItem; x: -parent.width } },
+                    State { name: "shown"; when: feedsItem.visible; PropertyChanges { target: feedsItem; x: 0 } }
+                ]
+
+                transitions: Transition {
+                    NumberAnimation { properties: "x"; duration: 300; easing.type: "InOutQuad" }
+                }
+
+            }
+
+            Articles {
+                // Loads the articleLost view and delegate
+                id: articlesItem;
+                property string hideReadArticles: config.hideReadArticles
+                visible: false;
+
+                states: [
+                    State { name: "shown"; when: articleDisplay.visible; PropertyChanges { target: articlesItem; x: -parent.width }
+                    },
+                    State { name: "articleShown"; when: articlesItem.visible; PropertyChanges { target: articlesItem; x: 0 }
+                    }
+                ]
+
+                transitions: Transition {
+                    NumberAnimation { properties: "x"; duration: 300; easing.type: "InOutQuad" }
+                }
+            }
+
+            ArticleDisplay{
+                // Loads the WebView
+                id: articleDisplay;
+                //anchors.top: toolBar.bottom;
+                //anchors.bottom: screen.bottom;
+                height: parent.height;
+                visible: false;
+                property string feedid: articlesItem.feedid;
+                property string articleid: "";
+                property int articleindex: 0;
+                property string nextArticle: "";
+                property string prevArticle: "";
+                property string url: (articleid == "") ? "" : "http://localhost:8000/html/" + articleDisplay.feedid + "/" + articleDisplay.articleid;
+
+                gradient: Gradient {
+                    GradientStop {
+                        position: 0.00;
+                        color: "#ffffff";
+                    }
+                    GradientStop {
+                        position: 1.00;
+                        color: "#ffffff";
+                    }
+                }
+
+                states: State { name: "shown"; when: articleDisplay.visible; PropertyChanges { target: articleDisplay; x: 0 }
+                    }
+
+                transitions: Transition {
+                    NumberAnimation { properties: "x"; duration: 300; easing.type: "InOutQuad" }
+                }
+        }
+        }
+    }
+}
diff --git a/src/qml/FeedingitUI.qmlproject b/src/qml/FeedingitUI.qmlproject
new file mode 100644 (file)
index 0000000..53f5ecb
--- /dev/null
@@ -0,0 +1,18 @@
+/* File generated by QtCreator */
+
+import QmlProject 1.0
+
+Project {
+    /* Include .qml, .js, and image files from current directory and subdirectories */
+    QmlFiles {
+        directory: "."
+    }
+    JavaScriptFiles {
+        directory: "."
+    }
+    ImageFiles {
+        directory: "."
+    }
+    /* List of plugin directories passed to QML runtime */
+    // importPaths: [ "../exampleplugin" ]
+}
diff --git a/src/qml/FeedingitUI.qmlproject.user b/src/qml/FeedingitUI.qmlproject.user
new file mode 100644 (file)
index 0000000..cc55c6d
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE QtCreatorProject>
+<qtcreator>
+ <data>
+  <variable>ProjectExplorer.Project.ActiveTarget</variable>
+  <value type="int">0</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.EditorSettings</variable>
+  <valuemap type="QVariantMap">
+   <value key="EditorConfiguration.Codec" type="QByteArray">Default</value>
+  </valuemap>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Target.0</variable>
+  <valuemap type="QVariantMap">
+   <value key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName" type="QString">QML Viewer</value>
+   <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">QML Viewer</value>
+   <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">QmlProjectManager.QmlTarget</value>
+   <value key="ProjectExplorer.Target.ActiveBuildConfiguration" type="int">-1</value>
+   <value key="ProjectExplorer.Target.ActiveDeployConfiguration" type="int">-1</value>
+   <value key="ProjectExplorer.Target.ActiveRunConfiguration" type="int">0</value>
+   <value key="ProjectExplorer.Target.BuildConfigurationCount" type="int">0</value>
+   <value key="ProjectExplorer.Target.DeployConfigurationCount" type="int">0</value>
+   <valuemap key="ProjectExplorer.Target.RunConfiguration.0" type="QVariantMap">
+    <value key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName" type="QString"></value>
+    <value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">QML Viewer</value>
+    <value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">QmlProjectManager.QmlRunConfiguration</value>
+    <value key="QmlProjectManager.QmlRunConfiguration.MainScript" type="QString">CurrentFile</value>
+    <value key="QmlProjectManager.QmlRunConfiguration.QDeclarativeViewerArguments" type="QString"></value>
+    <value key="QmlProjectManager.QmlRunConfiguration.QtVersion" type="int">4</value>
+    <value key="RunConfiguration.QmlDebugServerPort" type="uint">3768</value>
+    <value key="RunConfiguration.UseCppDebugger" type="bool">false</value>
+    <value key="RunConfiguration.UseQmlDebugger" type="bool">true</value>
+   </valuemap>
+   <value key="ProjectExplorer.Target.RunConfigurationCount" type="int">1</value>
+  </valuemap>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.TargetCount</variable>
+  <value type="int">1</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Updater.EnvironmentId</variable>
+  <value type="QString">{6449687d-a4d3-4afc-95ac-89e1027ef47e}</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Updater.FileVersion</variable>
+  <value type="int">8</value>
+ </data>
+</qtcreator>
diff --git a/src/qml/Feeds.qml b/src/qml/Feeds.qml
new file mode 100644 (file)
index 0000000..8683087
--- /dev/null
@@ -0,0 +1,65 @@
+import Qt 4.7
+
+Item {
+    //anchors.fill: parent;
+    width: parent.width;
+    property string catid : ""
+    x: parent.width; height: parent.height;
+    anchors.top: parent.top; anchors.bottom: parent.bottom
+
+    function reload() {
+        feeds.reload()
+    }
+
+    //Component.onCompleted: { console.log(x + " /") }
+
+    ListView {
+        id: feedList; model: feeds; delegate: feedDelegate; z: 6
+        width: parent.width; height: parent.height; /*x: 0;*/
+        cacheBuffer: 100;
+        flickDeceleration: 1500
+    }
+
+    XmlListModel {
+
+        id: feeds
+
+        source: catid == "" ? "" : "http://localhost:8000/feeds/" + catid //+ "?onlyUnread=" + parent.hideReadArticles
+        query: "/xml/feed"
+
+        XmlRole { name: "title"; query: "feedname/string()" }
+        XmlRole { name: "feedid"; query: "feedid/string()"; isKey: true }
+        XmlRole { name: "unread"; query: "unread/string()"; isKey: true }
+        XmlRole { name: "updatedDate"; query: "updatedDate/string()" }
+        XmlRole { name: "icon"; query: "icon/string()" }
+    }
+
+    Component {
+        id: feedDelegate
+
+        Item {
+            id: wrapper; width: wrapper.ListView.view.width;
+            visible: (unread == "0" && feedsItem.hideReadFeeds=="True") ? false : true
+            height: (visible) ? 86 : 0
+
+            Item {
+                id: moveMe
+                Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+                Rectangle {
+                    x: 3; y: 4; width: 77; height: 77; color: "#000000"; smooth: true
+                    Image { width:32; height: 32; anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter; source: (icon == "False") ? "" : icon }
+                }
+
+                Column {
+                    x: 92; width: wrapper.ListView.view.width - 95; y: 5; spacing: 2
+                    Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+                    Text { text: updatedDate + " / " + qsTr("%1 unread items").arg(unread); color: (unread=="0") ? "white" : "#7b97fd"; width: parent.width; font.bold: false; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+                }
+            }
+            MouseArea { anchors.fill: wrapper; onClicked: { container.feedClicked(feedid) } }
+        }
+
+    }
+
+}
diff --git a/src/qml/TestWebview.qml b/src/qml/TestWebview.qml
new file mode 100644 (file)
index 0000000..79e05fc
--- /dev/null
@@ -0,0 +1,66 @@
+import Qt 4.7
+import QtWebKit 1.0
+import "common" as Common
+
+Rectangle {
+    width: 380
+    height: 480
+
+    //anchors.top: parent.top; anchors.bottom: parent.bottom;
+    color: "white";
+
+    property string url: "";
+    Flickable {
+        id: flickable
+        //anchors.fill: screen;
+        height: parent.height;
+        width: parent.width;
+        contentWidth: webView.width*scale; //Math.max(screen.width,webView.width*webView.scale)
+        contentHeight: Math.max(parent.height,webView.height*webView.scale)
+
+        WebView {
+            id: webView
+            url: "http://www.google.com";
+            //url: "/home/user/.feedingit/640fb167aca8bf5318ed721c5162f5eb.d/56a86b6b1675716ab54db83b1a78ab4c.html"
+            preferredWidth: flickable.width
+            preferredHeight: flickable.height
+            settings.defaultFontSize: 32
+            scale: slider.value;
+            //smooth: false
+            //width: 200
+            //width: parent.width; height: parent.height;
+//            Rectangle {
+//                             color: "#10000000"
+//                             anchors.fill: parent
+//            }
+            //onLoadFinished: {console.log("Hello"); url="javascript:void(document.body.style.background='red');" }
+            onLoadFinished: {console.log(url);/* url="javascript:(function() { " +
+                                                              "document.getElementsByTagName('body')[0].style.background = 'red'; " +
+                                                              "})()"; console.log(url);*/ /*heuristicZoom(0,0,100)*/ }
+        }
+    }
+        Common.Slider {
+            id: slider; visible: true
+            minimum: 0.2;
+            maximum: 2;
+            value: 1
+            property real prevScale: 1
+            anchors {
+                bottom: parent.bottom; bottomMargin: 65
+                left: parent.left; leftMargin: 25
+                right: parent.right; rightMargin: 25
+            }
+            onValueChanged: {
+                if (webView.width * value > flickable.width) {
+                    var xoff = (flickable.width/2 + flickable.contentX) * value / prevScale;
+                    flickable.contentX = xoff - flickable.width/2;
+                }
+                if (webView.height * value > flickable.height) {
+                    var yoff = (flickable.height/2 + flickable.contentY) * value / prevScale;
+                    flickable.contentY = yoff - flickable.height/2;
+                }
+                prevScale = value;
+            }
+            Component.onCompleted: { value=0; value=1; }
+        }
+}
diff --git a/src/qml/common/Button.qml b/src/qml/common/Button.qml
new file mode 100644 (file)
index 0000000..74b5aea
--- /dev/null
@@ -0,0 +1,38 @@
+import Qt 4.7
+
+Item {
+    id: container
+
+    signal clicked
+
+    property string text
+
+    BorderImage {
+        id: buttonImage
+        source: "images/toolbutton.sci"
+        width: container.width; height: container.height
+    }
+    BorderImage {
+        id: pressed
+        opacity: 0
+        source: "images/toolbutton.sci"
+        width: container.width; height: container.height
+    }
+    MouseArea {
+        id: mouseRegion
+        anchors.fill: buttonImage
+        onClicked: { container.clicked(); }
+    }
+    Text {
+        color: "white"
+        anchors.centerIn: buttonImage; font.bold: true
+        text: container.text; style: Text.Raised; styleColor: "black"
+    }
+    states: [
+        State {
+            name: "Pressed"
+            when: mouseRegion.pressed == true
+            PropertyChanges { target: pressed; opacity: 1 }
+        }
+    ]
+}
diff --git a/src/qml/common/Config.qml b/src/qml/common/Config.qml
new file mode 100644 (file)
index 0000000..a99ada0
--- /dev/null
@@ -0,0 +1,6 @@
+import Qt 4.7
+
+Rectangle {
+    width: 640
+    height: 480
+}
diff --git a/src/qml/common/ConfirmationMessage.qml b/src/qml/common/ConfirmationMessage.qml
new file mode 100644 (file)
index 0000000..1a5f1f8
--- /dev/null
@@ -0,0 +1,50 @@
+import Qt 4.7
+
+Rectangle {
+    id: confirmationMessage
+    signal okClicked
+    signal cancelClicked
+
+    property alias text: question.text
+
+    border.color: "black";
+    border.width : 4;
+    radius: 10;
+    color: "white"
+    height: 160;
+    width: 160;
+    z: 10;
+    anchors.fill: parent
+
+    Text {
+        id: question
+        text: qsTr("Are you sure?")
+        width: parent.width; height: 80
+        horizontalAlignment: Text.AlignHCenter
+        verticalAlignment: Text.AlignVCenter
+        anchors.top: parent.top
+        //anchors.bottom: parent.bottom
+        anchors.margins: 10;
+        //anchors.verticalCenter: parent.verticalCenter
+    }
+
+    Button {
+        id: ok
+        text: qsTr("OK")
+        anchors.left: parent.left; anchors.margins: 5; y: 3; width: 80; height: 60
+        anchors.top: question.bottom
+        //anchors.bottom: parent.bottom
+        onClicked: confirmationMessage.okClicked()
+    }
+
+    Button {
+        id: cancel
+        text: qsTr("Cancel")
+        anchors.right: parent.right; anchors.margins: 5; y: 3; width: 80; height: 60
+        anchors.top: question.bottom
+        //anchors.bottom: parent.bottom
+        anchors.left: ok.right
+        onClicked: confirmationMessage.cancelClicked()
+    }
+
+}
diff --git a/src/qml/common/Menu.qml b/src/qml/common/Menu.qml
new file mode 100644 (file)
index 0000000..ec31242
--- /dev/null
@@ -0,0 +1,121 @@
+import Qt 4.7
+
+Item {
+//    anchors.fill: parent;
+    width: 300; //height: 0;
+    //anchors.top: parent.top; anchors.bottom: parent.bottom
+    y: -parent.height
+
+    function getConfig() {
+        //console.log("XX\n")
+        var doc = new XMLHttpRequest();
+        doc.onreadystatechange = function() {
+            if (doc.readyState == XMLHttpRequest.DONE) {
+                var a = doc.responseXML.documentElement;
+                //for (var ii = 0; ii < a.childNodes.length; ++ii) {
+                //    console.log(ii+ " " + a.childNodes[ii].firstChild.nodeValue);
+                //}
+
+                //console.log("node: " + a.childNodes[0].nodeValue)
+                config.hideReadFeeds = a.childNodes[0].firstChild.nodeValue;
+                config.hideReadArticles = a.childNodes[1].firstChild.nodeValue;
+                //console.log("feed " + hideReadFeeds + "\n")
+                //console.log("Articles " + hideReadArticles + "\n")
+//                showRequestInfo("Headers -->");
+//                showRequestInfo(doc.getAllResponseHeaders ());
+//                showRequestInfo("Last modified -->");
+//                showRequestInfo(doc.getResponseHeader ("Last-Modified"));
+            }
+        }
+
+        doc.open("GET", "http://localhost:8000/config");
+        doc.send();
+
+    }
+
+    Switch {
+        id: hideReadFeedsSwitch;
+        text: "Hide Read Feeds";
+        value: config.hideReadFeeds
+        onClicked: config.hideReadFeeds = (config.hideReadFeeds == "False") ? "True" : "False"
+    }
+
+    Switch {
+        id: hideReadArticlesSwitch;
+        text: "Hide Read Articles";
+        value: config.hideReadArticles
+        onClicked: config.hideReadArticles = (config.hideReadArticles == "False") ? "True" : "False"
+        anchors.top: hideReadFeedsSwitch.bottom
+    }
+
+    Rectangle {
+        id: closeButton
+        height: 50;
+        gradient: Gradient {
+            GradientStop {
+                position: 0.00;
+                color: "#343434";
+            }
+            GradientStop {
+                position: 1.00;
+                color: "#ffffff";
+            }
+        }
+        radius: 10;
+        width:  parent.width
+        anchors.top: hideReadArticlesSwitch.bottom
+
+        MouseArea {
+            id: mouseRegion
+            anchors.fill: closeButton
+            onClicked: { config.isShown = false }
+        }
+    }
+
+//    ListView {
+//        id: configList; model: configs; delegate: configDelegate; z: 6;
+//        cacheBuffer: 100; width: parent.width; height: parent.height;
+//    }
+
+//    XmlListModel {
+
+//        id: configs
+
+//        //source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+tags+"&" : "")+"format=rss2"
+//        //source: "/home/ymarcoz/feedlist.xml"
+//        source: "http://localhost:8000/config"
+//        query: "/xml/config"
+//        //namespaceDeclarations: "declare namespace media=\"http://search.yahoo.com/mrss/\";"
+
+//        XmlRole { name: "hideReadFeeds"; query: "hideReadFeeds/string()" }
+//        XmlRole { name: "hideReadArticles"; query: "hideReadArticles/string()" }
+//        //XmlRole { name: "catid"; query: "catid/string()"; isKey: true }
+
+//    }
+
+//    Component {
+//        id: configDelegate
+
+//        Item {
+
+//            id: wrapper; width: wrapper.ListView.view.width; height: 86
+//            Item {
+//                id: moveMe
+//                height: parent.height
+//                Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+//                Rectangle {
+//                    x: 6; y: 4; width: 77; height: parent.height - 9; color: "white"; smooth: true
+
+//                }
+//                Column {
+//                    x: 92; width: wrapper.ListView.view.width - 95; y: 15; spacing: 2
+//                    Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+//                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+//                }
+//            }
+//            MouseArea { anchors.fill: wrapper; onClicked: { container.categoryClicked(catid); } }
+//        }
+//    }
+
+    Component.onCompleted: getConfig();
+}
diff --git a/src/qml/common/Slider.qml b/src/qml/common/Slider.qml
new file mode 100644 (file)
index 0000000..c210196
--- /dev/null
@@ -0,0 +1,36 @@
+import Qt 4.7
+
+Item {
+    id: slider; width: 340; height: 48
+
+    // value is read/write.
+    property real value
+    onValueChanged: { handle.x = 2 + (value - minimum) * slider.xMax / (maximum - minimum); }
+    property real maximum: 1
+    property real minimum: 1
+    property int xMax: slider.width - handle.width - 4
+
+    Rectangle {
+        anchors.fill: parent
+        border.color: "white"; border.width: 0; radius: 8
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#66343434" }
+            GradientStop { position: 1.0; color: "#66000000" }
+        }
+    }
+
+    Rectangle {
+        id: handle; smooth: true
+        x: slider.width / 2 - handle.width / 2; y: 2; width: 30; height: slider.height-4; radius: 6
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "lightgray" }
+            GradientStop { position: 1.0; color: "gray" }
+        }
+
+        MouseArea {
+            anchors.fill: parent; drag.target: parent
+            drag.axis: "XAxis"; drag.minimumX: 2; drag.maximumX: slider.xMax+2
+            onPositionChanged: { value = (maximum - minimum) * (handle.x-2) / slider.xMax + minimum; }
+        }
+    }
+}
diff --git a/src/qml/common/Switch.qml b/src/qml/common/Switch.qml
new file mode 100644 (file)
index 0000000..4c10deb
--- /dev/null
@@ -0,0 +1,76 @@
+import Qt 4.7
+
+Item {
+    id: container
+
+    signal clicked
+
+    property string text
+    property string value
+
+    width: parent.width;
+    height: 86;
+    //anchors.fill: parent;
+
+//    BorderImage {
+//        id: buttonImage
+//        source: "images/toolbutton.sci"
+//        width: container.width; height: container.height
+//    }
+//    BorderImage {
+//        id: pressed
+//        opacity: 0
+//        source: "images/toolbutton.sci"
+//        width: container.width; height: container.height
+//    }
+
+    Rectangle {
+        id: back
+        width: parent.width;
+        height: 82;
+        color: "#343434";
+        border.width : 4;
+        border.color: "black";
+        radius: 10;
+    }
+
+    Rectangle {
+        id: valueSwitch
+        color: (value=="False") ? "red" : "green";
+        border.width : 4;
+        border.color: "black";
+        radius: 10;
+        height: 40;
+        width: 40;
+        anchors.verticalCenter: back.verticalCenter
+        //anchors.verticalCenter: parent.verticalCenter
+        anchors.margins: 10;
+        anchors.right: back.right;
+        Text {
+            color: "white"
+            anchors.centerIn: valueSwitch; font.bold: true
+            text: (container.value == "False") ? "OFF" : "ON"; style: Text.Raised; styleColor: "black"
+        }
+    }
+
+    MouseArea {
+        id: mouseRegion
+        anchors.fill: back
+        onClicked: { container.clicked(); }
+    }
+    Text {
+        color: "white"
+        /*anchors.centerIn: back;*/ font.bold: true
+        anchors.left: parent.left;
+        anchors.margins: 10
+        anchors.verticalCenter: back.verticalCenter
+        text: container.text; style: Text.Raised; styleColor: "black"
+    }
+//    states: [
+//        State {
+//            name: "Pressed"
+//            when: mouseRegion.pressed == true
+//            PropertyChanges { target: pressed; opacity: 1 }
+//        }
+//    ]
+}
diff --git a/src/qml/common/ToolBar.qml b/src/qml/common/ToolBar.qml
new file mode 100644 (file)
index 0000000..d557112
--- /dev/null
@@ -0,0 +1,86 @@
+import Qt 4.7
+
+Item {
+    id: toolbar
+
+    property alias button1Label: button1.text
+    property alias button2Label: button2.text
+    property alias prevLabel: prevButton.text
+    property alias nextLabel: nextButton.text
+    property alias markAllLabel: markAllButton.text
+    property alias zoomLabel: zoomButton.text
+    property alias taskSwitcherLabel: taskSwitcherButton.text
+    property alias nextVisible: nextButton.visible
+    property alias prevVisible: prevButton.visible
+    property alias markAllVisible: markAllButton.visible
+    property alias zoomVisible: zoomButton.visible
+    signal button1Clicked
+    signal button2Clicked
+    signal prevClicked
+    signal nextClicked
+    signal markAllClicked
+    signal zoomClicked
+    signal taskSwitcherClicked
+
+    //BorderImage { source: "images/titlebar.sci"; width: parent.width; height: parent.height + 14; y: -7 }
+    Rectangle {
+        anchors.fill: parent; color: "#343434";
+        border.color: "black"
+        gradient: Gradient {
+            GradientStop {
+                position: 0.00;
+                color: "#343434";
+            }
+            GradientStop {
+                position: 1.00;
+                color: "#ffffff";
+            }
+        }
+
+        Button {
+            id: taskSwitcherButton
+            anchors.left: parent.left; anchors.leftMargin: 5; y: 3; width: 160; height: 60
+            onClicked: toolbar.taskSwitcherClicked()
+        }
+
+        Button {
+            id: button1
+            anchors.left: taskSwitcherButton.right; anchors.leftMargin: 5; y: 3; width: 160; height: 60
+            onClicked: toolbar.button1Clicked()
+        }
+
+        Button {
+            id: button2
+            anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 160; height: 60
+            onClicked: toolbar.button2Clicked()
+        }
+
+        Button {
+            id: markAllButton
+            visible: false
+            anchors.left: button1.right; anchors.rightMargin: 5; y: 3; width: 240; height: 60
+            onClicked: toolbar.markAllClicked()
+        }
+
+        Button {
+            id: prevButton
+            visible: false
+            anchors.left: button1.right; anchors.rightMargin: 5; y: 3; width: 120; height: 60
+            onClicked: toolbar.prevClicked()
+        }
+
+        Button {
+            id: nextButton
+            visible: false
+            anchors.right: button2.left; anchors.rightMargin: 5; y: 3; width: 120; height: 60
+            onClicked: toolbar.nextClicked()
+        }
+
+        Button {
+            id: zoomButton
+            visible: false
+            anchors.right: nextButton.left; anchors.rightMargin: 5; y: 3; width: 80; height: 60
+            onClicked: toolbar.zoomClicked()
+        }
+    }
+}
diff --git a/src/qml/common/images/toolbutton.png b/src/qml/common/images/toolbutton.png
new file mode 100644 (file)
index 0000000..1131001
Binary files /dev/null and b/src/qml/common/images/toolbutton.png differ
diff --git a/src/qml/common/images/toolbutton.sci b/src/qml/common/images/toolbutton.sci
new file mode 100644 (file)
index 0000000..9e4f965
--- /dev/null
@@ -0,0 +1,5 @@
+border.left: 15
+border.top: 4
+border.bottom: 4
+border.right: 15
+source: toolbutton.png
diff --git a/src/qml/debug.log b/src/qml/debug.log
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/qml/i18n/FeedingIt.ts b/src/qml/i18n/FeedingIt.ts
new file mode 100644 (file)
index 0000000..0351c1a
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en_CA">
+<context>
+    <name>FeedingItUI2</name>
+    <message>
+        <location filename="../FeedingItUI2.qml" line="53"/>
+        <source>Back</source>
+        <translation type="unfinished">Back 2</translation>
+    </message>
+</context>
+</TS>
diff --git a/src/qml/i18n/qml_en.qm b/src/qml/i18n/qml_en.qm
new file mode 100644 (file)
index 0000000..a284e5c
Binary files /dev/null and b/src/qml/i18n/qml_en.qm differ
diff --git a/src/qml/i18n/qml_en.ts b/src/qml/i18n/qml_en.ts
new file mode 100644 (file)
index 0000000..5d6e957
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en_CA">
+<context>
+    <name>FeedingItUI2</name>
+    <message>
+        <location filename="../FeedingItUI2.qml" line="75"/>
+        <source>Back</source>
+        <translation>Back 2</translation>
+    </message>
+    <message>
+        <location filename="../FeedingItUI2.qml" line="75"/>
+        <source>Config</source>
+        <translation>Config</translation>
+    </message>
+</context>
+<context>
+    <name>Feeds</name>
+    <message>
+        <source>unreadItems</source>
+        <translation type="obsolete"> %1 unread items</translation>
+    </message>
+    <message>
+        <location filename="../Feeds.qml" line="55"/>
+        <source>%1 unread items</source>
+        <translation>%1 unread items</translation>
+    </message>
+</context>
+</TS>
diff --git a/src/qml/old-FeedingitUI.qml b/src/qml/old-FeedingitUI.qml
new file mode 100644 (file)
index 0000000..c978a50
--- /dev/null
@@ -0,0 +1,162 @@
+import Qt 4.7
+
+Item {
+    id: screen; width: 800; height: 480;
+//    property bool inListView : false;
+    Rectangle {
+        id: background
+        anchors.fill: parent; color: "#343434";
+
+        Text {
+            text: "Hello World"
+            anchors.centerIn: parent
+            MouseArea {
+                id: mouseRegion
+                anchors.fill: parent
+                onClicked: { screen.inListView = true; }
+            }
+        }
+    }
+//    states: State {
+//        name: "ListView"; when: screen.inListView == true
+//        PropertyChanges { target: feedList; x: 0 }
+//        //PropertyChanges { target: photoGridView; x: -(parent.width * 1.5) }
+//    }
+
+    transitions: Transition {
+        NumberAnimation { properties: "x"; duration: 500; easing.type: "InOutQuad" }
+    }
+
+    ListView {
+        id: categoryList; model: categories; delegate: categoryDelegate; z: 6
+        property string shownCategory : ""
+        /*height: parent.height;*/ x: 0 /*-(parent.width * 1.5)*/; cacheBuffer: 100; width: parent.width;
+//        states: State {
+//                 name: "showOneCategory"; when: categoryList.shownCategory != ""
+//                 PropertyChanges { target: categories; query: "/xml/category[catid=[\"" + categoryList.shownCategory + "\"]" }
+//             }
+    }
+
+    ListView {
+        id: feedList; model: feeds; delegate: feedDelegate; z: 6
+        width: parent.width; height: parent.height; x: parent.width;
+        cacheBuffer: 100;
+//        opacity: 0;
+        //anchors.top: categoryList.bottom
+    }
+
+    XmlListModel {
+
+        id: categories
+
+        //source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+tags+"&" : "")+"format=rss2"
+        source: "/home/ymarcoz/feedlist.xml"
+        query: "/xml/category"
+        //namespaceDeclarations: "declare namespace media=\"http://search.yahoo.com/mrss/\";"
+
+        XmlRole { name: "title"; query: "catname/string()" }
+        XmlRole { name: "catid"; query: "catid/string()"; isKey: true }
+
+    }
+
+    XmlListModel {
+        property string catid : ""
+        id: feeds
+
+        //source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+tags+"&" : "")+"format=rss2"
+        source: "/home/ymarcoz/feedlist.xml"
+        query: "/xml/category/feed"
+        //namespaceDeclarations: "declare namespace media=\"http://search.yahoo.com/mrss/\";"
+
+        XmlRole { name: "title"; query: "feedname/string()" }
+        XmlRole { name: "feedid"; query: "feedid/string()" }
+    }
+
+    Component {
+        id: categoryDelegate
+
+        Item {
+
+            function categoryClicked() {
+//                if (categoryList.shownCategory == catid)
+//                {
+                    categoryList.shownCategory = ""
+//                    categories.query="/xml/category"
+//                    feedList.opacity = 0;
+//                } else {
+//                    categoryList.shownCategory = catid;
+//                    categories.query="/xml/category[catid=\"" + catid + "\"]";
+                    feeds.query = "/xml/category[catid=\"" + catid +"\"]/feed"
+                    feedList.x = 0
+                    //feeds.reload()
+//                    feedList.opacity = 100
+//                }
+
+                //feedList.model = feeds;
+            }
+            id: wrapper; width: wrapper.ListView.view.width; height: 86
+            Item {
+                id: moveMe
+                height: parent.height
+                Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+                Rectangle {
+                    x: 6; y: 4; width: 77; height: parent.height - 9; color: "white"; smooth: true
+
+                }
+                Column {
+                    x: 92; width: wrapper.ListView.view.width - 95; y: 15; spacing: 2
+                    Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+                }
+            }
+            MouseArea { anchors.fill: wrapper; onClicked: { categoryClicked() } }
+
+            // Animate adding and removing of items:
+
+//            ListView.onAdd: SequentialAnimation {
+//                PropertyAction { target: wrapper; property: "height"; value: 0 }
+//                NumberAnimation { target: wrapper; property: "height"; to: 86; duration: 250; easing.type: Easing.InOutQuad }
+//            }
+
+//            ListView.onRemove: SequentialAnimation {
+//                PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: true }
+//                NumberAnimation { target: wrapper; property: "height"; to: 0; duration: 250; easing.type: Easing.InOutQuad }
+
+//                // Make sure delayRemove is set back to false so that the item can be destroyed
+//                PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: false }
+//            }
+
+        }
+
+    }
+
+    Component {
+        id: feedDelegate
+
+        Item {
+
+            function feedClicked() {
+                //feeds.query = "/xml/category[catid=\"" + catid +"\"]/feed"
+                //feeds.reload()
+                //feedList.model = feeds;
+            }
+            id: wrapper; width: wrapper.ListView.view.width; height: 86
+            Item {
+                id: moveMe
+                Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: 84; width: wrapper.width; y: 1 }
+                Rectangle {
+                    x: 6; y: 4; width: 77; height: 77; color: "#ff0000"; smooth: true
+
+                }
+                Column {
+                    x: 92; width: wrapper.ListView.view.width - 95; y: 15; spacing: 2
+                    Text { text: title; color: "white"; width: parent.width; font.bold: true; elide: Text.ElideRight; style: Text.Raised; styleColor: "black" }
+                    //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
+                }
+            }
+            MouseArea { anchors.fill: wrapper; onClicked: { feedClicked() } }
+        }
+
+    }
+
+}
index 7f3c2b1..2befd61 100644 (file)
@@ -126,8 +126,10 @@ class Feed:
                    entry["author"]
                except:
                    entry["author"] = None
+               if(not(entry.has_key("id"))):
+                   entry["id"] = None 
                tmpEntry = {"title":entry["title"], "content":self.extractContent(entry),
-                            "date":date, "link":entry["link"], "author":entry["author"]}
+                            "date":date, "link":entry["link"], "author":entry["author"], "id":entry["id"]}
                id = self.generateUniqueId(tmpEntry)
                
                #articleTime = time.mktime(self.entries[id]["dateTuple"])
@@ -240,7 +242,10 @@ class Feed:
         return self.db.execute("SELECT date FROM feed WHERE id=?;", (id,) ).fetchone()[0]
     
     def generateUniqueId(self, entry):
-        return getId(str(entry["date"]) + str(entry["title"]))
+        if(entry["id"] != None):
+            return getId(str(entry["id"]))
+        else:
+            return getId(str(entry["date"]) + str(entry["title"]))
     
     def getIds(self, onlyUnread=False):
         if onlyUnread:
@@ -285,7 +290,7 @@ class Feed:
         text += "<html><head><title>" + title + "</title>"
         text += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>\n'
         #text += '<style> body {-webkit-user-select: none;} </style>'
-        text += '</head><body><div><a href=\"' + link + '\">' + title + "</a>"
+        text += '</head><body background=\"white\"><div><a href=\"' + link + '\">' + title + "</a>"
         if author != None:
             text += "<BR /><small><i>Author: " + author + "</i></small>"
         text += "<BR /><small><i>Date: " + date + "</i></small></div>"
@@ -528,7 +533,6 @@ class Listing:
         return keys
     
     def getCategoryTitle(self, id):
-        print id
         row = self.db.execute("SELECT title FROM categories WHERE id=?;", (id, )).fetchone()
         return row[0]