From d5d4436f5a06c420108316564e08163f69ec3994 Mon Sep 17 00:00:00 2001 From: gounux Date: Wed, 13 Mar 2024 14:35:33 +0100 Subject: [PATCH 01/30] Add RSS methods to get last X articles and/or RDP items --- qtribu/logic/rss_reader.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/qtribu/logic/rss_reader.py b/qtribu/logic/rss_reader.py index 985fcd4f..31bb207a 100644 --- a/qtribu/logic/rss_reader.py +++ b/qtribu/logic/rss_reader.py @@ -13,7 +13,7 @@ import logging import xml.etree.ElementTree as ET from email.utils import parsedate -from typing import Optional +from typing import Optional, List # QGIS from qgis.core import Qgis, QgsSettings @@ -117,6 +117,23 @@ def latest_item(self) -> RssItem: return self.FEED_ITEMS[0] + def latest_items(self, count: int = 36) -> List[RssItem]: + """Returns the latest feed items. + :param count: number of items to fetch + :type count: int + :return: latest feed items + :rtype: List[RssItem] + """ + if count <= 0: + raise ValueError("Number of RSS items to get must be > 0") + if not self.FEED_ITEMS: + logger.warning( + "Feed has not been loaded, so it's impossible to " + "return the latest item." + ) + return [] + return self.FEED_ITEMS[:count] + @property def has_new_content(self) -> bool: """Compare the saved item guid (in plugin settings) with feed latest item to \ @@ -206,3 +223,17 @@ def tr(self, message: str) -> str: :rtype: str """ return QCoreApplication.translate(self.__class__.__name__, message) + + +class RssArticlesMiniReader(RssMiniReader): + PATTERN_INCLUDE: list = ["articles/"] + + def __init__(self): + super().__init__() + + +class RssRdpMiniReader(RssMiniReader): + PATTERN_INCLUDE: list = ["rdp/"] + + def __init__(self): + super().__init__() From 0bb5c0dac20792a807ece9870904305c631ac664 Mon Sep 17 00:00:00 2001 From: gounux Date: Wed, 13 Mar 2024 14:36:05 +0100 Subject: [PATCH 02/30] Add geotribu toolbox --- qtribu/gui/geotribu_toolbox.py | 137 ++++++++++++++++++ qtribu/gui/geotribu_toolbox.ui | 250 +++++++++++++++++++++++++++++++++ 2 files changed, 387 insertions(+) create mode 100644 qtribu/gui/geotribu_toolbox.py create mode 100644 qtribu/gui/geotribu_toolbox.ui diff --git a/qtribu/gui/geotribu_toolbox.py b/qtribu/gui/geotribu_toolbox.py new file mode 100644 index 00000000..abd7d709 --- /dev/null +++ b/qtribu/gui/geotribu_toolbox.py @@ -0,0 +1,137 @@ +from pathlib import Path + +from PyQt5.QtCore import QModelIndex, Qt, QUrl +from PyQt5.QtGui import QDesktopServices, QStandardItem, QStandardItemModel +from PyQt5.QtWidgets import QAction, QListView, QMenu, QMessageBox + +from qgis.gui import QgsDockWidget +from qgis.PyQt import uic +from qgis.PyQt.QtWidgets import QWidget + +from qtribu.gui.form_rdp_news import RdpNewsForm +from qtribu.logic.rss_reader import RssArticlesMiniReader, RssRdpMiniReader +from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager + + +class GeotribuToolbox(QgsDockWidget): + + def __init__(self, parent: QWidget = None): + """ + QgsDockWidget for geotribu toolbox + + :param parent: parent widget or application + :type parent: QgsDockWidget + """ + super().__init__(parent) + self.log = PlgLogger().log + self.plg_settings = PlgOptionsManager() + uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) + + # articles lists and treeviews + self.articles_list_view = QListView() + self.articles_model = QStandardItemModel(self.articles_list_view) + self.articles_tree_view.setModel(self.articles_model) + self.articles_tree_view.doubleClicked.connect(self.on_article_double_clicked) + self.articles_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) + self.articles_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) + + # RDP lists and treeviews + self.rdp_list_view = QListView() + self.rdp_model = QStandardItemModel(self.rdp_list_view) + self.rdp_tree_view.setModel(self.rdp_model) + self.rdp_tree_view.doubleClicked.connect(self.on_article_double_clicked) + self.rdp_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) + self.rdp_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) + + # buttons actions + self.form_rdp_news = None + self.submit_article_button.clicked.connect(self.submit_new_article) + self.refresh_articles_list_button.clicked.clicked(self.refresh_articles_list) + self.submit_news_button.clicked.connect(self.submit_news) + self.refresh_articles_list_button.clicked.clicked(self.refresh_rdp_list) + + self.refresh_articles_list() + self.refresh_rdp_list() + + # region articles + + def submit_new_article(self) -> None: + self.log("Opening form to submit a new article") + github_url = "https://github.com/geotribu/website/issues/new?assignees=aurelienchaumet%2Cguts%2Cigeofr&labels=contribution+externe%2Crdp%2Ctriage&projects=&template=RDP_NEWS.yml" + QDesktopServices.openUrl(QUrl(github_url)) + + def refresh_articles_list(self) -> None: + self.log("Refreshing articles list") + # clean article items + self.articles_model.clear() + # fetch last RSS items + qntwk = NetworkRequestsManager() + rss_reader = RssArticlesMiniReader() + rss_reader.read_feed(qntwk.get_from_source(headers=rss_reader.HEADERS)) + # create QStandardItems with RSS items and add them to treeview's model + articles = rss_reader.latest_items() + for article in articles: + article_item = QStandardItem(article.title) + article_item.setEditable(False) + self.articles_model.invisibleRootItem().appendRow(article_item) + + def on_article_double_clicked(self, index: QModelIndex) -> None: + self.log(f"Opening article at index {index}") + # TODO: open article in QWeb window + + def on_open_article_context_menu(self) -> None: + selected_index = next(i for i in self.articles_tree_view.selectedIndexes()) + self.log(f"Opening article at index {selected_index}") + # TODO: add actions when right-clic on an article item: open in browser, open in QWeb, open GitHub PR, contact author... + + # endregion + + # region GeoRDP + + def submit_news(self) -> None: + self.log("Opening form to submit a news") + if not self.form_rdp_news: + self.form_rdp_news = RdpNewsForm() + self.form_rdp_news.setModal(True) + self.form_rdp_news.finished.connect(self._post_form_rdp_news) + self.form_rdp_news.show() + + def on_form_rdp_news_finished(self, dialog_result: int) -> None: + """Perform actions after the GeoRDP news form has been closed. + + :param dialog_result: dialog's result code. Accepted (1) or Rejected (0) + :type dialog_result: int + """ + if self.form_rdp_news: + # if accept button, save user inputs + if dialog_result == 1: + self.form_rdp_news.wdg_author.save_settings() + # clean up + self.form_rdp_news.deleteLater() + self.form_rdp_news = None + + def refresh_rdp_list(self) -> None: + self.log("Refreshing RDP list") + # clean articles_tree_view QTreeView items + self.rdp_model.clear() + # fetch last RSS items + qntwk = NetworkRequestsManager() + rss_reader = RssRdpMiniReader() + rss_reader.read_feed(qntwk.get_from_source(headers=rss_reader.HEADERS)) + # create QStandardItems with RSS items + rdps = rss_reader.latest_items() + for rdp in rdps: + rdp_item = QStandardItem(rdp.title) + rdp_item.setEditable(False) + self.rdp_model.invisibleRootItem().appendRow(rdp_item) + + def on_rdp_double_clicked(self, index: QModelIndex) -> None: + self.log(f"Opening RDP at index {index}") + # TODO: open RDP in QWeb window + + def on_open_rdp_context_menu(self) -> None: + selected_index = next(i for i in self.rdp_tree_view.selectedIndexes()) + self.log(f"Opening RDP at index {selected_index}") + # TODO: add actions when right-clic on a RDP item: open in browser, open in QWeb, open GitHub PR, contact authors... + + # endregion diff --git a/qtribu/gui/geotribu_toolbox.ui b/qtribu/gui/geotribu_toolbox.ui new file mode 100644 index 00000000..a5c505b8 --- /dev/null +++ b/qtribu/gui/geotribu_toolbox.ui @@ -0,0 +1,250 @@ + + + geotribu_toolbox + + + + 0 + 0 + 441 + 887 + + + + + 0 + 0 + + + + Geotribu Toolbox + + + + + 0 + 0 + + + + + 5 + + + 5 + + + 10 + + + 5 + + + 10 + + + + + + 0 + 0 + + + + + 0 + 1 + + + + + 0 + 1 + + + + Articles + + + false + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Submit Article + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Refresh list + + + + + + + + + + 0 + 0 + + + + + 0 + 1 + + + + + 0 + 1 + + + + true + + + + + + + + + + + 0 + 0 + + + + + 0 + 3 + + + + + 0 + 5 + + + + GeoRDP + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Submit News + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Refresh list + + + + + + + + + + 0 + 0 + + + + + 0 + 2 + + + + + 0 + 2 + + + + + + + true + + + + + + + + + + + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+
+ + article_groupbox + submit_article_button + refresh_articles_list_button + articles_tree_view + geordp_groupbox + submit_news_button + refresh_rdp_list_button + rdp_tree_view + + + +
From a58d2ec39e07ee6152366c39b415730eaa7d4b81 Mon Sep 17 00:00:00 2001 From: gounux Date: Wed, 13 Mar 2024 14:37:46 +0100 Subject: [PATCH 03/30] Ignore jb stuff --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f1c7164e..1deb7766 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,6 @@ dmypy.json *.qm *.zip + +# jb stuff +.idea From 93b1c6136271d58473c63215e86a23ab49094db1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:42:29 +0000 Subject: [PATCH 04/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtribu/gui/geotribu_toolbox.py | 12 +++++++----- qtribu/logic/rss_reader.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/qtribu/gui/geotribu_toolbox.py b/qtribu/gui/geotribu_toolbox.py index abd7d709..22cefdaf 100644 --- a/qtribu/gui/geotribu_toolbox.py +++ b/qtribu/gui/geotribu_toolbox.py @@ -2,8 +2,7 @@ from PyQt5.QtCore import QModelIndex, Qt, QUrl from PyQt5.QtGui import QDesktopServices, QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QAction, QListView, QMenu, QMessageBox - +from PyQt5.QtWidgets import QListView from qgis.gui import QgsDockWidget from qgis.PyQt import uic from qgis.PyQt.QtWidgets import QWidget @@ -14,7 +13,6 @@ class GeotribuToolbox(QgsDockWidget): - def __init__(self, parent: QWidget = None): """ QgsDockWidget for geotribu toolbox @@ -33,7 +31,9 @@ def __init__(self, parent: QWidget = None): self.articles_tree_view.setModel(self.articles_model) self.articles_tree_view.doubleClicked.connect(self.on_article_double_clicked) self.articles_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.articles_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) + self.articles_tree_view.customContextMenuRequested.connect( + self.on_open_article_context_menu + ) # RDP lists and treeviews self.rdp_list_view = QListView() @@ -41,7 +41,9 @@ def __init__(self, parent: QWidget = None): self.rdp_tree_view.setModel(self.rdp_model) self.rdp_tree_view.doubleClicked.connect(self.on_article_double_clicked) self.rdp_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.rdp_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) + self.rdp_tree_view.customContextMenuRequested.connect( + self.on_open_article_context_menu + ) # buttons actions self.form_rdp_news = None diff --git a/qtribu/logic/rss_reader.py b/qtribu/logic/rss_reader.py index 31bb207a..2bfbb468 100644 --- a/qtribu/logic/rss_reader.py +++ b/qtribu/logic/rss_reader.py @@ -13,7 +13,7 @@ import logging import xml.etree.ElementTree as ET from email.utils import parsedate -from typing import Optional, List +from typing import List, Optional # QGIS from qgis.core import Qgis, QgsSettings From a1784329c77abf09cae7f8c84a7f229eaac6000a Mon Sep 17 00:00:00 2001 From: gounux Date: Thu, 14 Mar 2024 06:48:08 +0100 Subject: [PATCH 05/30] Add JSON feed client --- qtribu/logic/json_feed.py | 68 ++++++++++++++++++++++++++++++++++ qtribu/toolbelt/preferences.py | 1 + 2 files changed, 69 insertions(+) create mode 100644 qtribu/logic/json_feed.py diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py new file mode 100644 index 00000000..888bd785 --- /dev/null +++ b/qtribu/logic/json_feed.py @@ -0,0 +1,68 @@ +from datetime import datetime +from typing import List, Any, Dict, Optional + +from qtribu.__about__ import __title__, __version__ +from qtribu.logic import RssItem +from qtribu.toolbelt import PlgOptionsManager, PlgLogger + +import requests +from requests import Response + +HEADERS: dict = { + b"Accept": b"application/json", + b"User-Agent": bytes(f"{__title__}/{__version__}", "utf8"), +} + +FETCH_UPDATE_INTERVAL_SECONDS = 7200 + + +class JsonFeedClient: + items: Optional[List[RssItem]] = None + last_fetch_date: Optional[datetime] = None + + def __init__( + self, url: str = PlgOptionsManager.get_plg_settings().json_feed_source + ): + """Class initialization.""" + self.log = PlgLogger().log + self.url = url + + 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() + > FETCH_UPDATE_INTERVAL_SECONDS + ): + r: Response = requests.get(self.url, headers=HEADERS) + r.raise_for_status() + self.items = [self._map_item(i) for i in r.json()["items"]] + self.last_fetch_date = datetime.now() + return [i for i in self.items if self._matches(query, i)] + + @staticmethod + def _map_item(item: Dict[str, Any]) -> RssItem: + return RssItem( + abstract=item.get("content_html"), + author=[i["name"] for i in item.get("authors")], + categories=item.get("tags", []), + date_pub=datetime.fromisoformat(item.get("date_published")), + guid=item.get("id"), + image_length=666, + image_type=item.get("image"), + image_url=item.get("image"), + title=item.get("title"), + url=item.get("url"), + ) + + @staticmethod + def _matches(query: str, item: RssItem) -> bool: + """Moteur de recherche du turfu""" + return ( + query.upper() in item.abstract.upper() + or query.upper() in ",".join(item.author).upper() + or query.upper() in ",".join(item.categories).upper() + or query.upper() in item.date_pub.isoformat().upper() + or query.upper() in item.image_url.upper() + or query.upper() in item.title.upper() + or query.upper() in item.url.upper() + ) diff --git a/qtribu/toolbelt/preferences.py b/qtribu/toolbelt/preferences.py index dccf1750..2872be26 100644 --- a/qtribu/toolbelt/preferences.py +++ b/qtribu/toolbelt/preferences.py @@ -29,6 +29,7 @@ class PlgSettingsStructure: # RSS feed rss_source: str = "https://geotribu.fr/feed_rss_created.xml" + json_feed_source: str = "https://geotribu.fr/feed_json_created.json" # usage browser: int = 1 From 56955d09b315c98c43fb645ac89f7b08697ed631 Mon Sep 17 00:00:00 2001 From: gounux Date: Thu, 14 Mar 2024 06:48:41 +0100 Subject: [PATCH 06/30] Add geotribu contents QDialog --- qtribu/gui/dlg_contents.py | 161 +++++++++++++++++++++ qtribu/gui/dlg_contents.ui | 230 ++++++++++++++++++++++++++++++ qtribu/gui/geotribu_toolbox.py | 137 ------------------ qtribu/gui/geotribu_toolbox.ui | 250 --------------------------------- 4 files changed, 391 insertions(+), 387 deletions(-) create mode 100644 qtribu/gui/dlg_contents.py create mode 100644 qtribu/gui/dlg_contents.ui delete mode 100644 qtribu/gui/geotribu_toolbox.py delete mode 100644 qtribu/gui/geotribu_toolbox.ui diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py new file mode 100644 index 00000000..5514847b --- /dev/null +++ b/qtribu/gui/dlg_contents.py @@ -0,0 +1,161 @@ +from pathlib import Path +from typing import List, Dict + +from PyQt5.QtCore import QModelIndex, Qt, QUrl +from PyQt5.QtGui import QDesktopServices, QIcon, QStandardItem, QStandardItemModel +from PyQt5.QtWidgets import QListView, QMessageBox +from qgis.PyQt import uic +from qgis.PyQt.QtWidgets import QDialog, QWidget + +from qtribu.__about__ import DIR_PLUGIN_ROOT +from qtribu.gui.form_rdp_news import RdpNewsForm +from qtribu.logic import RssItem +from qtribu.logic.json_feed import JsonFeedClient +from qtribu.toolbelt import PlgLogger, PlgOptionsManager + + +class GeotribuContentsDialog(QDialog): + contents: Dict[int, List[RssItem]] = {} + + def __init__(self, parent: QWidget = None): + """ + QDialog for geotribu contents + + :param parent: parent widget or application + :type parent: QgsDockWidget + """ + super().__init__(parent) + self.log = PlgLogger().log + self.plg_settings = PlgOptionsManager() + self.json_feed_client = JsonFeedClient() + uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) + + # buttons actions + self.form_rdp_news = 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) + + # 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 + ) + + self.refresh_list(expand_all=True) + + @staticmethod + def _open_url_in_browser(url: str) -> None: + QDesktopServices.openUrl(QUrl(url)) + + def submit_article(self) -> None: + self._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: + self.log("Opening form to submit a news") + if not self.form_rdp_news: + self.form_rdp_news = RdpNewsForm() + self.form_rdp_news.setModal(True) + self.form_rdp_news.finished.connect(self._post_form_rdp_news) + self.form_rdp_news.show() + + def donate(self) -> None: + self._open_url_in_browser("https://fr.tipeee.com/geotribu") + + def refresh_list(self, expand_all: bool = False) -> None: + # fetch last RSS items using JSONFeed + rss_contents = self.json_feed_client.fetch(query=self.search_line_edit.text()) + 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() + + # populate treeview + 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) + # 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] + ) + + def on_search_text_changed(self) -> None: + # do nothing if text is too small + current = self.search_line_edit.text() + if current != "": + self.refresh_list(expand_all=True) + if len(current) < 3: + return + self.refresh_list() + + @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 + + @staticmethod + def _build_item_from_content(content: RssItem) -> QStandardItem: + 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) + 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_browser(content.url) + + 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()] + QMessageBox.warning( + self, + self.tr("Right clic"), + self.tr(f"You just right-clicked on content '{content.title}'"), + ) + # TODO: add actions when right-clic on an article item: open in browser, open in QWeb, open GitHub PR, contact author... diff --git a/qtribu/gui/dlg_contents.ui b/qtribu/gui/dlg_contents.ui new file mode 100644 index 00000000..0370f7ef --- /dev/null +++ b/qtribu/gui/dlg_contents.ui @@ -0,0 +1,230 @@ + + + geotribu_toolbox + + + + 0 + 0 + 609 + 717 + + + + + 0 + 0 + + + + Geotribu Contents + + + + + + 5 + + + 5 + + + 10 + + + 5 + + + 10 + + + + + + 0 + 0 + + + + Actions + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Submit Article + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Submit News + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Donate + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + Refresh list + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 3 + + + + + 0 + 5 + + + + Contents + + + + + + + + + 0 + 0 + + + + Search : + + + + + + + 255 + + + true + + + + + + + + + + 0 + 0 + + + + + 0 + 2 + + + + + 0 + 2 + + + + + + + true + + + false + + + false + + + + + + + + + + + + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+
+ + contents_groupbox + submit_news_button + contents_tree_view + + + +
diff --git a/qtribu/gui/geotribu_toolbox.py b/qtribu/gui/geotribu_toolbox.py deleted file mode 100644 index abd7d709..00000000 --- a/qtribu/gui/geotribu_toolbox.py +++ /dev/null @@ -1,137 +0,0 @@ -from pathlib import Path - -from PyQt5.QtCore import QModelIndex, Qt, QUrl -from PyQt5.QtGui import QDesktopServices, QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QAction, QListView, QMenu, QMessageBox - -from qgis.gui import QgsDockWidget -from qgis.PyQt import uic -from qgis.PyQt.QtWidgets import QWidget - -from qtribu.gui.form_rdp_news import RdpNewsForm -from qtribu.logic.rss_reader import RssArticlesMiniReader, RssRdpMiniReader -from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager - - -class GeotribuToolbox(QgsDockWidget): - - def __init__(self, parent: QWidget = None): - """ - QgsDockWidget for geotribu toolbox - - :param parent: parent widget or application - :type parent: QgsDockWidget - """ - super().__init__(parent) - self.log = PlgLogger().log - self.plg_settings = PlgOptionsManager() - uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) - - # articles lists and treeviews - self.articles_list_view = QListView() - self.articles_model = QStandardItemModel(self.articles_list_view) - self.articles_tree_view.setModel(self.articles_model) - self.articles_tree_view.doubleClicked.connect(self.on_article_double_clicked) - self.articles_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.articles_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) - - # RDP lists and treeviews - self.rdp_list_view = QListView() - self.rdp_model = QStandardItemModel(self.rdp_list_view) - self.rdp_tree_view.setModel(self.rdp_model) - self.rdp_tree_view.doubleClicked.connect(self.on_article_double_clicked) - self.rdp_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.rdp_tree_view.customContextMenuRequested.connect(self.on_open_article_context_menu) - - # buttons actions - self.form_rdp_news = None - self.submit_article_button.clicked.connect(self.submit_new_article) - self.refresh_articles_list_button.clicked.clicked(self.refresh_articles_list) - self.submit_news_button.clicked.connect(self.submit_news) - self.refresh_articles_list_button.clicked.clicked(self.refresh_rdp_list) - - self.refresh_articles_list() - self.refresh_rdp_list() - - # region articles - - def submit_new_article(self) -> None: - self.log("Opening form to submit a new article") - github_url = "https://github.com/geotribu/website/issues/new?assignees=aurelienchaumet%2Cguts%2Cigeofr&labels=contribution+externe%2Crdp%2Ctriage&projects=&template=RDP_NEWS.yml" - QDesktopServices.openUrl(QUrl(github_url)) - - def refresh_articles_list(self) -> None: - self.log("Refreshing articles list") - # clean article items - self.articles_model.clear() - # fetch last RSS items - qntwk = NetworkRequestsManager() - rss_reader = RssArticlesMiniReader() - rss_reader.read_feed(qntwk.get_from_source(headers=rss_reader.HEADERS)) - # create QStandardItems with RSS items and add them to treeview's model - articles = rss_reader.latest_items() - for article in articles: - article_item = QStandardItem(article.title) - article_item.setEditable(False) - self.articles_model.invisibleRootItem().appendRow(article_item) - - def on_article_double_clicked(self, index: QModelIndex) -> None: - self.log(f"Opening article at index {index}") - # TODO: open article in QWeb window - - def on_open_article_context_menu(self) -> None: - selected_index = next(i for i in self.articles_tree_view.selectedIndexes()) - self.log(f"Opening article at index {selected_index}") - # TODO: add actions when right-clic on an article item: open in browser, open in QWeb, open GitHub PR, contact author... - - # endregion - - # region GeoRDP - - def submit_news(self) -> None: - self.log("Opening form to submit a news") - if not self.form_rdp_news: - self.form_rdp_news = RdpNewsForm() - self.form_rdp_news.setModal(True) - self.form_rdp_news.finished.connect(self._post_form_rdp_news) - self.form_rdp_news.show() - - def on_form_rdp_news_finished(self, dialog_result: int) -> None: - """Perform actions after the GeoRDP news form has been closed. - - :param dialog_result: dialog's result code. Accepted (1) or Rejected (0) - :type dialog_result: int - """ - if self.form_rdp_news: - # if accept button, save user inputs - if dialog_result == 1: - self.form_rdp_news.wdg_author.save_settings() - # clean up - self.form_rdp_news.deleteLater() - self.form_rdp_news = None - - def refresh_rdp_list(self) -> None: - self.log("Refreshing RDP list") - # clean articles_tree_view QTreeView items - self.rdp_model.clear() - # fetch last RSS items - qntwk = NetworkRequestsManager() - rss_reader = RssRdpMiniReader() - rss_reader.read_feed(qntwk.get_from_source(headers=rss_reader.HEADERS)) - # create QStandardItems with RSS items - rdps = rss_reader.latest_items() - for rdp in rdps: - rdp_item = QStandardItem(rdp.title) - rdp_item.setEditable(False) - self.rdp_model.invisibleRootItem().appendRow(rdp_item) - - def on_rdp_double_clicked(self, index: QModelIndex) -> None: - self.log(f"Opening RDP at index {index}") - # TODO: open RDP in QWeb window - - def on_open_rdp_context_menu(self) -> None: - selected_index = next(i for i in self.rdp_tree_view.selectedIndexes()) - self.log(f"Opening RDP at index {selected_index}") - # TODO: add actions when right-clic on a RDP item: open in browser, open in QWeb, open GitHub PR, contact authors... - - # endregion diff --git a/qtribu/gui/geotribu_toolbox.ui b/qtribu/gui/geotribu_toolbox.ui deleted file mode 100644 index a5c505b8..00000000 --- a/qtribu/gui/geotribu_toolbox.ui +++ /dev/null @@ -1,250 +0,0 @@ - - - geotribu_toolbox - - - - 0 - 0 - 441 - 887 - - - - - 0 - 0 - - - - Geotribu Toolbox - - - - - 0 - 0 - - - - - 5 - - - 5 - - - 10 - - - 5 - - - 10 - - - - - - 0 - 0 - - - - - 0 - 1 - - - - - 0 - 1 - - - - Articles - - - false - - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - Submit Article - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - Refresh list - - - - - - - - - - 0 - 0 - - - - - 0 - 1 - - - - - 0 - 1 - - - - true - - - - - - - - - - - 0 - 0 - - - - - 0 - 3 - - - - - 0 - 5 - - - - GeoRDP - - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - Submit News - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - Refresh list - - - - - - - - - - 0 - 0 - - - - - 0 - 2 - - - - - 0 - 2 - - - - - - - true - - - - - - - - - - - - QgsCollapsibleGroupBox - QGroupBox -
qgscollapsiblegroupbox.h
- 1 -
-
- - article_groupbox - submit_article_button - refresh_articles_list_button - articles_tree_view - geordp_groupbox - submit_news_button - refresh_rdp_list_button - rdp_tree_view - - - -
From 76bdb3f1cc0966a41a0189ee3da1ed580658aa32 Mon Sep 17 00:00:00 2001 From: gounux Date: Thu, 14 Mar 2024 06:48:57 +0100 Subject: [PATCH 07/30] Add plugin actions to open contents dialog --- qtribu/plugin_main.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index 85646e03..de231ef8 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -17,6 +17,7 @@ # project from qtribu.__about__ import DIR_PLUGIN_ROOT, __icon_path__, __title__, __uri_homepage__ +from qtribu.gui.dlg_contents import GeotribuContentsDialog from qtribu.gui.dlg_settings import PlgOptionsFactory from qtribu.gui.form_rdp_news import RdpNewsForm from qtribu.logic import RssMiniReader, SplashChanger, WebViewer @@ -65,6 +66,7 @@ def initGui(self): # -- Forms self.form_rdp_news = None + self.form_contents = None # -- Actions self.action_run = QAction( @@ -75,6 +77,14 @@ def initGui(self): self.action_run.setToolTip(self.tr("Newest article")) self.action_run.triggered.connect(self.run) + self.action_contents = QAction( + QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_orange_no_text.svg")), + self.tr("Contents"), + self.iface.mainWindow(), + ) + self.action_contents.setToolTip(self.tr("Contents")) + self.action_contents.triggered.connect(self.contents) + self.action_rdp_news = QAction( QIcon(QgsApplication.iconPath("mActionHighlightFeature.svg")), self.tr("Propose a news to the next GeoRDP"), @@ -105,6 +115,7 @@ def initGui(self): # -- Menu self.iface.addPluginToWebMenu(__title__, self.action_run) + self.iface.addPluginToWebMenu(__title__, self.action_contents) self.iface.addPluginToWebMenu(__title__, self.action_rdp_news) self.iface.addPluginToWebMenu(__title__, self.action_splash) self.iface.addPluginToWebMenu(__title__, self.action_settings) @@ -149,6 +160,7 @@ def initGui(self): # -- Toolbar self.iface.addToolBarIcon(self.action_run) + self.iface.addToolBarIcon(self.action_contents) self.iface.addToolBarIcon(self.action_rdp_news) # -- Post UI initialization @@ -160,6 +172,7 @@ def unload(self): self.iface.removePluginWebMenu(__title__, self.action_help) self.iface.removePluginWebMenu(__title__, self.action_rdp_news) self.iface.removePluginWebMenu(__title__, self.action_run) + self.iface.removePluginWebMenu(__title__, self.action_contents) self.iface.removePluginWebMenu(__title__, self.action_settings) self.iface.removePluginWebMenu(__title__, self.action_splash) @@ -169,6 +182,7 @@ def unload(self): # -- Clean up toolbar self.iface.removeToolBarIcon(self.action_run) + self.iface.removeToolBarIcon(self.action_contents) self.iface.removeToolBarIcon(self.action_rdp_news) # -- Clean up preferences panel in QGIS settings @@ -273,6 +287,13 @@ def run(self): except Exception as err: raise err + def contents(self): + """Action to open contents dialog""" + if not self.form_contents: + self.form_contents = GeotribuContentsDialog() + self.form_contents.setModal(True) + self.form_contents.show() + def open_form_rdp_news(self) -> None: """Open the form to create a GeoRDP news.""" if not self.form_rdp_news: From 60029e8a56718e0aaf58102f47b1ee1b59f3929c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:50:06 +0000 Subject: [PATCH 08/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtribu/gui/dlg_contents.py | 2 +- qtribu/logic/json_feed.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 5514847b..bb1bb83e 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List, Dict +from typing import Dict, List from PyQt5.QtCore import QModelIndex, Qt, QUrl from PyQt5.QtGui import QDesktopServices, QIcon, QStandardItem, QStandardItemModel diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py index 888bd785..fe7e3fc8 100644 --- a/qtribu/logic/json_feed.py +++ b/qtribu/logic/json_feed.py @@ -1,13 +1,13 @@ from datetime import datetime -from typing import List, Any, Dict, Optional - -from qtribu.__about__ import __title__, __version__ -from qtribu.logic import RssItem -from qtribu.toolbelt import PlgOptionsManager, PlgLogger +from typing import Any, Dict, List, Optional import requests from requests import Response +from qtribu.__about__ import __title__, __version__ +from qtribu.logic import RssItem +from qtribu.toolbelt import PlgLogger, PlgOptionsManager + HEADERS: dict = { b"Accept": b"application/json", b"User-Agent": bytes(f"{__title__}/{__version__}", "utf8"), From ea3bde4a30c7f658cfd45a2bf124595cbe8160e7 Mon Sep 17 00:00:00 2001 From: gounux Date: Thu, 14 Mar 2024 07:01:21 +0100 Subject: [PATCH 09/30] Fix illogic stuff --- qtribu/gui/dlg_contents.py | 3 ++- qtribu/logic/json_feed.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index bb1bb83e..9b2a924a 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -105,8 +105,9 @@ def refresh_list(self, expand_all: bool = False) -> None: def on_search_text_changed(self) -> None: # do nothing if text is too small current = self.search_line_edit.text() - if current != "": + if current == "": self.refresh_list(expand_all=True) + return if len(current) < 3: return self.refresh_list() diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py index fe7e3fc8..338b0038 100644 --- a/qtribu/logic/json_feed.py +++ b/qtribu/logic/json_feed.py @@ -57,6 +57,9 @@ def _map_item(item: Dict[str, Any]) -> RssItem: @staticmethod def _matches(query: str, item: RssItem) -> bool: """Moteur de recherche du turfu""" + words = query.split(" ") + if len(words) > 1: + return all([JsonFeedClient._matches(w, item) for w in words]) return ( query.upper() in item.abstract.upper() or query.upper() in ",".join(item.author).upper() From 0e0e3cf9338c20e56a34163b5d0863e7e43e3bef Mon Sep 17 00:00:00 2001 From: gounux Date: Mon, 18 Mar 2024 09:54:46 +0100 Subject: [PATCH 10/30] Add context menu on content right clic --- qtribu/gui/dlg_contents.py | 44 ++++++++++++++++++++++++++++---------- qtribu/gui/dlg_contents.ui | 4 ++-- qtribu/logic/web_viewer.py | 3 +++ qtribu/plugin_main.py | 6 +++++- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 9b2a924a..f70dc622 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,15 +1,20 @@ from pathlib import Path from typing import Dict, List -from PyQt5.QtCore import QModelIndex, Qt, QUrl -from PyQt5.QtGui import QDesktopServices, QIcon, QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QListView, QMessageBox from qgis.PyQt import uic -from qgis.PyQt.QtWidgets import QDialog, QWidget +from qgis.PyQt.QtCore import QModelIndex, Qt, QUrl +from qgis.PyQt.QtGui import ( + QCursor, + QDesktopServices, + QIcon, + QStandardItem, + QStandardItemModel, +) +from qgis.PyQt.QtWidgets import QAction, QDialog, QListView, QMenu, QWidget from qtribu.__about__ import DIR_PLUGIN_ROOT from qtribu.gui.form_rdp_news import RdpNewsForm -from qtribu.logic import RssItem +from qtribu.logic import RssItem, WebViewer from qtribu.logic.json_feed import JsonFeedClient from qtribu.toolbelt import PlgLogger, PlgOptionsManager @@ -28,6 +33,7 @@ def __init__(self, parent: QWidget = None): self.log = PlgLogger().log self.plg_settings = PlgOptionsManager() self.json_feed_client = JsonFeedClient() + self.web_viewer = WebViewer() uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) # buttons actions @@ -56,6 +62,10 @@ def __init__(self, parent: QWidget = None): def _open_url_in_browser(url: str) -> None: QDesktopServices.openUrl(QUrl(url)) + 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: self._open_url_in_browser( "https://github.com/geotribu/website/issues/new?labels=contribution+externe%2Carticle%2Ctriage&projects=&template=ARTICLE.yml" @@ -145,7 +155,7 @@ def on_content_double_clicked(self, index: QModelIndex) -> None: return year = list(self.contents)[index.parent().row()] content = self.contents[year][index.row()] - self._open_url_in_browser(content.url) + 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()) @@ -154,9 +164,21 @@ def on_open_content_context_menu(self) -> None: return year = list(self.contents)[selected_index.parent().row()] content = self.contents[year][selected_index.row()] - QMessageBox.warning( - self, - self.tr("Right clic"), - self.tr(f"You just right-clicked on content '{content.title}'"), + + 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) ) - # TODO: add actions when right-clic on an article item: open in browser, open in QWeb, open GitHub PR, contact author... + 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 0370f7ef..b28734ec 100644 --- a/qtribu/gui/dlg_contents.ui +++ b/qtribu/gui/dlg_contents.ui @@ -6,8 +6,8 @@ 0 0 - 609 - 717 + 1080 + 640 diff --git a/qtribu/logic/web_viewer.py b/qtribu/logic/web_viewer.py index 29f8bd7d..5acb9023 100644 --- a/qtribu/logic/web_viewer.py +++ b/qtribu/logic/web_viewer.py @@ -85,6 +85,9 @@ def display_web_page(self, url: str): push=True, ) + def set_window_title(self, title: str) -> None: + self.wdg_web.setWindowTitle(title) + def tr(self, message: str) -> str: """Translation method. diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index de231ef8..c0117754 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -285,13 +285,17 @@ def run(self): key="latest_content_guid", value=self.rss_rdr.latest_item.guid ) except Exception as err: + self.log( + message=self.tr(f"Michel, we've got a problem: {err}"), + log_level=2, + push=True, + ) raise err def contents(self): """Action to open contents dialog""" if not self.form_contents: self.form_contents = GeotribuContentsDialog() - self.form_contents.setModal(True) self.form_contents.show() def open_form_rdp_news(self) -> None: From 14c7d565474817adde7916cb5fbb003b82400df6 Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:07:06 +0200 Subject: [PATCH 11/30] Refactor url in browser opening --- qtribu/gui/dlg_contents.py | 17 ++++------------- qtribu/gui/dlg_settings.py | 10 ++++------ qtribu/gui/form_rdp_news.py | 8 ++++---- qtribu/logic/web_viewer.py | 5 +++-- qtribu/plugin_main.py | 16 +++++++--------- qtribu/toolbelt/commons.py | 5 +++++ 6 files changed, 27 insertions(+), 34 deletions(-) create mode 100644 qtribu/toolbelt/commons.py diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index f70dc622..a8d07421 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -2,14 +2,8 @@ from typing import Dict, List from qgis.PyQt import uic -from qgis.PyQt.QtCore import QModelIndex, Qt, QUrl -from qgis.PyQt.QtGui import ( - QCursor, - QDesktopServices, - QIcon, - QStandardItem, - QStandardItemModel, -) +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 qtribu.__about__ import DIR_PLUGIN_ROOT @@ -17,6 +11,7 @@ from qtribu.logic import RssItem, WebViewer from qtribu.logic.json_feed import JsonFeedClient from qtribu.toolbelt import PlgLogger, PlgOptionsManager +from qtribu.toolbelt.commons import open_url_in_browser class GeotribuContentsDialog(QDialog): @@ -58,16 +53,12 @@ def __init__(self, parent: QWidget = None): self.refresh_list(expand_all=True) - @staticmethod - def _open_url_in_browser(url: str) -> None: - QDesktopServices.openUrl(QUrl(url)) - 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: - self._open_url_in_browser( + open_url_in_browser( "https://github.com/geotribu/website/issues/new?labels=contribution+externe%2Carticle%2Ctriage&projects=&template=ARTICLE.yml" ) diff --git a/qtribu/gui/dlg_settings.py b/qtribu/gui/dlg_settings.py index d5ba3ed1..b3df9b60 100644 --- a/qtribu/gui/dlg_settings.py +++ b/qtribu/gui/dlg_settings.py @@ -12,8 +12,7 @@ from qgis.core import QgsApplication from qgis.gui import QgsOptionsPageWidget, QgsOptionsWidgetFactory from qgis.PyQt import uic -from qgis.PyQt.Qt import QUrl -from qgis.PyQt.QtGui import QDesktopServices, QIcon +from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QButtonGroup # project @@ -25,6 +24,7 @@ __version__, ) from qtribu.toolbelt import PlgLogger, PlgOptionsManager +from qtribu.toolbelt.commons import open_url_in_browser from qtribu.toolbelt.preferences import PlgSettingsStructure # ############################################################################ @@ -61,15 +61,13 @@ def __init__(self, parent=None): # customization self.btn_help.setIcon(QIcon(QgsApplication.iconPath("mActionHelpContents.svg"))) - self.btn_help.pressed.connect( - partial(QDesktopServices.openUrl, QUrl(__uri_homepage__)) - ) + self.btn_help.pressed.connect(partial(open_url_in_browser(__uri_homepage__))) self.btn_report.setIcon( QIcon(QgsApplication.iconPath("console/iconSyntaxErrorConsole.svg")) ) self.btn_report.pressed.connect( - partial(QDesktopServices.openUrl, QUrl(f"{__uri_tracker__}new/choose")) + partial(open_url_in_browser(f"{__uri_tracker__}new/choose")) ) self.btn_reset_read_history.setIcon( diff --git a/qtribu/gui/form_rdp_news.py b/qtribu/gui/form_rdp_news.py index 7ac35d96..7828347c 100644 --- a/qtribu/gui/form_rdp_news.py +++ b/qtribu/gui/form_rdp_news.py @@ -14,14 +14,15 @@ # PyQGIS from qgis.core import QgsApplication from qgis.PyQt import uic -from qgis.PyQt.QtCore import Qt, QUrl -from qgis.PyQt.QtGui import QDesktopServices, QIcon +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QDialog # plugin from qtribu.__about__ import DIR_PLUGIN_ROOT from qtribu.constants import GEORDP_NEWS_CATEGORIES, GEORDP_NEWS_ICONS, GeotribuImage from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager +from qtribu.toolbelt.commons import open_url_in_browser class RdpNewsForm(QDialog): @@ -77,8 +78,7 @@ def __init__(self, parent=None): # connect help button self.btn_box.helpRequested.connect( partial( - QDesktopServices.openUrl, - QUrl("https://contribuer.geotribu.fr/rdp/add_news/"), + open_url_in_browser("https://contribuer.geotribu.fr/rdp/add_news/"), ) ) diff --git a/qtribu/logic/web_viewer.py b/qtribu/logic/web_viewer.py index 5acb9023..3ff01194 100644 --- a/qtribu/logic/web_viewer.py +++ b/qtribu/logic/web_viewer.py @@ -14,7 +14,8 @@ # PyQGIS from qgis.PyQt.QtCore import QCoreApplication, Qt -from qgis.PyQt.QtGui import QDesktopServices + +from qtribu.toolbelt.commons import open_url_in_browser try: from qgis.PyQt.QtWebKitWidgets import QWebView @@ -71,7 +72,7 @@ def display_web_page(self, url: str): self.wdg_web.resize(1000, 600) else: - QDesktopServices.openUrl(qntwk.build_url(url)) + open_url_in_browser(qntwk.build_url(url)) self.log( message=self.tr("Last article from Geotribu loaded and displayed."), diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index d424748a..e41fe390 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -11,8 +11,8 @@ # PyQGIS from qgis.core import Qgis, QgsApplication, QgsSettings from qgis.gui import QgisInterface -from qgis.PyQt.QtCore import QCoreApplication, QLocale, QTranslator, QUrl -from qgis.PyQt.QtGui import QDesktopServices, QIcon +from qgis.PyQt.QtCore import QCoreApplication, QLocale, QTranslator +from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QAction # project @@ -22,6 +22,7 @@ from qtribu.gui.form_rdp_news import RdpNewsForm from qtribu.logic import RssMiniReader, SplashChanger, WebViewer from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager +from qtribu.toolbelt.commons import open_url_in_browser # ############################################################################ # ########## Classes ############### @@ -111,7 +112,7 @@ def initGui(self): self.iface.mainWindow(), ) self.action_help.triggered.connect( - partial(QDesktopServices.openUrl, QUrl(__uri_homepage__)) + partial(open_url_in_browser(__uri_homepage__)) ) self.action_settings = QAction( @@ -142,8 +143,7 @@ def initGui(self): ) self.action_geotribu.triggered.connect( partial( - QDesktopServices.openUrl, - QUrl("https://geotribu.fr"), + open_url_in_browser("https://geotribu.fr"), ) ) @@ -153,8 +153,7 @@ def initGui(self): ) self.action_georezo.triggered.connect( partial( - QDesktopServices.openUrl, - QUrl("https://georezo.net/forum/viewforum.php?id=55"), + open_url_in_browser("https://georezo.net/forum/viewforum.php?id=55"), ) ) self.action_osgeofr = QAction( @@ -163,8 +162,7 @@ def initGui(self): ) self.action_osgeofr.triggered.connect( partial( - QDesktopServices.openUrl, - QUrl("https://www.osgeo.fr/"), + open_url_in_browser("https://www.osgeo.fr/"), ) ) self.iface.helpMenu().addAction(self.action_georezo) diff --git a/qtribu/toolbelt/commons.py b/qtribu/toolbelt/commons.py new file mode 100644 index 00000000..7bb3e579 --- /dev/null +++ b/qtribu/toolbelt/commons.py @@ -0,0 +1,5 @@ +from qgis.PyQt.QtCore import QDesktopServices, QUrl + + +def open_url_in_browser(url: str) -> bool: + return QDesktopServices.openUrl(QUrl(url)) From 78e677111fe1324c66477f9c60c23eeb856a01c8 Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:12:15 +0200 Subject: [PATCH 12/30] Fix QDesktopServices imports --- qtribu/toolbelt/commons.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qtribu/toolbelt/commons.py b/qtribu/toolbelt/commons.py index 7bb3e579..20f54437 100644 --- a/qtribu/toolbelt/commons.py +++ b/qtribu/toolbelt/commons.py @@ -1,4 +1,5 @@ -from qgis.PyQt.QtCore import QDesktopServices, QUrl +from qgis.PyQt.QtCore import QUrl +from qgis.PyQt.QtGui import QDesktopServices def open_url_in_browser(url: str) -> bool: From 5e5dd90ef0840112b466eafd61b947f8f2e90c9d Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:16:28 +0200 Subject: [PATCH 13/30] Add docstring --- qtribu/toolbelt/commons.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qtribu/toolbelt/commons.py b/qtribu/toolbelt/commons.py index 20f54437..1bc4f262 100644 --- a/qtribu/toolbelt/commons.py +++ b/qtribu/toolbelt/commons.py @@ -3,4 +3,12 @@ def open_url_in_browser(url: str) -> bool: + """Opens an url in a browser using user's desktop environment + + :param url: url to open + :type url: str + + :return: true if successful otherwise false + :rtype: bool + """ return QDesktopServices.openUrl(QUrl(url)) From ae8f425e78bd6935f87089d54373d841ec097e4b Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:26:09 +0200 Subject: [PATCH 14/30] Fix open url calls --- qtribu/gui/dlg_contents.py | 2 +- qtribu/gui/dlg_settings.py | 4 ++-- qtribu/gui/form_rdp_news.py | 3 ++- qtribu/plugin_main.py | 11 +++++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index a8d07421..9a15583c 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -71,7 +71,7 @@ def submit_news(self) -> None: self.form_rdp_news.show() def donate(self) -> None: - self._open_url_in_browser("https://fr.tipeee.com/geotribu") + open_url_in_browser("https://geotribu.fr/team/sponsoring/") def refresh_list(self, expand_all: bool = False) -> None: # fetch last RSS items using JSONFeed diff --git a/qtribu/gui/dlg_settings.py b/qtribu/gui/dlg_settings.py index b3df9b60..be572971 100644 --- a/qtribu/gui/dlg_settings.py +++ b/qtribu/gui/dlg_settings.py @@ -61,13 +61,13 @@ def __init__(self, parent=None): # customization self.btn_help.setIcon(QIcon(QgsApplication.iconPath("mActionHelpContents.svg"))) - self.btn_help.pressed.connect(partial(open_url_in_browser(__uri_homepage__))) + self.btn_help.pressed.connect(partial(open_url_in_browser, __uri_homepage__)) self.btn_report.setIcon( QIcon(QgsApplication.iconPath("console/iconSyntaxErrorConsole.svg")) ) self.btn_report.pressed.connect( - partial(open_url_in_browser(f"{__uri_tracker__}new/choose")) + partial(open_url_in_browser, f"{__uri_tracker__}new/choose") ) self.btn_reset_read_history.setIcon( diff --git a/qtribu/gui/form_rdp_news.py b/qtribu/gui/form_rdp_news.py index 7828347c..a38f8e0d 100644 --- a/qtribu/gui/form_rdp_news.py +++ b/qtribu/gui/form_rdp_news.py @@ -78,7 +78,8 @@ def __init__(self, parent=None): # connect help button self.btn_box.helpRequested.connect( partial( - open_url_in_browser("https://contribuer.geotribu.fr/rdp/add_news/"), + open_url_in_browser, + "https://contribuer.geotribu.fr/rdp/add_news/", ) ) diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index e41fe390..e415a1c2 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -112,7 +112,7 @@ def initGui(self): self.iface.mainWindow(), ) self.action_help.triggered.connect( - partial(open_url_in_browser(__uri_homepage__)) + partial(open_url_in_browser, __uri_homepage__) ) self.action_settings = QAction( @@ -143,7 +143,8 @@ def initGui(self): ) self.action_geotribu.triggered.connect( partial( - open_url_in_browser("https://geotribu.fr"), + open_url_in_browser, + "https://geotribu.fr", ) ) @@ -153,7 +154,8 @@ def initGui(self): ) self.action_georezo.triggered.connect( partial( - open_url_in_browser("https://georezo.net/forum/viewforum.php?id=55"), + open_url_in_browser, + "https://georezo.net/forum/viewforum.php?id=55", ) ) self.action_osgeofr = QAction( @@ -162,7 +164,8 @@ def initGui(self): ) self.action_osgeofr.triggered.connect( partial( - open_url_in_browser("https://www.osgeo.fr/"), + open_url_in_browser, + "https://www.osgeo.fr/", ) ) self.iface.helpMenu().addAction(self.action_georezo) From 912c16ea898e34e0a98e28f2c0bd20e2af7bc5da Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:27:54 +0200 Subject: [PATCH 15/30] Delete empty file --- qtribu/gui/geotribu_toolbox.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 qtribu/gui/geotribu_toolbox.py diff --git a/qtribu/gui/geotribu_toolbox.py b/qtribu/gui/geotribu_toolbox.py deleted file mode 100644 index e69de29b..00000000 From d80ed292192072eeb1e2425f673c68926580a2d1 Mon Sep 17 00:00:00 2001 From: gounux Date: Sat, 13 Apr 2024 20:33:03 +0200 Subject: [PATCH 16/30] Fix post rdp form connect --- qtribu/gui/dlg_contents.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 9a15583c..e8212476 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -70,6 +70,20 @@ def submit_news(self) -> None: self.form_rdp_news.finished.connect(self._post_form_rdp_news) self.form_rdp_news.show() + def _post_form_rdp_news(self, dialog_result: int) -> None: + """Perform actions after the GeoRDP news form has been closed. + + :param dialog_result: dialog's result code. Accepted (1) or Rejected (0) + :type dialog_result: int + """ + if self.form_rdp_news: + # if accept button, save user inputs + if dialog_result == 1: + self.form_rdp_news.wdg_author.save_settings() + # clean up + self.form_rdp_news.deleteLater() + self.form_rdp_news = None + def donate(self) -> None: open_url_in_browser("https://geotribu.fr/team/sponsoring/") From c812fe9234ab6c0039cfe7180261439e257aa845 Mon Sep 17 00:00:00 2001 From: gounux Date: Mon, 15 Apr 2024 15:50:24 +0200 Subject: [PATCH 17/30] 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")], From ced5979eaa5615c8e4a69e49b2b34b8c4d9087e3 Mon Sep 17 00:00:00 2001 From: gounux Date: Mon, 15 Apr 2024 16:03:11 +0200 Subject: [PATCH 18/30] Add docstrings --- qtribu/gui/dlg_contents.py | 20 +++++++++++++++++++- qtribu/logic/json_feed.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 8ba5b898..45b66621 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -104,6 +104,12 @@ def donate(self) -> None: open_url_in_browser("https://geotribu.fr/team/sponsoring/") def refresh_list(self, query_action: Callable[[], str]) -> None: + """ + Refresh content list as well as treewidget that list all contents + + :param query_action: action to call for potentially filtering contents + :type query_action: Callable[[], str] + """ # fetch last RSS items using JSONFeed rss_contents = self.json_feed_client.fetch(query=query_action()) years = sorted(set([c.date_pub.year for c in rss_contents]), reverse=True) @@ -129,6 +135,15 @@ def refresh_list(self, query_action: Callable[[], str]) -> None: @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int) def on_tree_view_item_click(self, item: QTreeWidgetItem, column: int): + """ + Method called when a content item is clicked + + :param item: item that is clicked by user + :type item: QTreeWidgetItem + :param column: column that is clicked by user + :type column: int + """ + # TODO print(item, column, item.text(column)) def on_search_text_changed(self) -> None: @@ -151,7 +166,10 @@ def on_category_changed(self, value: str) -> None: @staticmethod def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: """ - Builds a QTreeWidgetItem from a RSS content + Builds a QTreeWidgetItem from a RSS content item + + :param content: content to generate item for + :type content: RssItem """ item = QTreeWidgetItem( [ diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py index 29267ce3..772e5e75 100644 --- a/qtribu/logic/json_feed.py +++ b/qtribu/logic/json_feed.py @@ -17,6 +17,10 @@ class JsonFeedClient: + """ + Class representing a Geotribu's JSON feed client + """ + items: Optional[List[RssItem]] = None last_fetch_date: Optional[datetime] = None @@ -28,6 +32,14 @@ def __init__( self.url = url def fetch(self, query: str = "") -> list[RssItem]: + """ + Fetch RSS feed items using JSON Feed + + :param query: filter to look for items matching this query + :type query: str + + :return: list of RssItem objects matching the query filter + """ if not self.items or ( self.last_fetch_date and (datetime.now() - self.last_fetch_date).total_seconds() @@ -40,6 +52,11 @@ def fetch(self, query: str = "") -> list[RssItem]: return [i for i in self.items if self._matches(query, i)] def categories(self) -> list[str]: + """ + Get a list of all categories available in the RSS feed + + :return: list of categories available in the RSS feed + """ tags = [] for content in self.fetch(): tags.extend([c.lower() for c in content.categories]) @@ -47,6 +64,14 @@ def categories(self) -> list[str]: @staticmethod def _map_item(item: dict[str, Any]) -> RssItem: + """ + Map raw JSON object coming from JSON feed to an RssItem object + + :param item: raw JSON object + :type item: dict[str, Any] + + :return: RssItem + """ return RssItem( abstract=item.get("content_html"), author=[i["name"] for i in item.get("authors")], @@ -62,7 +87,16 @@ def _map_item(item: dict[str, Any]) -> RssItem: @staticmethod def _matches(query: str, item: RssItem) -> bool: - """Moteur de recherche du turfu""" + """ + Check if item matches given query + + :param query: filter to look for items matching this query + :type query: str + :param item: RssItem to check + :type item: RssItem + + :return: True if item matches given query, False if not + """ words = query.split(" ") if len(words) > 1: return all([JsonFeedClient._matches(w, item) for w in words]) From 423ef43eb3e94097194c9b266c83df6e8cda6a0e Mon Sep 17 00:00:00 2001 From: gounux Date: Mon, 15 Apr 2024 17:03:58 +0200 Subject: [PATCH 19/30] Edit icons --- qtribu/gui/dlg_contents.py | 16 ++++++++++++++++ qtribu/gui/dlg_contents.ui | 9 +++++++++ qtribu/plugin_main.py | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 45b66621..d79bc2fb 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Callable, Dict, List +from qgis.core import QgsApplication from qgis.PyQt import QtCore, QtWidgets, uic from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem, QWidget @@ -30,15 +31,30 @@ def __init__(self, parent: QWidget = None): self.json_feed_client = JsonFeedClient() self.web_viewer = WebViewer() uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) + self.setWindowIcon( + QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_green_no_text.svg")) + ) # buttons actions self.form_rdp_news = None self.submit_article_button.clicked.connect(self.submit_article) + self.submit_article_button.setIcon( + QgsApplication.getThemeIcon("mActionEditTable.svg") + ) self.submit_news_button.clicked.connect(self.submit_news) + self.submit_news_button.setIcon( + QgsApplication.getThemeIcon("mActionAllEdits.svg") + ) self.donate_button.clicked.connect(self.donate) + self.donate_button.setIcon( + QgsApplication.getThemeIcon("mActionAddAllToOverview.svg") + ) self.refresh_list_button.clicked.connect( partial(self.refresh_list, lambda: self.search_line_edit.text()) ) + self.refresh_list_button.setIcon( + QgsApplication.getThemeIcon("mActionHistory.svg") + ) # search actions self.search_line_edit.textChanged.connect(self.on_search_text_changed) diff --git a/qtribu/gui/dlg_contents.ui b/qtribu/gui/dlg_contents.ui index dcb26a43..e987eb73 100644 --- a/qtribu/gui/dlg_contents.ui +++ b/qtribu/gui/dlg_contents.ui @@ -183,6 +183,12 @@ + + true + + + true + 4 @@ -192,6 +198,9 @@ true + + 64 + true diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index e415a1c2..db9af321 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -92,7 +92,7 @@ def initGui(self): self.action_run.triggered.connect(self.run) self.action_contents = QAction( - QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_orange_no_text.svg")), + QgsApplication.getThemeIcon("mActionConditionalFormatting.svg"), self.tr("Contents"), self.iface.mainWindow(), ) From 9533caa23aa4c45fae097aa22bde474c47255425 Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 11:00:50 +0200 Subject: [PATCH 20/30] Change toolbar icon --- qtribu/plugin_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index db9af321..e415a1c2 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -92,7 +92,7 @@ def initGui(self): self.action_run.triggered.connect(self.run) self.action_contents = QAction( - QgsApplication.getThemeIcon("mActionConditionalFormatting.svg"), + QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_orange_no_text.svg")), self.tr("Contents"), self.iface.mainWindow(), ) From 51d605d34f651d7acbfe7e726c51c26ab9d444c2 Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 11:04:44 +0200 Subject: [PATCH 21/30] Set tooltip on all columns --- qtribu/gui/dlg_contents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index d79bc2fb..26b2d2da 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -195,7 +195,8 @@ def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: ",".join(content.categories), ] ) - item.setToolTip(1, content.abstract) + for i in range(4): + item.setToolTip(i, content.abstract) icon_file = ( "logo_orange_no_text" if "Revue de presse" in content.title From 4d98baaec7e97a1a8b769a5243c6df6f39b2671c Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 11:18:05 +0200 Subject: [PATCH 22/30] Add authors combobox --- qtribu/gui/dlg_contents.py | 8 ++++++++ qtribu/gui/dlg_contents.ui | 10 ++++++++++ qtribu/logic/json_feed.py | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 26b2d2da..979eecc6 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -59,6 +59,11 @@ def __init__(self, parent: QWidget = None): # search actions self.search_line_edit.textChanged.connect(self.on_search_text_changed) + # authors combobox + for author in self.json_feed_client.authors(): + self.authors_combobox.addItem(author) + self.authors_combobox.currentTextChanged.connect(self.on_author_changed) + # categories combobox for cat in self.json_feed_client.categories(): self.categories_combobox.addItem(cat) @@ -176,6 +181,9 @@ def on_search_text_changed(self) -> None: return self.refresh_list(lambda: current) + def on_author_changed(self, value: str) -> None: + self.refresh_list(lambda: value) + def on_category_changed(self, value: str) -> None: self.refresh_list(lambda: value) diff --git a/qtribu/gui/dlg_contents.ui b/qtribu/gui/dlg_contents.ui index e987eb73..72fd942e 100644 --- a/qtribu/gui/dlg_contents.ui +++ b/qtribu/gui/dlg_contents.ui @@ -169,6 +169,16 @@ + + + + Filter by author : + + + + + + diff --git a/qtribu/logic/json_feed.py b/qtribu/logic/json_feed.py index 772e5e75..a49aba9a 100644 --- a/qtribu/logic/json_feed.py +++ b/qtribu/logic/json_feed.py @@ -51,6 +51,18 @@ 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 authors(self) -> list[str]: + """ + Get a list of authors available in the RSS feed + + :return: list of authors + """ + authors = [] + for content in self.fetch(): + for ca in content.author: + authors.append(" ".join([a.title() for a in ca.split(" ")])) + return sorted(set(authors)) + def categories(self) -> list[str]: """ Get a list of all categories available in the RSS feed From 802977b6ff3a157dbce1c83bae6ad70e2e751e9a Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 11:38:21 +0200 Subject: [PATCH 23/30] Add marker value --- qtribu/gui/dlg_contents.py | 40 ++++++++++++++++++++++++++++---------- qtribu/gui/dlg_contents.ui | 22 ++++++--------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 979eecc6..5e84cf1e 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -14,6 +14,8 @@ from qtribu.toolbelt import PlgLogger, PlgOptionsManager from qtribu.toolbelt.commons import open_url_in_browser +MARKER_VALUE = "---" + class GeotribuContentsDialog(QDialog): contents: Dict[int, List[RssItem]] = {} @@ -49,29 +51,25 @@ def __init__(self, parent: QWidget = None): self.donate_button.setIcon( QgsApplication.getThemeIcon("mActionAddAllToOverview.svg") ) - self.refresh_list_button.clicked.connect( - partial(self.refresh_list, lambda: self.search_line_edit.text()) - ) - self.refresh_list_button.setIcon( - QgsApplication.getThemeIcon("mActionHistory.svg") - ) # search actions self.search_line_edit.textChanged.connect(self.on_search_text_changed) # authors combobox + self.authors_combobox.addItem(MARKER_VALUE) for author in self.json_feed_client.authors(): self.authors_combobox.addItem(author) self.authors_combobox.currentTextChanged.connect(self.on_author_changed) # categories combobox + self.categories_combobox.addItem(MARKER_VALUE) 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 + # tree widget initialization self.contents_tree_widget.setHeaderLabels( - ["Date", "Title", "Author(s)", "Categories"] + [self.tr("Date"), self.tr("Title"), self.tr("Author(s)"), self.tr("Categories")] ) self.contents_tree_widget.itemClicked.connect(self.on_tree_view_item_click) @@ -164,8 +162,9 @@ def on_tree_view_item_click(self, item: QTreeWidgetItem, column: int): :param column: column that is clicked by user :type column: int """ - # TODO - print(item, column, item.text(column)) + # open URL of content (in column at index 4 which is not displayed) + url, title = item.text(4), item.text(1) + self._open_url_in_webviewer(url, title) def on_search_text_changed(self) -> None: """ @@ -182,9 +181,29 @@ def on_search_text_changed(self) -> None: self.refresh_list(lambda: current) def on_author_changed(self, value: str) -> None: + """ + Function triggered when author combobox is changed + + :param value: text value of the selected author + :type value: str + """ + self.search_line_edit.setText("") + if value == MARKER_VALUE: + self.refresh_list(lambda: self.search_line_edit.text()) + return self.refresh_list(lambda: value) def on_category_changed(self, value: str) -> None: + """ + Function triggered when category/tag combobox is changed + + :param value: text value of the selected category + :type value: str + """ + self.search_line_edit.setText("") + if value == MARKER_VALUE: + self.refresh_list(lambda: self.search_line_edit.text()) + return self.refresh_list(lambda: value) @staticmethod @@ -201,6 +220,7 @@ def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: content.title, ",".join(content.author), ",".join(content.categories), + content.url ] ) for i in range(4): diff --git a/qtribu/gui/dlg_contents.ui b/qtribu/gui/dlg_contents.ui index 72fd942e..a29f4327 100644 --- a/qtribu/gui/dlg_contents.ui +++ b/qtribu/gui/dlg_contents.ui @@ -99,22 +99,6 @@ - - - - - 0 - 0 - - - - PointingHandCursor - - - Refresh list - - - @@ -161,6 +145,12 @@ + + + 0 + 0 + + 255 From b76ae0152b22e56671740a21184144265f1d379c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Apr 2024 09:38:35 +0000 Subject: [PATCH 24/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtribu/gui/dlg_contents.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 5e84cf1e..b76516bb 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,4 +1,3 @@ -from functools import partial from pathlib import Path from typing import Callable, Dict, List @@ -69,7 +68,12 @@ def __init__(self, parent: QWidget = None): # tree widget initialization self.contents_tree_widget.setHeaderLabels( - [self.tr("Date"), self.tr("Title"), self.tr("Author(s)"), self.tr("Categories")] + [ + self.tr("Date"), + self.tr("Title"), + self.tr("Author(s)"), + self.tr("Categories"), + ] ) self.contents_tree_widget.itemClicked.connect(self.on_tree_view_item_click) @@ -220,7 +224,7 @@ def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: content.title, ",".join(content.author), ",".join(content.categories), - content.url + content.url, ] ) for i in range(4): From 2d8c5b85ae2cf035ebcd06a6c9e3c2453a3189bd Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 11:49:07 +0200 Subject: [PATCH 25/30] Refactor opening in webviewer methods --- qtribu/gui/dlg_contents.py | 14 ++++---------- qtribu/logic/web_viewer.py | 5 ++--- qtribu/plugin_main.py | 6 +++--- qtribu/toolbelt/commons.py | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 5e84cf1e..04a02499 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,18 +1,17 @@ -from functools import partial from pathlib import Path from typing import Callable, Dict, List -from qgis.core import QgsApplication from qgis.PyQt import QtCore, QtWidgets, uic from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem, QWidget +from qgis.core import QgsApplication from qtribu.__about__ import DIR_PLUGIN_ROOT from qtribu.gui.form_rdp_news import RdpNewsForm -from qtribu.logic import RssItem, WebViewer +from qtribu.logic import RssItem from qtribu.logic.json_feed import JsonFeedClient from qtribu.toolbelt import PlgLogger, PlgOptionsManager -from qtribu.toolbelt.commons import open_url_in_browser +from qtribu.toolbelt.commons import open_url_in_browser, open_url_in_webviewer MARKER_VALUE = "---" @@ -31,7 +30,6 @@ def __init__(self, parent: QWidget = None): self.log = PlgLogger().log self.plg_settings = PlgOptionsManager() self.json_feed_client = JsonFeedClient() - self.web_viewer = WebViewer() uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) self.setWindowIcon( QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_green_no_text.svg")) @@ -76,10 +74,6 @@ def __init__(self, parent: QWidget = None): 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 @@ -164,7 +158,7 @@ def on_tree_view_item_click(self, item: QTreeWidgetItem, column: int): """ # open URL of content (in column at index 4 which is not displayed) url, title = item.text(4), item.text(1) - self._open_url_in_webviewer(url, title) + open_url_in_webviewer(url, title) def on_search_text_changed(self) -> None: """ diff --git a/qtribu/logic/web_viewer.py b/qtribu/logic/web_viewer.py index 3ff01194..5acb9023 100644 --- a/qtribu/logic/web_viewer.py +++ b/qtribu/logic/web_viewer.py @@ -14,8 +14,7 @@ # PyQGIS from qgis.PyQt.QtCore import QCoreApplication, Qt - -from qtribu.toolbelt.commons import open_url_in_browser +from qgis.PyQt.QtGui import QDesktopServices try: from qgis.PyQt.QtWebKitWidgets import QWebView @@ -72,7 +71,7 @@ def display_web_page(self, url: str): self.wdg_web.resize(1000, 600) else: - open_url_in_browser(qntwk.build_url(url)) + QDesktopServices.openUrl(qntwk.build_url(url)) self.log( message=self.tr("Last article from Geotribu loaded and displayed."), diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index e415a1c2..90e6d282 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -22,7 +22,8 @@ from qtribu.gui.form_rdp_news import RdpNewsForm from qtribu.logic import RssMiniReader, SplashChanger, WebViewer from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager -from qtribu.toolbelt.commons import open_url_in_browser +from qtribu.toolbelt.commons import open_url_in_browser, open_url_in_webviewer + # ############################################################################ # ########## Classes ############### @@ -69,7 +70,6 @@ def __init__(self, iface: QgisInterface): # sub-modules self.rss_rdr = RssMiniReader() self.splash_chgr = SplashChanger(self) - self.web_viewer = WebViewer() def initGui(self): """Set up plugin UI elements.""" @@ -289,7 +289,7 @@ def run(self): if not self.rss_rdr.latest_item: self.post_ui_init() - self.web_viewer.display_web_page(url=self.rss_rdr.latest_item.url) + open_url_in_webviewer(self.rss_rdr.latest_item.url, self.rss_rdr.latest_item.title) self.action_run.setIcon( QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_green_no_text.svg")) ) diff --git a/qtribu/toolbelt/commons.py b/qtribu/toolbelt/commons.py index 1bc4f262..36fac1fe 100644 --- a/qtribu/toolbelt/commons.py +++ b/qtribu/toolbelt/commons.py @@ -1,6 +1,10 @@ from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtGui import QDesktopServices +from qtribu.logic import WebViewer + +web_viewer = WebViewer() + def open_url_in_browser(url: str) -> bool: """Opens an url in a browser using user's desktop environment @@ -12,3 +16,16 @@ def open_url_in_browser(url: str) -> bool: :rtype: bool """ return QDesktopServices.openUrl(QUrl(url)) + + +def open_url_in_webviewer(url: str, window_title: str) -> None: + """Opens an url in Geotribu's webviewer + + :param url: url to open + :type url: str + + :param window_title: title to give to the webviewer window + :type window_title: str + """ + web_viewer.display_web_page(url) + web_viewer.set_window_title(window_title) From f6f7e99fcefb5f38cce96f3d1e1301c300b4ec7a Mon Sep 17 00:00:00 2001 From: gounux Date: Sun, 28 Apr 2024 12:10:29 +0200 Subject: [PATCH 26/30] Add article suggestion form --- qtribu/gui/dlg_contents.py | 36 ++++- qtribu/gui/form_article.py | 190 +++++++++++++++++++++++ qtribu/gui/form_article.ui | 292 ++++++++++++++++++++++++++++++++++++ qtribu/gui/form_rdp_news.py | 2 +- 4 files changed, 513 insertions(+), 7 deletions(-) create mode 100644 qtribu/gui/form_article.py create mode 100644 qtribu/gui/form_article.ui diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 04a02499..6c89fbc5 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -1,12 +1,13 @@ from pathlib import Path from typing import Callable, Dict, List +from qgis.core import QgsApplication from qgis.PyQt import QtCore, QtWidgets, uic from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem, QWidget -from qgis.core import QgsApplication from qtribu.__about__ import DIR_PLUGIN_ROOT +from qtribu.gui.form_article import ArticleForm from qtribu.gui.form_rdp_news import RdpNewsForm from qtribu.logic import RssItem from qtribu.logic.json_feed import JsonFeedClient @@ -36,6 +37,7 @@ def __init__(self, parent: QWidget = None): ) # buttons actions + self.form_article = None self.form_rdp_news = None self.submit_article_button.clicked.connect(self.submit_article) self.submit_article_button.setIcon( @@ -67,7 +69,12 @@ def __init__(self, parent: QWidget = None): # tree widget initialization self.contents_tree_widget.setHeaderLabels( - [self.tr("Date"), self.tr("Title"), self.tr("Author(s)"), self.tr("Categories")] + [ + self.tr("Date"), + self.tr("Title"), + self.tr("Author(s)"), + self.tr("Categories"), + ] ) self.contents_tree_widget.itemClicked.connect(self.on_tree_view_item_click) @@ -79,9 +86,26 @@ 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" - ) + self.log("Opening form to submit an article") + if not self.form_article: + self.form_article = ArticleForm() + self.form_article.setModal(True) + self.form_article.finished.connect(self._post_form_article) + self.form_article.show() + + def _post_form_article(self, dialog_result: int) -> None: + """Perform actions after the article form has been closed. + + :param dialog_result: dialog's result code. Accepted (1) or Rejected (0) + :type dialog_result: int + """ + if self.form_article: + # if accept button, save user inputs + if dialog_result == 1: + self.form_article.wdg_author.save_settings() + # clean up + self.form_article.deleteLater() + self.form_article = None def submit_news(self) -> None: """ @@ -214,7 +238,7 @@ def _build_tree_widget_item_from_content(content: RssItem) -> QTreeWidgetItem: content.title, ",".join(content.author), ",".join(content.categories), - content.url + content.url, ] ) for i in range(4): diff --git a/qtribu/gui/form_article.py b/qtribu/gui/form_article.py new file mode 100644 index 00000000..37ca580b --- /dev/null +++ b/qtribu/gui/form_article.py @@ -0,0 +1,190 @@ +#! python3 # noqa: E265 + +""" + Form to submit a news for a GeoRDP. +TODO: markdown highlight https://github.com/rupeshk/MarkdownHighlighter/blob/master/editor.py +https://github.com/baudren/NoteOrganiser/blob/devel/noteorganiser/syntax.py +""" + +# standard +from functools import partial +from pathlib import Path + +# PyQGIS +from qgis.PyQt import uic +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QDialog + +# plugin +from qtribu.__about__ import DIR_PLUGIN_ROOT +from qtribu.constants import GEORDP_NEWS_ICONS, GeotribuImage +from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager +from qtribu.toolbelt.commons import open_url_in_browser + + +class ArticleForm(QDialog): + """QDialog form to submit an article.""" + + LOCAL_CDN_PATH: Path = Path().home() / ".geotribu/cdn/" + + def __init__(self, parent=None): + """Constructor. + + :param parent: parent widget or application + :type parent: QWidget + """ + super().__init__(parent) + uic.loadUi(Path(__file__).parent / f"{Path(__file__).stem}.ui", self) + + self.log = PlgLogger().log + self.plg_settings = PlgOptionsManager() + self.qntwk = NetworkRequestsManager() + + # custom icon + self.setWindowIcon(QIcon(str(DIR_PLUGIN_ROOT / "resources/images/news.png"))) + + # icon combobox + self.cbb_icon_populate() + self.cbb_icon.textActivated.connect(self.cbb_icon_selected) + + # publication + self.chb_license.setChecked( + self.plg_settings.get_value_from_key( + key="license_global_accept", exp_type=bool + ) + ) + + # connect help button + self.btn_box.helpRequested.connect( + partial( + open_url_in_browser, + "https://contribuer.geotribu.fr/rdp/add_news/", + ) + ) + + def cbb_icon_populate(self) -> None: + """Populate combobox of article icons.""" + # save current index + current_item_idx = self.cbb_icon.currentIndex() + + # clear + self.cbb_icon.clear() + + # populate + self.cbb_icon.addItem("", None) + for rdp_icon in GEORDP_NEWS_ICONS: + if rdp_icon.kind != "icon": + continue + + if rdp_icon.local_path().is_file(): + self.cbb_icon.addItem( + QIcon(str(rdp_icon.local_path().resolve())), rdp_icon.name, rdp_icon + ) + else: + self.cbb_icon.addItem(rdp_icon.name, rdp_icon) + + # icon tooltip + self.cbb_icon.setItemData( + GEORDP_NEWS_ICONS.index(rdp_icon) + 1, + rdp_icon.description, + Qt.ToolTipRole, + ) + + # restore current index + self.cbb_icon.setCurrentIndex(current_item_idx) + + def cbb_icon_selected(self) -> None: + """Download selected icon locally if it doesn't exist already.""" + selected_icon: GeotribuImage = self.cbb_icon.currentData() + if not selected_icon: + return + + icon_local_path = selected_icon.local_path() + if not icon_local_path.is_file(): + self.log( + message=f"Icon doesn't exist locally: {icon_local_path}", log_level=4 + ) + icon_local_path.parent.mkdir(parents=True, exist_ok=True) + self.qntwk.download_file( + remote_url=selected_icon.url, + local_path=str(icon_local_path.resolve()), + ) + # repopulate combobx to get updated items icons + self.cbb_icon_populate() + + def accept(self) -> bool: + """Auto-connected to the OK button (within the button box), i.e. the `accepted` + signal. Check if required form fields are correctly filled. + + :return: False if some check fails. True and emit accepted() signal if everything is ok. + :rtype: bool + """ + invalid_fields = [] + error_message = "" + + # check title + if len(self.lne_title.text()) < 3: + invalid_fields.append(self.lne_title) + error_message += self.tr( + "- A title is required, with at least 3 characters.\n" + ) + + # check description + if len(self.txt_description.toPlainText()) < 25: + invalid_fields.append(self.txt_description) + error_message += self.tr( + "- Description is not long enough (25 characters at least).\n" + ) + if len(self.txt_description.toPlainText()) > 160: + invalid_fields.append(self.txt_description) + error_message += self.tr( + "- Description is too long (160 characters at least).\n" + ) + + # check license + if not self.chb_license.isChecked(): + invalid_fields.append(self.chb_license) + error_message += self.tr("- License must be accepted.\n") + + # check author firstname + if len(self.wdg_author.lne_firstname.text()) < 2: + invalid_fields.append(self.wdg_author.lne_firstname) + error_message += self.tr( + "- For attribution purpose, author's firstname is required.\n" + ) + + # check author lastname + if len(self.wdg_author.lne_lastname.text()) < 2: + invalid_fields.append(self.wdg_author.lne_lastname) + error_message += self.tr( + "- For attribution purpose, author's lastname is required.\n" + ) + + # check author email + if len(self.wdg_author.lne_email.text()) < 5: + invalid_fields.append(self.wdg_author.lne_email) + error_message += self.tr( + "- For attribution purpose, author's email is required.\n" + ) + + # inform + if len(invalid_fields): + self.log( + message=self.tr("Some of required fields are incorrectly filled."), + push=True, + log_level=2, + duration=20, + button=True, + button_label=self.tr("See details..."), + button_more_text=self.tr( + "Fields in bold must be filled. Missing fields:\n" + ) + + error_message, + ) + for wdg in invalid_fields: + wdg.setStyleSheet("border: 1px solid red;") + return False + else: + super().accept() + return True diff --git a/qtribu/gui/form_article.ui b/qtribu/gui/form_article.ui new file mode 100644 index 00000000..59d3505f --- /dev/null +++ b/qtribu/gui/form_article.ui @@ -0,0 +1,292 @@ + + + dlg_form_rdp_news + + + + 0 + 0 + 763 + 1060 + + + + GeoRDP - News Form + + + 0.900000000000000 + + + + + + true + + + true + + + + + + + 11 + + + + The news + + + false + + + + + + + 11 + 75 + true + + + + The title must be concise and avoid some special characters, especially at the end + + + Title: + + + + + + + + + + Icon: + + + + + + + true + + + + + + + Keywords: + + + + + + + true + + + + + + + + 11 + 75 + true + + + + Description: + + + + + + + IBeamCursor + + + QFrame::StyledPanel + + + + + + + + + + + 150 + 0 + + + + Qt::Horizontal + + + + + + + Publication + + + + + + + 11 + 75 + true + + + + License: + + + + + + + I accept that my contribution is published under the CC BY-NC-SA 4.0 + + + false + + + + + + + 300 + + + + + + + Comment: + + + + + + + Transparency: + + + + + + + I'm not related to the published content. If not, I give some details in the comment area. + + + + + + + + + + + 10 + 0 + + + + QFrame::Plain + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + AuthoringWidget + QWidget +
qtribu.gui.wdg_authoring
+ 1 +
+
+ + + + btn_box + accepted() + dlg_form_rdp_news + accept() + + + 248 + 254 + + + 157 + 274 + + + + + btn_box + rejected() + dlg_form_rdp_news + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/qtribu/gui/form_rdp_news.py b/qtribu/gui/form_rdp_news.py index a38f8e0d..be99df22 100644 --- a/qtribu/gui/form_rdp_news.py +++ b/qtribu/gui/form_rdp_news.py @@ -190,7 +190,7 @@ def accept(self) -> bool: if len(self.txt_body.toPlainText()) < 25: invalid_fields.append(self.txt_body) error_message += self.tr( - "- News is not long enougth (25 characters at least).\n" + "- News is not long enough (25 characters at least).\n" ) # check license From 5d9b8e14bba91eef2c60968bb443b7204986c803 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:10:57 +0000 Subject: [PATCH 27/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtribu/plugin_main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index 90e6d282..a530a785 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -20,11 +20,10 @@ from qtribu.gui.dlg_contents import GeotribuContentsDialog from qtribu.gui.dlg_settings import PlgOptionsFactory from qtribu.gui.form_rdp_news import RdpNewsForm -from qtribu.logic import RssMiniReader, SplashChanger, WebViewer +from qtribu.logic import RssMiniReader, SplashChanger from qtribu.toolbelt import NetworkRequestsManager, PlgLogger, PlgOptionsManager from qtribu.toolbelt.commons import open_url_in_browser, open_url_in_webviewer - # ############################################################################ # ########## Classes ############### # ################################## @@ -289,7 +288,9 @@ def run(self): if not self.rss_rdr.latest_item: self.post_ui_init() - open_url_in_webviewer(self.rss_rdr.latest_item.url, self.rss_rdr.latest_item.title) + open_url_in_webviewer( + self.rss_rdr.latest_item.url, self.rss_rdr.latest_item.title + ) self.action_run.setIcon( QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_green_no_text.svg")) ) From 29a980dd1e20dad64ef354fc48955a55b3a3a88d Mon Sep 17 00:00:00 2001 From: "Julien M." Date: Sun, 28 Apr 2024 21:41:35 +0200 Subject: [PATCH 28/30] ui: change donate icon --- qtribu/gui/dlg_contents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtribu/gui/dlg_contents.py b/qtribu/gui/dlg_contents.py index 6c89fbc5..04a3f238 100644 --- a/qtribu/gui/dlg_contents.py +++ b/qtribu/gui/dlg_contents.py @@ -49,7 +49,7 @@ def __init__(self, parent: QWidget = None): ) self.donate_button.clicked.connect(self.donate) self.donate_button.setIcon( - QgsApplication.getThemeIcon("mActionAddAllToOverview.svg") + QgsApplication.getThemeIcon("mIconCertificateTrusted.svg") ) # search actions From 2eeeee4d53a8aed3a825335913ff80949e7b91a1 Mon Sep 17 00:00:00 2001 From: "Julien M." Date: Sun, 28 Apr 2024 21:44:32 +0200 Subject: [PATCH 29/30] ui: change content browser icon --- qtribu/plugin_main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qtribu/plugin_main.py b/qtribu/plugin_main.py index a530a785..3ae3412a 100644 --- a/qtribu/plugin_main.py +++ b/qtribu/plugin_main.py @@ -87,11 +87,12 @@ def initGui(self): self.tr("Newest article"), self.iface.mainWindow(), ) + self.action_run.setToolTip(self.tr("Newest article")) self.action_run.triggered.connect(self.run) self.action_contents = QAction( - QIcon(str(DIR_PLUGIN_ROOT / "resources/images/logo_orange_no_text.svg")), + QgsApplication.getThemeIcon("mActionOpenTableVisible.svg"), self.tr("Contents"), self.iface.mainWindow(), ) From ec9c839430c776c15c77c20bfb0bb01d3e72136c Mon Sep 17 00:00:00 2001 From: "Julien M." Date: Sun, 28 Apr 2024 21:45:12 +0200 Subject: [PATCH 30/30] ui: reduce dlg opacity --- qtribu/gui/form_article.ui | 2 +- qtribu/gui/form_rdp_news.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qtribu/gui/form_article.ui b/qtribu/gui/form_article.ui index 59d3505f..698ff427 100644 --- a/qtribu/gui/form_article.ui +++ b/qtribu/gui/form_article.ui @@ -14,7 +14,7 @@ GeoRDP - News Form - 0.900000000000000 + 0.98 diff --git a/qtribu/gui/form_rdp_news.ui b/qtribu/gui/form_rdp_news.ui index d5c08425..3004e22a 100644 --- a/qtribu/gui/form_rdp_news.ui +++ b/qtribu/gui/form_rdp_news.ui @@ -14,7 +14,7 @@ GeoRDP - News Form - 0.900000000000000 + 0.98