From c812fe9234ab6c0039cfe7180261439e257aa845 Mon Sep 17 00:00:00 2001 From: gounux Date: Mon, 15 Apr 2024 15:50:24 +0200 Subject: [PATCH] Add categories combobox and treewidget --- qtribu/gui/dlg_contents.py | 153 +++++++++++++++++-------------------- qtribu/gui/dlg_contents.ui | 65 +++++++++------- qtribu/logic/json_feed.py | 12 ++- 3 files changed, 115 insertions(+), 115 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index e8212476..8ba5b898 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,10 +1,10 @@ +from functools import partial from pathlib import Path -from typing import Dict, List +from typing import Callable, Dict, List -from qgis.PyQt import uic -from qgis.PyQt.QtCore import QModelIndex, Qt -from qgis.PyQt.QtGui import QCursor, QIcon, QStandardItem, QStandardItemModel -from qgis.PyQt.QtWidgets import QAction, QDialog, QListView, QMenu, QWidget +from qgis.PyQt import QtCore, QtWidgets, uic +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem, QWidget from qtribu.__about__ import DIR_PLUGIN_ROOT from qtribu.gui.form_rdp_news import RdpNewsForm @@ -36,33 +36,45 @@ def __init__(self, parent: QWidget = None): self.submit_article_button.clicked.connect(self.submit_article) self.submit_news_button.clicked.connect(self.submit_news) self.donate_button.clicked.connect(self.donate) - self.refresh_list_button.clicked.connect(self.refresh_list) + self.refresh_list_button.clicked.connect( + partial(self.refresh_list, lambda: self.search_line_edit.text()) + ) # search actions self.search_line_edit.textChanged.connect(self.on_search_text_changed) - # articles lists and treeviews - self.contents_list_view = QListView() - self.contents_model = QStandardItemModel(self.contents_list_view) - self.contents_tree_view.setModel(self.contents_model) - self.contents_tree_view.doubleClicked.connect(self.on_content_double_clicked) - self.contents_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.contents_tree_view.customContextMenuRequested.connect( - self.on_open_content_context_menu + # categories combobox + for cat in self.json_feed_client.categories(): + self.categories_combobox.addItem(cat) + self.categories_combobox.currentTextChanged.connect(self.on_category_changed) + + # treet widget initialization + self.contents_tree_widget.setHeaderLabels( + ["Date", "Title", "Author(s)", "Categories"] ) + self.contents_tree_widget.itemClicked.connect(self.on_tree_view_item_click) - self.refresh_list(expand_all=True) + self.refresh_list(lambda: self.search_line_edit.text()) + self.contents_tree_widget.expandAll() def _open_url_in_webviewer(self, url: str, window_title: str) -> None: self.web_viewer.display_web_page(url) self.web_viewer.set_window_title(window_title) def submit_article(self) -> None: + """ + Submit article action + Usually launched when clicking on button + """ open_url_in_browser( "https://github.com/geotribu/website/issues/new?labels=contribution+externe%2Carticle%2Ctriage&projects=&template=ARTICLE.yml" ) def submit_news(self) -> None: + """ + Submit RDP news action + Usually launched when clicking on button + """ self.log("Opening form to submit a news") if not self.form_rdp_news: self.form_rdp_news = RdpNewsForm() @@ -85,105 +97,76 @@ def _post_form_rdp_news(self, dialog_result: int) -> None: self.form_rdp_news = None def donate(self) -> None: + """ + Donate action + Usually launched when clicking on button + """ open_url_in_browser("https://geotribu.fr/team/sponsoring/") - def refresh_list(self, expand_all: bool = False) -> None: + def refresh_list(self, query_action: Callable[[], str]) -> None: # fetch last RSS items using JSONFeed - rss_contents = self.json_feed_client.fetch(query=self.search_line_edit.text()) + rss_contents = self.json_feed_client.fetch(query=query_action()) years = sorted(set([c.date_pub.year for c in rss_contents]), reverse=True) self.contents = { y: [c for c in rss_contents if c.date_pub.year == y] for y in years } - # save expanded item states - expanded = [ - expand_all - or self.contents_tree_view.isExpanded(self.contents_model.index(i, 0)) - for i in range(len(years)) - ] # clean treeview items - self.contents_model.clear() + self.contents_tree_widget.clear() - # populate treeview + # populate treewidget + items = [] for i, year in enumerate(years): # create root item for year - year_item = self._build_root_item(year) - self.contents_model.invisibleRootItem().appendRow(year_item) + item = QTreeWidgetItem([str(year)]) # create contents items for content in self.contents[year]: - content_item = self._build_item_from_content(content) - year_item.setChild(year_item.rowCount(), 0, content_item) - self.contents_tree_view.setExpanded( - self.contents_model.index(i, 0), expanded[i] - ) + child = self._build_tree_widget_item_from_content(content) + item.addChild(child) + items.append(item) + self.contents_tree_widget.insertTopLevelItems(0, items) + self.contents_tree_widget.expandAll() + + @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int) + def on_tree_view_item_click(self, item: QTreeWidgetItem, column: int): + print(item, column, item.text(column)) def on_search_text_changed(self) -> None: + """ + Method called when search box is changed + Should get search + """ # do nothing if text is too small current = self.search_line_edit.text() if current == "": - self.refresh_list(expand_all=True) + self.refresh_list(lambda: current) return if len(current) < 3: return - self.refresh_list() + self.refresh_list(lambda: current) - @staticmethod - def _build_root_item(year: int) -> QStandardItem: - item = QStandardItem(str(year)) - item.setEditable(False) - font = item.font() - font.setBold(True) - item.setFont(font) - return item + def on_category_changed(self, value: str) -> None: + self.refresh_list(lambda: value) @staticmethod - def _build_item_from_content(content: RssItem) -> QStandardItem: + def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: + """ + Builds a QTreeWidgetItem from a RSS content + """ + item = QTreeWidgetItem( + [ + content.date_pub.strftime("%d %B"), + content.title, + ",".join(content.author), + ",".join(content.categories), + ] + ) + item.setToolTip(1, content.abstract) icon_file = ( "logo_orange_no_text" if "Revue de presse" in content.title else "logo_green_no_text" ) icon = QIcon(str(DIR_PLUGIN_ROOT / f"resources/images/{icon_file}.svg")) - text = "{date_pub} - {title} ({authors}) - {tags}".format( - date_pub=content.date_pub.strftime("%d.%m"), - title=content.title, - authors=",".join(content.author), - tags=",".join(content.categories), - ) - item = QStandardItem(icon, text) - item.setEditable(False) + item.setIcon(1, icon) return item - - def on_content_double_clicked(self, index: QModelIndex) -> None: - # if parent year item has been double clicked - if index.parent().row() < 0: - return - year = list(self.contents)[index.parent().row()] - content = self.contents[year][index.row()] - self._open_url_in_webviewer(content.url, content.title) - - def on_open_content_context_menu(self) -> None: - selected_index = next(i for i in self.contents_tree_view.selectedIndexes()) - # if parent year item has been selected - if selected_index.parent().row() < 0: - return - year = list(self.contents)[selected_index.parent().row()] - content = self.contents[year][selected_index.row()] - - content_menu = QMenu("Content menu", self) - - # open in browser action - open_browser_action = QAction(self.tr("Open in browser"), self) - open_browser_action.triggered.connect( - lambda checked: self._open_url_in_browser(content.url) - ) - content_menu.addAction(open_browser_action) - - # open in webviewer action - open_webviewer_action = QAction(self.tr("Open in webviewer"), self) - open_webviewer_action.triggered.connect( - lambda checked: self._open_url_in_webviewer(content.url, content.title) - ) - content_menu.addAction(open_webviewer_action) - - content_menu.exec(QCursor.pos()) diff --git a/qtribu/gui/dlg_contents.ui b/qtribu/gui/dlg_contents.ui index b28734ec..dcb26a43 100644 --- a/qtribu/gui/dlg_contents.ui +++ b/qtribu/gui/dlg_contents.ui @@ -169,40 +169,52 @@ + + + + Filter by category : + + + + + + - - - - 0 - 0 - - - - - 0 - 2 - - - - - 0 - 2 - - - - - - - true + + + 4 - false + true - false + true + + + true + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + @@ -223,7 +235,6 @@ contents_groupbox submit_news_button - contents_tree_view diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py index 338b0038..29267ce3 100644 --- a/qtribu/logic/json_feed.py +++ b/qtribu/logic/json_feed.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, List, Optional import requests from requests import Response @@ -27,7 +27,7 @@ def __init__( self.log = PlgLogger().log self.url = url - def fetch(self, query: str = "") -> List[RssItem]: + def fetch(self, query: str = "") -> list[RssItem]: if not self.items or ( self.last_fetch_date and (datetime.now() - self.last_fetch_date).total_seconds() @@ -39,8 +39,14 @@ def fetch(self, query: str = "") -> List[RssItem]: self.last_fetch_date = datetime.now() return [i for i in self.items if self._matches(query, i)] + def categories(self) -> list[str]: + tags = [] + for content in self.fetch(): + tags.extend([c.lower() for c in content.categories]) + return sorted(set(tags)) + @staticmethod - def _map_item(item: Dict[str, Any]) -> RssItem: + def _map_item(item: dict[str, Any]) -> RssItem: return RssItem( abstract=item.get("content_html"), author=[i["name"] for i in item.get("authors")],