Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace QWebViews by native Qt widgets #325

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions qgis_resource_sharing/collection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@
import logging
import shutil
import traceback
from typing import Dict

from qgis.PyQt.QtCore import QObject, pyqtSignal

from qgis_resource_sharing import config
from qgis_resource_sharing.__about__ import __title__
from qgis_resource_sharing.config import (
COLLECTION_INSTALLED_STATUS,
COLLECTION_NOT_INSTALLED_STATUS,
)
from qgis_resource_sharing.config import CollectionStatus
from qgis_resource_sharing.repository_handler import BaseRepositoryHandler
from qgis_resource_sharing.resource_handler import BaseResourceHandler
from qgis_resource_sharing.utilities import (
SUPPORTED_RESOURCES_MAP,
local_collection_path,
render_template,
resources_path,
)

LOGGER = logging.getLogger(__title__)
Expand Down Expand Up @@ -88,8 +84,8 @@ def get_collection_id(self, register_name, repo_url):
hex_dig = hash_object.hexdigest()
return hex_dig

def get_html(self, collection_id):
"""Return the details of a collection as HTML, given its id.
def get_collection(self, collection_id: str) -> Dict[str, str]:
"""Return the details of a collection, given its id.

:param collection_id: The id of the collection
:type collection_id: str
Expand All @@ -107,15 +103,11 @@ def get_html(self, collection_id):
html = html + ".<br><i>Reinstall</i> to update"
if resource_types == 0:
html = "<i>No standard resources found</i>."
if config.COLLECTIONS[collection_id]["status"] != COLLECTION_INSTALLED_STATUS:
if config.COLLECTIONS[collection_id]["status"] != CollectionStatus.INSTALLED:
html = "<i>Unknown before installation</i>"

config.COLLECTIONS[collection_id]["resources_html"] = html
context = {
"resources_path": str(resources_path()),
"collection": config.COLLECTIONS[collection_id],
}
return render_template("collection_details.html", context)
return config.COLLECTIONS[collection_id]

def get_installed_collections(self, repo_url=None):
"""Get all installed collections for a given repository URL.
Expand All @@ -131,7 +123,7 @@ def get_installed_collections(self, repo_url=None):
"""
installed_collections = {}
for collection_id, collection in config.COLLECTIONS.items():
if collection["status"] != COLLECTION_INSTALLED_STATUS:
if collection["status"] != CollectionStatus.INSTALLED:
continue

if repo_url:
Expand Down Expand Up @@ -172,7 +164,7 @@ def install(self, collection_id):
resource_handler_instance = resource_handler(collection_id)
resource_handler_instance.install()

config.COLLECTIONS[collection_id]["status"] = COLLECTION_INSTALLED_STATUS
config.COLLECTIONS[collection_id]["status"] = CollectionStatus.INSTALLED

def uninstall(self, collection_id):
"""Uninstall the collection.
Expand All @@ -190,7 +182,7 @@ def uninstall(self, collection_id):
if collection_dir.exists():
shutil.rmtree(str(collection_dir))

config.COLLECTIONS[collection_id]["status"] = COLLECTION_NOT_INSTALLED_STATUS
config.COLLECTIONS[collection_id]["status"] = CollectionStatus.NOT_INSTALLED

# Should items from other installed collections be reinstalled
# "automatically"?
Expand Down
15 changes: 11 additions & 4 deletions qgis_resource_sharing/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from enum import IntEnum

"""
# Put the COLLECTIONS object (dict) in the module namespace
# (http://effbot.org/pyfaq/how-do-i-share-global-variables-across-modules.htm)
Expand All @@ -15,7 +17,7 @@
'author_email': email,
'repository_url': self.url,
'repository_name': <the name of the repository>,
'status': COLLECTION_NOT_INSTALLED_STATUS,
'status': CollectionStatus.NOT_INSTALLED,
'name': parser.get(collection, 'name'),
'tags': parser.get(collection, 'tags'),
'description': parser.get(collection, 'description'),
Expand All @@ -35,8 +37,13 @@
}
"""

COLLECTION_NOT_INSTALLED_STATUS = 0
COLLECTION_INSTALLED_STATUS = 1
COLLECTION_ALL_STATUS = 2

class CollectionStatus(IntEnum):
"""Describe the status of a collection"""

NOT_INSTALLED = 0
INSTALLED = 1
ALL = 2


COLLECTIONS = {}
6 changes: 3 additions & 3 deletions qgis_resource_sharing/gui/custom_sort_filter_proxy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from qgis.PyQt.QtCore import QSortFilterProxyModel, Qt

from qgis_resource_sharing.config import COLLECTION_INSTALLED_STATUS
from qgis_resource_sharing.config import CollectionStatus

COLLECTION_NAME_ROLE = Qt.UserRole + 1
COLLECTION_DESCRIPTION_ROLE = Qt.UserRole + 2
Expand Down Expand Up @@ -54,10 +54,10 @@ def filterAcceptsRow(self, row_num, source_parent):
>= 0
)

if self.accepted_status == COLLECTION_INSTALLED_STATUS:
if self.accepted_status == CollectionStatus.INSTALLED:
# For installed collection status
collection_status = self.sourceModel().data(index, COLLECTION_STATUS_ROLE)
status = collection_status == COLLECTION_INSTALLED_STATUS
status = collection_status == CollectionStatus.INSTALLED
else:
status = True

Expand Down
169 changes: 169 additions & 0 deletions qgis_resource_sharing/gui/resource_sharing_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""
/***************************************************************************
QGIS Resource Sharing - a QGIS plugin
Download collections shared by other users
-------------------
begin : 2024-03-05
git sha : $Format:%H$
copyright : (C) 2024 by Jean Felder
email : jean dot felder at oslandia dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""

import logging
from typing import List, Optional

from qgis.PyQt import uic
from qgis.PyQt.QtCore import QUrl
from qgis.PyQt.QtGui import QPixmap
from qgis.PyQt.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from qgis.PyQt.QtWidgets import QLabel, QWidget

from qgis_resource_sharing.collection_manager import CollectionManager
from qgis_resource_sharing.config import CollectionStatus
from qgis_resource_sharing.utilities import ui_path

# -- GLOBALS
FORM_CLASS, _ = uic.loadUiType(str(ui_path("resource_sharing_details_base.ui")))
LOGGER = logging.getLogger("QGIS Resource Sharing")


class QgsResourceSharingDetails(QWidget, FORM_CLASS):
"""Widget to display the details of a collection as a grid"""

def __init__(self, parent: Optional[QWidget] = None) -> None:
"""Constructor.

:param parent: Optional widget to use as parent
:type parent: QWidget
"""
super(QgsResourceSharingDetails, self).__init__(parent)
self.setupUi(self)

self._collection_manager = CollectionManager()
self._network_manager = QNetworkAccessManager(self)
self._network_manager.finished.connect(self._on_request_response)
self._network_replies: List[QNetworkReply] = []

def set_content(self, collection_id: str) -> None:
"""Return the details of a collection as HTML, given its id.

:param collection_id: The id of the collection
:type collection_id: str
"""
# reset
self.setMinimumWidth(400)
for reply in self._network_replies:
reply.abort()

# get collection information
collection = self._collection_manager.get_collection(collection_id)

# title
self.titleLabel.setText(f'<h1>{collection["name"]}</h1>')
LOGGER.debug(
f"Set content of collection {collection['name']} with id {collection_id}"
)

# description and tags
self.descriptionContent.setText(collection["description"])
self.tagsContent.setText(collection["tags"])

# resources
show_resources_html = collection["status"] == CollectionStatus.INSTALLED
self.resourcesLabel.setVisible(show_resources_html)
self.resourcesContent.setVisible(show_resources_html)
self.resourcesContent.setText(collection["resources_html"])
self.resourcesLine.setVisible(show_resources_html)

# preview images
# remove the previous images
for idx in reversed(range(self.verticalLayoutPreview.count())):
self.verticalLayoutPreview.itemAt(idx).widget().setParent(None)

# load the new images
for preview_path in collection["preview"]:
reply = self._network_manager.get(QNetworkRequest(QUrl(preview_path)))
self._network_replies.append(reply)

visible_preview = len(collection["preview"]) > 0
self.previewsLabel.setVisible(visible_preview)
self.previewsContent.setVisible(visible_preview)
self.previewsLine.setVisible(visible_preview)

preview_label_text = "Previews"
if len(collection["preview"]) > 1:
preview_label_text += f" ({len(collection['preview'])})"

self.previewsLabel.setText(preview_label_text)

# repository url
res_url = collection["repository_url"]
self.urlContent.setText(f'<a href="{res_url}">{res_url}</a>')

# license
# FIXME: a license may contain an image which is not visible
# at the moment
if collection["license"]:
self.licenseLabel.setVisible(True)
self.licenseContent.setVisible(True)
self.licenseLine.setVisible(True)
self.licenseLabel.setText("License")
license_text = collection["license"]
if collection["license_url"]:
license_url = collection["license_url"]
license_text += f' (read <a href="{license_url}">here</a>)'

self.licenseContent.setText(license_text)
elif collection["license_url"]:
self.licenseLabel.setVisible(True)
self.licenseContent.setVisible(True)
self.licenseLine.setVisible(True)
self.licenseLabel.setText("License File")
license_url = collection["license_url"]
license_text = f'Read the license <a href="{license_url}">here</a>'
self.licenseContent.setText(license_text)
else:
self.licenseLabel.setVisible(False)
self.licenseContent.setVisible(False)
self.licenseLine.setVisible(False)

# author and email
self.authorContent.setText(collection["author"])
email = collection["author_email"]
self.emailContent.setText(f'<a href="mailto:{email}">{email}</a>')

def _on_request_response(self, network_reply: QNetworkReply) -> None:
"""Load the content of a network request as an image.

:param network_reply: a network reply wich contains image data
:type collection_id: QNetworkReply
"""
self._network_replies.remove(network_reply)
if network_reply.error() != QNetworkReply.NetworkError.NoError:
LOGGER.error(f"Unable to download image from {network_reply.url()}")
return

img_data = network_reply.readAll()
pixmap = QPixmap()
pixmap.loadFromData(img_data)
label = QLabel(self)
label.setPixmap(pixmap)
self.verticalLayoutPreview.addWidget(label)

LOGGER.debug(f"Image downloaded from {network_reply.url()}")

# the min width of the container is the size of the widest image
# + the size of the other labels
new_width = pixmap.width() + 180
if new_width > self.minimumWidth():
self.setMinimumWidth(new_width)
Loading
Loading