Skip to content

Commit

Permalink
Merge pull request #3097 from SasView/2952-make-documentation-viewer-…
Browse files Browse the repository at this point in the history
…accessible

Make Documentation Viewer Accessible Throughout GUI
  • Loading branch information
krzywon authored Sep 18, 2024
2 parents 79e22f3 + f6719a1 commit 324e1a6
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 74 deletions.
21 changes: 17 additions & 4 deletions src/sas/qtgui/MainWindow/GuiManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from sas.qtgui.Utilities.ResultPanel import ResultPanel
from sas.qtgui.Utilities.OrientationViewer.OrientationViewer import show_orientation_viewer
from sas.qtgui.Utilities.HidableDialog import hidable_dialog
from sas.qtgui.Utilities.DocViewWidget import DocViewWindow
from sas.qtgui.Utilities.DocRegenInProgess import DocRegenProgress
from sas.qtgui.Utilities.Reports.ReportDialog import ReportDialog
from sas.qtgui.Utilities.Preferences.PreferencesPanel import PreferencesPanel
Expand Down Expand Up @@ -369,10 +368,13 @@ def fileWasRead(self, data):
"""
pass

def showHelp(self, url):
@classmethod
def showHelp(cls, url):
"""
Open a local url in the default browser
"""
counter = 1
window_name = "help_window"
# Remove leading forward slashes from relative paths to allow easy Path building
if isinstance(url, str):
url = url.lstrip("//")
Expand All @@ -382,8 +384,19 @@ def showHelp(self, url):
else:
url_abs = Path(url)
try:
# Help window shows itself
self.helpWindow = DocViewWindow(parent=self, source=url_abs)
# In order to have multiple help windows open simultaneously, we need to create a new class variable
# If we just reassign the old one, the old window will be destroyed

# Have we found a name not assigned to a window?
potential_help_window = getattr(cls, window_name, None)
while potential_help_window and potential_help_window.isVisible():
window_name = f"help_window_{counter}"
potential_help_window = getattr(cls, window_name, None)
counter += 1

# Assign new variable to the GuiManager
setattr(cls, window_name, GuiUtils.showHelp(url_abs))

except Exception as ex:
logging.warning("Cannot display help. %s" % ex)

Expand Down
18 changes: 14 additions & 4 deletions src/sas/qtgui/Perspectives/Fitting/FittingOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def __init__(self, config=None):
# Assign appropriate validators
self.assignValidators()

# Assign signals
self.addSignals()
self.cmdHelp.setText(f'Help: {self.cbAlgorithm.currentText()}')

# To prevent errors related to parent, connect the combo box changes once the widget is instantiated
self.cbAlgorithm.currentIndexChanged.connect(self.onAlgorithmChange)
self.cbAlgorithmDefault.currentIndexChanged.connect(self.onDefaultAlgorithmChange)
Expand All @@ -87,6 +91,10 @@ def _restoreFromConfig(self):
name = [n.name for n in fitters.FITTERS if n.id == self.current_fitter_id][0]
self.cbAlgorithm.setCurrentIndex(self.cbAlgorithm.findText(name))
self._algorithm_change(self.cbAlgorithm.currentIndex())

def addSignals(self):
self.cmdHelp.clicked.connect(self.onHelp)
self.cbAlgorithm.currentIndexChanged.connect(lambda: self.cmdHelp.setText(f"{self.cmdHelp.text().split(' ')[0]} {self.cbAlgorithm.currentText()}"))

def assignValidators(self):
"""
Expand Down Expand Up @@ -222,15 +230,17 @@ def onHelp(self):
"""
Show the "Fitting options" section of help
"""
tree_location = GuiUtils.HELP_DIRECTORY_LOCATION
tree_location += "/user/qtgui/Perspectives/Fitting/"
from sas.qtgui.MainWindow.GuiManager import GuiManager
tree_location = "/user/qtgui/Perspectives/Fitting/"

# Actual file anchor will depend on the combo box index
# Note that we can be clusmy here, since bad current_fitter_id
# will just make the page displayed from the top
helpfile = "optimizer.html#fit-" + self.current_fitter_id
selected_fit_algorithm = self.cbAlgorithm.currentText()
fitter_id = [n.id for n in fitters.FITTERS if n.name == selected_fit_algorithm][0]
helpfile = "optimizer.html#fit-" + fitter_id
help_location = tree_location + helpfile
webbrowser.open('file://' + os.path.realpath(help_location))
GuiManager.showHelp(help_location)

def widgetFromOption(self, option_id, current_fitter=None):
"""
Expand Down
8 changes: 3 additions & 5 deletions src/sas/qtgui/Perspectives/Fitting/GPUOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,9 @@ def helpButtonClicked(self):
"""
Open the help menu when the help button is clicked
"""

# TODO - in future this should be linked to the local documentation, once there is a robust way of doing that

# Display the page in default browser
webbrowser.open("https://www.sasview.org/docs/user/qtgui/Perspectives/Fitting/gpu_setup.html#device-selection")
from sas.qtgui.MainWindow.GuiManager import GuiManager
help_location = "user/qtgui/Perspectives/Fitting/gpu_setup.html#device-selection"
GuiManager.showHelp(help_location)

def reject(self):
"""
Expand Down
134 changes: 79 additions & 55 deletions src/sas/qtgui/Perspectives/Fitting/UI/FittingOptionsUI.ui
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,26 @@
<string>Fit Algorithms </string>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QComboBox" name="cbAlgorithm">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QStackedWidget" name="stackedWidget">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>4</number>
</property>
<widget class="QWidget" name="page_dream">
<layout class="QGridLayout" name="gridLayout_2">
Expand Down Expand Up @@ -433,16 +446,36 @@
</widget>
<widget class="QWidget" name="page_amoeba">
<layout class="QGridLayout" name="gridLayout_10">
<item row="2" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>382</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Nelder-Mead Simplex</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="0">
<widget class="QLabel" name="label_20">
<item row="2" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Steps:</string>
<string>Simplex radius:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>x tolerance:</string>
</property>
</widget>
</item>
Expand All @@ -453,31 +486,31 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Starts:</string>
<item row="3" column="1">
<widget class="QLineEdit" name="ftol_amoeba">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Used to determine when the fit has reached the point where no significant improvement is expected. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="starts_amoeba">
<item row="2" column="1">
<widget class="QLineEdit" name="radius_amoeba">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tells the optimizer to restart a given number of times. Each time it restarts it uses a random starting point.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The initial size of the simplex, as a portion of the bounds defining the parameter space.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_22">
<item row="1" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Simplex radius:</string>
<string>Starts:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="radius_amoeba">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The initial size of the simplex, as a portion of the bounds defining the parameter space.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item row="0" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Steps:</string>
</property>
</widget>
</item>
Expand All @@ -488,59 +521,50 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="ftol_amoeba">
<item row="4" column="1">
<widget class="QLineEdit" name="xtol_amoeba">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Used to determine when the fit has reached the point where no significant improvement is expected. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>x tolerance:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="xtol_amoeba">
<item row="1" column="1">
<widget class="QLineEdit" name="starts_amoeba">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Used to determine when the fit has reached the point where no significant improvement is expected. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tells the optimizer to restart a given number of times. Each time it restarts it uses a random starting point.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>382</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="cbAlgorithm">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontal_layout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cmdHelp">
<property name="text">
<string>Help</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
Expand Down
16 changes: 10 additions & 6 deletions src/sas/qtgui/Utilities/DocViewWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,18 @@ class DocViewWindow(QtWidgets.QDialog, Ui_DocViewerWindow):
Instantiates a window to view documentation using a QWebEngineViewer widget
"""

def __init__(self, parent=None, source: Path = None):
def __init__(self, source: Path = None):
"""The DocViewWindow class is an HTML viewer built into SasView.
:param parent: Any Qt object with a communicator that can trigger events.
:param source: The Path to the html file.
"""
super(DocViewWindow, self).__init__(parent._parent)
self.parent = parent
# Avoid circular imports by importing the communicate class as a class attribute
#from sas.qtgui.Utilities.GuiUtils import Communicate
from sas.qtgui.Utilities.GuiUtils import Communicate
self.communicate = Communicate()

super(DocViewWindow, self).__init__(None)
self.setupUi(self)
self.setWindowTitle("Documentation Viewer")

Expand All @@ -88,7 +92,7 @@ def initializeSignals(self):
"""Initialize all external signals that will trigger events for the window."""
self.editButton.clicked.connect(self.onEdit)
self.closeButton.clicked.connect(self.onClose)
self.parent.communicate.documentationRegeneratedSignal.connect(self.refresh)
self.communicate.documentationRegeneratedSignal.connect(self.refresh)
self.webEngineViewer.urlChanged.connect(self.updateTitle)

def onEdit(self):
Expand Down Expand Up @@ -263,7 +267,7 @@ def regenerateHtml(self, file_name: PATH_LIKE):
:param file_name: A file-path like object that needs regeneration.
"""
logging.info("Starting documentation regeneration...")
self.parent.communicate.documentationRegenInProgressSignal.emit()
self.communicate.documentationRegenInProgressSignal.emit()
d = threads.deferToThread(self.regenerateDocs, target=file_name)
d.addCallback(self.docRegenComplete)
self.regen_in_progress = True
Expand All @@ -287,6 +291,6 @@ def docRegenComplete(self, return_val):
This method is likely called as a thread call back, but no value is used from that callback return.
"""
self.loadHtml()
self.parent.communicate.documentationRegeneratedSignal.emit()
self.communicate.documentationRegeneratedSignal.emit()
logging.info("Documentation regeneration completed.")
self.regen_in_progress = False
18 changes: 18 additions & 0 deletions src/sas/qtgui/Utilities/GuiUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
from sasdata.dataloader.data_info import Sample, Source, Vector
from sasdata.dataloader.data_info import Detector, Process, TransmissionSpectrum
from sasdata.dataloader.data_info import Aperture, Collimation
from sas.sascalc.doc_regen.makedocumentation import HELP_DIRECTORY_LOCATION, PATH_LIKE
from sas.qtgui.Plotting.Plottables import View
from sas.qtgui.Plotting.Plottables import PlottableTheory1D
from sas.qtgui.Plotting.Plottables import PlottableFit1D
from sas.qtgui.Plotting.Plottables import Text
from sas.qtgui.Plotting.Plottables import Chisq
from sas.qtgui.MainWindow.DataState import DataState
from sas.qtgui.Utilities.DocViewWidget import DocViewWindow

from sas.sascalc.fit.AbstractFitEngine import FResult
from sas.sascalc.fit.AbstractFitEngine import FitData1D, FitData2D
Expand Down Expand Up @@ -1452,3 +1454,19 @@ def enum(*sequential, **named):
"""Create an enumeration object from a list of strings"""
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)

def showHelp(url: PATH_LIKE):
if isinstance(url, str):
url = url.lstrip("//")
url = Path(url)
url_abs = HELP_DIRECTORY_LOCATION / url if str(HELP_DIRECTORY_LOCATION.resolve()) not in str(url.absolute()) else url

try:
help_window = DocViewWindow(source=url_abs)
help_window.show()
help_window.activateWindow()
help_window.setFocus()
# Return the window so the caller can keep it in scope to prevent garbage collection
return help_window
except Exception as ex:
logging.warning(f"Cannot display help: {ex}")

0 comments on commit 324e1a6

Please sign in to comment.