Skip to content

Commit

Permalink
Update Add/Update SpineOpt Wizard
Browse files Browse the repository at this point in the history
Related to issue #3002
  • Loading branch information
ptsavol committed Nov 8, 2024
1 parent 18814fa commit 34573ae
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 31 deletions.
2 changes: 1 addition & 1 deletion spinetoolbox/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
LATEST_PROJECT_VERSION = 13

# For the Add/Update SpineOpt wizard
REQUIRED_SPINE_OPT_VERSION = "0.8.3"
REQUIRED_SPINE_OPT_VERSION = "0.9.0"

# Invalid characters for directory names
# NOTE: "." is actually valid in a directory name but this is
Expand Down
10 changes: 6 additions & 4 deletions spinetoolbox/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1062,24 +1062,26 @@ def file_is_valid(parent, file_path, msgbox_title, extra_check=None):
return True


def dir_is_valid(parent, dir_path, msgbox_title):
def dir_is_valid(parent, dir_path, msgbox_title, msg=None):
"""Checks that given path is a directory. Needed in
SettingsWdiget and KernelEditor because the QLineEdits
SettingsWidget and KernelEditor because the QLineEdits
are editable. Returns True when dir_path is an empty string so that
we can use default values (e.g. from line edit place holder text)
we can use default values (e.g. from line edit placeholder text)
Args:
parent (QWidget): Parent widget for the message box
dir_path (str): Directory path to check
msgbox_title (str): Message box title
msg (str): Warning message
Returns:
bool: True if given path is an empty string or if path is an existing directory, False otherwise
"""
if not msg:
msg = "Please select a valid directory"
if dir_path == "":
return True
if not os.path.isdir(dir_path):
msg = "Please select a valid directory"
# noinspection PyCallByClass, PyArgumentList
QMessageBox.warning(parent, msgbox_title, msg)
return False
Expand Down
106 changes: 83 additions & 23 deletions spinetoolbox/widgets/add_up_spine_opt_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""Classes for custom QDialogs for julia setup."""
from enum import IntEnum, auto
from PySide6.QtCore import Qt, Slot
from PySide6.QtGui import QCursor
from PySide6.QtGui import QCursor, QTextCursor
from PySide6.QtWidgets import (
QCheckBox,
QFileDialog,
Expand Down Expand Up @@ -72,6 +72,7 @@ def __init__(self, parent, julia_exe, julia_project):
self.setPage(_PageId.ADD_UP_SPINE_OPT_AGAIN, AddUpSpineOptAgainPage(self))
self.setPage(_PageId.TOTAL_FAILURE, TotalFailurePage(self))
self.setStartId(_PageId.INTRO)
self.setOption(QWizard.WizardOption.NoCancelButtonOnLastPage)


class IntroPage(QWizardPage):
Expand Down Expand Up @@ -238,11 +239,10 @@ def initializePage(self):
processing, code, process = {
"add": (
"Installing",
'using Pkg; pkg"registry add General https://github.com/spine-tools/SpineJuliaRegistry.git"; '
'pkg"add SpineOpt"',
'using Pkg; Pkg.Registry.add("General"); Pkg.add("SpineOpt")',
"installation",
),
"update": ("Updating", 'using Pkg; pkg"up SpineOpt"', "update"),
"update": ("Updating", 'using Pkg; Pkg.update("SpineOpt")', "update"),
}[self.wizard().required_action]
self.setTitle(f"{processing} SpineOpt")
julia_exe = self.field("julia_exe")
Expand Down Expand Up @@ -331,10 +331,23 @@ def __init__(self, parent):
self.setTitle("Troubleshooting")
msg = "Select your problem from the list."
self._button1 = QRadioButton("None of the below")
self._button2 = QRadioButton("Installing SpineOpt fails with one of the following messages (or similar):")
msg2a = MonoSpaceFontTextBrowser(self)
msg2b = MonoSpaceFontTextBrowser(self)
msg2a.append(
self._button2 = QRadioButton("Installing SpineOpt fails with the following message (or similar):")
msg2 = MonoSpaceFontTextBrowser(self)
msg2.append(
"""
\u22ee<br>
error: GitError(Code:ERROR, Class:SSL, Your Julia is built with a SSL/TLS engine that libgit2
doesn't know how to configure to use a file or directory of certificate authority roots,
but your environment specifies one via the SSL_CERT_FILE variable. If you believe your
system's root certificates are safe to use, you can `export JULIA_SSL_CA_ROOTS_PATH=""`
in your environment to use those instead.<br>
\u22ee
"""
)
self._button3 = QRadioButton("Installing SpineOpt fails with one of the following messages (or similar):")
msg3a = MonoSpaceFontTextBrowser(self)
msg3b = MonoSpaceFontTextBrowser(self)
msg3a.append(
"""
\u22ee<br>
Updating git-repo `https://github.com/spine-tools/SpineJuliaRegistry`<br>
Expand All @@ -343,7 +356,7 @@ def __init__(self, parent):
\u22ee
"""
)
msg2b.append(
msg3b.append(
"""
\u22ee<br>
Updating git-repo `https://github.com/spine-tools/SpineJuliaRegistry`<br>
Expand All @@ -352,9 +365,9 @@ def __init__(self, parent):
\u22ee
"""
)
self._button3 = QRadioButton("On Windows 7, installing SpineOpt fails with the following message (or similar):")
msg3 = MonoSpaceFontTextBrowser(self)
msg3.append(
self._button4 = QRadioButton("On Windows 7, installing SpineOpt fails with the following message (or similar):")
msg4 = MonoSpaceFontTextBrowser(self)
msg4.append(
"""
\u22ee<br>
Downloading artifact: OpenBLAS32<br>
Expand All @@ -375,11 +388,14 @@ def __init__(self, parent):
layout.addWidget(self._button1)
layout.addStretch()
layout.addWidget(self._button2)
layout.addWidget(msg2a)
layout.addWidget(msg2b)
layout.addWidget(msg2)
layout.addStretch()
layout.addWidget(self._button3)
layout.addWidget(msg3)
layout.addWidget(msg3a)
layout.addWidget(msg3b)
layout.addStretch()
layout.addWidget(self._button4)
layout.addWidget(msg4)
layout.addStretch()
button_view_log = QPushButton("View log")
widget_view_log = QWidget()
Expand All @@ -388,16 +404,24 @@ def __init__(self, parent):
layout_view_log.addWidget(button_view_log)
layout.addWidget(widget_view_log)
layout.addStretch()
cursor = QTextCursor()
cursor.movePosition(QTextCursor.MoveOperation.Start, QTextCursor.MoveMode.MoveAnchor)
msg2.setTextCursor(cursor) # Scroll to the beginning of the document
msg3a.setTextCursor(cursor)
msg3b.setTextCursor(cursor)
msg4.setTextCursor(cursor)
self.registerField("problem1", self._button1)
self.registerField("problem2", self._button2)
self.registerField("problem3", self._button3)
self.registerField("problem4", self._button4)
self._button1.toggled.connect(lambda _: self.completeChanged.emit())
self._button2.toggled.connect(lambda _: self.completeChanged.emit())
self._button3.toggled.connect(lambda _: self.completeChanged.emit())
self._button4.toggled.connect(lambda _: self.completeChanged.emit())
button_view_log.clicked.connect(self._show_log)

def isComplete(self):
return self.field("problem1") or self.field("problem2") or self.field("problem3")
return self.field("problem1") or self.field("problem2") or self.field("problem3") or self.field("problem4")

@Slot(bool)
def _show_log(self, _=False):
Expand Down Expand Up @@ -426,24 +450,25 @@ def initializePage(self):
self._initialize_page_solution2()
elif self.field("problem3"):
self._initialize_page_solution3()
elif self.field("problem4"):
self._initialize_page_solution4()

def _initialize_page_solution1(self):
self.setFinalPage(False)
action = {"add": "Install SpineOpt", "update": "Update SpineOpt"}[self.wizard().required_action]
julia = self.field("julia_exe")
env = self.field("julia_project")
if not env:
install_cmds = f"""
<span style="color:green;">julia> </span><span>import Pkg</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add("General")</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add(Pkg.RegistrySpec(url="https://github.com/spine-tools/SpineJuliaRegistry"))</span><br>
<span style="color:green;">julia> </span><span>Pkg.add("SpineOpt")</span><br>"""
else:
install_cmds = f"""
<span style="color:green;">julia> </span><span>import Pkg</span><br>
<span style="color:green;">julia> </span><span>cd("{env}")</span><br>
<span style="color:green;">julia> </span><span>Pkg.activate(".")</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add("General")</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add(Pkg.RegistrySpec(url="https://github.com/spine-tools/SpineJuliaRegistry"))</span><br>
<span style="color:green;">julia> </span><span>Pkg.add("SpineOpt")</span><br>"""
if not env:
update_cmds = """
Expand Down Expand Up @@ -477,7 +502,42 @@ def _initialize_page_solution1(self):
self.setButtonText(QWizard.WizardButton.CommitButton, action)

def _initialize_page_solution2(self):
action = {"add": "Install SpineOpt", "update": "Update SpineOpt"}[self.wizard().required_action]
self.setFinalPage(True)
julia = self.field("julia_exe")
env = self.field("julia_project")
self.setTitle("<b>JULIA_SSL_CA_ROOTS_PATH</b> environment variable missing")
description = (
"<p>You are most likely running Toolbox in a Conda environment and the issue "
"you're facing is due to a missing environment variable. The simplest solution "
"is to open the Julia REPL from the Anaconda Prompt, add the environment variable, "
"and then install SpineOpt.</p>"
"<p>To do this, open your Anaconda prompt and start the Julia REPL using "
f"command:<br><br><i>{julia}</i><br><br>In the Julia REPL, enter the commands below (gray text, "
"not the green one). After entering the commands, SpineOpt should be installed. If you run into "
"other problems, please <a href=https://github.com/spine-tools/SpineOpt.jl/issues>open an issue "
"with SpineOpt</a>.</p>"
)
if not env:
install_cmds = f"""
<span style="color:green;">julia> </span><span>using Pkg</span><br>
<span style="color:green;">julia> </span><span>ENV["JULIA_SSL_CA_ROOTS_PATH"] = ""</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add("General")</span><br>
<span style="color:green;">julia> </span><span>Pkg.add("SpineOpt")</span><br>"""
else:
install_cmds = f"""
<span style="color:green;">julia> </span><span>using Pkg</span><br>
<span style="color:green;">julia> </span><span>cd("{env}")</span><br>
<span style="color:green;">julia> </span><span>Pkg.activate(".")</span><br>
<span style="color:green;">julia> </span><span>ENV["JULIA_SSL_CA_ROOTS_PATH"] = ""</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add("General")</span><br>
<span style="color:green;">julia> </span><span>Pkg.add("SpineOpt")</span><br>"""
cmd_browser = MonoSpaceFontTextBrowser(self)
cmd_browser.append(install_cmds)
self.layout().addWidget(HyperTextLabel(description))
self.layout().addWidget(cmd_browser)

def _initialize_page_solution3(self):
self.setFinalPage(True)
julia = self.field("julia_exe")
self.setTitle("Reset Julia General Registry")
description = (
Expand All @@ -491,16 +551,15 @@ def _initialize_page_solution2(self):
)
cmds = f"""
<span style="color:green;">julia> </span><span>import Pkg</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.rm("SpineRegistry")</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.rm("General")</span><br>
<span style="color:green;">julia> </span><span>Pkg.Registry.add()</span><br>"""
cmd_browser = MonoSpaceFontTextBrowser(self)
cmd_browser.append(cmds)
self.layout().addWidget(HyperTextLabel(description))
self.layout().addWidget(cmd_browser)
self.setButtonText(QWizard.WizardButton.CommitButton, action)

def _initialize_page_solution3(self):
def _initialize_page_solution4(self):
self.setFinalPage(True)
action = {"add": "Install SpineOpt", "update": "Update SpineOpt"}[self.wizard().required_action]
self.setTitle("Update Windows Management Framework")
description = (
Expand All @@ -514,9 +573,10 @@ def _initialize_page_solution3(self):
"</ul></p>"
)
self.layout().addWidget(HyperTextLabel(description))
self.setButtonText(QWizard.WizardButton.CommitButton, action)

def nextId(self):
if self.field("problem2") or self.field("problem3") or self.field("problem4"):
return -1
return _PageId.ADD_UP_SPINE_OPT_AGAIN


Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/widgets/custom_qtextbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, parent):
self._frame_format.setBorder(1)
self._selected_frame_format = QTextFrameFormat(self._frame_format)
palette = self.palette()
self._selected_frame_format.setBackground(QBrush(palette.color(QPalette.Highlight).darker()))
self._selected_frame_format.setBackground(QBrush(palette.color(QPalette.ColorRole.Highlight).darker()))
self._executions_menu.aboutToShow.connect(self._populate_executions_menu)
self._executions_menu.triggered.connect(self._select_execution)

Expand Down Expand Up @@ -226,12 +226,13 @@ def set_item_log_selected(self, selected):
frame.setFrameFormat(frame_format)


class MonoSpaceFontTextBrowser(CustomQTextBrowser):
class MonoSpaceFontTextBrowser(QTextBrowser):
def __init__(self, parent):
"""
Args:
parent (QWidget): Parent widget
"""
super().__init__(parent=parent)
font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
self.setStyleSheet(TEXTBROWSER_SS)
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
self.setFont(font)
6 changes: 6 additions & 0 deletions spinetoolbox/widgets/settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ def _show_install_julia_wizard(self, _=False):
def _show_add_up_spine_opt_wizard(self, _=False):
"""Opens the add/update SpineOpt wizard."""
use_julia_jupyter_console, julia_path, julia_project_path, julia_kernel = self._get_julia_settings()
if julia_project_path != "@." and not dir_is_valid(
self,
julia_project_path,
"Invalid Julia Project",
"Julia project must be an existing directory, @., or empty"):
return
settings = QSettings("SpineProject", "AddUpSpineOptWizard")
settings.setValue("appSettings/useJuliaKernel", use_julia_jupyter_console)
settings.setValue("appSettings/juliaPath", julia_path)
Expand Down

0 comments on commit 34573ae

Please sign in to comment.