class RSSReaderWidget(QWidget): def __init__(self, config_dir): super(RSSReaderWidget, self).__init__() self.feed_file_path = os.path.join(config_dir, "rss-reader", "feeds.json") self.feed_area = QWidget() self.feed_list = QListWidget() self.feed_list.setStyleSheet( """QListView {background: #4D5250; show-decoration-selected: 1; selection-background-color: #464646;}""") panel_layout = QVBoxLayout() panel_layout.setSpacing(0) panel_layout.setContentsMargins(0, 0, 0, 0) panel_layout.addWidget(self.feed_list) self.feed_area.setLayout(panel_layout) self.article_area = QWidget() self.article_list = QListWidget() self.article_list.setStyleSheet( """QListView {background: #FFF; show-decoration-selected: 1; selection-background-color: #EEE;}""") self.article_list.verticalScrollBar().setStyleSheet("QScrollBar {width:0px;}"); article_layout = QVBoxLayout() article_layout.setSpacing(0) article_layout.setContentsMargins(0, 0, 0, 0) self.browser = BrowserView(config_dir) article_layout.addWidget(self.article_list) article_layout.addWidget(self.browser) article_layout.setStretchFactor(self.article_list, 1) article_layout.setStretchFactor(self.browser, 3) self.article_area.setLayout(article_layout) self.welcome_page = QWidget() self.welcome_page_box = QVBoxLayout() self.welcome_page_box.setSpacing(10) self.welcome_page_box.setContentsMargins(0, 0, 0, 0) welcome_title_label = QLabel("Welcome to EAF RSS Reader!") welcome_title_label.setFont(QFont('Arial', 24)) welcome_title_label.setStyleSheet("QLabel {color: #333; font-weight: bold; margin: 20px;}") welcome_title_label.setAlignment(Qt.AlignHCenter) add_subscription_label = QLabel("Press 'a' to subscribe to a feed!") add_subscription_label.setFont(QFont('Arial', 20)) add_subscription_label.setStyleSheet("QLabel {color: #grey;}") add_subscription_label.setAlignment(Qt.AlignHCenter) self.welcome_page_box.addStretch(1) self.welcome_page_box.addWidget(welcome_title_label) self.welcome_page_box.addWidget(add_subscription_label) self.welcome_page_box.addStretch(1) self.welcome_page.setLayout(self.welcome_page_box) self.right_area = QStackedWidget() self.right_area.addWidget(self.welcome_page) self.right_area.addWidget(self.article_area) if self.has_feed(): self.right_area.setCurrentIndex(1) else: self.right_area.setCurrentIndex(0) hbox = QHBoxLayout() hbox.setSpacing(0) hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.feed_area) hbox.addWidget(self.right_area) hbox.setStretchFactor(self.feed_area, 1) hbox.setStretchFactor(self.right_area, 3) self.setLayout(hbox) self.feed_list.itemActivated.connect(self.handle_feed) self.article_list.itemActivated.connect(self.handle_article) self.feed_object_dict = {} self.init_select_line = False self.fetch_feeds() def has_feed(self): if os.path.exists(self.feed_file_path): try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) return len(feed_dict.keys()) > 0 except Exception: return False return False def fetch_feeds(self): if os.path.exists(self.feed_file_path): try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) for index, feed_link in enumerate(feed_dict): self.fetch_feed(feed_link, index == 0) except Exception: pass def handle_feed(self, feed_item): if feed_item.feed_link in self.feed_object_dict: self.init_article_area(self.feed_object_dict[feed_item.feed_link]) def handle_article(self, article_item): article_item.mark_as_read() self.browser.setUrl(QUrl(article_item.post_link)) def fetch_feed(self, feed_link, refresh_ui): fetchThread = FetchRSSThread(feed_link) fetchThread.fetch_rss.connect(lambda f_object, f_link, f_title: self.handle_rss(f_object, f_link, f_title, refresh_ui)) fetchThread.invalid_rss.connect(self.handle_invalid_rss) object_name = "feed_thread_" + feed_link setattr(self, object_name, fetchThread) getattr(self, object_name).start() def add_subscription(self, feed_link): if not self.feed_exists(feed_link): self.fetch_feed(feed_link, True) else: self.buffer.message_to_emacs.emit("Feed already exists: " + feed_link) def delete_subscription(self): feed_count = self.feed_list.count() current_row = self.feed_list.currentRow() feed_link = self.feed_list.currentItem().feed_link feed_title = self.feed_list.currentItem().feed_title self.feed_list.takeItem(current_row) with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_link in feed_dict: del feed_dict[feed_link] with open(self.feed_file_path, "w") as f: f.write(json.dumps(feed_dict)) if feed_count <= 1: self.feed_list.clear() self.article_list.clear() self.browser.setUrl(QUrl("")) self.right_area.setCurrentIndex(0) else: if current_row < feed_count - 1: self.feed_list.setCurrentRow(current_row) else: self.feed_list.setCurrentRow(feed_count - 2) self.handle_feed(self.feed_list.currentItem()) self.buffer.message_to_emacs.emit("Removed feed: " + feed_title) def feed_exists(self, feed_link): if not os.path.exists(self.feed_file_path): return False try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) return feed_link in feed_dict except Exception: import traceback traceback.print_exc() return False def save_feed(self, feed_object, feed_link, feed_title): touch(self.feed_file_path) article_ids = list(map(lambda post: post.id if hasattr(post, 'id') else post.link, feed_object.entries)) try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_link not in feed_dict: feed_dict[feed_link] = { "title": feed_title, "unread_articles": article_ids } self.save_feed_dict(feed_dict) self.buffer.message_to_emacs.emit("Add feed: " + feed_link) except Exception: import traceback traceback.print_exc() self.save_feed_dict({feed_link : { "title": feed_title, "unread_articles": article_ids }}) self.buffer.message_to_emacs.emit("Add feed: " + feed_link) def save_feed_dict(self, feed_dict): with open(self.feed_file_path, "w") as f: f.write(json.dumps(feed_dict)) def handle_rss(self, feed_object, feed_link, feed_title, refresh_ui): feed_object.feed_link = feed_link self.feed_object_dict[feed_link] = feed_object self.save_feed(feed_object, feed_link, feed_title) self.right_area.setCurrentIndex(1) feed_item = QListWidgetItem(self.feed_list) feed_item.feed_link = feed_link feed_item.feed_title = feed_title feed_item_widget = RSSFeedItem(feed_object, len(feed_object.entries)) feed_item.update_article_num = feed_item_widget.update_article_num feed_item.setSizeHint(feed_item_widget.sizeHint()) self.feed_list.addItem(feed_item) self.feed_list.setItemWidget(feed_item, feed_item_widget) unread_articles = [] with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_object.feed_link in feed_dict: if "unread_articles" in feed_dict[feed_object.feed_link]: unread_articles = ["unread_articles"] for index in range(self.feed_list.count()): feed_item = self.feed_list.item(index) if feed_item.feed_link == feed_object.feed_link: feed_item.update_article_num(len(unread_articles)) break if refresh_ui: self.init_article_area(feed_object) def init_article_area(self, feed_object): self.browser.setUrl(QUrl(feed_object.entries[0].link)) self.article_list.clear() unread_articles = [] with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_object.feed_link in feed_dict and "unread_articles" in feed_dict[feed_object.feed_link]: unread_articles = feed_dict[feed_object.feed_link]["unread_articles"] for index, post in enumerate(feed_object.entries): item_widget = RSSArticleItemWidget(feed_object, post, unread_articles) item = QListWidgetItem(self.article_list) item.mark_as_read = item_widget.mark_as_read item.post_link = item_widget.post_link item.setSizeHint(item_widget.sizeHint()) item_widget.mark_article_read.connect(self.mark_article_read) self.article_list.addItem(item) self.article_list.setItemWidget(item, item_widget) if index == 0: item.mark_as_read() self.article_list.setCurrentRow(0) if not self.init_select_line: self.init_select_line = True self.feed_list.setCurrentRow(0) def handle_invalid_rss(self, feed_link): self.buffer.message_to_emacs.emit("Invalid feed link: " + feed_link) def mark_article_read(self, feed_link, post_link): if os.path.exists(self.feed_file_path): try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_link in feed_dict: unread_articles = feed_dict[feed_link]["unread_articles"] if post_link in unread_articles: unread_articles.remove(post_link) feed_dict[feed_link]["unread_articles"] = unread_articles with open(self.feed_file_path, "w") as f: f.write(json.dumps(feed_dict)) for index in range(self.feed_list.count()): feed_item = self.feed_list.item(index) if feed_item.feed_link == feed_link: feed_item.update_article_num(len(unread_articles)) break except Exception: pass def next_subscription(self): feed_count = self.feed_list.count() current_row = self.feed_list.currentRow() if current_row < feed_count - 1: self.feed_list.setCurrentRow(current_row + 1) self.feed_list.scrollToItem(self.feed_list.currentItem()) self.handle_feed(self.feed_list.currentItem()) else: self.buffer.message_to_emacs.emit("End of subscribed feeds") def prev_subscription(self): current_row = self.feed_list.currentRow() if current_row > 0: self.feed_list.setCurrentRow(current_row - 1) self.feed_list.scrollToItem(self.feed_list.currentItem()) self.handle_feed(self.feed_list.currentItem()) else: self.buffer.message_to_emacs.emit("Beginning of subscribed feeds") def first_subscription(self): self.feed_list.setCurrentRow(0) self.feed_list.scrollToItem(self.feed_list.currentItem()) self.handle_feed(self.feed_list.currentItem()) def last_subscription(self): feed_count = self.feed_list.count() self.feed_list.setCurrentRow(feed_count - 1) self.feed_list.scrollToItem(self.feed_list.currentItem()) self.handle_feed(self.feed_list.currentItem()) def next_article(self): article_count = self.article_list.count() current_row = self.article_list.currentRow() if current_row < article_count - 1: self.article_list.setCurrentRow(current_row + 1) self.article_list.scrollToItem(self.article_list.currentItem()) self.handle_article(self.article_list.currentItem()) else: self.buffer.message_to_emacs.emit("End of articles") def prev_article(self): current_row = self.article_list.currentRow() if current_row > 0: self.article_list.setCurrentRow(current_row - 1) self.article_list.scrollToItem(self.article_list.currentItem()) self.handle_article(self.article_list.currentItem()) else: self.buffer.message_to_emacs.emit("Beginning of articles") def first_article(self): self.article_list.setCurrentRow(0) self.article_list.scrollToItem(self.article_list.currentItem()) self.handle_article(self.article_list.currentItem()) def last_article(self): article_count = self.article_list.count() self.article_list.setCurrentRow(article_count - 1) self.article_list.scrollToItem(self.article_list.currentItem()) self.handle_article(self.article_list.currentItem())
class RSSReaderWidget(QWidget): def __init__(self): super(RSSReaderWidget, self).__init__() self.feed_file_path = os.path.expanduser( "~/.emacs.d/eaf/rss-reader/feeds.json") self.feed_area = QWidget() self.feed_list = QListWidget() self.feed_list.setStyleSheet("""QListWidget{background: #4D5250;}""") panel_layout = QVBoxLayout() panel_layout.setSpacing(0) panel_layout.setContentsMargins(0, 0, 0, 0) panel_layout.addWidget(self.feed_list) self.feed_area.setLayout(panel_layout) self.article_area = QWidget() self.article_list = QListWidget() self.article_list.verticalScrollBar().setStyleSheet( "QScrollBar {width:0px;}") article_layout = QVBoxLayout() article_layout.setSpacing(0) article_layout.setContentsMargins(0, 0, 0, 0) self.browser = BrowserView() article_layout.addWidget(self.article_list) article_layout.addWidget(self.browser) article_layout.setStretchFactor(self.article_list, 1) article_layout.setStretchFactor(self.browser, 3) self.article_area.setLayout(article_layout) self.welcome_page = QWidget() self.welcome_page_box = QVBoxLayout() self.welcome_page_box.setSpacing(10) self.welcome_page_box.setContentsMargins(0, 0, 0, 0) welcome_title_label = QLabel("Welcome to EAF RSS Reader!") welcome_title_label.setFont(QFont('Arial', 24)) welcome_title_label.setStyleSheet( "QLabel {color: black; font-weight: bold; margin: 20px;}") welcome_title_label.setAlignment(Qt.AlignHCenter) add_subscription_label = QLabel("Press key 'a' to add subscription") add_subscription_label.setFont(QFont('Arial', 20)) add_subscription_label.setStyleSheet("QLabel {color: #333;}") add_subscription_label.setAlignment(Qt.AlignHCenter) self.welcome_page_box.addStretch(1) self.welcome_page_box.addWidget(welcome_title_label) self.welcome_page_box.addWidget(add_subscription_label) self.welcome_page_box.addStretch(1) self.welcome_page.setLayout(self.welcome_page_box) self.right_area = QStackedWidget() self.right_area.addWidget(self.welcome_page) self.right_area.addWidget(self.article_area) self.right_area.setCurrentIndex(0) hbox = QHBoxLayout() hbox.setSpacing(0) hbox.setContentsMargins(0, 0, 0, 0) hbox.addWidget(self.feed_area) hbox.addWidget(self.right_area) hbox.setStretchFactor(self.feed_area, 1) hbox.setStretchFactor(self.right_area, 3) self.setLayout(hbox) self.feed_list.itemActivated.connect(self.handle_feed) self.article_list.itemActivated.connect(self.handle_article) self.feed_object_dict = {} self.fetch_feeds() def fetch_feeds(self): if os.path.exists(self.feed_file_path): try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) for index, feed_link in enumerate(feed_dict): self.fetch_feed(feed_link, index == 0) except Exception: pass def handle_feed(self, feed_item): if feed_item.feed_link in self.feed_object_dict: self.update_article_area( self.feed_object_dict[feed_item.feed_link]) def handle_article(self, article_item): self.browser.setUrl(QUrl(article_item.post_link)) def fetch_feed(self, feed_link, refresh_ui): fetchThread = FetchRSSThread(feed_link) fetchThread.fetch_rss.connect( lambda f_object, f_link, f_title: self.handle_rss( f_object, f_link, f_title, refresh_ui)) fetchThread.invalid_rss.connect(self.handle_invalid_rss) object_name = "feed_thread_" + feed_link setattr(self, object_name, fetchThread) getattr(self, object_name).start() def add_subscription(self, feed_link): if not self.feed_is_exits(feed_link): self.fetch_feed(feed_link, True) else: self.message_to_emacs.emit("Feed has exists: " + feed_link) def feed_is_exits(self, feed_link): if not os.path.exists(self.feed_file_path): return False try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) return feed_link in feed_dict except Exception: import traceback traceback.print_exc() return False def save_feed(self, feed_link, feed_title): if not os.path.exists(self.feed_file_path): basedir = os.path.dirname(self.feed_file_path) if not os.path.exists(basedir): os.makedirs(basedir) with open(self.feed_file_path, "a"): os.utime(self.feed_file_path, None) try: with open(self.feed_file_path, "r") as feed_file: feed_dict = json.loads(feed_file.read()) if feed_link not in feed_dict: feed_dict[feed_link] = {"title": feed_title} with open(self.feed_file_path, "w") as f: f.write(json.dumps(feed_dict)) self.message_to_emacs.emit("Add feed: " + feed_link) except Exception: import traceback traceback.print_exc() with open(self.feed_file_path, "w") as f: f.write(json.dumps({feed_link: {"title": feed_title}})) self.message_to_emacs.emit("Add feed: " + feed_link) def handle_rss(self, feed_object, feed_link, feed_title, refresh_ui): self.feed_object_dict[feed_link] = feed_object self.save_feed(feed_link, feed_title) self.right_area.setCurrentIndex(1) feed_item = QListWidgetItem(self.feed_list) feed_item.feed_link = feed_link feed_item_widget = RSSFeedItem(feed_object, len(feed_object.entries)) feed_item.setSizeHint(feed_item_widget.sizeHint()) self.feed_list.addItem(feed_item) self.feed_list.setItemWidget(feed_item, feed_item_widget) if refresh_ui: self.update_article_area(feed_object) def update_article_area(self, feed_object): self.browser.setUrl(QUrl(feed_object.entries[0].link)) self.article_list.clear() for post in feed_object.entries: item_widget = RSSArticleItem(post) item = QListWidgetItem(self.article_list) item.post_link = item_widget.post_link item.setSizeHint(item_widget.sizeHint()) self.article_list.addItem(item) self.article_list.setItemWidget(item, item_widget) def handle_invalid_rss(self, feed_link): self.message_to_emacs.emit("Invalid feed link: " + feed_link)