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)
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)