checkpoint. don't want to lose this work
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets, QtGui
|
||||||
|
|
||||||
from ui.installer_wizard import InstallerWizard
|
from ui.installer_wizard import InstallerWizard
|
||||||
|
|
||||||
@@ -8,6 +8,11 @@ if __name__ == "__main__":
|
|||||||
# create the QApplication that will manage this window
|
# create the QApplication that will manage this window
|
||||||
app = QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
app.setApplicationName("Portable Computing Toolkit Installer")
|
app.setApplicationName("Portable Computing Toolkit Installer")
|
||||||
|
try:
|
||||||
|
app.setStyle(QtWidgets.QStyleFactory.create('WindowsVista'))
|
||||||
|
except:
|
||||||
|
app.setStyle(QtWidgets.QStyleFactory.create('Fusion'))
|
||||||
|
app.setWindowIcon(QtGui.QIcon(str(Path(__file__).parent/"resources/icons/system-software-installer-2.png")))
|
||||||
|
|
||||||
# create a window
|
# create a window
|
||||||
main_window = InstallerWizard()
|
main_window = InstallerWizard()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"category": "development.tools.source code management",
|
"category": "development.tools.source code management",
|
||||||
"depends on": "Git",
|
"depends on": "Git",
|
||||||
"homepage": "http://gitextensions.github.io/",
|
"homepage": "http://gitextensions.github.io/",
|
||||||
|
"icon url": "https://github.com/gitextensions/gitextensions/raw/master/Logo/git-extensions-logo-64px.png",
|
||||||
"search": {
|
"search": {
|
||||||
"selector": "",
|
"selector": "",
|
||||||
"filename regex": "",
|
"filename regex": "",
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
"page": "https://www.msys2.org/",
|
"page": "https://www.msys2.org/",
|
||||||
"category": "development.languages.C / C++",
|
"category": "development.languages.C / C++",
|
||||||
"homepage": "https://www.msys2.org",
|
"homepage": "https://www.msys2.org",
|
||||||
|
"icon url": "https://avatars1.githubusercontent.com/u/6759993?s=200&v=4",
|
||||||
"search": {
|
"search": {
|
||||||
"selector": "",
|
"selector": "",
|
||||||
"filename regex": "",
|
"filename regex": "",
|
||||||
@@ -185,6 +187,7 @@
|
|||||||
"page": "https://portableapps.com/apps/office/cherrytree-portable",
|
"page": "https://portableapps.com/apps/office/cherrytree-portable",
|
||||||
"category": "office.notes",
|
"category": "office.notes",
|
||||||
"homepage": "https://www.giuspen.com/cherrytree/",
|
"homepage": "https://www.giuspen.com/cherrytree/",
|
||||||
|
"icon url": "https://www.giuspen.com/icons_softw/cherrytree.png",
|
||||||
"search": {
|
"search": {
|
||||||
"selector": "",
|
"selector": "",
|
||||||
"filename regex": "",
|
"filename regex": "",
|
||||||
@@ -355,6 +358,7 @@
|
|||||||
"page": "https://github.com/Eugeny/terminus/releases/latest",
|
"page": "https://github.com/Eugeny/terminus/releases/latest",
|
||||||
"category": "utilities.terminals",
|
"category": "utilities.terminals",
|
||||||
"homepage": "https://eugeny.github.io/terminus/",
|
"homepage": "https://eugeny.github.io/terminus/",
|
||||||
|
"icon url": "https://github.com/Eugeny/terminus/raw/master/app/assets/tray.png",
|
||||||
"search": {
|
"search": {
|
||||||
"selector": "",
|
"selector": "",
|
||||||
"filename regex": "",
|
"filename regex": "",
|
||||||
|
|||||||
@@ -25,13 +25,18 @@ class InstallerWizard(QtWidgets.QWizard):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
loadUi(Path(__file__).parent / "installer_wizard.ui", baseinstance=self)
|
loadUi(Path(__file__).parent / "installer_wizard.ui", baseinstance=self)
|
||||||
|
self.setWindowIcon(QtWidgets.QApplication.instance().windowIcon())
|
||||||
self.intro_page.validatePage = self._requirements_check
|
self.intro_page.validatePage = self._requirements_check
|
||||||
|
self.selection_page.initializePage = self._load_tools
|
||||||
self.currentIdChanged.connect
|
self.currentIdChanged.connect
|
||||||
self.intro.anchorClicked.connect(self._link_clicked)
|
self.intro.anchorClicked.connect(self._link_clicked)
|
||||||
self.license.anchorClicked.connect(self._link_clicked)
|
self.license.anchorClicked.connect(self._link_clicked)
|
||||||
self.install_location.setValidator(Location_Validator(parent=self))
|
self.install_location.setValidator(Location_Validator(parent=self))
|
||||||
self.location_page.registerField("Location*", self.install_location)
|
self.location_page.registerField("Location*", self.install_location)
|
||||||
self.browse_button.clicked.connect(self._select_location)
|
self.browse_button.clicked.connect(self._select_location)
|
||||||
|
self.selection_menu.itemClicked.connect(self._open_homepage)
|
||||||
|
self.selection_menu.itemCollapsed.connect(lambda x: self.selection_menu.resizeColumnToContents(0))
|
||||||
|
self.selection_menu.itemExpanded.connect(lambda x: self.selection_menu.resizeColumnToContents(0))
|
||||||
#Change button text on license page
|
#Change button text on license page
|
||||||
self.license_page.setButtonText(QtWidgets.QWizard.NextButton, "Agree")
|
self.license_page.setButtonText(QtWidgets.QWizard.NextButton, "Agree")
|
||||||
self.license_page.setButtonText(QtWidgets.QWizard.CancelButton, "Decline")
|
self.license_page.setButtonText(QtWidgets.QWizard.CancelButton, "Decline")
|
||||||
@@ -115,25 +120,122 @@ class InstallerWizard(QtWidgets.QWizard):
|
|||||||
self._display_error(f"Encountered a {QtCore.QMetaEnum.valueToKey(reply.error())} while testing Internet connectivity")
|
self._display_error(f"Encountered a {QtCore.QMetaEnum.valueToKey(reply.error())} while testing Internet connectivity")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _display_error(self, message, exception = None):
|
def _display_error(self, message, details = None):
|
||||||
|
"""Displays an error message.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
message {str} -- The contents of the error message to be displayed
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
details {Any} -- Any string-printable object that will be set in the more details, typically an Exception object (default: {None})
|
||||||
|
"""
|
||||||
message=QtWidgets.QMessageBox(icon=QtWidgets.QMessageBox.Critical,parent=self,text=message)
|
message=QtWidgets.QMessageBox(icon=QtWidgets.QMessageBox.Critical,parent=self,text=message)
|
||||||
if exception:
|
if details:
|
||||||
message.setDetailedText(str(exception))
|
message.setDetailedText(str(details))
|
||||||
message.show()
|
message.show()
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _load_tools(self):
|
def _load_tools(self):
|
||||||
request = QtNetwork.QNetworkRequest(QtCore.QUrl(f"{self._project_page}/blob/master/resources/supported_tools.json"))
|
"""Loads tool data from supported_tools.json hosted on the project Github page
|
||||||
|
"""
|
||||||
|
request = QtNetwork.QNetworkRequest(QtCore.QUrl("https://raw.githubusercontent.com/norweeg/portable-computing-toolkit-installer/initial_dev/portable_computing_toolkit_installer/resources/supported_tools.json"))
|
||||||
|
request.setAttribute(QtNetwork.QNetworkRequest.CacheLoadControlAttribute, QtNetwork.QNetworkRequest.AlwaysNetwork)
|
||||||
reply = self._network_manager.get(request)
|
reply = self._network_manager.get(request)
|
||||||
while reply.isRunning():
|
while reply.isRunning():
|
||||||
QtWidgets.QApplication.instance().processEvents()
|
QtWidgets.QApplication.instance().processEvents()
|
||||||
QtCore.QThread.currentThread().msleep(100)
|
QtCore.QThread.currentThread().msleep(100)
|
||||||
if not reply.error():
|
if not reply.error():
|
||||||
try:
|
try:
|
||||||
self.tools = json.loads(reply.readAll())
|
self.__tools__ = json.loads(bytearray(reply.readAll()))
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
self._display_error(f"Unable to decode {request.url.toString()}", e)
|
self._display_error(f"Unable to decode {request.url.toString()}", e).accepted.connect(self.back)
|
||||||
else:
|
else:
|
||||||
self._display_error(f"Encountered a {QtCore.QMetaEnum.valueToKey(reply.error())} error while loading supported tools")
|
self._populate_menu()
|
||||||
return False
|
else:
|
||||||
|
self._display_error(f"Encountered an error while loading supported tools from {request.url().toString()}", reply.errorString()).finished.connect(lambda x: self.back())
|
||||||
|
|
||||||
|
|
||||||
|
def _populate_menu(self):
|
||||||
|
"""Populates the selection menu with items loaded from supported_tools.json
|
||||||
|
"""
|
||||||
|
for name, info in self.__tools__.items():
|
||||||
|
placement = [level.capitalize().strip() for level in info["category"].split(".") if level] + [name]
|
||||||
|
|
||||||
|
root = self.selection_menu.invisibleRootItem()
|
||||||
|
children = [root.child(i) for i in range(root.childCount())]
|
||||||
|
while placement:
|
||||||
|
current_level = placement.pop()
|
||||||
|
|
||||||
|
if current_level in [child.text(0) for child in children]:
|
||||||
|
root = list(filter(lambda child: child.text(0) == current_level, children)).pop()
|
||||||
|
else:
|
||||||
|
new_item = QtWidgets.QTreeWidgetItem(root)
|
||||||
|
new_item.setText(0, current_level)
|
||||||
|
new_item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsUserCheckable|QtCore.Qt.ItemIsEnabled)
|
||||||
|
new_item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||||
|
if current_level == name:
|
||||||
|
new_item.setFlags(new_item.flags()|QtCore.Qt.ItemNeverHasChildren)
|
||||||
|
new_item.setData(1, QtCore.Qt.DecorationRole, QtGui.QIcon(str(Path(__file__).parent.parent/"resources/icons/internet-web-browser-4.png")))
|
||||||
|
new_item.setData(1, QtCore.Qt.ToolTipRole, info["homepage"])
|
||||||
|
try:
|
||||||
|
self._get_icon(new_item, info["icon url"])
|
||||||
|
except KeyError:
|
||||||
|
self._get_icon(new_item)
|
||||||
|
try:
|
||||||
|
if info["depends on"] == current_level: #tool depends on itself makes it mandatory
|
||||||
|
new_item.setFlags(new_item.flags()^QtCore.Qt.ItemIsUserCheckable)
|
||||||
|
new_item.setCheckState(0, QtCore.Qt.Checked)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
info["widget"] = new_item
|
||||||
|
else:
|
||||||
|
new_item.setFlags(new_item.flags()|QtCore.Qt.ItemIsAutoTristate)
|
||||||
|
|
||||||
|
root.addChild(new_item)
|
||||||
|
new_item.setExpanded(False)
|
||||||
|
self.selection_menu.resizeColumnToContents(0)
|
||||||
|
self.selection_menu.resizeColumnToContents(1)
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot("QTreeWidgetItem*", "int")
|
||||||
|
def _open_homepage(self, item, column):
|
||||||
|
"""Receives the itemClicked signal from the selection_menu QTreeWidget and if the user has clicked a homepage, opens it in a new browser tab.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
item {QTreeWidgetItem} -- The item that was clicked
|
||||||
|
column {int} -- The index of the column of the item that was clicked
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
#if the item has a valid URL, open it, otherwise do nothing
|
||||||
|
if column and QtCore.QUrl(item.data(column, QtCore.Qt.ToolTipRole), QtCore.QUrl.StrictMode).scheme().startswith("http"):
|
||||||
|
webbrowser.open_new_tab(item.data(column, QtCore.Qt.ToolTipRole))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_icon(self, tree_item, icon_url = None):
|
||||||
|
"""Given a QTreeWidgetItem, this will initiate a request to download an icon for it. If icon_url is specified, that will be fetched, otherwise
|
||||||
|
the favicon of the tree item's homepage.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
tree_item {QTreeWidgetItem} -- a tree item with a specified homepage to set an icon on
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
icon_url {str} -- A string representing the url of the icon to fetch instead of the favicon of the homepage (default: {None})
|
||||||
|
"""
|
||||||
|
if not icon_url:
|
||||||
|
icon_url = QtCore.QUrl(f"http://www.google.com/s2/favicons?domain={QtCore.QUrl(tree_item.data(1, QtCore.Qt.ToolTipRole)).toString()}")
|
||||||
|
icon_request = self._network_manager.get(QtNetwork.QNetworkRequest(icon_url))
|
||||||
|
#when download is complete, validates request returned without error and sets icon
|
||||||
|
icon_request.finished.connect(lambda: self._set_icon(tree_item, icon_request))
|
||||||
|
|
||||||
|
def _set_icon(self, tree_item, request):
|
||||||
|
"""Validates a network request and sets its result as the icon of a QTreeWidgetItem.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
tree_item {QTreeWidgetItem} -- A tree widget item to set the icon of
|
||||||
|
request {QNetworkReply} -- The results of the HTTP GET request whose data will be used as the icon for tree_item
|
||||||
|
"""
|
||||||
|
if not request.error():
|
||||||
|
pixmap = QtGui.QPixmap()
|
||||||
|
pixmap.loadFromData(request.readAll())
|
||||||
|
tree_item.setIcon(0, QtGui.QIcon(pixmap))
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<enum>QWizard::ClassicStyle</enum>
|
<enum>QWizard::ClassicStyle</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="options">
|
<property name="options">
|
||||||
<set>QWizard::IndependentPages|QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage|QWizard::NoCancelButtonOnLastPage</set>
|
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage|QWizard::NoCancelButtonOnLastPage</set>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWizardPage" name="intro_page">
|
<widget class="QWizardPage" name="intro_page">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@@ -135,10 +135,34 @@ p, li { white-space: pre-wrap; }
|
|||||||
<widget class="QWizardPage" name="selection_page">
|
<widget class="QWizardPage" name="selection_page">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeWidget" name="treeWidget">
|
<widget class="QTreeWidget" name="selection_menu">
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::NoSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="animated">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
<attribute name="headerDefaultSectionSize">
|
<attribute name="headerDefaultSectionSize">
|
||||||
<number>300</number>
|
<number>300</number>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
<attribute name="headerShowSortIndicator" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
<column>
|
<column>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Name</string>
|
<string>Name</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user