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.
        """