Esempio n. 1
0
    def on_actionEdit_Feed_activated(self, b=None):
        if b is not None:
            return

        item = self.ui.feeds.currentItem()
        _id = None
        if item:
            fitem = item.parent()
            if not fitem:
                fitem = item
            _id = fitem._id

        feed = backend.Feed.get_by(xmlurl=_id)
        if not feed:  # No feed selected
            return

        if not self.feed_properties:
            from feedproperties import Feed_Properties

            self.feed_properties = Feed_Properties()
        self.ui.vsplitter.addWidget(self.feed_properties)

        # get feed and load data into the widget
        self.feed_properties.name.setText(backend.h2t(feed.name))
        self.feed_properties.url.setText(feed.url or "")
        self.feed_properties.xmlurl.setText(feed.xmlurl)

        self.feed_properties.show()
Esempio n. 2
0
class Main(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        # Load settings
        self.settings = QtCore.QSettings("ralsina", "Kakawana")
        self.restoreGeometry(self.settings.value("geometry").toByteArray())
        self.restoreState(self.settings.value("state").toByteArray())

        self.mode = 0
        self.showAllFeeds = False
        self.showAllPosts = False
        self.keepGoogleSynced = True

        self.enclosures = []
        self.feed_properties = None
        # This is always the same
        uifile = os.path.join(os.path.abspath(os.path.dirname(__file__)), "main.ui")
        uic.loadUi(uifile, self)
        self.ui = self
        # QtWebKit.QWebSettings.globalSettings().\
        # setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

        # Tray Icon
        self.tray = TrayIcon(self)
        self.tray.show()
        self.tray.activated.connect(self.trayActivated)

        # Fix column widths in feed list
        header = self.ui.feeds.header()
        header.setResizeMode(0, QtGui.QHeaderView.Fixed)
        header.resizeSection(0, 24)
        self.feeds.setSortingEnabled(True)
        self.feeds.sortItems(2, QtCore.Qt.DescendingOrder)
        header.hideSection(2)

        self.enclosureLayout = QtGui.QVBoxLayout(self.enclosureContainer)
        self.enclosureContainer.setLayout(self.enclosureLayout)
        self.enclosureContainer.hide()

        # Smart 'Space' that jumps to next post if needed
        self.addAction(self.ui.actionSpace)
        self.addAction(self.ui.actionNext)
        self.addAction(self.ui.actionPrevious)
        self.addAction(self.ui.actionStar)
        self.addAction(self.ui.actionFind)
        self.ui.searchWidget.hide()
        self.ui.actionFind.triggered.connect(self.ui.searchWidget.show)

        # Zoom actions
        self.ui.actionLarger.triggered.connect(lambda: self.ui.html.setZoomFactor(self.ui.html.zoomFactor() + 0.2))
        self.ui.actionSmaller.triggered.connect(lambda: self.ui.html.setZoomFactor(self.ui.html.zoomFactor() - 0.2))
        self.ui.actionNormal.triggered.connect(lambda: self.ui.html.setZoomFactor(1))

        self.ui.html.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateExternalLinks)
        self.ui.html.page().linkClicked.connect(self.linkClicked)
        self.ui.html.page().linkHovered.connect(self.showTempStatus)
        self.progressBar = QtGui.QProgressBar()
        self.progressBar.setMaximum(100)
        self.progressBar.setMaximumWidth(150)
        self.statusbar.addPermanentWidget(self.progressBar, False)
        self.ui.html.page().loadProgress.connect(self.progressBar.setValue)
        self.ui.html.page().loadStarted.connect(self.progressBar.show)
        self.ui.html.page().loadFinished.connect(self.progressBar.hide)

        # Show about kakawana on startup
        self.on_actionAbout_Kakawana_activated()

        self.loadFeeds(-1)

        self.showStatusBar = True
        self.ui.actionShow_Status_Bar.setChecked(self.showStatusBar)

        self.fetcher = Process(target=fetcher)
        self.fetcher.daemon = True
        self.fetcher.start()

        self.update_timer = QtCore.QTimer()
        self.update_timer.timeout.connect(self.get_updates)
        self.update_timer.start(2000)

        self.scheduled_updates = QtCore.QTimer()
        self.scheduled_updates.timeout.connect(self.updateOneFeed)
        self.scheduled_updates.start(30000)

        self.googleList = []

    def showTempStatus(self, msg, *args):
        self.statusbar.showMessage(msg, 5000)

    def on_actionShow_Status_Bar_toggled(self, checked):
        """Show/Hide status bar"""
        self.ui.statusbar.setVisible(checked)
        self.showStatusBar = checked

    def on_actionSync_Read_Items_activated(self, b=None):
        """Fetches the readingList from google and marks all known
        articles as read. Saves that list as self.googleList"""

        if b is not None:
            return
        progress = QtGui.QProgressDialog(self)
        progress.setLabelText(self.tr("Connecting to Google Reader"))
        progress.show()
        progress.setAutoClose(True)
        QtCore.QCoreApplication.processEvents()

        reader = self.getGoogleReader()
        userJson = reader.httpGet(reader.READING_LIST_URL, {"n": 5000})
        allPosts = json.loads(userJson, strict=False)["items"]
        userInfo = reader.getUserInfo()
        readtag = u"user/%s/state/com.google/read" % userInfo["userId"]
        changedFeeds = set()
        progress.setLabelText(self.tr("Syncing"))
        progress.setMaximum(len(allPosts))
        for i, p in enumerate(allPosts):
            progress.setValue(i)
            if i % 10 == 0:
                QtCore.QCoreApplication.processEvents()
            if progress.wasCanceled():
                break
            if readtag in p["categories"]:
                # It's read
                p2 = backend.Post.get_by(url=p["alternate"][0]["href"])
                f1 = backend.Feed.get_by(xmlurl=p["origin"]["streamId"][5:])
                if p2 and f1 and p2.feed == f1 and p2.read == False:
                    p2.read = True
                    print "Marking post: ", p2._id
                    if f1 not in changedFeeds:
                        changedFeeds.add(f1)
                elif not p2:
                    print "Can't find post:", p["alternate"]
        self.googleList = allPosts
        backend.saveData()
        progress.close()
        # Update all changed feeds in the UI
        # FIXME: this can be made smarter
        self.refreshFeeds()

    def trayActivated(self, reason):
        if reason == None:
            return
        if reason == self.tray.Trigger:
            if self.isVisible():
                self.hide()
            else:
                self.show()
                self.raise_()

    def on_actionMark_All_As_Read_triggered(self, b=None):
        """Mark all visible posts in the current feed as read"""
        if b is not None:
            return
        print "Marking feed as Read"
        item = self.ui.feeds.currentItem()
        fitem = item.parent()
        if not fitem:
            fitem = item
        if fitem._id in (-1, -2):
            return
        for i in range(fitem.childCount()):
            _id = fitem.child(i)._id
            if _id:
                post = backend.Post.get_by(_id=_id)
                post.read = True
        if not self.showAllPosts:
            fitem.takeChildren()
        backend.saveData()
        self.on_feeds_itemClicked(item=fitem, column=1)

    def linkClicked(self, url):
        if unicode(url.scheme()) == "cmd":
            # These are fake URLs that trigger kakawana's actions
            cmd = unicode(url.host()).lower()
            print "COMMAND:", cmd  # This is the action name

            # Feed commands
            if cmd == "mark-all-read":
                print "Triggering mark-all-read"
                self.ui.on_actionMark_All_As_Read_triggered()

            elif cmd == "refresh":
                self.updateCurrentFeed()

            elif cmd == "unsubscribe":
                self.on_actionDelete_Feed_activated()

            # Post commands
            elif cmd == "keep-unread":
                self.actionKeep_Unread.trigger()

            elif cmd == "star":
                self.actionStar.setChecked(True)
            elif cmd == "unstar":
                self.actionStar.setChecked(False)

        else:
            QtGui.QDesktopServices.openUrl(url)

    def updateStarredFeed(self):
        """Updates the 'starred posts' feed"""
        fitem = self.findFeedItem(-2)
        fitem.takeChildren()
        posts = backend.Post.query.filter(backend.Post.star == True)
        for post in posts:
            pitem = post.createItem(fitem)

    def updateRecentFeed(self):
        """Updates the 'recent posts' feed"""
        fitem = self.findFeedItem(-1)
        fitem.takeChildren()
        posts = backend.Post.query.filter(backend.Post.read == False).order_by("date desc").limit(10).all()
        for post in posts:
            pitem = post.createItem(fitem)

    def updateCurrentFeed(self):
        """Launches a forced update for the current feed.

        If it's a real feed, the update is queued.
        If it's a meta feed, the update is immediate.

        """
        item = self.ui.feeds.currentItem()
        fitem = item.parent()
        if not fitem:
            fitem = item
        if fitem._id == -1:  # Recent posts, do it now
            self.updateRecentFeed()

        elif fitem._id == -2:  # Starred
            self.updateStarredFeed()

        else:  # Plain feed, do it non-blocking
            feed = backend.Feed.get_by(xmlurl=fitem._id)
            if feed:
                print "Manual update of: ", feed.xmlurl
                fetcher_in.put(["update", feed.xmlurl, feed.etag, feed.check_date])

    def updateOneFeed(self):
        """Launches an update for the feed that needs it most"""
        feeds = backend.Feed.query.order_by("check_date").limit(1).all()
        if feeds:
            feed = feeds[0]
            print feed.check_date
            # Only check if it has not been checked in at least 10 minutes
            if (datetime.datetime.now() - feed.check_date).seconds > 600:
                print "Scheduled update of: ", feed.xmlurl
                fetcher_in.put(["update", feed.xmlurl, feed.etag, feed.check_date])

    def get_updates(self):
        try:
            cmd = fetcher_out.get(False)  # Don't block
        except:
            return
        if cmd[0] == "updated":
            xmlurl = cmd[1]
            feed = backend.Feed.get_by(xmlurl=xmlurl)
            if feed:
                feed.addPosts(cmd[2])
                self.updateFeed(xmlurl)
        elif cmd[0] == "added":
            print "Adding feed to DB"
            xmlurl = cmd[1]
            feed_data = cmd[2]
            f = backend.Feed.createFromFPData(xmlurl, feed_data)
            if self.keepGoogleSynced:
                # Add this feed to google reader
                # FIXME: what if the feed is already subscribed in google?

                reader = self.getGoogleReader2()
                if reader:
                    print "Adding to google:", f.xmlurl, f.name
                    reader.subscribe_feed(f.xmlurl, f.name)
            f.addPosts(feed_data)
            self.updateFeed(f.xmlurl)

    def findPostItem(self, post):
        """Given a post, returns the item where this post is displayed.
        This is the item at feed->post not one in recent or other places.
        Those are generated dynamically when the pseudofeed opens.
        """
        fitem = self.findFeedItem(post.feed.xmlurl)
        for i in range(fitem.childCount()):
            if fitem.child(i)._id == post._id:
                return fitem.child(i)
        return None

    def findFeedItem(self, feed_id):
        for i in range(self.ui.feeds.topLevelItemCount()):
            fitem = self.ui.feeds.topLevelItem(i)
            if fitem._id == feed_id:
                return fitem
        return None

    def updateFeed(self, feed_id):
        # feed_id is a Feed.xmlurl, which is also item._id
        fitem = self.findFeedItem(feed_id)
        if fitem:
            feed = backend.Feed.get_by(xmlurl=feed_id)
            if fitem == self.feeds.currentItem():
                # It's the current item, needs to have its children displayed
                self.showFeedPosts(fitem, feed)

            else:
                # It's not the current item, just show the item count

                # This is the one to update
                unread_count = min(100, len(filter(lambda p: not p.read, feed.posts)))
                fitem.setText(1, backend.h2t("%s (%d)" % (feed.name, unread_count)))
                if unread_count:
                    fitem.setBackground(0, QtGui.QBrush(QtGui.QColor("lightgreen")))
                    fitem.setBackground(1, QtGui.QBrush(QtGui.QColor("lightgreen")))
                else:
                    fitem.setBackground(0, QtGui.QBrush(QtGui.QColor(200, 200, 200)))
                    fitem.setBackground(1, QtGui.QBrush(QtGui.QColor(200, 200, 200)))
                if (
                    (
                        self.ui.feeds.currentItem()
                        and (fitem in [self.ui.feeds.currentItem(), self.ui.feeds.currentItem().parent()])
                    )
                    or self.showAllFeeds
                    or unread_count
                ):
                    fitem.setHidden(False)
                else:
                    fitem.setHidden(True)
        else:
            # This is actually a new feed, so reload
            # feed list
            self.refreshFeeds()

    def loadFeeds(self, expandedFeedId=None, currentItemId=None):
        """Creates all items for feeds and posts.

        If expandedFeedId is set, that feed's item will be expanded.
        if currentItemId is set, that item will be current (FIXME)

        """
        scrollTo = None
        feeds = backend.Feed.query.order_by("name").all()
        feeds.sort(cmp=lambda x, y: -cmp(x.name.lower(), y.name.lower()))
        self.ui.feeds.clear()
        self.feed_icon = QtGui.QIcon(":/icons/feed.svg")
        self.warning_icon = QtGui.QIcon(":/icons/warning.svg")
        self.error_icon = QtGui.QIcon(":/icons/error.svg")
        # Add "some recent"
        fitem = QtGui.QTreeWidgetItem(["", "Recent", "BB"])
        fitem.setBackground(0, QtGui.QBrush(QtGui.QColor("lightgreen")))
        fitem.setIcon(0, self.feed_icon)
        fitem.setBackground(1, QtGui.QBrush(QtGui.QColor("lightgreen")))
        fitem._id = -1
        self.ui.feeds.addTopLevelItem(fitem)
        if expandedFeedId == -1:
            fitem.setExpanded(True)
            scrollTo = fitem

        # for post in posts:
        # pitem = post.createItem(fitem)

        # posts = backend.Post.query.filter(backend.Post.star==True)
        fitem = QtGui.QTreeWidgetItem(["", "Starred", "BA"])
        fitem.setBackground(0, QtGui.QBrush(QtGui.QColor("lightgreen")))
        fitem.setIcon(0, self.feed_icon)
        fitem.setBackground(1, QtGui.QBrush(QtGui.QColor("lightgreen")))
        fitem._id = -2
        self.ui.feeds.addTopLevelItem(fitem)
        if expandedFeedId == -2:
            fitem.setExpanded(True)

        # for post in posts:
        # pitem=post.createItem(fitem)

        i = 0
        # FIXME: this does a lot of unnecesary work
        for feed in feeds:
            i += 1
            if i % 5 == 0:
                QtCore.QCoreApplication.instance().processEvents()
            unread_count = min(100, backend.Post.query.filter_by(feed=feed, read=False).count())
            tt = backend.h2t(feed.name)
            tt2 = "A%09d" % (i)
            fitem = QtGui.QTreeWidgetItem(["", "%s (%d)" % (tt, unread_count), tt2])
            if feed.bad_check_count > 5:
                fitem.setIcon(0, self.error_icon)
            elif feed.last_status == 301:
                fitem.setIcon(0, self.warning_icon)
            else:
                fitem.setIcon(0, self.feed_icon)
            if unread_count:
                fitem.setBackground(0, QtGui.QBrush(QtGui.QColor("lightgreen")))
                fitem.setBackground(1, QtGui.QBrush(QtGui.QColor("lightgreen")))
            else:
                fitem.setBackground(0, QtGui.QBrush(QtGui.QColor(200, 200, 200)))
                fitem.setBackground(1, QtGui.QBrush(QtGui.QColor(200, 200, 200)))

            fitem._id = feed.xmlurl
            self.ui.feeds.addTopLevelItem(fitem)
            if expandedFeedId == feed.xmlurl:
                fitem.setExpanded(True)
                scrollTo = fitem
            if fitem._id == expandedFeedId or self.showAllFeeds or unread_count:
                fitem.setHidden(False)
            else:
                fitem.setHidden(True)

        if scrollTo:
            self.ui.feeds.scrollToItem(scrollTo)

    def on_feeds_itemClicked(self, item=None, column=1):
        if item is None:
            return
        fitem = item.parent()
        if fitem:  # Post
            p = backend.Post.get_by(_id=item._id)
            data = pickle.loads(base64.b64decode(p.data))

            # We display differently depending on current mode
            # The modes are:
            # ["Feed Decides", "Site", "Feed", "Fast Site", "Fast Feed"]

            # Use feed mode as feed decides for a while
            if self.mode == 0:
                self.mode = 2
            if self.mode == 0:
                # Feed decides
                self.ui.html.load(QtCore.QUrl(p.url))
            elif self.mode == 1:
                # Site mode
                self.ui.html.load(QtCore.QUrl(p.url))
            elif self.mode == 2:
                # Feed mode

                # content = ''
                # if 'content' in data:
                # content = '<hr>'.join([c.value for c in data['content']])
                # elif 'summary' in data:
                # content = data['summary']
                # elif 'value' in data:
                # content = data['value']
                # else:
                # print "Can't find content in this entry"
                # print data

                ## Rudimentary NON-html detection
                # if not '<' in content:
                # content=escape(content).replace('\n\n', '<p>')

                self.ui.html.setHtml(
                    renderTemplate(
                        "post.tmpl",
                        post=p,
                        data=data,
                        content=p.content,
                        cssdir=tmplDir,
                        escapedposturl=cgi.escape(p.url),
                        escapedposttitle=cgi.escape(p.title),
                    )
                )
            elif self.mode == 3:
                # Fast site mode
                fname = os.path.join(backend.dbdir, "cache", "%s.jpg" % hashlib.md5(p._id).hexdigest())
                if os.path.exists(fname):
                    self.ui.html.setHtml("""<img src="file://%s" style="max-width:100%%;">""" % fname)
                else:
                    self.ui.html.load(QtCore.QUrl(p.url))
            elif self.mode == 4:
                # Fast Feed mode
                pass

            # Enclosures
            for enclosure in self.enclosures:
                enclosure.hide()
                enclosure.deleteLater()
            self.enclosures = []
            resize = False
            for e in data.get("enclosures", []):
                # FIXME: add generic 'download' enclosure widget
                cls = None
                if hasattr(e, "type"):
                    if e.type.startswith("audio"):
                        cls = AudioPlayer
                    elif e.type.startswith("video") or e.href.split(".")[-1] in ["ogv", "m4v", "avi", "mp4"]:
                        cls = VideoPlayer
                        resize = True
                    if cls:
                        player = cls(e.href, self.enclosureContainer)
                        player.show()
                        self.enclosures.append(player)
                        self.enclosureLayout.addWidget(player)
            if self.enclosures:
                self.enclosureContainer.show()
                if resize:
                    self.enclosureContainer.setGeometry(0, 0, self.width(), 200)
            else:
                self.enclosureContainer.hide()

            p.read = True
            if column == 0:
                print "Star clicked setting to:", not p.star
                p.star = not p.star
                if p.star:
                    item.setIcon(0, QtGui.QIcon(":/icons/star.svg"))
                else:
                    item.setIcon(0, QtGui.QIcon(":/icons/star2.svg"))

            backend.saveData()

            item.setForeground(1, QtGui.QBrush(QtGui.QColor("lightgray")))
            if fitem._id != p.feed.xmlurl:  # Also mark as read in the post's feed
                item = self.findPostItem(p)
            if item:
                item.setForeground(1, QtGui.QBrush(QtGui.QColor("lightgray")))
                if p.star:
                    item.setIcon(0, QtGui.QIcon(":/icons/star.svg"))
                else:
                    item.setIcon(0, QtGui.QIcon(":/icons/star2.svg"))

            # Update unread count
            self.updateFeed(p.feed.xmlurl)
        else:  # Feed
            self.enclosureContainer.hide()
            self.updateCurrentFeed()
            feed = backend.Feed.get_by(xmlurl=item._id)
            if feed:
                # Timeline data structure
                tdata = {}
                # tdata['events']=[{
                #    'start': p.date.strftime(r"%b %d %Y %H:%M:00 GMT"),
                #    'title': p.title,
                #    'link': p.url,
                #    } for p in feed.posts]
                data = pickle.loads(base64.b64decode(feed.data))
                if "status" in data:
                    status = data["status"]
                else:
                    status = "Unknown"
                self.ui.html.setHtml(
                    renderTemplate(
                        "feed.tmpl", timelinedata=json.dumps(tdata), feed=feed, cssdir=tmplDir, status=status
                    )
                )
            else:
                self.ui.html.setHtml("")

            if not item.isExpanded():
                self.ui.feeds.collapseAll()
                item.takeChildren()
                item.setExpanded(True)
            self.showFeedPosts(item, feed)

    def showFeedPosts(self, item, feed):
        """Given a feed and an item, it shows the feed's posts as
        children of the item, and updates what the feed item shows"""
        if feed:
            unread_count = min(len(filter(lambda p: not p.read, feed.posts)), 100)
            item.setText(1, backend.h2t("%s (%d)" % (feed.name, unread_count)))

            items = {}
            for i in range(item.childCount()):
                items[item.child(i)._id] = item
            if self.showAllPosts:
                postList = feed.posts[:100]
            else:
                postList = (
                    backend.Post.query.filter_by(feed=feed, read=False).order_by(-backend.Post.date).limit(100).all()
                )
            for i, post in enumerate(postList):
                if i % 10 == 0:
                    QtCore.QCoreApplication.instance().processEvents()
                if post.read == False or self.showAllPosts:
                    # Should be visible

                    if post._id not in items:
                        # But it's not there
                        pitem = post.createItem(item)

        elif item._id == -1:
            self.updateRecentFeed()
        elif item._id == -2:
            self.updateStarredFeed()

    def on_actionNew_Feed_triggered(self, b=None):
        """Ask for site or feed URL and add it to backend"""

        # FIXME: this is silly slow and blocking.

        if b is not None:
            return
        url, r = QtGui.QInputDialog.getText(self, "Kakawana - New feed", "Enter the URL for the site")
        if not r:
            return
        url = unicode(url)
        feeds = []
        feedurls = feedfinder.feeds(url)
        if not feedurls:
            # Didn't find any feeds
            QtGui.QMessageBox.critical(
                self, self.tr("Kakawana - Error"), self.tr("Couldn't find any feeds at that URL.")
            )
            return
        for furl in feedurls:
            f = feedparser.parse(furl)
            feeds.append(f)
        if len(feeds) > 1:
            items = [u"%d - %s" % (i, feed["feed"]["title"]) for i, feed in enumerate(feeds)]
            ll = QtCore.QStringList()
            for i in items:
                ll.append(QtCore.QString(i))
            item, ok = QtGui.QInputDialog.getItem(
                self, u"Kakawana - New feed", u"What feed do you prefer for this site?", ll, editable=False
            )
            if not ok:
                return
            # Finally, this is the feed URL
            feed = feeds[items.index(unicode(item))]
            furl = feedurls[items.index(unicode(item))]
        else:
            feed = feeds[0]
            furl = feedurls[0]
        f = backend.Feed.createFromFPData(furl, feed)

        f.addPosts(feed=feed)
        self.loadFeeds(f.xmlurl)
        if self.keepGoogleSynced:
            # Add this feed to google reader
            reader = self.getGoogleReader2()
            if reader:
                print "Adding to google:", f.xmlurl, f.name
                reader.subscribe_feed(f.xmlurl, f.name)

    def modeChange(self, mode=None):
        # if not isinstance(mode, int):
        # return
        self.mode = mode
        print "Switching to mode:", mode
        self.on_feeds_itemClicked(self.ui.feeds.currentItem())

    def on_actionUpdate_Feed_activated(self, b=None):
        if b is not None:
            return
        self.updateCurrentFeed()

    def refreshFeeds(self):
        """Like a loadFeeds, but always keeps the current one open"""
        # FIXME: this is very inefficient
        item = self.ui.feeds.currentItem()
        _id = None
        _pid = None
        if item:
            fitem = item.parent()
            _pid = item._id
            if not fitem:
                fitem = item
            _id = fitem._id
        self.loadFeeds(_id, currentItemId=_pid)

    def on_actionEdit_Feed_activated(self, b=None):
        if b is not None:
            return

        item = self.ui.feeds.currentItem()
        _id = None
        if item:
            fitem = item.parent()
            if not fitem:
                fitem = item
            _id = fitem._id

        feed = backend.Feed.get_by(xmlurl=_id)
        if not feed:  # No feed selected
            return

        if not self.feed_properties:
            from feedproperties import Feed_Properties

            self.feed_properties = Feed_Properties()
        self.ui.vsplitter.addWidget(self.feed_properties)

        # get feed and load data into the widget
        self.feed_properties.name.setText(backend.h2t(feed.name))
        self.feed_properties.url.setText(feed.url or "")
        self.feed_properties.xmlurl.setText(feed.xmlurl)

        self.feed_properties.show()

    def on_actionConfigure_Google_Account_activated(self, b=None):
        if b is not None:
            return
        from google_import import Google_Import

        d = Google_Import(parent=self)
        username = keyring.get_password("kakawana", "google_username") or ""
        password = keyring.get_password("kakawana", "google_password") or ""

        d.username.setText(username)
        d.password.setText(password)
        if username or password:
            d.remember.setChecked(True)
        r = d.exec_()
        if r == QtGui.QDialog.Rejected:
            return None
        username = unicode(d.username.text())
        password = unicode(d.password.text())
        if d.remember.isChecked():
            # Save in appropiate keyring
            keyring.set_password("kakawana", "google_username", username)
            keyring.set_password("kakawana", "google_password", password)
        return username, password

    def getGoogleReader(self):
        reader = None
        if self.keepGoogleSynced:
            username = keyring.get_password("kakawana", "google_username") or ""
            password = keyring.get_password("kakawana", "google_password") or ""
            if not username or not password:
                # Show config dialog
                username, password = on_actionConfigure_Google_Account_activated()
            if username and password:
                auth = gr.ClientAuth(username, password)
                reader = gr.GoogleReader(auth)
        return reader

    def getGoogleReader2(self):
        reader = None
        if self.keepGoogleSynced:
            username = keyring.get_password("kakawana", "google_username") or ""
            password = keyring.get_password("kakawana", "google_password") or ""
            if not username or not password:
                # Show config dialog
                username, password = on_actionConfigure_Google_Account_activated()
            if username and password:
                reader = GoogleReaderClient(username, password)
        return reader

    def on_actionImport_Google_Reader_activated(self, b=None):
        if b is not None:
            return
        reader = self.getGoogleReader()
        if not reader:
            return
        reader.buildSubscriptionList()
        feeds = reader.getFeeds()
        for f in feeds:
            f1 = backend.Feed.update_or_create(dict(name=f.title.decode("utf-8"), xmlurl=f.url), surrogate=False)
        backend.saveData()
        self.refreshFeeds()

    def on_actionSync_Google_Feeds_activated(self, b=None):
        if b is not None:
            return

        print "Syncing google feed subscriptions"

        reader = self.getGoogleReader()
        if not reader:
            return

        reader.buildSubscriptionList()
        g_feeds = reader.getFeeds()
        # Check what feeds exist in google and not here:
        new_in_google = []
        print g_feeds
        for f in g_feeds:
            print f.url, type(backend.Feed.get_by(xmlurl=f.url))
            if not backend.Feed.get_by(xmlurl=f.url):
                new_in_google.append(f.url)
                # Add it to the DB via fetcher
                fetcher_in.put(["add", f.url])
        print "New in Google: %d feeds" % len(new_in_google)

        # Check what feeds exist here and not in google:
        g_feed_dict = {}
        for f in g_feeds:
            g_feed_dict[f.url] = f
        new_here = []
        for f in backend.Feed.query.all():
            if f.xmlurl not in g_feed_dict:
                new_here.append([f.xmlurl, f.name])
        print "New here: %d feeds" % len(new_here)
        # FIXME: don't aways do this
        reader = self.getGoogleReader2()
        if reader:
            for xmlurl, name in new_here:
                print "Adding to gogle:", xmlurl, name
                reader.subscribe_feed(xmlurl, name)

        # Check what feeds have been deleted here since last sync:

    def on_actionShow_All_Posts_toggled(self, b=None):
        print "SAP:", b
        self.showAllPosts = b
        item = self.ui.feeds.currentItem()
        fitem = item.parent()
        if not fitem:
            fitem = item
        if fitem._id in (-1, -2):
            return
        self.on_feeds_itemClicked(item=fitem, column=1)

    def on_actionShow_All_Feeds_toggled(self, b=None):
        print "SAF:", b
        self.showAllFeeds = b
        self.refreshFeeds()

    def on_actionSpace_activated(self, b=None):
        """Scroll down the current post, or jump to the next one"""
        if b is not None:
            return
        frame = self.html.page().mainFrame()
        if frame.scrollBarMaximum(QtCore.Qt.Vertical) == frame.scrollPosition().y():
            self.on_actionNext_activated()
        else:
            frame.scroll(0, self.html.height())

    def on_actionNext_activated(self, b=None):
        """Jump to the beginning of the next post"""
        if b is not None:
            return
        item = self.ui.feeds.currentItem()
        if not item:
            item = self.ui.feeds.topLevelItem(0)
        if item:
            item = self.ui.feeds.itemBelow(item)
            self.ui.feeds.setCurrentItem(item)
            self.on_feeds_itemClicked(item)

    def on_actionPrevious_activated(self, b=None):
        """Jump to the beginning of the previous post"""
        if b is not None:
            return
        item = self.ui.feeds.currentItem()
        if not item:
            item = self.ui.feeds.topLevelItem(0)
        if item:
            item = self.ui.feeds.itemAbove(item)
            self.ui.feeds.setCurrentItem(item)
            self.on_feeds_itemClicked(item)

    def on_actionKeep_Unread_activated(self, b=None):
        """Mark the current post as unread"""
        # FIXME this **can** be called without the item being current
        # if the user calls it from the page link, so it should take an ID
        # as optional argument
        if b is not None:
            return
        item = self.ui.feeds.currentItem()
        if not item.parent():
            return  # Not a post
        post = backend.Post.get_by(_id=item._id)
        if not post:
            return
        post.read = False
        backend.saveData()
        self.refreshFeeds()

    def on_actionStar_toggled(self, b=None):
        """Mark the current post as unread"""
        # FIXME this **can** be called without the item being current
        # if the user calls it from the page link, so it should take an ID
        # as optional argument
        if b is None:
            return
        item = self.ui.feeds.currentItem()
        if not item.parent():
            return  # Not a post
        post = backend.Post.get_by(_id=item._id)
        if not post:
            return
        post.star = b
        backend.saveData()
        self.refreshFeeds()

    def on_actionDelete_Feed_activated(self, b=None):
        """Unsubscribe from current feed"""
        if b is not None:
            return
        item = self.ui.feeds.currentItem()
        if item:
            fitem = item.parent()
            if not fitem:
                fitem = item
            feed = backend.Feed.get_by(xmlurl=fitem._id)
            if not feed:
                return

            # Got the current feed, now, must delete it
            feed.delete()
            # Delete all feedless posts
            for p in backend.Post.query.filter_by(feed=None).all():
                p.delete()
            backend.saveData()

            # May need to delete feed from google
            if self.keepGoogleSynced:
                try:
                    # Add this feed to google reader
                    reader = self.getGoogleReader2()
                    if reader:
                        print "Unsubscribing at google: ", fitem._id
                        reader.unsubscribe_feed(fitem._id)
                except Exception, e:
                    # I'm not going to worry if this fails
                    print e
            self.loadFeeds()