0.9 beta 3 - Added features to qml interface (dbus, edit mode...)
authorYves Marcoz <yves@marcoz.org>
Mon, 15 Nov 2010 05:15:04 +0000 (21:15 -0800)
committerYves Marcoz <yves@marcoz.org>
Mon, 15 Nov 2010 05:15:04 +0000 (21:15 -0800)
23 files changed:
src/FeedingIt
src/FeedingIt-Web.py
src/config.py
src/qml/ArticleViewer.qml
src/qml/Categories.qml
src/qml/FeedingIt.qml
src/qml/Feeds.qml
src/qml/common/AddCat.qml [new file with mode: 0644]
src/qml/common/AddFeed.qml [new file with mode: 0644]
src/qml/common/Button.qml
src/qml/common/Config.qml [deleted file]
src/qml/common/LineInput.qml [new file with mode: 0644]
src/qml/common/ManageSubs.qml [new file with mode: 0644]
src/qml/common/Menu.qml
src/qml/common/ToolBar.qml
src/qml/common/images/AppletCloseButton.png [new file with mode: 0644]
src/qml/common/images/delete.png [new file with mode: 0644]
src/qml/common/images/feedingit.png [new file with mode: 0644]
src/qml/common/images/lineedit.png [new file with mode: 0644]
src/qml/common/images/lineedit.sci [new file with mode: 0644]
src/qml/common/images/loading.png [new file with mode: 0644]
src/qml/common/images/plus.png [new file with mode: 0644]
src/rss_sqlite.py

index ebd4991..ba0ea3c 100644 (file)
@@ -11,15 +11,17 @@ dbus)
        #cp feedingit_status.desktop /usr/share/applications/hildon-status-menu/
        nice python2.5 update_feeds.py
        ;;
-qml)
+noqml)
     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
+    python2.5 FeedingIt.py
     ;;
 *)
     cd /opt/FeedingIt
-    python2.5 FeedingIt.py
+    python2.5 FeedingIt-Web.py 2>&1 >/dev/null &
+    pid=`pidof python2.5 FeedingIt-Web.py`
+    sleep 1
+    qmlviewer -opengl -fullscreen qml/FeedingIt.qml
+    kill $pid
     ;;
+
 esac
\ No newline at end of file
index a11c816..d261444 100644 (file)
@@ -5,9 +5,18 @@ from xml import sax
 from cgi import escape
 from re import sub
 from htmlentitydefs import name2codepoint
+from gconf import client_get_default
+from urllib2 import ProxyHandler
+from threading import Thread
+from os.path import isfile, isdir, exists
+from os import mkdir, remove, stat
 
 CONFIGDIR = "/home/user/.feedingit/"
 
+updatingFeeds = []
+#commands = [("addFeed","httpwww"), ("openFeed", "xxxx"), ("openArticle", ("feedid","artid"))]
+commands = [("openFeed", "1,", "61ac1458d761423344998dc76770e36e")]
+
 def unescape(text):
     def fixup(m):
         text = m.group(0)
@@ -29,11 +38,74 @@ def unescape(text):
         return text # leave as is
     return sub("&#?\w+;", fixup, text)
 
-class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
+def sanitize(text):
+    from cgi import escape
+    return escape(text).encode('ascii', 'xmlcharrefreplace')
+
+def start_server():
+    global listing
+    listing = Listing(CONFIGDIR)
+    httpd = BaseHTTPServer.HTTPServer(("127.0.0.1", PORT), Handler)
+    httpd.serve_forever()    
+
+class App():
+    def addFeed(self, url):
+        commands.append(("addFeed",url))
+    
+    def getStatus(self):
+        pass
+    
+    def openFeed(self, key):
+        cat = listing.getFeedCategory(key)
+        commands.append( ("openFeed", cat, key) )
+    
+    def OpenArticle(self, key, id):
+        cat = listing.getFeedCategory(key)
+        commands.append( ("openArticle", (cat, key, id)) )
     
+class Download(Thread):
+    def __init__(self, listing, keys):
+        Thread.__init__(self)
+        self.listing = listing
+        self.keys = keys
+        
+    def run (self):
+        for key in self.keys:
+            print "Start update: %s" % key
+            updatingFeeds.append(key)
+            (use_proxy, proxy) = config.getProxy()
+            try:
+                if use_proxy:
+                        self.listing.updateFeed(key, proxy=proxy, imageCache=config.getImageCache() )
+                else:
+                        self.listing.updateFeed(key, imageCache=config.getImageCache() )
+            except:
+                print "Error updating feed: %s" %key
+            updatingFeeds.remove(key)
+            print "End update: %s" % key
+
+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 updateAll(self):
+        import subprocess
+        subprocess.Popen("FeedingIt update", shell=True)
+    
+    def getCommands(self):
+        
+        commandXml = "<commands>"
+        for item in commands:
+            if item[0]=="addFeed":
+                commandXml += "<command c='addFeed'>%s</command>" %(sanitize(item[1]))
+            if item[0]=="openFeed":
+                commandXml += "<command c='openFeed' cat='%s'>%s</command>" % (sanitize(item[1]), sanitize(item[2]) )
+            if item[0]=="openArticle":
+                commandXml += "<command c='openArticle' cat='%s' key='%s'>%s</command>" %(sanitize(item[1], sanitize(item[2][0]), sanitize(item[2][1])) )
+            commands.remove(item)
+        commandXml += "</commands>"
+        return commandXml
     
     def getConfigXml(self):
         xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><xml>"
@@ -64,6 +136,10 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
             xml += "<unread>%s</unread>" %listing.getFeedNumberOfUnreadItems(key)
             xml += "<updatedDate>%s</updatedDate>" %listing.getFeedUpdateTime(key)
             xml += "<icon>%s</icon>" %listing.getFavicon(key)
+            if key in updatingFeeds:
+                xml += "<updating>True</updating>"
+            else:
+                xml += "<updating>False</updating>"
             xml += "</feed>"
         xml += "</xml>"
         return xml
@@ -93,14 +169,9 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
         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":
@@ -117,7 +188,6 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
             feed = listing.getFeed(key)
             try:
                 file = open(feed.getContentLink(article))
-                #feed.setEntryRead(article)
                 html = file.read().replace("body", "body bgcolor='#ffffff'", 1)
                 file.close()
             except:
@@ -128,6 +198,15 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
             self.wfile.write(html)
             #listing.updateUnread(key)
             return
+        elif request[1] == "isUpdating":
+            xml = "<xml>"
+            key = request[2]
+            if (key in updatingFeeds) or ((key=="") and (len(updatingFeeds)>0)):
+                xml += "<updating>True</updating>"
+            else:
+                xml += "<updating>False</updating>"
+            xml += self.getCommands()
+            xml += "</xml>"
         elif request[1] == "read":
             key = request[2]
             article = request[3]
@@ -152,6 +231,38 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
         elif request[1] == "task":
             self.openTaskSwitch()
             xml = "<xml>OK</xml>"
+        elif request[1] == "deleteCat":
+            key = request[2]
+            listing.removeCategory(key)
+            xml = "<xml>OK</xml>"
+        elif request[1] == "deleteFeed":
+            key = request[3]
+            listing.removeFeed(key)
+            xml = "<xml>OK</xml>"
+        elif request[1] == "addFeed":
+            cat = request[2]
+            name = request[3]
+            url = arguments.get("url","")
+            listing.addFeed(name, url, category=cat)
+            xml = "<xml>OK</xml>"
+        elif request[1] == "updateFeed":
+            key = request[2]
+            download = Download(listing, [key,])
+            download.start()
+            xml = "<xml>OK</xml>"
+        elif request[1]=="updateAll":
+            feeds = []
+            for cat in listing.getListOfCategories():
+                for feed in listing.getSortedListOfKeys("Manual", category=cat):
+                    feeds.append(feed)
+            print feeds
+            download = Download(listing, feeds)
+            download.start()
+            xml = "<xml>OK</xml>"
+        elif request[1] == "addCat":
+            catName = request[2]
+            listing.addCategory(catName)
+            xml = "<xml>OK</xml>"
         else:
             self.send_error(404, "File not found")
             return
@@ -162,8 +273,28 @@ class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
 
 PORT = 8000
 
-listing = Listing(CONFIGDIR)
+if not isdir(CONFIGDIR):
+    try:
+        mkdir(CONFIGDIR)
+    except:
+        print "Error: Can't create configuration directory"
+        from sys import exit
+        exit(1)
+
+from config import Config
+config = Config(None,CONFIGDIR+"config.ini")
+
+import thread
+
+#print "serving at port", PORT
+thread.start_new_thread(start_server, ())
+
+from feedingitdbus import ServerObject
+#from updatedbus import UpdateServerObject, get_lock
+import gobject
+gobject.threads_init()
+import dbus.mainloop.glib
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
-httpd = BaseHTTPServer.HTTPServer(("127.0.0.1", PORT), Handler)
-print "serving at port", PORT
-httpd.serve_forever()
+mainloop = gobject.MainLoop()
+mainloop.run()
index 712ee5a..2bb04f2 100644 (file)
 # Version     : 0.6.1
 # Description : Simple RSS Reader
 # ============================================================================
+try:
+    import gtk
+    import hildon
+    from gobject import idle_add
+except:
+    pass
 
-import gtk
-import hildon
 from ConfigParser import RawConfigParser
-from gobject import idle_add
 from gconf import client_get_default
 from urllib2 import ProxyHandler
 
index eb70c96..775ca8b 100644 (file)
@@ -33,6 +33,28 @@ Item {
         }
     }
 
+    function markAllAsRead() {
+        if (feedid!="") {
+            var doc = new XMLHttpRequest();
+            //console.log(articlesItem.url+"&markAllAsRead=True")
+            var url = "http://localhost:8000/articles/" + feedid + "?markAllAsRead=True"
+            console.log(url)
+            doc.open("GET", url);
+            doc.send();
+            articles.reload();
+        }
+    }
+
+    function viewArticle(articleid) {
+        var index = 0;
+        for (var i=0; i<articleList.count; ++i) {
+            if (articles.get(0).articleid==articleid) {
+                index = i;
+            }
+        }
+        articleView.positionViewAtIndex(index, ListView.Contain); articleView.visible = true;
+    }
+
     ListView {
         id: articleList; model: visualModel.parts.list; z: 6
         width: parent.width; height: parent.height; /*x: 0;*/
@@ -51,6 +73,18 @@ Item {
         highlightMoveDuration: 300;
     }
 
+    Rectangle {
+        id: noArticle
+        width: parent.width; height: parent.height;
+        visible: false;
+        z:8;
+        Text { text: qsTr("No articles available"); }
+        states: State {
+            name: "noArticle"; when: articles.count==0
+            PropertyChanges { target: noArticle; visible: true; }
+        }
+    }
+
     VisualDataModel {
         id: visualModel;
         delegate: Package {
index bce5f7b..885ff97 100644 (file)
@@ -4,6 +4,11 @@ Item {
 //    anchors.fill: parent;
     width: parent.width; height: parent.height;
     //anchors.top: parent.top; anchors.bottom: parent.bottom
+    property bool inEditMode: true
+
+    function reload() {
+        categories.reload();
+    }
 
     ListView {
         id: categoryList; model: categories; delegate: categoryDelegate; z: 6;
@@ -14,15 +19,11 @@ Item {
 
         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 {
@@ -44,8 +45,18 @@ Item {
                     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" }
                 }
+                Item {
+                    x: wrapper.ListView.view.width - 64; y: 12
+                    height: 58; width: 58;
+                    //anchors.horizontalCenter: parent.horizontalCenter;
+                    Image { source: "common/images/delete.png" }
+                    MouseArea {
+                        anchors.fill: parent; onClicked: { container.categoryDeleted(catid); }
+                    }
+                    visible: inEditMode
+                }
             }
-            MouseArea { anchors.fill: wrapper; onClicked: { container.categoryClicked(catid); } }
+            MouseArea { enabled: !inEditMode; anchors.fill: wrapper; onClicked: { container.categoryClicked(catid); } }
         }
     }
 }
index 06fb83a..2df208e 100644 (file)
@@ -12,6 +12,7 @@ Item {
         /*anchors.fill: parent;*/ color: "#343434";
         anchors.centerIn: parent
         transformOrigin: Item.Center
+        property bool editMode: false
 
         function categoryClicked(catid) {
             feedsItem.catid = catid;
@@ -19,8 +20,9 @@ Item {
             feedsItem.visible = true;
         }
 
-        function feedClicked(feedid) {
+        function feedClicked(feedid, updating) {
             flipper.feedid = feedid;
+            toolBar.feedUpdating = updating;
             flipper.visible = true;
         }
 
@@ -47,6 +49,7 @@ Item {
             }
             if (flipper.visible) {
                 feedsItem.reload();
+                toolBar.feedUpdating = false;
                 flipper.visible = false;
                 flipper.feedid = "";
                 return;
@@ -74,6 +77,51 @@ Item {
             }
         }
 
+        function categoryDeleted(catid) {
+            confirmationMessage.catid=catid;
+            confirmationMessage.state="deleteCat";
+        }
+
+        function feedDeleted(catid, feedid) {
+            confirmationMessage.catid=catid;
+            confirmationMessage.feedid=feedid;
+            confirmationMessage.state="deleteFeed";
+        }
+
+        function addCategory(categoryName) {
+            var doc = new XMLHttpRequest();
+            var url = "http://localhost:8000/addCat/"+categoryName
+            doc.open("GET", url);
+            doc.send();
+            categoriesItem.reload();
+            addCat.visible=false;
+        }
+
+        function addFeed(catid, feedName, feedURL) {
+            var doc = new XMLHttpRequest();
+            var url = "http://localhost:8000/addFeed/"+catid + "/" + feedName + "?url=" + feedURL
+            doc.open("GET", url);
+            doc.send();
+            feedsItem.reload();
+            console.log(addFeedDialog.visible)
+            addFeedDialog.visible=false;
+            console.log(addFeedDialog.visible)
+        }
+
+        function updateClicked(feedid) {
+            var doc = new XMLHttpRequest();
+            var url = "http://localhost:8000/updateFeed/" + feedid
+            doc.open("GET", url);
+            doc.send();
+        }
+
+        function updateAllClicked() {
+            var doc = new XMLHttpRequest();
+            var url = "http://localhost:8000/updateAll"
+            doc.open("GET", url);
+            doc.send();
+        }
+
         Common.Menu {
             id: config
             z: 5
@@ -98,19 +146,54 @@ Item {
 
         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
+            property string catid: "";
+            property string feedid: "";
+
+            function action() {
+                if (state=="markAll") {
+                    flipper.markAllAsRead();
+                    state="hidden"
+                    feedsItem.reload()
+                    return;
+                }
+                if (state=="deleteCat") {
+                    var doc = new XMLHttpRequest();
+                    var url = "http://localhost:8000/deleteCat/"+catid
+                    doc.open("GET", url);
+                    doc.send();
+                    categoriesItem.reload();
+                    state="hidden";
+                    return;
+                }
+                if (state=="deleteFeed") {
+                    var doc = new XMLHttpRequest();
+                    var url = "http://localhost:8000/deleteFeed/"+catid+"/"+feedid
+                    doc.open("GET", url);
+                    doc.send();
+                    feedsItem.reload();
+                    state="hidden";
+                    return;
+                }
             }
+            visible: false
+            onOkClicked: action()
             onCancelClicked: visible=false
+            state: "hidden"
+            states: [ State {name: "markAll";
+                    PropertyChanges { target: confirmationMessage; text: qsTr("Do you want to mark all items as read?") }
+                    PropertyChanges { target: confirmationMessage; visible: true; }
+
+                }, State {name: "deleteCat";
+                    PropertyChanges { target: confirmationMessage; text: qsTr("Do you want to delete this category?") }
+                    PropertyChanges { target: confirmationMessage; visible: true; }
+                }, State {name: "deleteFeed";
+                    PropertyChanges { target: confirmationMessage; text: qsTr("Do you want to delete this feed and all its articles?") }
+                    PropertyChanges { target: confirmationMessage; visible: true; }
+                }, State {name: "hidden";
+                    PropertyChanges { target: confirmationMessage; visible: false; }
+                }
+            ]
+
         }
 
         Common.ToolBar {
@@ -125,8 +208,7 @@ Item {
             onPrevClicked: flipper.prev();
             onNextClicked: flipper.next();
             onMarkAllClicked: {
-                confirmationMessage.text = qsTr("Do you want to mark all items as read?");
-                confirmationMessage.visible = true;
+                confirmationMessage.state = "markAll";
             }
             onZoomClicked: { flipper.zoomEnabled = !flipper.zoomEnabled; }
             onTaskSwitcherClicked: {
@@ -134,24 +216,48 @@ Item {
                 var url = "http://localhost:8000/task"
                 doc.open("GET", url);
                 doc.send();
-                //var xmlDoc=doc.responseXML;
             }
-            //onRotateClicked: { container.rotation=-90; container.width=screen.height; container.height=screen.width;  }
+            onAddClicked: {
+                if (feedsItem.visible) {
+                    addFeedDialog.feedName="";
+                    addFeedDialog.catid = feedsItem.catid;
+                    addFeedDialog.visible = true;
+                    return;
+                }
+                if (categoriesItem.visible) {
+                    addCat.catName="";
+                    addCat.visible=true;
+                    return;
+                }
+            }
+            onUpdateClicked: {
+                if (flipper.visible) {
+                    toolBar.feedUpdating = true
+                    container.updateClicked(flipper.feedid);
+                } else {
+                    container.updateAllClicked();
+                }
+            }
 
             states: [ State {
                 name: "navButtons"; when: flipper.articleShown
                 PropertyChanges { target: toolBar; nextVisible: !container.inPortrait; }
                 PropertyChanges { target: toolBar; prevVisible: !container.inPortrait; }
                 PropertyChanges { target: toolBar; zoomVisible: true; }
+                PropertyChanges { target: toolBar; addVisible: false; }
             },
                 State {
                     name: "feedButtons"; when: (flipper.visible)&&(!flipper.articleShown)
                     PropertyChanges { target: toolBar; markAllVisible: true; }
+                    PropertyChanges { target: toolBar; addVisible: false; }
+                    PropertyChanges { target: toolBar; updateVisible: true; }
                 },
                 State {
                     name: "quitButton"; when: (!feedsItem.visible)
                     PropertyChanges { target: toolBar; quitVisible: true;}
                     PropertyChanges { target: toolBar; backVisible: false; }
+                    PropertyChanges { target: toolBar; updateVisible: true; }
+                    //PropertyChanges { target: toolBar; addVisible: true; }
                 }
             ]
         }
@@ -165,10 +271,118 @@ Item {
             anchors.top: toolBar.bottom; anchors.bottom: parent.bottom
             y: toolBar.height;
 
+            Common.AddCat {
+                visible: false;
+                id: addCat
+                width: parent.width;
+                height: parent.height;
+                z: 10;
+            }
+
+            Common.AddFeed {
+                visible: false;
+                id: addFeedDialog
+                width: parent.width;
+                height: parent.height;
+                z: 10;
+            }
+
+            Timer {
+                function checkUpdates() {
+                        if (categoriesItem.visible && !feedsItem.visible) {
+                            var doc = new XMLHttpRequest();
+                            var url = "http://localhost:8000/isUpdating/"
+                            doc.onreadystatechange = function() {
+                                if (doc.readyState == XMLHttpRequest.DONE) {
+                                    var xmlDoc = doc.responseXML.documentElement;
+                                    //var els = xmlDoc.getElementsByTagName("updating");
+                                    var isUpdating = xmlDoc.firstChild.firstChild.nodeValue;
+
+                                    console.log(isUpdating);
+                                    if (isUpdating=="True") {
+                                            toolBar.feedUpdating = true;
+                                    } else {
+                                        if (toolBar.feedUpdating) {
+                                            // We changed from updating to not updating, so we reload the listing
+                                            toolBar.feedUpdating = false;
+                                            categoriesItem.reload();
+                                        }
+                                    }
+                                    var commands = xmlDoc.lastChild.childNodes;
+                                    for (var ii = 0; ii < commands.length; ++ii) {
+                                        // process the commands
+                                        var command = commands[ii].attributes[0].value; //("c")
+                                        console.log(command)
+                                        if (command=="openFeed") {
+                                            // Open feed feed
+                                            var catid = commands[ii].attributes[1].value;
+                                            var feedid = commands[ii].firstChild.nodeValue;
+                                            if (!flipper.visible) {
+                                                container.categoryClicked(catid);
+                                                container.feedClicked(feedid,false);
+                                                console.log("feedid: " + feedid);
+                                            }
+                                        }
+                                        if (command=="openArticle") {
+                                            // Open feed and article
+                                            var feedid = commands[ii].attributes[2].value; //("key");
+                                            var articleid = commands[ii].firstChild.nodeValue;
+                                            if (!flipper.visible) {
+                                                container.categoryClicked(catid);
+                                                container.feedClicked(feedid,false);
+                                                container.articleClicked(articleid, index)
+                                                console.log("art: "+feedid+"/"+articleid);
+                                            }
+                                        }
+                                        if (command=="addFeed") {
+                                            // Open the addFeed dialog
+                                            var url = commands[ii].firstChild.nodeValue;
+                                            console.log("add: "+url)
+                                        }
+                                    }
+
+                                }
+                            }
+                            doc.open("GET", url);
+                            doc.send();
+                            //categoriesItem.reload()
+                        }
+                        if (feedsItem.visible && !flipper.visible) {
+                            //feedsItem.reload()
+                        }
+                        if (flipper.visible) {
+                            var doc = new XMLHttpRequest();
+                            var url = "http://localhost:8000/isUpdating/" + flipper.feedid
+                            doc.onreadystatechange = function() {
+                                if (doc.readyState == XMLHttpRequest.DONE) {
+                                    var a = doc.responseXML.documentElement;
+                                    console.log(a.firstChild.nodeValue);
+                                    if (a.firstChild.nodeValue=="True") {
+                                            toolBar.feedUpdating = true;
+                                    } else {
+                                        if (toolBar.feedUpdating) {
+                                            // We changed from updating to not updating, so we reload the listing
+                                            toolBar.feedUpdating = false;
+                                            flipper.reload();
+                                        }
+                                    }
+                                }
+                            }
+                            doc.open("GET", url);
+                            doc.send();
+
+                            //flipper.reload()
+                        }
+                    }
+                interval: 2000; running: true; repeat: true
+                onTriggered: checkUpdates();
+            }
+
             Categories {
                 // Loads the categoryList view and delegate
                 id: categoriesItem
                 property bool isShown: true;
+                inEditMode: container.editMode;
 
                 states: State {
                     name: "shown"; when: categoriesItem.isShown == false
@@ -187,6 +401,7 @@ Item {
                 id: feedsItem;
                 property string hideReadFeeds: config.hideReadFeeds
                 visible: false;
+                inEditMode: container.editMode;
 
                 states: [
                     State { name: "articlesShown"; when: flipper.visible; PropertyChanges { target: feedsItem; x: -parent.width } },
index 8683087..abd75f8 100644 (file)
@@ -4,6 +4,7 @@ Item {
     //anchors.fill: parent;
     width: parent.width;
     property string catid : ""
+    property bool inEditMode: true
     x: parent.width; height: parent.height;
     anchors.top: parent.top; anchors.bottom: parent.bottom
 
@@ -32,6 +33,7 @@ Item {
         XmlRole { name: "unread"; query: "unread/string()"; isKey: true }
         XmlRole { name: "updatedDate"; query: "updatedDate/string()" }
         XmlRole { name: "icon"; query: "icon/string()" }
+        XmlRole { name: "updating"; query: "updating/string()"; isKey: true }
     }
 
     Component {
@@ -47,7 +49,12 @@ Item {
                 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 }
+                    Image { width:32; height: 32; anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter;
+                        source: (updating=="True")? "common/images/loading.png" : (icon == "False") ? "common/images/feedingit.png" : icon;
+                        NumberAnimation on rotation {
+                            from: 0; to: 360; running: (updating=="True"); loops: Animation.Infinite; duration: 900
+                        }
+                    }
                 }
 
                 Column {
@@ -57,7 +64,7 @@ Item {
                     //Text { text: feedname; width: parent.width; elide: Text.ElideLeft; color: "#cccccc"; style: Text.Raised; styleColor: "black" }
                 }
             }
-            MouseArea { anchors.fill: wrapper; onClicked: { container.feedClicked(feedid) } }
+            MouseArea { anchors.fill: wrapper; onClicked: { container.feedClicked(feedid, updating=="True") } }
         }
 
     }
diff --git a/src/qml/common/AddCat.qml b/src/qml/common/AddCat.qml
new file mode 100644 (file)
index 0000000..9d5092d
--- /dev/null
@@ -0,0 +1,40 @@
+import Qt 4.7
+
+Rectangle {
+    id: addCat;
+    width: 200 //parent.width
+    height: 172
+    color: "white"
+    property alias catName: categoryName.text
+    MouseArea { anchors.fill: parent; onClicked: {} }
+    Column {
+        Row {
+            width: addCat.width
+            height: 86;
+            Text { anchors.verticalCenter: parent.verticalCenter; text: qsTr("Category name:") }
+            LineInput{
+                id: categoryName
+                anchors.centerIn: parent
+                width: 140
+                focus: true
+            }
+        }
+        Row {
+            width: addCat.width
+            Button {
+                id: ok
+                text: qsTr("OK")
+                anchors.margins: 5; y: 3; width: 80; height: 60
+                onClicked: container.addCategory(categoryName.text)
+            }
+
+            Button {
+                id: cancel
+                text: qsTr("Cancel")
+                anchors.margins: 5; y: 3; width: 80; height: 60
+                onClicked: addCat.visible=false;
+            }
+        }
+    }
+
+}
diff --git a/src/qml/common/AddFeed.qml b/src/qml/common/AddFeed.qml
new file mode 100644 (file)
index 0000000..84b806a
--- /dev/null
@@ -0,0 +1,51 @@
+import Qt 4.7
+
+Rectangle {
+    id: addFeed;
+    width: 500 //parent.width
+    height: 172
+    color: "white"
+    property alias feedName: feedName.text
+    property string catid
+    MouseArea { anchors.fill: parent; onClicked: {} }
+    Column {
+        Row {
+            width: addFeed.width
+            height: 86;
+            Text { anchors.verticalCenter: parent.verticalCenter; text: qsTr("Feed name:") }
+            LineInput{
+                id: feedName
+                anchors.centerIn: parent
+                width: 140
+                focus: true
+            }
+        }
+        Row {
+            width: addFeed.width
+            height: 86;
+            Text { anchors.verticalCenter: parent.verticalCenter; text: qsTr("Feed URL:") }
+            LineInput{
+                id: feedURL
+                anchors.centerIn: parent
+                width: 140
+                focus: true
+                text: "http://"
+            }
+        }
+        Row {
+            width: addFeed.width
+            Button {
+                id: ok
+                text: qsTr("OK")
+                anchors.margins: 5; y: 3; width: 80; height: 60
+                onClicked: container.addFeed(catid, feedName.text, feedURL.text)
+            }
+            Button {
+                id: cancel
+                text: qsTr("Cancel")
+                anchors.margins: 5; y: 3; width: 80; height: 60
+                onClicked: addFeed.visible=false;
+            }
+        }
+    }
+}
index 2ae248c..ca97c62 100644 (file)
@@ -9,6 +9,8 @@ Item {
     property string imageSource: ""
     property int imageRotation: 0
 
+    property alias iconRotation: icon.rotation
+
     BorderImage {
         id: buttonImage
         source: "images/toolbutton.sci"
@@ -23,6 +25,7 @@ Item {
         //visible: (container.imageSource=="")
     }
     Image {
+        id: icon
         source: container.imageSource
         rotation: container.imageRotation
         //fillMode: Image.PreserveAspectFit
diff --git a/src/qml/common/Config.qml b/src/qml/common/Config.qml
deleted file mode 100644 (file)
index a99ada0..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-import Qt 4.7
-
-Rectangle {
-    width: 640
-    height: 480
-}
diff --git a/src/qml/common/LineInput.qml b/src/qml/common/LineInput.qml
new file mode 100644 (file)
index 0000000..2167015
--- /dev/null
@@ -0,0 +1,20 @@
+import Qt 4.7
+
+FocusScope {
+    property alias text: input.text
+    property alias maximumLength: input.maximumLength
+    //anchors.centerIn: parent
+    width: 180; height: 28
+    BorderImage {
+        source: "images/lineedit.sci"
+        anchors.fill: parent
+    }
+    TextInput {
+        id: input
+        color: "#151515"; selectionColor: "green"
+        font.pixelSize: 16; font.bold: true
+        width: parent.width-16
+        anchors.centerIn: parent
+        focus: true
+    }
+}
diff --git a/src/qml/common/ManageSubs.qml b/src/qml/common/ManageSubs.qml
new file mode 100644 (file)
index 0000000..f80f1c5
--- /dev/null
@@ -0,0 +1,14 @@
+import Qt 4.7
+
+Rectangle {
+    width: 640
+    height: 480
+
+    ListView {
+        id: categoryList; model: categories; delegate: categoryDelegate; z: 6;
+        cacheBuffer: 100; width: parent.width; height: parent.height;
+    }
+
+
+
+}
index 2666228..fc01480 100644 (file)
@@ -57,6 +57,14 @@ Item {
         anchors.top: hideReadArticlesSwitch.bottom
     }
 
+    Switch {
+        id: editMode;
+        text: qsTr("Enter Edit Mode");
+        value: container.editMode ? "True" : "False"
+        onClicked: { container.editMode=!container.editMode; }
+        anchors.top: lockRotation.bottom
+    }
+
     Rectangle {
         id: closeButton
         height: 50;
@@ -72,7 +80,7 @@ Item {
         }
         radius: 10;
         width:  parent.width
-        anchors.top: lockRotation.bottom
+        anchors.top: editMode.bottom
 
         MouseArea {
             id: mouseRegion
index e6da99c..24d1861 100644 (file)
@@ -17,6 +17,10 @@ Item {
     property alias markAllVisible: markAllButton.visible
     property alias zoomVisible: zoomButton.visible
     property alias quitVisible: quitButton.visible
+    property alias addVisible: addButton.visible
+    property alias updateVisible: updateFeedButton.visible
+
+    property bool feedUpdating: false
 
     signal menuClicked
     signal backClicked
@@ -25,6 +29,8 @@ Item {
     signal markAllClicked
     signal zoomClicked
     signal taskSwitcherClicked
+    signal addClicked
+    signal updateClicked
     //signal rotateClicked
 
     //BorderImage { source: "images/titlebar.sci"; width: parent.width; height: parent.height + 14; y: -7 }
@@ -42,75 +48,98 @@ Item {
             }
         }
 
-        Button {
-            id: taskSwitcherButton
-            anchors.left: parent.left; anchors.leftMargin: 5; y: 3; width: 116; height: 60
-            onClicked: toolbar.taskSwitcherClicked()
-            imageSource: "images/wmTaskLauncherIcon.png"
-        }
+        Row {
+            anchors.fill: parent
+            Button {
+                id: taskSwitcherButton
+                /*anchors.left: parent.left;*/ anchors.leftMargin: 5; y: 3; width: 116; height: 60
+                onClicked: toolbar.taskSwitcherClicked()
+                imageSource: "images/wmTaskLauncherIcon.png"
+            }
 
-        Button {
-            id: menuButton
-            anchors.left: taskSwitcherButton.right; anchors.leftMargin: 5; y: 3; width: 60; height: 60
-            onClicked: toolbar.menuClicked()
-            imageSource: "images/wmEditIcon.png"
-        }
+            Button {
+                id: menuButton
+                /*anchors.left: taskSwitcherButton.right;*/ anchors.leftMargin: 5; y: 3; width: 60; height: 60
+                onClicked: toolbar.menuClicked()
+                imageSource: "images/wmEditIcon.png"
+            }
 
-        Button {
-            id: backButton
-            anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 116; height: 60
-            onClicked: toolbar.backClicked()
-            imageSource: "images/wmBackIcon.png"
-        }
+            Button {
+                id: addButton
+                visible: true; /*anchors.left: menuButton.right;*/
+                anchors.rightMargin: 5; y: 3; width: 60; height: 60
+                onClicked: toolbar.addClicked()
+                imageSource: "images/plus.png"
 
-        Button {
-            id: markAllButton
-            visible: false
-            anchors.left: menuButton.right; anchors.rightMargin: 5; y: 3; width: 60; height: 60
-            onClicked: toolbar.markAllClicked()
-            imageSource: "images/checkmark.png"
-        }
+            }
 
-        Button {
-            id: prevButton
-            visible: false
-            anchors.left: menuButton.right; anchors.rightMargin: 5; y: 3; width: 120; height: 60
-            onClicked: toolbar.prevClicked()
-            imageSource: "images/InputMethodShiftButtonNormal.png"
-            imageRotation: -90;
-        }
+            Button {
+                id: updateFeedButton
+                visible: false; /*anchors.left: menuButton.right;*/
+                anchors.rightMargin: 5; y: 3; width: 60; height: 60
+                onClicked: toolbar.updateClicked()
+                //imageSource: (!feedUpdating) ? "images/rotate.png" : "images/loading.png"
+                NumberAnimation on iconRotation {
+                    from: 0; to: 360; running: (visible == true) && (feedUpdating); loops: Animation.Infinite; duration: 900
+                }
+                state: "update"
+                states : [State {name: "loading"; when: (feedUpdating);
+                        PropertyChanges {target: updateFeedButton; imageSource: "images/loading.png" }
+                    }, State { name: "update"; when: (!feedUpdating);
+                        PropertyChanges {target: updateFeedButton; iconRotation: 0}
+                        PropertyChanges {target: updateFeedButton; imageSource: "images/rotate.png"}
+                    }
+                ]
+            }
 
-        Button {
-            id: nextButton
-            visible: false
-            anchors.right: zoomButton.left; anchors.rightMargin: 5; y: 3; width: 120; height: 60
-            onClicked: toolbar.nextClicked()
-            imageSource: "images/InputMethodShiftButtonNormal.png"
-            imageRotation: 90
-        }
+            Button {
+                id: markAllButton
+                visible: false
+                /*anchors.left: updateFeedButton.right;*/ anchors.rightMargin: 5; y: 3; width: 60; height: 60
+                onClicked: toolbar.markAllClicked()
+                imageSource: "images/checkmark.png"
+            }
 
-        Button {
-            id: zoomButton
-            visible: false
-            anchors.right: backButton.left; anchors.rightMargin: 5; y: 3; width: 80; height: 60
-            onClicked: toolbar.zoomClicked()
-            imageSource: "images/Zoom-In-icon.png"
-        }
+            Button {
+                id: prevButton
+                visible: false
+                /*anchors.left: menuButton.right;*/ anchors.rightMargin: 5; y: 3; width: 120; height: 60
+                onClicked: toolbar.prevClicked()
+                imageSource: "images/InputMethodShiftButtonNormal.png"
+                imageRotation: -90;
+            }
 
-        Button {
-            id: quitButton
-            visible: false
-            anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 116; height: 60
-            onClicked: toolbar.backClicked()
-            imageSource: "images/wmCloseIcon.png"
-        }
+            Button {
+                id: zoomButton
+                visible: false
+                /*anchors.right: backButton.left; */anchors.rightMargin: 5; y: 3; width: 80; height: 60
+                onClicked: toolbar.zoomClicked()
+                imageSource: "images/Zoom-In-icon.png"
+            }
 
-//        Button {
-//            id: rotateButton
-//            visible: true; anchors.left: taskSwitcherButton.right;
-//            anchors.rightMargin: 5; y: 3; width: 60; height: 60
-//            onClicked: toolbar.rotateClicked()
-//            imageSource: "images/rotate.png"
-//        }
+            Button {
+                id: nextButton
+                visible: false
+                /*anchors.right: zoomButton.left;*/ anchors.rightMargin: 5; y: 3; width: 120; height: 60
+                onClicked: toolbar.nextClicked()
+                imageSource: "images/InputMethodShiftButtonNormal.png"
+                imageRotation: 90
+            }
+
+            Button {
+                id: backButton
+                anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 116; height: 60
+                onClicked: toolbar.backClicked()
+                imageSource: "images/wmBackIcon.png"
+            }
+
+            Button {
+                id: quitButton
+                visible: false
+                anchors.right: parent.right; anchors.rightMargin: 5; y: 3; width: 116; height: 60
+                onClicked: toolbar.backClicked()
+                imageSource: "images/wmCloseIcon.png"
+            }
+        }
     }
 }
diff --git a/src/qml/common/images/AppletCloseButton.png b/src/qml/common/images/AppletCloseButton.png
new file mode 100644 (file)
index 0000000..8d6c5a2
Binary files /dev/null and b/src/qml/common/images/AppletCloseButton.png differ
diff --git a/src/qml/common/images/delete.png b/src/qml/common/images/delete.png
new file mode 100644 (file)
index 0000000..f74dcdb
Binary files /dev/null and b/src/qml/common/images/delete.png differ
diff --git a/src/qml/common/images/feedingit.png b/src/qml/common/images/feedingit.png
new file mode 100644 (file)
index 0000000..07fbf83
Binary files /dev/null and b/src/qml/common/images/feedingit.png differ
diff --git a/src/qml/common/images/lineedit.png b/src/qml/common/images/lineedit.png
new file mode 100644 (file)
index 0000000..2cc38dc
Binary files /dev/null and b/src/qml/common/images/lineedit.png differ
diff --git a/src/qml/common/images/lineedit.sci b/src/qml/common/images/lineedit.sci
new file mode 100644 (file)
index 0000000..054bff7
--- /dev/null
@@ -0,0 +1,5 @@
+border.left: 10
+border.top: 10
+border.bottom: 10
+border.right: 10
+source: lineedit.png
diff --git a/src/qml/common/images/loading.png b/src/qml/common/images/loading.png
new file mode 100644 (file)
index 0000000..47a1589
Binary files /dev/null and b/src/qml/common/images/loading.png differ
diff --git a/src/qml/common/images/plus.png b/src/qml/common/images/plus.png
new file mode 100644 (file)
index 0000000..6d7f93e
Binary files /dev/null and b/src/qml/common/images/plus.png differ
index 2befd61..3b86846 100644 (file)
@@ -512,6 +512,9 @@ class Listing:
         
     def getFeedUrl(self, key):
         return self.db.execute("SELECT url FROM feeds WHERE id=?;", (key,)).fetchone()[0]
+    
+    def getFeedCategory(self, key):
+        return self.db.execute("SELECT category FROM feeds WHERE id=?;", (key,)).fetchone()[0]
         
     def getListOfFeeds(self, category=None):
         if category: