Source code for schrodinger.ui.qt.appframework2.baseapp

import os
import sys

import psutil

import schrodinger
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui import maestro_ui
from schrodinger.ui.qt import messagebox
from schrodinger.ui.qt import style
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt import utils as qt_utils
from schrodinger.ui.qt.appframework2 import debug
from schrodinger.ui.qt.appframework2 import validation
from schrodinger.utils import qapplication

maestro = schrodinger.get_maestro()

MODE_MAESTRO = 1
MODE_STANDALONE = 2
MODE_SUBPANEL = 3
MODE_CANVAS = 4

SETPANELOPTIONS = 0
SETUP = 1
SETDEFAULTS = 2
LAYOUT = 3

PANEL_HAS_NO_TITLE = 'PANEL HAS NO TITLE'

DEV_SYSTEM = 'SCHRODINGER_SRC' in os.environ


[docs]class DockWidget(maestro_ui.MM_QDockWidget): """ This class propagates closeEvents to the App so that panel knows that it is closed. Otherwise maestro callbacks will not get removed. """
[docs] def __init__(self, object_name, dockarea): super().__init__(object_name, dockarea, True, True)
[docs] def closeEvent(self, event): """ Do not close the floating python panel and instead emit a SIGNAL to Maestro which redocks the panel back into main window. This is a temporary workaround for Qt5 redock issues """ if sys.platform.startswith("darwin") and self.isFloating(): # If the panel is currently undocked, re-dock it instead of # closing it: event.ignore() self.pythonPanelAboutToBeClosed.emit(self) else: self.hide() self.widget().closeEvent(event)
Super = QtWidgets.QWidget
[docs]class BasePanel(QtWidgets.QWidget): # Emitted when the panel is closed. Expected by KNIME. gui_closed = QtCore.pyqtSignal()
[docs] def __init__(self, stop_before=None, parent=None, in_knime=False, workspace_st_file=None): """ :param stop_before: Exit the constructor before specified step. :type stop_before: int :param parent: Parent widget, if any. :type parent: QWidget :param in_knime: Whether we are currently running under KNIME - a mode in which input selector is hidden, optionally a custom Workspace Structure is specified, and buttom bar has OK & Cancel buttons. :type in_knime: bool :param workspace_st_file: Structure to be returned by getWorkspaceStructure() when in_knime is True. :type workspace_st_file: bool """ self.application = None self.run_mode = None try: object.__getattribute__(self, "in_knime") except AttributeError: self.in_knime = in_knime # TODO: Convert to run_mode eventually # This assignment is done in the constructor, so that panels could # use getWorkspaceStructure() from setup() methods. self.workspace_st_file = workspace_st_file self.startUp() self.dock_widget = None super(BasePanel, self).__init__(parent=parent) style.apply_styles() style.apply_legacy_spacing_style(self) if stop_before == SETPANELOPTIONS: return self.setPanelOptions() if stop_before == SETUP: return self.setup() if stop_before == SETDEFAULTS: return self.setDefaults() if stop_before == LAYOUT: return self.layOut()
[docs] def startUp(self): if self.application: return # Prevents startUp from being run twice procname = psutil.Process().name() if maestro: self.application = QtWidgets.QApplication.instance() self.run_mode = MODE_MAESTRO elif procname.startswith("canvas"): self.application = QtWidgets.QApplication.instance() self.run_mode = MODE_CANVAS else: self.application = QtWidgets.QApplication.instance() if not self.application: self.application = qapplication.get_application() self.run_mode = MODE_STANDALONE else: self.run_mode = MODE_SUBPANEL
[docs] def setPanelOptions(self): self.maestro_dockable = False self.dock_area = Qt.RightDockWidgetArea self.title = PANEL_HAS_NO_TITLE self.ui = None self.allowed_run_modes = (MODE_MAESTRO, MODE_STANDALONE, MODE_SUBPANEL, MODE_CANVAS)
[docs] def setup(self): # FIXME re-factor to remove duplication if self.ui: self.ui_widget = QtWidgets.QWidget(self) self.ui.setupUi(self.ui_widget) elif hasattr(self, 'setupUi'): self.ui_widget = QtWidgets.QWidget(self) self.ui_widget = QtWidgets.QWidget(self) self.setupUi(self.ui_widget)
[docs] def setDefaults(self): pass
[docs] def setStyleSheet(self, stylesheet): # Override to ensure that style sheet changes after the first are # actually applied require_refresh = bool(self.styleSheet()) super().setStyleSheet(stylesheet) if require_refresh: qt_utils.update_widget_style(self)
[docs] def layOut(self): self.panel_layout = swidgets.SVBoxLayout(self) self.panel_layout.setContentsMargins(2, 2, 2, 2) if self.ui: self.panel_layout.addWidget(self.ui_widget) if self.maestro_dockable and maestro: # It is possible that currently allow docking preference is OFF, # but user can turn it ON later. So, always create docking widget. self._layOutDockWidget()
def _layOutDockWidget(self): """ Creates a dock widget, which will act as an outer container for this panel. This should only be called when the panel is meant to be dockable. The window title is also copied from the panel to the dock widget. """ object_name = self.objectName() + '_dockwidget' self.dock_widget = DockWidget(object_name, self.dock_area) self.dock_widget.setObjectName(object_name) self.dock_widget.setWidget(self) self.dock_widget.setWindowTitle(self.windowTitle()) self.dock_widget.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) if maestro: # This function checks docking location preference and sets # the dock widget accordingly. self.dock_widget.setupDockWidget.emit() hub = maestro_ui.MaestroHub.instance() hub.preferenceChanged.connect(self._dockPrefChanged) def _dockPrefChanged(self, option): """ Slot to reconfigure dock panel due to dock preference changes. Docking preference change can be one of the following: - Allow docking - Disallow docking - Allow docking in main window - Allow docking in floating window User can switch between above mentioned options, so dock panel needs to be reconfigured accordingly. :param option: Name of the changed Maestro preference. :type option: str """ if option in ["docklocation", "dockingpanels"]: self.dock_widget.reconfigureDockWidget.emit(self)
[docs] def showEvent(self, show_event): """ Override the normal processing when the panel is shown. """ # Maestro crash when undocking and again redocking panel. # Fix for MAE-25846, MAE-36228 if self.maestro_dockable and self.dock_widget: self.dock_widget.raise_() QtWidgets.QWidget.showEvent(self, show_event)
if sys.platform.startswith("linux"):
[docs] def setVisible(self, set_visible): qt_utils.linux_setVisible_positioning_workaround( self, super(), set_visible)
[docs] def runMode(self): return self.run_mode
[docs] def run(self): runmode = self.runMode() mode_allowed = True try: mode_allowed = runmode in self.allowed_run_modes except TypeError: mode_allowed = (runmode == self.allowed_run_modes) if runmode == MODE_STANDALONE: if not mode_allowed: self.error('This panel cannot be run outside of Maestro.') return return self.runStandalone() if runmode == MODE_SUBPANEL: if not mode_allowed: self.error('This panel cannot be run as a subpanel.') return return self.runSubpanel() if runmode == MODE_MAESTRO: if not mode_allowed: self.error('This panel cannot be run in Maestro.') return return self.runMaestro() if runmode == MODE_CANVAS: if not mode_allowed: self.error('This panel cannot be run in Canvas.') return return self.runCanvas()
[docs] def cleanup(self): if self.maestro_dockable: self.dock_widget = None
[docs] def closeEvent(self, event): """ Close panel and quit application if this is a top level standalone window. Otherwise, hide the panel. """ if self.runMode() in (MODE_STANDALONE, MODE_CANVAS): self.cleanup() Super.closeEvent(self, event) else: # When running as a sub-panel, only hide self.hide() self.gui_closed.emit()
[docs] def runSubpanel(self): if not maestro or self.parent() is not maestro.get_main_window(): if not self.dock_widget: self.setWindowFlags(self.windowFlags() | Qt.Window) if not self.isVisible(): # QWidget.show can be very slow (2s) so call it only if panel # is currently not visible. This speeds up the call to panel() # method. self.show() if self.dock_widget: # only initiated when maestro_dockable is True and # preference allows docking self.dock_widget.show() self.dock_widget.raise_() self.raise_() self.activateWindow()
[docs] def runMaestro(self): """ This can be extended in derived classes to perform maestro-only tasks such as setting up the mini-monitor or connecting maestro callbacks """ if not self.dock_widget: top = (maestro.get_command_option('prefer', 'showpanelsontop') == 'True') if top: self.setParent(maestro.get_main_window()) self.setWindowFlags(Qt.Tool) else: self.setParent(None) self.runSubpanel()
[docs] def runCanvas(self): """ This handles Canvas-specific logic """ # if parentless, we need to clear this attribute if not self.parent(): self.setAttribute(Qt.WA_QuitOnClose, False) self.runSubpanel()
[docs] def runStandalone(self): self.show() self.application.setQuitOnLastWindowClosed(True) self.application.exec()
[docs] def show(self): super().show() self.setAttribute(QtCore.Qt.WA_Resized)
[docs] def parent(self): try: parent = Super.parent(self) except RuntimeError: parent = None return parent
def __str__(self): return '%s.%s' % (self.__module__, self.__class__.__name__) #========================================================================= # Properties #========================================================================= @property def title(self): return self.windowTitle() @title.setter def title(self, title): if self.dock_widget: self.dock_widget.setWindowTitle(title) return self.setWindowTitle(title) #========================================================================= # Copied from original appframework #=========================================================================
[docs] @qt_utils.remove_wait_cursor def warning(self, text, preferences=None, key=""): """ Display a warning dialog with the specified text. If preferences and key are both supplied, then the dialog will contain a "Don't show this again" checkbox. Future invocations of this dialog with the same preferences and key values will obey the user's show preference. :type text: str :param text: The information to display in the dialog :param preferences: obsolete; ignored. :type key: str :param key: The key to store the preference under. If specified, a "Do not show again" checkbox will be rendered in the dialog box. :rtype: None """ messagebox.show_warning(parent=self, text=text, save_response_key=key)
[docs] @qt_utils.remove_wait_cursor def info(self, text, preferences=None, key=""): """ Display an information dialog with the specified text. If preferences and key are both supplied, then the dialog will contain a "Don't show this again" checkbox. Future invocations of this dialog with the same preferences and key values will obey the user's show preference. :type text: str :param text: The information to display in the dialog :param preferences: obsolete; ignored. :type key: str :param key: The key to store the preference under. If specified, a "Do not show again" checkbox will be rendered in the dialog box. :rtype: None """ messagebox.show_info(parent=self, text=text, save_response_key=key)
[docs] @qt_utils.remove_wait_cursor def error(self, text, preferences=None, key=""): """ Display an error dialog with the specified text. If preferences and key are both supplied, then the dialog will contain a "Don't show this again" checkbox. Future invocations of this dialog with the same preferences and key values will obey the user's show preference. :type text: str :param text: The information to display in the dialog :param preferences: obsolete; ignored. :type key: str :param key: The key to store the preference under. If specified, a "Do not show again" checkbox will be rendered in the dialog box. :rtype: None """ messagebox.show_error(parent=self, text=text, save_response_key=key)
[docs] @qt_utils.remove_wait_cursor def question(self, msg, button1="OK", button2="Cancel", title="Question"): """ Display a prompt dialog window with specified text. Returns True if first button (default OK) is pressed, False otherwise. """ return messagebox.show_question(parent=self, text=msg, yes_text=button1, no_text=button2, add_cancel_btn=False)
[docs] def setWaitCursor(self, app_wide=True): """ Set the cursor to the wait cursor. This will be an hourglass, clock or similar. Call restoreCursor() to return to the default cursor. :type app_wide: bool :param app_wide: If True then this will apply to the entire application (including Maestro if running there). If False then this will apply only to this panel. """ if app_wide: self.application.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) else: self.setCursor(QtGui.QCursor(Qt.WaitCursor))
[docs] def restoreCursor(self, app_wide=True): """ Restore the application level cursor to the default. If 'app_wide' is True then if will be restored for the entire application, if it's False, it will be just for this panel. :type app_wide: bool :param app_wide: If True then this will restore the cursor for the entire application (including Maestro if running there). If False then this will apply only to this panel. """ if app_wide: self.application.restoreOverrideCursor() else: self.setCursor(QtGui.QCursor(Qt.ArrowCursor))
#=========================================================================== # Debugging #===========================================================================
[docs] def keyPressEvent(self, e): if not DEV_SYSTEM: return if e.key() == Qt.Key_F5: debug.start_gui_debug(self, globals(), locals())
[docs]class ValidatedPanel(BasePanel, validation.ValidationMixin):
[docs] def __init__(self, *args, **kwargs): validation.ValidationMixin.__init__(self) super(ValidatedPanel, self).__init__(*args, **kwargs)