Addon Manager: pylint cleanup of list diplay

This commit is contained in:
Chris Hennes
2022-07-31 15:38:34 -05:00
parent ab11797f22
commit 19f07586c9
2 changed files with 185 additions and 125 deletions

View File

@@ -21,14 +21,16 @@
# * USA *
# * *
# ***************************************************************************
from posixpath import normpath
""" Provides the PackageDetails widget. """
import os
from typing import Optional
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import os
import FreeCAD
import FreeCADGui
@@ -47,7 +49,6 @@ try:
except ImportError:
pass
from typing import Optional
translate = FreeCAD.Qt.translate
@@ -57,7 +58,7 @@ try:
from PySide2.QtWebEngineWidgets import *
HAS_QTWEBENGINE = True
except Exception:
except ImportError:
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
@@ -69,6 +70,8 @@ except Exception:
class PackageDetails(QWidget):
""" The PackageDetails QWidget shows package README information and provides
install, uninstall, and update buttons. """
back = Signal()
install = Signal(Addon)
@@ -104,13 +107,16 @@ class PackageDetails(QWidget):
self.ui.webView.loadFinished.connect(self.load_finished)
loading_html_file = os.path.join(os.path.dirname(__file__), "loading.html")
with open(loading_html_file, "r", errors="ignore") as f:
with open(loading_html_file, "r", errors="ignore", encoding="utf-8") as f:
html = f.read()
self.ui.loadingLabel.setHtml(html)
self.ui.loadingLabel.show()
self.ui.webView.hide()
def show_repo(self, repo: Addon, reload: bool = False) -> None:
""" The main entry point for this class, shows the package details and related buttons
for the provided repo. If reload is true, then even if this is already the current repo
the data is reloaded. """
# If this is the same repo we were already showing, we do not have to do the
# expensive refetch unless reload is true
@@ -162,6 +168,7 @@ class PackageDetails(QWidget):
self.display_repo_status(self.repo.update_status)
def display_repo_status(self, status):
""" Updates the contents of the widget to diplay the current install status of the widget. """
repo = self.repo
self.set_change_branch_button_state()
self.set_disable_button_state()
@@ -383,6 +390,10 @@ class PackageDetails(QWidget):
self.ui.labelWarningInfo.hide()
def requires_newer_freecad(self) -> Optional[str]:
""" If the current package is not installed, returns the first supported version of
FreeCAD, if one is set, or None if no information is available (or if the package is
already installed). """
# If it's not installed, check to see if it's for a newer version of FreeCAD
if self.repo.status() == Addon.Status.NOT_INSTALLED and self.repo.metadata:
# Only hide if ALL content items require a newer version, otherwise
@@ -399,7 +410,7 @@ class PackageDetails(QWidget):
if int(required_version[0]) > fc_major:
return first_supported_version
elif int(required_version[0]) == fc_major and len(required_version) > 1:
if int(required_version[0]) == fc_major and len(required_version) > 1:
if int(required_version[1]) > fc_minor:
return first_supported_version
return None
@@ -438,6 +449,7 @@ class PackageDetails(QWidget):
self.ui.buttonChangeBranch.show()
def set_disable_button_state(self):
""" Set up the enable/disable button based on the enabled/disabled state of the addon """
self.ui.buttonEnable.hide()
self.ui.buttonDisable.hide()
status = self.repo.status()
@@ -491,6 +503,7 @@ class PackageDetails(QWidget):
self.macro_readme_updated()
def macro_readme_updated(self):
""" Update the display of a Macro's README data. """
url = self.repo.macro.wiki
if not url:
url = self.repo.macro.url
@@ -575,13 +588,16 @@ class PackageDetails(QWidget):
self.ui.webView.page().runJavaScript(s)
def load_started(self):
""" Called when loading is started: sets up the progress bar """
self.ui.progressBar.show()
self.ui.progressBar.setValue(0)
def load_progress(self, progress: int):
""" Called during load to update the progress bar """
self.ui.progressBar.setValue(progress)
def load_finished(self, load_succeeded: bool):
""" Once loading is complete, update the dispaly of the progress bar and loading widget. """
self.ui.loadingLabel.hide()
self.ui.slowLoadLabel.hide()
self.ui.webView.show()
@@ -608,12 +624,14 @@ class PackageDetails(QWidget):
self.show_error_for(url)
def long_load_running(self):
""" Displays a message about loading taking a long time. """
if hasattr(self.ui, "webView") and self.ui.webView.isHidden():
self.ui.slowLoadLabel.show()
self.ui.loadingLabel.hide()
self.ui.webView.show()
def show_error_for(self, url: QUrl) -> None:
""" Displays error information. """
m = translate(
"AddonsInstaller", "Could not load README data from URL {}"
).format(url.toString())
@@ -621,6 +639,7 @@ class PackageDetails(QWidget):
self.ui.webView.setHtml(html)
def change_branch_clicked(self) -> None:
""" Loads the branch-switching dialog """
basedir = FreeCAD.getUserAppDataDir()
path_to_repo = os.path.join(basedir, "Mod", self.repo.name)
change_branch_dialog = ChangeBranchDialog(path_to_repo, self)
@@ -628,6 +647,7 @@ class PackageDetails(QWidget):
change_branch_dialog.exec()
def enable_clicked(self) -> None:
""" Called by the Enable button, enables this Addon and updates GUI to reflect that status. """
self.repo.enable()
self.repo.set_status(Addon.Status.PENDING_RESTART)
self.set_disable_button_state()
@@ -644,6 +664,7 @@ class PackageDetails(QWidget):
self.ui.labelWarningInfo.setStyleSheet("color:" + utils.bright_color_string())
def disable_clicked(self) -> None:
""" Called by the Disable button, disables this Addon and updates the GUI to reflect that status. """
self.repo.disable()
self.repo.set_status(Addon.Status.PENDING_RESTART)
self.set_disable_button_state()
@@ -662,6 +683,7 @@ class PackageDetails(QWidget):
)
def branch_changed(self, name: str) -> None:
""" Displays a dialog confirming the branch changed, and tries to access the metadata file from that branch. """
QMessageBox.information(
self,
translate("AddonsInstaller", "Success"),
@@ -706,6 +728,8 @@ if HAS_QTWEBENGINE:
self.settings().setAttribute(QWebEngineSettings.ErrorPageEnabled, False)
def acceptNavigationRequest(self, url, _type, isMainFrame):
""" A callback for navigation requests: this widget will only display navigation requests to the
FreeCAD Wiki (for translation purposes) -- anything else will open in a new window. """
if _type == QWebEnginePage.NavigationTypeLinkClicked:
# See if the link is to a FreeCAD Wiki page -- if so, follow it, otherwise ask the OS to open it
@@ -714,12 +738,14 @@ if HAS_QTWEBENGINE:
or url.host() == "wiki.freecadweb.org"
):
return super().acceptNavigationRequest(url, _type, isMainFrame)
else:
QDesktopServices.openUrl(url)
return False
QDesktopServices.openUrl(url)
return False
return super().acceptNavigationRequest(url, _type, isMainFrame)
def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
def javaScriptConsoleMessage(self, level, message, lineNumber, _):
""" Handle JavaScript console messages by optionally outputting them to the FreeCAD Console. This
must be manually enabled in this Python file by setting the global show_javascript_console_output
to true. """
global show_javascript_console_output
if show_javascript_console_output:
tag = translate("AddonsInstaller", "Page JavaScript reported")
@@ -732,6 +758,9 @@ if HAS_QTWEBENGINE:
class Ui_PackageDetails(object):
""" The generated UI from the Qt Designer UI file """
def setupUi(self, PackageDetails):
if not PackageDetails.objectName():
PackageDetails.setObjectName("PackageDetails")

View File

@@ -22,15 +22,17 @@
# * *
# ***************************************************************************
""" Defines the PackageList QWidget for displaying a list of Addons. """
from enum import IntEnum
import threading
import FreeCAD
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from enum import IntEnum
import threading
from Addon import Addon
from compact_view import Ui_CompactView
@@ -42,11 +44,13 @@ translate = FreeCAD.Qt.translate
class ListDisplayStyle(IntEnum):
""" The display mode of the list """
COMPACT = 0
EXPANDED = 1
class StatusFilter(IntEnum):
""" Predefined filers """
ANY = 0
INSTALLED = 1
NOT_INSTALLED = 2
@@ -89,7 +93,11 @@ class PackageList(QWidget):
status = pref.GetInt("StatusSelection", 0)
self.ui.comboStatus.setCurrentIndex(status)
# Pre-init of other members:
self.item_model = None
def setModel(self, model):
""" This is a model-view-controller widget: set its model. """
self.item_model = model
self.item_filter.setSourceModel(self.item_model)
self.item_filter.sort(0)
@@ -109,6 +117,8 @@ class PackageList(QWidget):
)
def on_listPackages_clicked(self, index: QModelIndex):
""" Determine what addon was selected and emit the itemSelected signal with it as
an argument. """
source_selection = self.item_filter.mapToSource(index)
selected_repo = self.item_model.repos[source_selection.row()]
self.itemSelected.emit(selected_repo)
@@ -166,6 +176,7 @@ class PackageList(QWidget):
self.item_filter.setFilterRegExp(text_filter)
def set_view_style(self, style: ListDisplayStyle) -> None:
""" Set the style (compact or expanded) of the list """
self.item_model.layoutAboutToBeChanged.emit()
self.item_delegate.set_view(style)
if style == ListDisplayStyle.COMPACT:
@@ -179,6 +190,7 @@ class PackageList(QWidget):
class PackageListItemModel(QAbstractListModel):
""" The model for use with the PackageList class. """
repos = []
write_lock = threading.Lock()
@@ -187,20 +199,20 @@ class PackageListItemModel(QAbstractListModel):
StatusUpdateRole = Qt.UserRole + 1
IconUpdateRole = Qt.UserRole + 2
def __init__(self, parent=None) -> None:
super().__init__(parent)
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
""" The number of rows """
if parent.isValid():
return 0
return len(self.repos)
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
""" Only one column, always returns 1. """
if parent.isValid():
return 0
return 1
def data(self, index: QModelIndex, role: int = Qt.DisplayRole):
""" Get the data for a given index and role. """
if not index.isValid():
return None
row = index.row()
@@ -219,52 +231,53 @@ class PackageListItemModel(QAbstractListModel):
"AddonsInstaller", "Click for details about macro {}"
).format(self.repos[row].display_name)
return tooltip
elif role == PackageListItemModel.DataAccessRole:
if role == PackageListItemModel.DataAccessRole:
return self.repos[row]
def headerData(self, section, orientation, role=Qt.DisplayRole):
def headerData(self, _unused1, _unused2, _role=Qt.DisplayRole):
""" No header in this implementation: always returns None. """
return None
def setData(self, index: QModelIndex, value, role=Qt.EditRole) -> None:
"""Set the data for this row. The column of the index is ignored."""
row = index.row()
self.write_lock.acquire()
if role == PackageListItemModel.StatusUpdateRole:
self.repos[row].set_status(value)
self.dataChanged.emit(
self.index(row, 2),
self.index(row, 2),
[PackageListItemModel.StatusUpdateRole],
)
elif role == PackageListItemModel.IconUpdateRole:
self.repos[row].icon = value
self.dataChanged.emit(
self.index(row, 0),
self.index(row, 0),
[PackageListItemModel.IconUpdateRole],
)
self.write_lock.release()
with self.write_lock:
if role == PackageListItemModel.StatusUpdateRole:
self.repos[row].set_status(value)
self.dataChanged.emit(
self.index(row, 2),
self.index(row, 2),
[PackageListItemModel.StatusUpdateRole],
)
elif role == PackageListItemModel.IconUpdateRole:
self.repos[row].icon = value
self.dataChanged.emit(
self.index(row, 0),
self.index(row, 0),
[PackageListItemModel.IconUpdateRole],
)
def append_item(self, repo: Addon) -> None:
""" Adds this addon to the end of the model. Thread safe. """
if repo in self.repos:
# Cowardly refuse to insert the same repo a second time
return
self.write_lock.acquire()
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.repos.append(repo)
self.endInsertRows()
self.write_lock.release()
with self.write_lock:
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.repos.append(repo)
self.endInsertRows()
def clear(self) -> None:
""" Clear the model, removing all rows. Thread safe. """
if self.rowCount() > 0:
self.write_lock.acquire()
self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
self.repos = []
self.endRemoveRows()
self.write_lock.release()
with self.write_lock:
self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
self.repos = []
self.endRemoveRows()
def update_item_status(self, name: str, status: Addon.Status) -> None:
""" Set the status of addon with name to status. """
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
@@ -273,6 +286,7 @@ class PackageListItemModel(QAbstractListModel):
return
def update_item_icon(self, name: str, icon: QIcon) -> None:
""" Set the icon for Addon with name to icon """
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
@@ -281,11 +295,11 @@ class PackageListItemModel(QAbstractListModel):
return
def reload_item(self, repo: Addon) -> None:
""" Sets the addon data for the given addon (based on its name) """
for index, item in enumerate(self.repos):
if item.name == repo.name:
self.write_lock.acquire()
self.repos[index] = repo
self.write_lock.release()
with self.write_lock:
self.repos[index] = repo
return
@@ -322,14 +336,17 @@ class PackageListItemDelegate(QStyledItemDelegate):
self.widget = self.expanded
def set_view(self, style: ListDisplayStyle) -> None:
""" Set the view of to style """
if not self.displayStyle == style:
self.displayStyle = style
def sizeHint(self, option, index):
def sizeHint(self, _option, index):
""" Attempt to figure out the correct height for the widget based on its current contents. """
self.update_content(index)
return self.widget.sizeHint()
def update_content(self, index):
""" Creates the display of the content for a given index. """
repo = index.data(PackageListItemModel.DataAccessRole)
if self.displayStyle == ListDisplayStyle.EXPANDED:
self.widget = self.expanded
@@ -347,57 +364,9 @@ class PackageListItemDelegate(QStyledItemDelegate):
self.widget.ui.labelDescription.setText(repo.metadata.Description)
self.widget.ui.labelVersion.setText(f"<i>v{repo.metadata.Version}</i>")
if self.displayStyle == ListDisplayStyle.EXPANDED:
maintainers = repo.metadata.Maintainer
maintainers_string = ""
if len(maintainers) == 1:
maintainers_string = (
translate("AddonsInstaller", "Maintainer")
+ f": {maintainers[0]['name']} <{maintainers[0]['email']}>"
)
elif len(maintainers) > 1:
n = len(maintainers)
maintainers_string = translate(
"AddonsInstaller", "Maintainers:", "", n
)
for maintainer in maintainers:
maintainers_string += (
f"\n{maintainer['name']} <{maintainer['email']}>"
)
self.widget.ui.labelMaintainer.setText(maintainers_string)
if repo.tags:
self.widget.ui.labelTags.setText(
translate("AddonsInstaller", "Tags")
+ ": "
+ ", ".join(repo.tags)
)
self._setup_expanded_package(repo)
elif repo.macro and repo.macro.parsed:
self.widget.ui.labelDescription.setText(repo.macro.comment)
version_string = ""
if repo.macro.version:
version_string = repo.macro.version + " "
if repo.macro.on_wiki:
version_string += "(wiki)"
elif repo.macro.on_git:
version_string += "(git)"
else:
version_string += "(unknown source)"
if repo.macro.date:
version_string = (
version_string
+ ", "
+ translate("AddonsInstaller", "updated")
+ " "
+ repo.macro.date
)
self.widget.ui.labelVersion.setText("<i>" + version_string + "</i>")
if self.displayStyle == ListDisplayStyle.EXPANDED:
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(
caption + ": " + repo.macro.author
)
else:
self.widget.ui.labelMaintainer.setText("")
self._setup_macro(repo)
else:
self.widget.ui.labelDescription.setText("")
self.widget.ui.labelVersion.setText("")
@@ -412,6 +381,62 @@ class PackageListItemDelegate(QStyledItemDelegate):
self.widget.adjustSize()
def _setup_expanded_package (self, repo:Addon):
""" Set up the display for a package in expanded view """
maintainers = repo.metadata.Maintainer
maintainers_string = ""
if len(maintainers) == 1:
maintainers_string = (
translate("AddonsInstaller", "Maintainer")
+ f": {maintainers[0]['name']} <{maintainers[0]['email']}>"
)
elif len(maintainers) > 1:
n = len(maintainers)
maintainers_string = translate(
"AddonsInstaller", "Maintainers:", "", n
)
for maintainer in maintainers:
maintainers_string += (
f"\n{maintainer['name']} <{maintainer['email']}>"
)
self.widget.ui.labelMaintainer.setText(maintainers_string)
if repo.tags:
self.widget.ui.labelTags.setText(
translate("AddonsInstaller", "Tags")
+ ": "
+ ", ".join(repo.tags)
)
def _setup_macro(self, repo:Addon):
""" Set up the display for a macro """
self.widget.ui.labelDescription.setText(repo.macro.comment)
version_string = ""
if repo.macro.version:
version_string = repo.macro.version + " "
if repo.macro.on_wiki:
version_string += "(wiki)"
elif repo.macro.on_git:
version_string += "(git)"
else:
version_string += "(unknown source)"
if repo.macro.date:
version_string = (
version_string
+ ", "
+ translate("AddonsInstaller", "updated")
+ " "
+ repo.macro.date
)
self.widget.ui.labelVersion.setText("<i>" + version_string + "</i>")
if self.displayStyle == ListDisplayStyle.EXPANDED:
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(
caption + ": " + repo.macro.author
)
else:
self.widget.ui.labelMaintainer.setText("")
def get_compact_update_string(self, repo: Addon) -> str:
"""Get a single-line string listing details about the installed version and date"""
@@ -501,6 +526,8 @@ class PackageListItemDelegate(QStyledItemDelegate):
return result
def paint(self, painter: QPainter, option: QStyleOptionViewItem, _: QModelIndex):
""" Main paint function: renders this widget into a given rectangle, successively drawing
all of its children. """
painter.save()
self.widget.resize(option.rect.size())
painter.translate(option.rect.topLeft())
@@ -521,36 +548,44 @@ class PackageListFilter(QSortFilterProxyModel):
self.hide_newer_freecad_required = False
def setPackageFilter(
self, type: int
self, package_type: int
) -> None: # 0=All, 1=Workbenches, 2=Macros, 3=Preference Packs
self.package_type = type
""" Set the package filter to package_type and refreshes. """
self.package_type = package_type
self.invalidateFilter()
def setStatusFilter(
self, status: int
) -> None: # 0=Any, 1=Installed, 2=Not installed, 3=Update available
""" Sets the status filter to status and refreshes. """
self.status = status
self.invalidateFilter()
def setHidePy2(self, hide_py2: bool) -> None:
""" Sets whether or not to hide Python 2-only Addons """
self.hide_py2 = hide_py2
self.invalidateFilter()
def setHideObsolete(self, hide_obsolete: bool) -> None:
""" Sets whether or not to hide Addons marked obsolete """
self.hide_obsolete = hide_obsolete
self.invalidateFilter()
def setHideNewerFreeCADRequired(self, hide_nfr: bool) -> None:
""" Sets whether or not to hide packages that have indicated they need a newer version
of FreeCAD than the one currently running. """
self.hide_newer_freecad_required = hide_nfr
self.invalidateFilter()
def lessThan(self, left, right) -> bool:
""" Enable sorting of display name (not case sensitive). """
l = self.sourceModel().data(left, PackageListItemModel.DataAccessRole)
r = self.sourceModel().data(right, PackageListItemModel.DataAccessRole)
return l.display_name.lower() < r.display_name.lower()
def filterAcceptsRow(self, row, parent=QModelIndex()):
""" Do the actual filtering (called automatically by Qt when drawing the list) """
index = self.sourceModel().createIndex(row, 0)
data = self.sourceModel().data(index, PackageListItemModel.DataAccessRole)
if self.package_type == 1:
@@ -607,7 +642,7 @@ class PackageListFilter(QSortFilterProxyModel):
if int(required_version[0]) > fc_major:
return False
elif int(required_version[0]) == fc_major and len(required_version) > 1:
if int(required_version[0]) == fc_major and len(required_version) > 1:
if int(required_version[1]) > fc_minor:
return False
@@ -630,29 +665,25 @@ class PackageListFilter(QSortFilterProxyModel):
for tag in data.tags:
if re.match(tag).hasMatch():
return True
return False
else:
return False
else:
re = self.filterRegExp()
if re.isValid():
re.setCaseSensitivity(Qt.CaseInsensitive)
if re.indexIn(name) != -1:
return False
# Only get here for Qt < 5.12
re = self.filterRegExp()
if re.isValid():
re.setCaseSensitivity(Qt.CaseInsensitive)
if re.indexIn(name) != -1:
return True
if re.indexIn(desc) != -1:
return True
if (
data.macro
and data.macro.comment
and re.indexIn(data.macro.comment) != -1
):
return True
for tag in data.tags:
if re.indexIn(tag) != -1:
return True
if re.indexIn(desc) != -1:
return True
if (
data.macro
and data.macro.comment
and re.indexIn(data.macro.comment) != -1
):
return True
for tag in data.tags:
if re.indexIn(tag) != -1:
return True
return False
else:
return False
return False
class Ui_PackageList(object):