Source code for schrodinger.ui.qt.appframework2.wizards
import enum
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import basewidgets
from schrodinger.ui.qt.appframework2 import settings
ACTION = 'wizard_action'
Actions = enum.Enum('Action', ['Next', 'Back', 'Cancel'])
[docs]class BaseWizardPanel(settings.SettingsPanelMixin, basewidgets.BaseWidget):
finished = QtCore.pyqtSignal()
[docs] def initSetOptions(self):
super(BaseWizardPanel, self).initSetOptions()
self.last_action = None
self.panel_settings.append((ACTION, 'last_action'))
[docs] def initSetUp(self):
super(BaseWizardPanel, self).initSetUp()
self.prev_panel_state = None
self.back_btn = QtWidgets.QPushButton('Back')
self.next_btn = QtWidgets.QPushButton('Next')
self.cancel_btn = QtWidgets.QPushButton('Cancel')
self.back_btn.clicked.connect(self.onBackClicked)
self.next_btn.clicked.connect(self.onNextClicked)
self.cancel_btn.clicked.connect(self.onCancelClicked)
[docs] def initLayOut(self):
super(BaseWizardPanel, self).initLayOut()
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.addWidget(self.back_btn)
self.button_layout.addStretch()
self.button_layout.addWidget(self.next_btn)
self.button_layout.addWidget(self.cancel_btn)
self.bottom_layout.addLayout(self.button_layout)
[docs] def initSetDefaults(self):
super(BaseWizardPanel, self).initSetDefaults()
self._configurePanelSettings()
[docs] def onBackClicked(self):
self.last_action = Actions.Back
if self.processBack() is False:
return
self.close()
[docs] def onNextClicked(self):
if self.processNext() is False:
return # don't go to the next panel
self.last_action = Actions.Next
self.close()
[docs] def onCancelClicked(self):
self.last_action = Actions.Cancel
self.close()
[docs] def run(self, prev_panel_state=None):
"""
Runs the panel. If the panel is being invoked by clicking "Next" on the
previous panel, pass in the previous panel's state so it can be
processed.
:param prev_panel_state: the previous panel's state
:type prev_panel_state: settings.PanelState or None
"""
if prev_panel_state:
self.processPrevPanel(prev_panel_state)
self.prev_panel_state = prev_panel_state
# If we exit the panel in any other way than hitting "Back" or "Next"
# it will be treated as a "Cancel" by default.
self.last_action = Actions.Cancel
self.saved_settings = self.getSettings()
super(BaseWizardPanel, self).run()
[docs] def closeEvent(self, event):
super(BaseWizardPanel, self).closeEvent(event)
if self.last_action == Actions.Cancel:
self.applySettings(self.saved_settings)
self.finished.emit()
#===========================================================================
# Main overrides - implement in child class
#===========================================================================
[docs] def processPrevPanel(self, state):
"""
Override this method to receive the settings from the previous panel
and processes them appropriately.
:param state: the state from the previous panel
:type state: settings.PanelState
"""
[docs] def processBack(self):
"""
Override this method to perform necessary actions when going back
"""
[docs] def processNext(self):
"""
Override this method to perform necessary actions when going to next
A return value of False will stay in the current panel, any other
value will continue to the next.
"""
[docs]class MultiPanelWizard(QtCore.QObject):
"""
The MultiPanelWizard allows a series of BaseWizardPanel instances to be
chained together to create a wizard. It manages navigation between the
panels as well as passing information from one panel to the next.
This class can be instantiated and used directly, or it can be subclassed
for more complex cases.
"""
finished = QtCore.pyqtSignal(object)
_singleton = None
[docs] @classmethod
def panel(cls, run=True):
"""
Launch a singleton instance of this class. If the wizard has already
been instantiated, the existing wizard instance will be re-opened and
brought to the front.
:param run: Whether to launch the panel
:type run: bool
:return: The singleton panel instance
:rtype: MultiPanelWizard
"""
if cls._singleton is None or not isinstance(cls._singleton, cls):
# The isinstance check covers cases of panel inheritance
cls._singleton = cls()
if run:
if cls._singleton.current_panel_index is None:
cls._singleton.run()
else:
panel = cls._singleton.currentPanel()
panel.show()
panel.raise_()
return cls._singleton
[docs] def __init__(self):
super(MultiPanelWizard, self).__init__()
self.panels = []
self.current_panel_index = None
self.application = QtWidgets.QApplication.instance()
if not self.application:
self.application = QtWidgets.QApplication([])
self.setup()
[docs] def addPanel(self, panel):
"""
Add a panel to the wizard. Panels are chained in the order they are
added.
:param panel: the panel to be added to the wizard's chain
:type panel: BaseWizardPanel
"""
self.panels.append(panel)
def _configurePanels(self):
"""
Adjusts the panels in the context of the chain - i.e. remove the "Back"
button from the first panel and change the "Next" button on the last
panel to "Finish". This should only be called after all the panels have
been added.
"""
first_panel = self.panels[0]
last_panel = self.panels[-1]
first_panel.back_btn.setVisible(False)
last_panel.next_btn.setText('Finish')
for panel in self.panels:
panel.finished.connect(self.processPanel)
[docs] def run(self):
"""
Run the wizard.
"""
self.current_panel_index = 0
self.last_panel_state = None
self._configurePanels()
self.startup()
self.runCurrentPanel()
[docs] def currentPanel(self):
"""
Returns the current panel or None if the wizard has not been started.
"""
if self.current_panel_index is None:
return None
return self.panels[self.current_panel_index]
[docs] def runCurrentPanel(self):
"""
Runs the current panel.
"""
panel = self.currentPanel()
panel.run(self.last_panel_state)
[docs] def processPanel(self):
"""
This method will get called whenever a panel is dismissed (via Next,
Back, or Cancel) and depending on the action chosen, will update the
current panel index to the appropriate value and run it or terminate the
wizard.
"""
panel = self.currentPanel()
if panel is None:
return
state = panel.getPanelState()
action = panel.last_action
if action == Actions.Next:
self.current_panel_index += 1
self.last_panel_state = state
elif action == Actions.Back:
self.current_panel_index -= 1
self.last_panel_state = None
if (action == Actions.Cancel or
self.current_panel_index >= len(self.panels) or
self.current_panel_index < 0):
self.last_panel_state = None
self.postProcess()
self.finished.emit(self)
return
self.runCurrentPanel()
[docs] def quit(self):
"""
Exit the wizard
"""
self.currentPanel().onCancelClicked()
#===========================================================================
# Overrides
#===========================================================================
[docs] def setup(self):
"""
If subclassing, override this method to add logic to be run during
initialization, such as adding wizard panels.
"""
[docs] def startup(self):
"""
If subclassing, override this method to add logic to be run right before
starting the wizard.
"""
[docs] def postProcess(self):
"""
If subclassing, override this method to add logic to be run after the
wizard has run. This method will be called whether the user finishes the
wizard or stops midway. To determine what to do, check the value of
self.current_panel_index.
"""