Пример #1
0
 def __init__(self, parent=None):
     """Creates a new instance of MainWindow.
     @param parent Qt parent.
     """
     super(MainWindow, self).__init__(parent)
     self.setupUi(self)
     self._backend = Backend()
     self._setupforumstable()
     self._setupthreadstable()
     self._setuppoststabwidget()
     self._setuparchivetab()
     self._openthreads = {}
     self._masterobserver = BackendObserver(self._backend, self)
     self._resumeobservedthreads()
     self.buttonManualArchive.clicked.connect(self._archivethreadmanually)
Пример #2
0
class MainWindow(QMainWindow, Ui_ui_4ca):
    """Main Window implementation."""
    def __init__(self, parent=None):
        """Creates a new instance of MainWindow.
        @param parent Qt parent.
        """
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self._backend = Backend()
        self._setupforumstable()
        self._setupthreadstable()
        self._setuppoststabwidget()
        self._setuparchivetab()
        self._openthreads = {}
        self._masterobserver = BackendObserver(self._backend, self)
        self._resumeobservedthreads()
        self.buttonManualArchive.clicked.connect(self._archivethreadmanually)

    def _setuparchivetab(self):
        self._setupfilterwidgets()
        self._setuparchivedthreadstable()
        self._setuparchivedpoststable()
        self.buttonShowThreads.clicked.connect(self._showarchivedthreads)

    def _setupfilterwidgets(self):
        self.widgetDateTo.hide()
        self.frameCategoryFilter.hide()
        self.frameForumFilter.hide()
        self.frameDateFilter.hide()
        self.frameTagFilter.hide()
        self.checkCategoryFilter.stateChanged.connect(
                self._categoryfilterstatechanged)
        self.checkForumFilter.stateChanged.connect(
                self._forumfilterstatechanged)
        self.checkDateFilter.stateChanged.connect(
                self._datefiltervisibilitystatechanged)
        self.checkTagFilter.stateChanged.connect(
                self._tagfilterstatechanged)
        self.radioDateOlder.clicked.connect(self._datefilterstatechanged)
        self.radioDateNewer.clicked.connect(self._datefilterstatechanged)
        self.radioDateBetween.clicked.connect(self._datefilterstatechanged)
        self._populatecategorytreewidget()
        self._populatetagswidget()

    def _populatetagswidget(self):
        """Populates the QListWidget, that contains all available tags for tag
        filtering.
        """
        for tag in self._backend.tags():
            item = QListWidgetItem(tag[1], self.listTags)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            item.setData(Qt.UserRole, tag[0])

    def _datefilterstatechanged(self):
        """The check state of one of the three date selection radio buttons
        has changed.
        """
        if self.radioDateBetween.isChecked():
            self.labelFrom.setText("From")
            self.widgetDateTo.show()
        else:
            self.widgetDateTo.hide()
            if self.radioDateOlder.isChecked():
                self.labelFrom.setText("Older Than")
            else:
                self.labelFrom.setText("Newer Than")

    def _populatecategorytreewidget(self):
        """Populates the QTreeWidget that shows the available categories for
        thread filtering.
        """
        self._backend.populatecategorytreewidget(self.treeCategory)
        self.treeCategory.expandAll()

    def _categoryfilterstatechanged(self, state):
        if state == Qt.Checked:
            self.frameCategoryFilter.show()
        else:
            self.frameCategoryFilter.hide()

    def _forumfilterstatechanged(self, state):
        if state == Qt.Checked:
            self.frameForumFilter.show()
        else:
            self.frameForumFilter.hide()

    def _datefiltervisibilitystatechanged(self, state):
        if state == Qt.Checked:
            self.frameDateFilter.show()
        else:
            self.frameDateFilter.hide()

    def _tagfilterstatechanged(self, state):
        if state == Qt.Checked:
            self.frameTagFilter.show()
        else:
            self.frameTagFilter.hide()

    def _setuparchivedpoststable(self):
        self.tableArchivedPosts.doubleClicked.connect(self._showarchivedpost)

    def _setuparchivedthreadstable(self):
        self.tableArchivedThreads.clicked.connect(self._archivedthreadselected)

    def _buildfilters(self):
        """Builds a list of lambda functions which serve as thread retrieval
        filters based on what filters are set in the filter widgets.
        """
        filters = []
        filters.append((self._buildcategoryfilters()))
        filters.append((self._buildforumfilters()))
        filters.append((self._builddatefilter()))
        filters.append((self._buildtagfilters()))
        return filters

    def _buildforumfilters(self):
        """Builds a list of lambda functions which will be used to filter
        archived threads by forums.
        """
        if self.checkForumFilter.checkState() == Qt.Checked:
            rowid = -1
            for row in self.tableForumFilter.selectedItems():
                if not rowid == row.row():
                    yield (lambda x:
                            x.forum() == row.data(Qt.UserRole)[1])
                    rowid = row.row()

    def _buildcategoryfilters(self):
        """Builds a list of lambda functions which will be used to filter
        archived threads by categories.
        """
        if self.checkCategoryFilter.checkState() == Qt.Checked:
            for row in self.treeCategory.selectionModel().selectedRows():
                cid, name, parent = row.data(Qt.UserRole)
                yield (lambda x:
                        self._backend.categoryischildof(x.categoryid(), cid)
                        or x.categoryid() == cid)

    def _buildtagfilters(self):
        """Builds a list of lambda functions which will be used to filter
        archived threads by tags.
        """
        if self.checkTagFilter.checkState() == Qt.Checked:
            for row in self.listTags.selectedItems():
                tid = row.data(Qt.UserRole)
                yield (lambda x: tid in (y[0] for y in x.tags()))

    def _builddatefilter(self):
        """Builds a lambda function which will be used to filter archived
        threads by date.
        """
        if self.checkDateFilter.checkState() == Qt.Checked:
            fromdate = self.dateFrom.date().toPython()
            if self.radioDateOlder.isChecked():
                yield (lambda x: datefromstring(x.timestamp()) < fromdate)
            elif self.radioDateNewer.isChecked():
                yield (lambda x: datefromstring(x.timestamp()) > fromdate)
            else:
                todate = self.dateTo.date().toPython()
                yield (lambda x: datefromstring(x.timestamp()) >= fromdate
                        and datefromstring(x.timestamp()) <= todate)

    def _showarchivedthreads(self):
        """Shows all threads that match the current filters."""
        self.tableArchivedThreads.setModel(
                ThreadsArchiveTableModel(
                    self._backend, self._buildfilters(), self))

    def _setupforumstable(self):
        self.tableForums.cellClicked.connect(self._forumselected)
        self._populateforums()

    def _populateforums(self):
        """Populates the forums tables (live and archived filters)."""
        self.tableForums.clear()
        forums = FourChanForum.forums()
        sortedforums = sorted(forums, key = lambda x: x)
        tables = (self.tableForums, self.tableForumFilter)
        for table in tables:
            table.setRowCount(len(forums))
            table.setColumnCount(2)
            i = 0
            for short in sortedforums:
                data = forums[short]
                i1 = QTableWidgetItem(short)
                i2 = QTableWidgetItem(data[1])
                i1.setData(Qt.UserRole, data)
                i2.setData(Qt.UserRole, data)
                table.setItem(i, 0, i1)
                table.setItem(i, 1, i2)
                i += 1

    def _forumselected(self, row, column):
        """A cell has been clicked on."""
        name = self.tableForums.item(row, 0).text()
        self.setforum(name)

    def _setupthreadstable(self):
        self.tableThreads.setModel(ThreadsTableModel(self.tableThreads))
        self.tableThreads.clicked.connect(self._threadselected)
        self.buttonRefreshThreads.clicked.connect(self.refreshThreads)

    def refreshThreads(self):
        """Refreshes the threads of the currently bound forum."""
        m = self.tableThreads.model()
        m.setforum(m.forum())
        self.tableThreads.scrollToTop()

    def setforum(self, name):
        """Sets the currently selected forum and updates the corresponding
        table.
        @param name Short forum name.
        """
        self.tableThreads.model().setforum(name)
        self.tableThreads.scrollToTop()

    def _archivedthreadselected(self, index):
        """A cell in the archived threads table has been clicked on."""
        thread = self.tableArchivedThreads.model().data(index, Qt.UserRole)
        self.tableArchivedPosts.setModel(PostsTableModel(thread, self))

    def _archivethreadmanually(self):
        """Shows a DialogObserveThread to manually archive a thread by pasting
        its url.
        """
        d = DialogObserveThread(self._backend)
        doit = d.exec_() == QDialog.Accepted
        if doit:
            thread = FourChanThread(FourChanThreadUrl(d.url()))
            observer = FourChanThreadObserver(thread, 30,
                    self._masterobserver)
            self._masterobserver.addobserver(observer)
            thread.refresh()

    def _threadselected(self, index):
        """A cell in the threads table has been clicked on."""
        thread = self.tableThreads.model().data(index, Qt.UserRole)
        self._addtabondemand(thread)

    def _setuppoststabwidget(self):
        self.tabPosts.tabCloseRequested.connect(self._removethread)

    def _removethread(self, idx):
        """Removes the given idx from the internal observed threads list."""
        for k, v in self._openthreads.iteritems():
            if v[0] == idx:
                self._openthreads.pop(k)
                self.tabPosts.removeTab(idx)
                break
        for k, v in self._openthreads.iteritems():
            if v[0] > idx:
                self._openthreads[k] = (v[0] - 1, v[1], v[2])

    def _addtabondemand(self, thread):
        """Adds a new tab for the given thread, if it doesn't exist yet.
        If it does, it will be made the current tab.
        @param thread Instance of FourChanThreahHeader
        """
        i = thread.id_()
        if not i in self._openthreads.keys():
            table = QTableView(self)
            table.setModel(LiveFeedModel(self))
            table.setAlternatingRowColors(True)
            table.setEditTriggers(QAbstractItemView.NoEditTriggers)
            table.setIconSize(QSize(192, 192))
            table.horizontalHeader().setVisible(False)
            table.horizontalHeader().setDefaultSectionSize(192)
            table.horizontalHeader().setStretchLastSection(True)
            table.verticalHeader().setVisible(False)
            table.verticalHeader().setDefaultSectionSize(192)

            table.doubleClicked.connect(self._showlivepost)

            idx = self.tabPosts.addTab(table, "/%s/%s" % (thread.forum(), i))
            self._openthreads[i] = (idx, thread, table)
            fct = FourChanThread(
                    FourChanThreadUrl(thread.url()), self._masterobserver)
            fct.postadded.connect(self._updatethread)
            self._masterobserver.addobserver(
                    FourChanThreadObserver(fct, parent=self))
            fct.refresh(True)
        else:
            idx = self._openthreads[i][0]
        self.tabPosts.setCurrentIndex(idx)

    def _showarchivedpost(self, index):
        """A post has been double-clicked on in the archived threads table.
        Show it.
        """
        table = self.tableArchivedPosts
        post = table.model().data(index, Qt.UserRole)
        self._showpostviewerdialog(post)

    def _showlivepost(self, index):
        """A post has been double-clicked on in a thread table.
        Show it.
        """
        table = self.tabPosts.widget(self.tabPosts.currentIndex())
        post = table.model().data(index, Qt.UserRole)
        self._showpostviewerdialog(post)

    def _showpostviewerdialog(self, post):
        d = QDialog(self)
        d.resize(1024, 768)
        l = QVBoxLayout()
        w = PostViewerWidget(post, self._backend, self._masterobserver, self)
        w.nextpostrequested.connect(self._putnextpost)
        w.previouspostrequested.connect(self._putpreviouspost)
        w.closing.connect(d.close)
        l.addWidget(w)
        d.setLayout(l)
        d.setModal(False)
        d.show()

    def _nextpostinsequence(self, widget, thread, reverse):
        """Gets the next or previous post of a post in a thread."""
        if not isinstance(widget, PostViewerWidget):
            raise TypeError("widget not of type PostViewerWidget but %s" %
                    str(type(widget)))
        found = False
        for post in sorted(thread.getposts(), key=lambda x: x.id(),
                reverse=reverse):
            if found: # found matching post in previous iteration
                return post
            if post.id() == widget.post().id():
                found = True
        return None

    def _putnextpost(self, widget, post):
        """Set the current post in the given PostViewerWidget to the next post
        in the thread.
        @param widget Instance of PostViewerWidget.
        @param post Instance of FourChanPost, whose next post should be shown
        """
        nxt = self._nextpostinsequence(widget, post.thread(), False)
        if not nxt is None:
            widget.setpost(nxt)

    def _putpreviouspost(self, widget, post):
        """Set the current post in the given PostViewerWidget to the previous
        post in the thread.
        @param widget Instance of PostViewerWidget.
        @param post Instance of FourChanPost, whose next post should be shown
        """
        prev = self._nextpostinsequence(widget, post.thread(), True)
        if not prev is None:
            widget.setpost(prev)

    def _updatethread(self, url, post):
        """A post was retrieved through an observer, so update the corresponding
        LiveFeedModel.
        """
        for k, v in self._openthreads.iteritems():
            if v[1].id_() == url.threadid():
                v[2].model().addpost(post)
                break

    def _resumeobservedthreads(self):
        """Tells the backend to resume thread observation in case any
        observations were cancelled."""
        for pk, thread in self._backend.observedthreads():
            print "Resumed observing thread %s" % thread.threadid()
            observer = FourChanThreadObserver(thread, 30,
                    self._masterobserver)
            self._masterobserver.addobserver(observer)