def test_interface_manager(self): manager = InterfaceManager() dialog = manager.createDialogFromName("AlgorithmDialogMockAlgorithm", -1) self.assertTrue(dialog is not None) input_widgets = dialog.findChildren(QLineEdit) self.assertEqual(len(input_widgets), 3)
def open_help_window(self): # Show the help documentation relevant to the plot type. if self.tab_widget_presenters[3] is not None: # If the dialog has the images tab then go to the section on image plots. InterfaceManager().showHelpPage(HELP_URL + '#image-plots') else: InterfaceManager().showHelpPage(HELP_URL + '#figureoptionsgear-png-ptions-menu')
def algorithm_screenshot(name: str, directory: str, version: int = -1, ext: str = ".png") -> str: """ Takes a snapshot of an algorithm dialog and saves it as an image named "name_dlg.png" Args: name: The name of the algorithm directory: An directory path where the image should be saved version: A version of the algorithm to use (default=latest) ext: An optional extension (including the period). Default=.png Returns: A full path to the image file """ ensure_directory_exists(directory) suffix = "" if version != -1: suffix = f"-v{version}" filename = os.path.join(directory, f"{name}{suffix}_dlg{ext}") manager = InterfaceManager() dialog = manager.createDialogFromName(name, version, None, True) dialog.adjustSize() try: take_picture(dialog, filename) picture = Screenshot(filename, dialog.width(), dialog.height()) finally: dialog.close() return picture
def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.editor.register_plugin() self.widgets.append(self.editor) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() self.widgets.append(self.workspacewidget) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery(globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect(lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.readSettings(CONF) self.config_updated() self.setup_layout() self.create_actions()
def execute_algorithm(self): """ Send a signal to a subscriber to execute the selected algorithm """ algorithm = self.get_selected_algorithm() if algorithm is not None: manager = InterfaceManager() dialog = manager.createDialogFromName(algorithm.name, algorithm.version) dialog.show()
def execute_algorithm(self): """ Send a signal to a subscriber to execute the selected algorithm """ algorithm = self.get_selected_algorithm() if algorithm is not None: manager = InterfaceManager() dialog = manager.createDialogFromName(algorithm.name, algorithm.version) dialog.show()
def set_material(self): """ Open a SetSampleMaterial algorithm dialog. """ presets = {"InputWorkspace": self.workspace.name()} manager = InterfaceManager() dialog = manager.createDialogFromName("SetSampleMaterial", -1, self.view, False, presets) # Subscribe to the algorithm so we can update the view when the values are changed. dialog.addAlgorithmObserver(self) dialog.setModal(True) dialog.show()
class CppInterfacesStartupTest(systemtesting.MantidSystemTest): """ A system test for testing that the c++ interfaces open ok. """ def __init__(self): super(CppInterfacesStartupTest, self).__init__() self._app = get_application() self._interface_manager = InterfaceManager() self._cpp_interface_names = UserSubWindowFactory.Instance().keys() def runTest(self): if len(self._cpp_interface_names) == 0: self.fail("Failed to find the names of the c++ interfaces.") for interface_name in self._cpp_interface_names: self._attempt_to_open_cpp_interface(interface_name) def _attempt_to_open_cpp_interface(self, interface_name): try: interface = self._interface_manager.createSubWindow(interface_name) interface.setAttribute(Qt.WA_DeleteOnClose, True) interface.show() interface.close() # Delete the interface manually because the destructor is not being called as expected on close (even with # Qt.WA_DeleteOnClose set to True). sip.delete(interface) self.assertTrue(sip.isdeleted(interface)) except Exception as ex: self.fail( f"Exception thrown when attempting to open the {interface_name} interface: {ex}." )
def setAlgorithms(self, algorithms, extensions, tooltips): """ Set the algorithms displayed on the dialog. Args: algorithms (list(str)): list of algorithms extensions (dict(str:str)): extension used by each algorithm tooltips (dict(str:str)): short doc of each algorithm """ for i in range(len(algorithms)): algo = algorithms[i] if algo in extensions: text = algo + " (" + extensions[algo] + ")" else: text = algo widget = QCheckBox(text, self) if algo in tooltips: widget.setToolTip(tooltips[algo]) self._widgets[algorithms[i]] = widget self.algoList.addWidget(widget, i, 0, Qt.AlignLeft) helpButton = QToolButton(self) helpButton.setText('...') helpButton.setIcon(icons.get_icon("mdi.help")) helpButton.clicked.connect( lambda _, a=algorithms[i]: InterfaceManager().showHelpPage( "qthelp://org.mantidproject/doc/algorithms/{}.html".format( a))) self.algoList.addWidget(helpButton, i, 1, Qt.AlignRight)
def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.editor.register_plugin() self.widgets.append(self.editor) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() self.widgets.append(self.workspacewidget) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery(globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect(lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.readSettings(CONF) self.config_updated() self.setup_layout() self.create_actions()
def custominterface_screenshot(name: str, directory: str, ext: str = ".png", widget_name: str = None): """ Takes a snapshot of a custom interface and saves it as an image named "name.png" Args: name: The name of the custom interface directory: An directory path where the image should be saved ext: An optional extension (including the period). Default=.png widget_name: An optional child widget of the interface to snapshot Returns: str: A full path to the image file """ ensure_directory_exists(directory) iface_mgr = InterfaceManager() window = iface_mgr.createSubWindow(name) if window is None: raise RuntimeError(f"Interface '{name}' could not be created") window.adjustSize() image_widget = window if widget_name is not None: image_widget = window.findChild(QWidget, widget_name) if image_widget is None: raise RuntimeError(f"Widget '{widget_name}' does not exist in interface '{name}'") filename = name.replace(' ', '_') + "_" + widget_name + "_widget" + ext else: filename = name.replace(' ', '_') + "_interface" + ext filepath = os.path.join(directory, filename) try: take_picture(image_widget, filepath) picture = Screenshot(filepath, image_widget.width(), image_widget.height()) finally: window.close() return picture
def copy_material(self): """ Open a CopySample algorithm dialog with the CopyMaterial option. """ presets = { "InputWorkspace": self.workspace.name(), "CopyName": "0", "CopyMaterial": "1", "CopyEnvironment": "0", "CopyShape": "0", "CopyLattice": "0", "CopyOrientationOnly": "0" } manager = InterfaceManager() dialog = manager.createDialogFromName("CopySample", -1, self.view, False, presets) # Subscribe to the algorithm so we can update the view when the values are changed. dialog.addAlgorithmObserver(self) dialog.setModal(True) dialog.show()
def execute_algorithm(self): """ Send a signal to a subscriber to execute the selected algorithm """ algorithm = self.get_selected_algorithm() presets = {} enabled = [] if algorithm is not None: if self._selected_workspaces_fn: selected_ws_names = self._selected_workspaces_fn() if selected_ws_names: property_name = self.presenter.find_input_workspace_property(algorithm) if property_name: presets[property_name] = selected_ws_names[0] # Keep it enabled enabled.append(property_name) manager = InterfaceManager() dialog = manager.createDialogFromName(algorithm.name, algorithm.version, None, False, presets, "", enabled) dialog.show()
def _show_concept_help(self, concept): InterfaceManager().showHelpPage( 'qthelp://org.sphinx.mantidproject/doc/' f'concepts/{concept}.html')
def action_open_external_link(self, url): InterfaceManager().showWebPage(url)
def action_open_extending_mantid(self): InterfaceManager().showWebPage( 'http://www.mantidproject.org/Extending_Mantid_With_Python')
def action_open_python_in_mantid(self): InterfaceManager().showWebPage( 'http://www.mantidproject.org/Python_In_Mantid')
def action_open_python_introduction(self): InterfaceManager().showWebPage( 'http://www.mantidproject.org/Introduction_To_Python')
def action_open_mantid_introduction(self): InterfaceManager().showWebPage( 'http://www.mantidproject.org/Mantid_Basic_Course')
class MainWindow(QMainWindow): DOCKOPTIONS = QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks def __init__(self): QMainWindow.__init__(self) # -- instance attributes -- self.setWindowTitle("Mantid Workbench") self.setObjectName("Mantid Workbench") # widgets self.messagedisplay = None self.ipythonconsole = None self.workspacewidget = None self.editor = None self.algorithm_selector = None self.plot_selector = None self.interface_manager = None self.widgets = [] # Widget layout map: required for use in Qt.connection self._layout_widget_info = None # Menus self.file_menu = None self.file_menu_actions = None self.view_menu = None self.view_menu_actions = None self.interfaces_menu = None self.help_menu = None self.help_menu_actions = None # Allow splash screen text to be overridden in set_splash self.splash = SPLASH # Layout self.setDockOptions(self.DOCKOPTIONS) # Project self.project = None self.project_recovery = None # Interfaces self.interface_manager = None self.interface_executor = None def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.editor.register_plugin() self.widgets.append(self.editor) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() self.widgets.append(self.workspacewidget) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery(globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect(lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.readSettings(CONF) self.config_updated() self.setup_layout() self.create_actions() def post_mantid_init(self): """Run any setup that requires mantid to have been initialized """ self.populate_menus() self.algorithm_selector.refresh() # turn on algorithm factory notifications from mantid.api import AlgorithmFactory algorithm_factory = AlgorithmFactory.Instance() algorithm_factory.enableNotifications() def set_splash(self, msg=None): if not self.splash: return if msg: self.splash.showMessage(msg, Qt.AlignBottom | Qt.AlignLeft | Qt.AlignAbsolute, QColor(Qt.black)) QApplication.processEvents(QEventLoop.AllEvents) def create_menus(self): self.file_menu = self.menuBar().addMenu("&File") self.view_menu = self.menuBar().addMenu("&View") self.interfaces_menu = self.menuBar().addMenu('&Interfaces') self.help_menu = self.menuBar().addMenu('&Help') def create_actions(self): # --- general application menu options -- # file menu action_open = create_action( self, "Open Script", on_triggered=self.open_file, shortcut="Ctrl+O", shortcut_context=Qt.ApplicationShortcut) action_load_project = create_action( self, "Open Project", on_triggered=self.load_project) action_save_script = create_action( self, "Save Script", on_triggered=self.save_script, shortcut="Ctrl+S", shortcut_context=Qt.ApplicationShortcut) action_save_script_as = create_action( self, "Save Script as...", on_triggered=self.save_script_as) action_save_project = create_action( self, "Save Project", on_triggered=self.save_project) action_save_project_as = create_action( self, "Save Project as...", on_triggered=self.save_project_as) action_manage_directories = create_action( self, "Manage User Directories", on_triggered=self.open_manage_directories) action_settings = create_action( self, "Settings", on_triggered=self.open_settings_window) action_quit = create_action( self, "&Quit", on_triggered=self.close, shortcut="Ctrl+Q", shortcut_context=Qt.ApplicationShortcut) self.file_menu_actions = [action_open, action_load_project, None, action_save_script, action_save_script_as, action_save_project, action_save_project_as, None, action_settings, None, action_manage_directories, None, action_quit] # view menu action_restore_default = create_action( self, "Restore Default Layout", on_triggered=self.prep_window_for_reset, shortcut="Shift+F10", shortcut_context=Qt.ApplicationShortcut) self.view_menu_actions = [action_restore_default, None] + self.create_widget_actions() # help menu action_mantid_help = create_action( self, "Mantid Help", on_triggered=self.open_mantid_help, shortcut='F1', shortcut_context=Qt.ApplicationShortcut) action_algorithm_descriptions = create_action( self, 'Algorithm Descriptions', on_triggered=self.open_algorithm_descriptions_help) action_mantid_concepts = create_action( self, "Mantid Concepts", on_triggered=self.open_mantid_concepts_help) action_mantid_homepage = create_action( self, "Mantid Homepage", on_triggered=self.open_mantid_homepage) action_mantid_forum = create_action( self, "Mantid Forum", on_triggered=self.open_mantid_forum) self.help_menu_actions = [ action_mantid_help, action_mantid_concepts, action_algorithm_descriptions, None, action_mantid_homepage, action_mantid_forum] def create_widget_actions(self): """ Creates menu actions to show/hide dockable widgets. This uses all widgets that are in self.widgets :return: A list of show/hide actions for all widgets """ widget_actions = [] for widget in self.widgets: action = widget.dockwidget.toggleViewAction() widget_actions.append(action) return widget_actions def populate_menus(self): # Link to menus add_actions(self.file_menu, self.file_menu_actions) add_actions(self.view_menu, self.view_menu_actions) add_actions(self.help_menu, self.help_menu_actions) self.populate_interfaces_menu() def launch_custom_python_gui(self, filename): self.interface_executor.execute(open(filename).read(), filename) def launch_custom_cpp_gui(self, interface_name): interface = self.interface_manager.createSubWindow(interface_name) interface.setAttribute(Qt.WA_DeleteOnClose, True) interface.show() def populate_interfaces_menu(self): """Populate then Interfaces menu with all Python and C++ interfaces""" interface_dir = ConfigService['mantidqt.python_interfaces_directory'] interfaces = self._discover_python_interfaces(interface_dir) interfaces.update(self._discover_cpp_interfaces()) keys = list(interfaces.keys()) keys.sort() for key in keys: submenu = self.interfaces_menu.addMenu(key) names = interfaces[key] names.sort() for name in names: if '.py' in name: action = submenu.addAction(name.replace('.py', '').replace('_', ' ')) script = os.path.join(interface_dir, name) action.triggered.connect(lambda checked_py, script=script: self.launch_custom_python_gui(script)) else: action = submenu.addAction(name) action.triggered.connect(lambda checked_cpp, name=name: self.launch_custom_cpp_gui(name)) def _discover_python_interfaces(self, interface_dir): """Return a dictionary mapping a category to a set of named Python interfaces""" items = ConfigService['mantidqt.python_interfaces'].split() # list of custom interfaces that are not qt4/qt5 compatible GUI_BLACKLIST = ['ISIS_Reflectometry_Old.py', 'Frequency_Domain_Analysis_Old.py', 'Frequency_Domain_Analysis.py', 'Elemental_Analysis.py'] # detect the python interfaces interfaces = {} for item in items: key, scriptname = item.split('/') if not os.path.exists(os.path.join(interface_dir, scriptname)): logger.warning('Failed to find script "{}" in "{}"'.format(scriptname, interface_dir)) continue if scriptname in GUI_BLACKLIST: logger.information('Not adding gui "{}"'.format(scriptname)) continue interfaces.setdefault(key, []).append(scriptname) return interfaces def _discover_cpp_interfaces(self): """Return a dictionary mapping a category to a set of named C++ interfaces""" interfaces = {} cpp_interface_factory = UserSubWindowFactory.Instance() interface_names = cpp_interface_factory.keys() for name in interface_names: categories = cpp_interface_factory.categories(name) if len(categories) == 0: categories = ["General"] for category in categories: interfaces.setdefault(category, []).append(name) return interfaces def add_dockwidget(self, plugin): """Create a dockwidget around a plugin and add the dock to window""" dockwidget, location = plugin.create_dockwidget() self.addDockWidget(location, dockwidget) # ----------------------- Layout --------------------------------- def setup_layout(self): """Assume this is a first run of the application and set layouts accordingly""" self.setup_default_layouts() def prep_window_for_reset(self): """Function to reset all dock widgets to a state where they can be ordered by setup_default_layout""" for widget in self.widgets: widget.dockwidget.setFloating(False) # Bring back any floating windows self.addDockWidget(Qt.LeftDockWidgetArea, widget.dockwidget) # Un-tabify all widgets self.setup_default_layouts() def setup_default_layouts(self): """Set or reset the layouts of the child widgets""" # layout definition logmessages = self.messagedisplay ipython = self.ipythonconsole workspacewidget = self.workspacewidget editor = self.editor algorithm_selector = self.algorithm_selector plot_selector = self.plot_selector default_layout = { 'widgets': [ # column 0 [[workspacewidget], [algorithm_selector, plot_selector]], # column 1 [[editor, ipython]], # column 2 [[logmessages]] ], 'width-fraction': [0.25, # column 0 width 0.50, # column 1 width 0.25], # column 2 width 'height-fraction': [[0.5, 0.5], # column 0 row heights [1.0], # column 1 row heights [1.0]] # column 2 row heights } with widget_updates_disabled(self): widgets_layout = default_layout['widgets'] # flatten list widgets = [item for column in widgets_layout for row in column for item in row] # show everything for w in widgets: w.toggle_view(True) # split everything on the horizontal for i in range(len(widgets) - 1): first, second = widgets[i], widgets[i + 1] self.splitDockWidget(first.dockwidget, second.dockwidget, Qt.Horizontal) # now arrange the rows for column in widgets_layout: for i in range(len(column) - 1): first_row, second_row = column[i], column[i + 1] self.splitDockWidget(first_row[0].dockwidget, second_row[0].dockwidget, Qt.Vertical) # and finally tabify those in the same position for column in widgets_layout: for row in column: for i in range(len(row) - 1): first, second = row[i], row[i + 1] self.tabifyDockWidget(first.dockwidget, second.dockwidget) # Raise front widget per row row[0].dockwidget.show() row[0].dockwidget.raise_() # ----------------------- Events --------------------------------- def closeEvent(self, event): # Check whether or not to save project if not self.project.saved: # Offer save if self.project.offer_save(self): # Cancel has been clicked event.ignore() return # Close editors if self.editor.app_closing(): # write out any changes to the mantid config file ConfigService.saveConfig(ConfigService.getUserFilename()) # write current window information to global settings object self.writeSettings(CONF) # Close all open plots # We don't want this at module scope here import matplotlib.pyplot as plt # noqa plt.close('all') app = QApplication.instance() if app is not None: app.closeAllWindows() # Kill the project recovery thread and don't restart should a save be in progress and clear out current # recovery checkpoint as it is closing properly self.project_recovery.stop_recovery_thread() self.project_recovery.closing_workbench = True self.project_recovery.remove_current_pid_folder() self.interface_manager.closeHelpWindow() event.accept() else: # Cancel was pressed when closing an editor event.ignore() # ----------------------- Slots --------------------------------- def open_file(self): # todo: when more file types are added this should # live in its own type filepath, _ = QFileDialog.getOpenFileName(self, "Open File...", "", "Python (*.py)") if not filepath: return self.editor.open_file_in_new_tab(filepath) def save_script(self): self.editor.save_current_file() def save_script_as(self): self.editor.save_current_file_as() def save_project(self): self.project.save() def save_project_as(self): self.project.save_as() def load_project(self): self.project.load() def open_manage_directories(self): ManageUserDirectories(self).exec_() def open_settings_window(self): settings = SettingsPresenter(self) settings.show() def config_updated(self): """ Updates the widgets that depend on settings from the Workbench Config. """ self.editor.load_settings_from_config(CONF) self.project.load_settings_from_config(CONF) def open_algorithm_descriptions_help(self): self.interface_manager.showAlgorithmHelp('') def open_mantid_concepts_help(self): self.interface_manager.showConceptHelp('') def open_mantid_help(self): self.interface_manager.showHelpPage('') def open_mantid_homepage(self): self.interface_manager.showWebPage('https://www.mantidproject.org') def open_mantid_forum(self): self.interface_manager.showWebPage('https://forum.mantidproject.org/') def readSettings(self, settings): qapp = QApplication.instance() qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(Qt, 'AA_EnableHighDpiScaling'): qapp.setAttribute(Qt.AA_EnableHighDpiScaling, settings.get('high_dpi_scaling')) # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.screenGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): self.restoreState(settings.get('MainWindow/state')) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettings'): widget.readSettings(settings) def writeSettings(self, settings): settings.set('MainWindow/size', self.size()) # QSize settings.set('MainWindow/position', self.pos()) # QPoint settings.set('MainWindow/state', self.saveState()) # QByteArray # write out settings for children AlgorithmInputHistory().writeSettings(settings) for widget in self.widgets: if hasattr(widget, 'writeSettings'): widget.writeSettings(settings)
def helpWindow(self): """ Popup the help window. """ InterfaceManager().showHelpPage( "qthelp://org.mantidproject/doc/interfaces/DrILL.html")
def _on_help_button_clicked(self): InterfaceManager().showCustomInterfaceHelp(self.doc)
def test_interface_manager(self): manager = InterfaceManager() dialog = manager.createDialogFromName("AlgorithmDialogMockAlgorithm", -1) self.assertTrue(dialog is not None) input_widgets = dialog.findChildren(QLineEdit) self.assertEqual(len(input_widgets), 3)
def __init__(self): super(CppInterfacesStartupTest, self).__init__() self._app = get_application() self._interface_manager = InterfaceManager() self._cpp_interface_names = UserSubWindowFactory.Instance().keys()
class MainWindow(QMainWindow): DOCKOPTIONS = QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks def __init__(self): QMainWindow.__init__(self) # -- instance attributes -- self.setWindowTitle(MAIN_WINDOW_TITLE) self.setObjectName(MAIN_WINDOW_OBJECT_NAME) # widgets self.memorywidget = None self.messagedisplay = None self.ipythonconsole = None self.workspacewidget = None self.workspacecalculator = None self.editor = None self.algorithm_selector = None self.plot_selector = None self.interface_manager = None self.script_repository = None self.widgets = [] # Widget layout map: required for use in Qt.connection self._layout_widget_info = None # Menus self.file_menu = None self.file_menu_actions = None self.view_menu = None self.view_menu_actions = None self.view_menu_layouts = None self.interfaces_menu = None self.help_menu = None self.help_menu_actions = None # Allow splash screen text to be overridden in set_splash self.splash = SPLASH # Layout self.setDockOptions(self.DOCKOPTIONS) # Project self.project = None self.project_recovery = None # Interfaces self.interface_manager = None self.interface_executor = None self.interface_list = None self.could_restore_state = False def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() # read settings early so that logging level is in place before framework mgr created self.messagedisplay.readSettings(CONF) self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.messagedisplay.display.setActiveScript( self.editor.editors.current_tab_filename) self.editor.register_plugin() self.widgets.append(self.editor) self.editor.editors.sig_code_exec_start.connect( self.messagedisplay.script_executing) self.editor.editors.sig_file_name_changed.connect( self.messagedisplay.file_name_modified) self.editor.editors.sig_current_tab_changed.connect( self.messagedisplay.current_tab_changed) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() prompt = CONF.get('project/prompt_on_deleting_workspace') self.workspacewidget.workspacewidget.enableDeletePrompt(bool(prompt)) self.widgets.append(self.workspacewidget) self.set_splash("Loading memory widget") from workbench.plugins.memorywidget import MemoryWidget self.memorywidget = MemoryWidget(self) self.memorywidget.register_plugin() self.widgets.append(self.memorywidget) # set the link between the algorithm and workspace widget self.algorithm_selector.algorithm_selector.set_get_selected_workspace_fn( self.workspacewidget.workspacewidget.getSelectedWorkspaceNames) from workbench.plugins.workspacecalculatorwidget import WorkspaceCalculatorWidget self.workspacecalculator = WorkspaceCalculatorWidget(self) self.workspacecalculator.register_plugin() self.widgets.append(self.workspacecalculator) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery( globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect( lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.setup_default_layouts() self.create_actions() self.readSettings(CONF) self.config_updated() self.override_python_input() # Ensure windows created after the main window have their own menu bars (on mac) QCoreApplication.setAttribute(Qt.AA_DontUseNativeMenuBar, True) def post_mantid_init(self): """Run any setup that requires mantid to have been initialized """ self.redirect_python_warnings() self.populate_menus() self.algorithm_selector.refresh() # turn on algorithm factory notifications from mantid.api import AlgorithmFactory algorithm_factory = AlgorithmFactory.Instance() algorithm_factory.enableNotifications() def set_splash(self, msg=None): if not self.splash: return if msg: self.splash.showMessage( msg, int(Qt.AlignBottom | Qt.AlignLeft | Qt.AlignAbsolute), QColor(Qt.black)) QApplication.processEvents(QEventLoop.AllEvents) def create_menus(self): self.file_menu = self.menuBar().addMenu("&File") self.view_menu = self.menuBar().addMenu("&View") self.interfaces_menu = self.menuBar().addMenu('&Interfaces') self.help_menu = self.menuBar().addMenu('&Help') def create_actions(self): # --- general application menu options -- # file menu action_open = create_action(self, "Open Script", on_triggered=self.open_file, shortcut="Ctrl+O") action_load_project = create_action(self, "Open Project", on_triggered=self.load_project) action_save_script = create_action(self, "Save Script", on_triggered=self.save_script, shortcut="Ctrl+S") action_save_script_as = create_action(self, "Save Script as...", on_triggered=self.save_script_as) action_generate_ws_script = create_action( self, "Generate Recovery Script", on_triggered=self.generate_script_from_workspaces) action_save_project = create_action(self, "Save Project", on_triggered=self.save_project) action_save_project_as = create_action( self, "Save Project as...", on_triggered=self.save_project_as) action_manage_directories = create_action( self, "Manage User Directories", on_triggered=self.open_manage_directories) action_script_repository = create_action( self, "Script Repository", on_triggered=self.open_script_repository) action_settings = create_action(self, "Settings", on_triggered=self.open_settings_window) action_quit = create_action(self, "&Quit", on_triggered=self.close, shortcut="Ctrl+Q") action_clear_all_memory = create_action( self, "Clear All Memory", on_triggered=self.clear_all_memory_action, shortcut="Ctrl+Shift+L") menu_recently_closed_scripts = RecentlyClosedScriptsMenu(self) self.editor.editors.sig_tab_closed.connect( menu_recently_closed_scripts.add_script_to_settings) self.file_menu_actions = [ action_open, action_load_project, None, action_save_script, action_save_script_as, menu_recently_closed_scripts, action_generate_ws_script, None, action_save_project, action_save_project_as, None, action_settings, None, action_manage_directories, None, action_script_repository, None, action_clear_all_memory, None, action_quit ] # view menu action_restore_default = create_action( self, "Restore Default Layout", on_triggered=self.setup_default_layouts, shortcut="Shift+F10", shortcut_context=Qt.ApplicationShortcut) self.view_menu_layouts = self.view_menu.addMenu("&User Layouts") self.populate_layout_menu() self.view_menu_actions = [action_restore_default, None ] + self.create_widget_actions() # help menu action_mantid_help = create_action( self, "Mantid Help", on_triggered=self.open_mantid_help, shortcut='F1', shortcut_context=Qt.ApplicationShortcut) action_algorithm_descriptions = create_action( self, 'Algorithm Descriptions', on_triggered=self.open_algorithm_descriptions_help) action_mantid_concepts = create_action( self, "Mantid Concepts", on_triggered=self.open_mantid_concepts_help) action_mantid_homepage = create_action( self, "Mantid Homepage", on_triggered=self.open_mantid_homepage) action_mantid_forum = create_action( self, "Mantid Forum", on_triggered=self.open_mantid_forum) action_about = create_action(self, "About Mantid Workbench", on_triggered=self.open_about) self.help_menu_actions = [ action_mantid_help, action_mantid_concepts, action_algorithm_descriptions, None, action_mantid_homepage, action_mantid_forum, None, action_about ] def create_widget_actions(self): """ Creates menu actions to show/hide dockable widgets. This uses all widgets that are in self.widgets :return: A list of show/hide actions for all widgets """ widget_actions = [] for widget in self.widgets: action = widget.dockwidget.toggleViewAction() widget_actions.append(action) return widget_actions def populate_menus(self): # Link to menus add_actions(self.file_menu, self.file_menu_actions) add_actions(self.view_menu, self.view_menu_actions) add_actions(self.help_menu, self.help_menu_actions) def launch_custom_python_gui(self, filename): self.interface_executor.execute(open(filename).read(), filename) def launch_custom_cpp_gui(self, interface_name, submenu=None): """Create a new interface window if one does not already exist, else show existing window""" object_name = 'custom-cpp-interface-' + interface_name window = find_window(object_name, QMainWindow) if window is None: interface = self.interface_manager.createSubWindow(interface_name) interface.setObjectName(object_name) interface.setAttribute(Qt.WA_DeleteOnClose, True) parent, flags = get_window_config() if parent: interface.setParent(parent, flags) interface.show() else: if window.windowState() == Qt.WindowMinimized: window.setWindowState(Qt.WindowActive) else: window.raise_() def populate_interfaces_menu(self): """Populate then Interfaces menu with all Python and C++ interfaces""" self.interfaces_menu.clear() interface_dir = _get_interface_dir() self.interface_list, registers_to_run = self._discover_python_interfaces( interface_dir) self._discover_cpp_interfaces(self.interface_list) hidden_interfaces = ConfigService[ 'interfaces.categories.hidden'].split(';') keys = list(self.interface_list.keys()) keys.sort() for key in keys: if key not in hidden_interfaces: submenu = self.interfaces_menu.addMenu(key) names = self.interface_list[key] names.sort() for name in names: if '.py' in name: action = submenu.addAction( name.replace('.py', '').replace('_', ' ')) script = os.path.join(interface_dir, name) action.triggered.connect( lambda checked_py, script=script: self. launch_custom_python_gui(script)) else: action = submenu.addAction(name) action.triggered.connect( lambda checked_cpp, name=name, key=key: self. launch_custom_cpp_gui(name, key)) # these register scripts contain code to register encoders and decoders to work with project save before the # corresponding interface has been initialised. This is a temporary measure pending harmonisation of cpp/python # interfaces for reg_list in registers_to_run.values(): for register in reg_list: file_path = os.path.join(interface_dir, register) with open(file_path) as handle: self.interface_executor.execute(handle.read(), file_path) def redirect_python_warnings(self): """By default the warnings module writes warnings to sys.stderr. stderr is assumed to be an error channel so we don't confuse warnings with errors this redirects warnings from the warnings module to mantid.logger.warning """ import warnings def to_mantid_warning(*args, **kwargs): logger.warning(warnings.formatwarning(*args, **kwargs)) warnings.showwarning = to_mantid_warning def _discover_python_interfaces(self, interface_dir): """Return a dictionary mapping a category to a set of named Python interfaces""" items = ConfigService['mantidqt.python_interfaces'].split() try: register_items = ConfigService[ 'mantidqt.python_interfaces_io_registry'].split() except KeyError: register_items = [] # detect the python interfaces interfaces = {} registers_to_run = {} for item in items: key, scriptname = item.split('/') reg_name = scriptname[:-3] + '_register.py' if reg_name in register_items and os.path.exists( os.path.join(interface_dir, reg_name)): registers_to_run.setdefault(key, []).append(reg_name) if not os.path.exists(os.path.join(interface_dir, scriptname)): logger.warning('Failed to find script "{}" in "{}"'.format( scriptname, interface_dir)) continue interfaces.setdefault(key, []).append(scriptname) return interfaces, registers_to_run def _discover_cpp_interfaces(self, interfaces): """Return a dictionary mapping a category to a set of named C++ interfaces""" cpp_interface_factory = UserSubWindowFactory.Instance() interface_names = cpp_interface_factory.keys() for name in interface_names: categories = cpp_interface_factory.categories(name) if len(categories) == 0: categories = ["General"] for category in categories: if category in interfaces.keys(): interfaces[category].append(name) else: interfaces[category] = [name] return interfaces def add_dockwidget(self, plugin): """Create a dockwidget around a plugin and add the dock to window""" dockwidget, location = plugin.create_dockwidget() self.addDockWidget(location, dockwidget) # ----------------------- Layout --------------------------------- def populate_layout_menu(self): self.view_menu_layouts.clear() try: layout_dict = CONF.get("MainWindow/user_layouts") except KeyError: layout_dict = {} layout_keys = sorted(layout_dict.keys()) layout_options = [] for item in layout_keys: layout_options.append( self.create_load_layout_action(item, layout_dict[item])) layout_options.append(None) action_settings = create_action( self, "Settings", on_triggered=self.open_settings_layout_window) layout_options.append(action_settings) add_actions(self.view_menu_layouts, layout_options) def create_load_layout_action(self, layout_name, layout): action_load_layout = create_action( self, layout_name, on_triggered=lambda: self.attempt_to_restore_state(layout)) return action_load_layout def prep_window_for_reset(self): """Function to reset all dock widgets to a state where they can be ordered by setup_default_layout""" for widget in self.widgets: widget.dockwidget.setFloating( False) # Bring back any floating windows self.addDockWidget(Qt.LeftDockWidgetArea, widget.dockwidget) # Un-tabify all widgets widget.toggle_view(False) def setup_default_layouts(self): """Set the default layouts of the child widgets""" # layout definition memorywidget = self.memorywidget logmessages = self.messagedisplay ipython = self.ipythonconsole workspacewidget = self.workspacewidget editor = self.editor algorithm_selector = self.algorithm_selector plot_selector = self.plot_selector workspacecalculator = self.workspacecalculator # If more than two rows are needed in a column, # arrange_layout function needs to be revisited. # In the first column, there are three widgets in two rows # as the algorithm_selector and plot_selector are tabified. default_layout = { 'widgets': [ # column 0 [[workspacewidget], [algorithm_selector, plot_selector]], # column 1 [[editor, ipython], [workspacecalculator]], # column 2 [[memorywidget], [logmessages]] ], } size = self.size() # Preserve size on reset self.arrange_layout(default_layout) self.resize(size) def arrange_layout(self, layout): """Arrange the layout of the child widgets according to the supplied layout""" self.prep_window_for_reset() widgets_layout = layout['widgets'] with widget_updates_disabled(self): # flatten list widgets = [ item for column in widgets_layout for row in column for item in row ] # show everything for w in widgets: w.toggle_view(True) # split everything on the horizontal for i in range(len(widgets) - 1): first, second = widgets[i], widgets[i + 1] self.splitDockWidget(first.dockwidget, second.dockwidget, Qt.Horizontal) # now arrange the rows for column in widgets_layout: for i in range(len(column) - 1): first_row, second_row = column[i], column[i + 1] self.splitDockWidget(first_row[0].dockwidget, second_row[0].dockwidget, Qt.Vertical) # and finally tabify those in the same position for column in widgets_layout: for row in column: for i in range(len(row) - 1): first, second = row[i], row[i + 1] self.tabifyDockWidget(first.dockwidget, second.dockwidget) # Raise front widget per row row[0].dockwidget.show() row[0].dockwidget.raise_() # ----------------------- Events --------------------------------- def closeEvent(self, event): if self.project is not None: if self.project.is_saving or self.project.is_loading: event.ignore() self.project.inform_user_not_possible() return # Check whether or not to save project if not self.project.saved: # Offer save if self.project.offer_save(self): # Cancel has been clicked event.ignore() return # Close editors if self.editor is None or self.editor.app_closing(): # write out any changes to the mantid config file ConfigService.saveConfig(ConfigService.getUserFilename()) # write current window information to global settings object self.writeSettings(CONF) # Close all open plots # We don't want this at module scope here import matplotlib.pyplot as plt # noqa plt.close('all') # Cancel all running (managed) algorithms AlgorithmManager.Instance().cancelAll() app = QApplication.instance() if app is not None: app.closeAllWindows() # Kill the project recovery thread and don't restart should a save be in progress and clear out current # recovery checkpoint as it is closing properly if self.project_recovery is not None: self.project_recovery.stop_recovery_thread() self.project_recovery.closing_workbench = True # Cancel memory widget thread if self.memorywidget is not None: self.memorywidget.presenter.cancel_memory_update() if self.interface_manager is not None: self.interface_manager.closeHelpWindow() if self.workspacecalculator is not None: self.workspacecalculator.view.closeEvent(event) if self.project_recovery is not None: # Do not merge this block with the above block that # starts with the same check. # We deliberately split the call to stop the recovery # thread and removal of the checkpoints folder to # allow for the maximum amount of time for the recovery # thread to finish. Any errors here are ignored as exceptions # on shutdown cannot be handled in a meaningful way. # Future runs of project recovery will clean any stale points # after a month self.project_recovery.remove_current_pid_folder( ignore_errors=True) event.accept() else: # Cancel was pressed when closing an editor event.ignore() # ----------------------- Slots --------------------------------- def open_file(self): # todo: when more file types are added this should # live in its own type defaultSaveDirectory = ConfigService['defaultsave.directory'] filepath, _ = QFileDialog.getOpenFileName(self, "Open File...", defaultSaveDirectory, "Python (*.py)") if not filepath: return self.editor.open_file_in_new_tab(filepath) def save_script(self): self.editor.save_current_file() def save_script_as(self): self.editor.save_current_file_as() def generate_script_from_workspaces(self): if not self.workspacewidget.empty_of_workspaces(): task = BlockingAsyncTaskWithCallback( target=self._generate_script_from_workspaces, blocking_cb=QApplication.processEvents) task.start() else: # Tell users they need a workspace to do that QMessageBox().warning( None, "No Workspaces!", "In order to generate a recovery script there needs to be some workspaces.", QMessageBox.Ok) def _generate_script_from_workspaces(self): script = "from mantid.simpleapi import *\n\n" + get_all_workspace_history_from_ads( ) QAppThreadCall(self.editor.open_script_in_new_tab)(script) def save_project(self): self.project.save(CONF) def save_project_as(self): self.project.open_project_save_dialog(CONF) def load_project(self): self.project.load() def open_manage_directories(self): manageuserdirectories.ManageUserDirectories.openManageUserDirectories() def open_script_repository(self): self.script_repository = ScriptRepositoryView(self) self.script_repository.loadScript.connect( self.editor.open_file_in_new_tab) self.script_repository.setAttribute(Qt.WA_DeleteOnClose, True) self.script_repository.show() def open_settings_window(self): settings = SettingsPresenter(self) settings.show() def open_settings_layout_window(self): settings = SettingsPresenter(self) settings.show() settings.general_settings.focus_layout_box() def clear_all_memory_action(self): """ Creates Question QMessageBox to check user wants to clear all memory when action is pressed from file menu """ msg = QMessageBox( QMessageBox.Question, "Clear All", "All workspaces and windows will be removed.\nAre you sure?") msg.addButton(QMessageBox.Ok) msg.addButton(QMessageBox.Cancel) msg.setWindowIcon(QIcon(':/images/MantidIcon.ico')) reply = msg.exec() if reply == QMessageBox.Ok: self.clear_all_memory() def clear_all_memory(self): """ Wrapper for call to FrameworkManager to clear all memory """ FrameworkManager.Instance().clear() def config_updated(self): """ Updates the widgets that depend on settings from the Workbench Config. """ self.editor.load_settings_from_config(CONF) self.project.load_settings_from_config(CONF) self.algorithm_selector.refresh() self.populate_interfaces_menu() self.workspacewidget.refresh_workspaces() def open_algorithm_descriptions_help(self): self.interface_manager.showAlgorithmHelp('') def open_mantid_concepts_help(self): self.interface_manager.showConceptHelp('') def open_mantid_help(self): UsageService.registerFeatureUsage(FeatureType.Feature.Interface, ["Mantid Help"], False) self.interface_manager.showHelpPage('') def open_mantid_homepage(self): self.interface_manager.showWebPage('https://www.mantidproject.org') def open_mantid_forum(self): self.interface_manager.showWebPage('https://forum.mantidproject.org/') def open_about(self): about = AboutPresenter(self) about.show() def readSettings(self, settings): qapp = QApplication.instance() # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) if settings.has('MainWindow/font'): font_string = settings.get('MainWindow/font').split(',') font = QFontDatabase().font(font_string[0], font_string[-1], int(font_string[1])) qapp.setFont(font) # reset font for ipython console to ensure it stays monospace self.ipythonconsole.console.reset_font() # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.availableGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) if x + w > desktop_geom.right(): x = desktop_geom.right() - w if y + h > desktop_geom.bottom(): y = desktop_geom.bottom() - h window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): if not self.restoreState(settings.get('MainWindow/state'), SAVE_STATE_VERSION): logger.warning( "The previous layout of workbench is not compatible with this version, reverting to default layout." ) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettingsIfNotDone'): widget.readSettingsIfNotDone(settings) def writeSettings(self, settings): settings.set('MainWindow/size', self.size()) # QSize settings.set('MainWindow/position', self.pos()) # QPoint settings.set('MainWindow/state', self.saveState(SAVE_STATE_VERSION)) # QByteArray # write out settings for children AlgorithmInputHistory().writeSettings(settings) for widget in self.widgets: if hasattr(widget, 'writeSettings'): widget.writeSettings(settings) def override_python_input(self): """Replace python input with a call to a qinputdialog""" builtins.input = QAppThreadCall(input_qinputdialog) def attempt_to_restore_state(self, state): if self.restoreState(state, SAVE_STATE_VERSION): return # The version number of the supplied state is older than the current version reply = QMessageBox.question( self, "Layout Restoration", "The selected layout is incompatible with this version of Workbench. Workbench will attempt to restore " "the layout, but it may appear differently from before.\nDo you wish to continue?", QMessageBox.Yes | QMessageBox.No) if not reply == QMessageBox.Yes: return for version in range(0, SAVE_STATE_VERSION): if self.restoreState(state, version): QMessageBox.information( self, "Success", "The layout was successfully restored.\nTo hide this warning in the future, delete the old " "layout in File > Settings, and save this as a new layout." ) return QMessageBox.warning(self, "Failure", "The layout was unable to be restored.", QMessageBox.Ok)
def function_help_dialog(self): InterfaceManager().showFitFunctionHelp( self.view.ui.functionBox.currentText())
class CrashReportPage(ErrorReportUIBase, ErrorReportUI): action = Signal(bool, int, str, str, str) quit_signal = Signal() free_text_edited = False interface_manager = InterfaceManager() def __init__(self, parent=None, show_continue_terminate=False): super(self.__class__, self).__init__(parent) self.setupUi(self) if qtpy.PYQT4: self.input_free_text.setPlainText(DEFAULT_PLAIN_TEXT) self.input_free_text.cursorPositionChanged.connect( self.check_placeholder_text) elif qtpy.PYQT5: self.input_free_text.setPlaceholderText(DEFAULT_PLAIN_TEXT) self.input_text = "" if not show_continue_terminate: self.continue_terminate_frame.hide() self.adjustSize() self.quit_signal.connect(QtWidgets.QApplication.instance().quit) self.icon.setPixmap(QtGui.QPixmap(":/crying_mantid.png")) self.requestTextBrowser.anchorClicked.connect( self.interface_manager.showWebPage) self.input_name_line_edit.textChanged.connect(self.set_button_status) self.input_email_line_edit.textChanged.connect(self.set_button_status) self.input_free_text.textChanged.connect(self.set_button_status) self.input_free_text.textChanged.connect( self.set_plain_text_edit_field) self.privacy_policy_label.linkActivated.connect( self.launch_privacy_policy) # The options on what to do after closing the window (exit/continue) self.radioButtonContinue.setChecked( True) # Set continue to be checked by default # These are the options along the bottom self.fullShareButton.clicked.connect(self.fullShare) self.nonIDShareButton.clicked.connect(self.nonIDShare) self.noShareButton.clicked.connect(self.noShare) self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.setWindowModality(QtCore.Qt.ApplicationModal) def quit(self): self.quit_signal.emit() def fullShare(self): self.action.emit(self.continue_working, 0, self.input_name, self.input_email, self.input_text) self.close() def nonIDShare(self): self.action.emit(self.continue_working, 1, self.input_name, self.input_email, self.input_text) self.close() def noShare(self): self.action.emit(self.continue_working, 2, self.input_name, self.input_email, self.input_text) self.close() def get_simple_line_edit_field(self, expected_type, line_edit): gui_element = getattr(self, line_edit) value_as_string = gui_element.text() return expected_type(value_as_string) if value_as_string else '' def set_plain_text_edit_field(self): self.input_text = self.get_plain_text_edit_field( text_edit="input_free_text", expected_type=str) def get_plain_text_edit_field(self, text_edit, expected_type): gui_element = getattr(self, text_edit) value_as_string = gui_element.toPlainText() return expected_type(value_as_string) if value_as_string else '' def check_placeholder_text(self): if not self.free_text_edited: self.free_text_edited = True self.input_free_text.setPlainText("") def launch_privacy_policy(self, link): self.interface_manager.showWebPage(link) def set_button_status(self): if self.input_text == '' and not self.input_name and not self.input_email: self.nonIDShareButton.setEnabled(True) else: self.nonIDShareButton.setEnabled(False) def display_message_box(self, title, message, details): msg = QMessageBox(self) msg.setIcon(QMessageBox.Warning) msg.setText(message) msg.setWindowTitle(title) msg.setDetailedText(details) msg.setStandardButtons(QMessageBox.Ok) msg.setDefaultButton(QMessageBox.Ok) msg.setEscapeButton(QMessageBox.Ok) msg.exec_() def set_report_callback(self, callback): self.action.connect(callback) @property def input_name(self): return self.get_simple_line_edit_field( line_edit="input_name_line_edit", expected_type=str) @property def input_email(self): return self.get_simple_line_edit_field( line_edit="input_email_line_edit", expected_type=str) @property def continue_working(self): return self.radioButtonContinue.isChecked()
def show_help_page_for_figure(cls, figure): fig_type = figure_type(figure) doc_url = HELP_PAGES[fig_type] InterfaceManager().showHelpPage(doc_url)
def action_open_help_window(self): InterfaceManager().showHelpPage( 'qthelp://org.mantidproject/doc/workbench/settings.html')
def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.messagedisplay.display.setActiveScript( self.editor.editors.current_tab_filename) self.editor.register_plugin() self.widgets.append(self.editor) self.editor.editors.sig_code_exec_start.connect( self.messagedisplay.script_executing) self.editor.editors.sig_file_name_changed.connect( self.messagedisplay.file_name_modified) self.editor.editors.sig_current_tab_changed.connect( self.messagedisplay.current_tab_changed) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() prompt = CONF.get('project/prompt_on_deleting_workspace') self.workspacewidget.workspacewidget.enableDeletePrompt(bool(prompt)) self.widgets.append(self.workspacewidget) # set the link between the algorithm and workspace widget self.algorithm_selector.algorithm_selector.set_get_selected_workspace_fn( self.workspacewidget.workspacewidget.getSelectedWorkspaceNames) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery( globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect( lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.setup_default_layouts() self.create_actions() self.readSettings(CONF) self.config_updated()
class MainWindow(QMainWindow): DOCKOPTIONS = QMainWindow.AllowTabbedDocks | QMainWindow.AllowNestedDocks def __init__(self): QMainWindow.__init__(self) # -- instance attributes -- self.setWindowTitle(MAIN_WINDOW_TITLE) self.setObjectName(MAIN_WINDOW_OBJECT_NAME) # widgets self.messagedisplay = None self.ipythonconsole = None self.workspacewidget = None self.editor = None self.algorithm_selector = None self.plot_selector = None self.interface_manager = None self.script_repository = None self.widgets = [] # Widget layout map: required for use in Qt.connection self._layout_widget_info = None # Menus self.file_menu = None self.file_menu_actions = None self.view_menu = None self.view_menu_actions = None self.view_menu_layouts = None self.interfaces_menu = None self.help_menu = None self.help_menu_actions = None # Allow splash screen text to be overridden in set_splash self.splash = SPLASH # Layout self.setDockOptions(self.DOCKOPTIONS) # Project self.project = None self.project_recovery = None # Interfaces self.interface_manager = None self.interface_executor = None self.interface_list = None def setup(self): # menus must be done first so they can be filled by the # plugins in register_plugin self.create_menus() # widgets # Log message display must be imported first self.set_splash("Loading message display") from workbench.plugins.logmessagedisplay import LogMessageDisplay self.messagedisplay = LogMessageDisplay(self) # this takes over stdout/stderr self.messagedisplay.register_plugin() self.widgets.append(self.messagedisplay) self.set_splash("Loading Algorithm Selector") from workbench.plugins.algorithmselectorwidget import AlgorithmSelector self.algorithm_selector = AlgorithmSelector(self) self.algorithm_selector.register_plugin() self.widgets.append(self.algorithm_selector) self.set_splash("Loading Plot Selector") from workbench.plugins.plotselectorwidget import PlotSelector self.plot_selector = PlotSelector(self) self.plot_selector.register_plugin() self.widgets.append(self.plot_selector) self.set_splash("Loading code editing widget") from workbench.plugins.editor import MultiFileEditor self.editor = MultiFileEditor(self) self.messagedisplay.display.setActiveScript( self.editor.editors.current_tab_filename) self.editor.register_plugin() self.widgets.append(self.editor) self.editor.editors.sig_code_exec_start.connect( self.messagedisplay.script_executing) self.editor.editors.sig_file_name_changed.connect( self.messagedisplay.file_name_modified) self.editor.editors.sig_current_tab_changed.connect( self.messagedisplay.current_tab_changed) self.set_splash("Loading IPython console") from workbench.plugins.jupyterconsole import JupyterConsole self.ipythonconsole = JupyterConsole(self) self.ipythonconsole.register_plugin() self.widgets.append(self.ipythonconsole) from workbench.plugins.workspacewidget import WorkspaceWidget self.workspacewidget = WorkspaceWidget(self) self.workspacewidget.register_plugin() prompt = CONF.get('project/prompt_on_deleting_workspace') self.workspacewidget.workspacewidget.enableDeletePrompt(bool(prompt)) self.widgets.append(self.workspacewidget) # set the link between the algorithm and workspace widget self.algorithm_selector.algorithm_selector.set_get_selected_workspace_fn( self.workspacewidget.workspacewidget.getSelectedWorkspaceNames) # Set up the project, recovery and interface manager objects self.project = Project(GlobalFigureManager, find_all_windows_that_are_savable) self.project_recovery = ProjectRecovery( globalfiguremanager=GlobalFigureManager, multifileinterpreter=self.editor.editors, main_window=self) self.interface_executor = PythonCodeExecution() self.interface_executor.sig_exec_error.connect( lambda errobj: logger.warning(str(errobj))) self.interface_manager = InterfaceManager() # uses default configuration as necessary self.setup_default_layouts() self.create_actions() self.readSettings(CONF) self.config_updated() def post_mantid_init(self): """Run any setup that requires mantid to have been initialized """ self.redirect_python_warnings() self.populate_menus() self.algorithm_selector.refresh() # turn on algorithm factory notifications from mantid.api import AlgorithmFactory algorithm_factory = AlgorithmFactory.Instance() algorithm_factory.enableNotifications() def set_splash(self, msg=None): if not self.splash: return if msg: self.splash.showMessage( msg, Qt.AlignBottom | Qt.AlignLeft | Qt.AlignAbsolute, QColor(Qt.black)) QApplication.processEvents(QEventLoop.AllEvents) def create_menus(self): self.file_menu = self.menuBar().addMenu("&File") self.view_menu = self.menuBar().addMenu("&View") self.interfaces_menu = self.menuBar().addMenu('&Interfaces') self.help_menu = self.menuBar().addMenu('&Help') def create_actions(self): # --- general application menu options -- # file menu action_open = create_action(self, "Open Script", on_triggered=self.open_file, shortcut="Ctrl+O", shortcut_context=Qt.ApplicationShortcut) action_load_project = create_action(self, "Open Project", on_triggered=self.load_project) action_save_script = create_action( self, "Save Script", on_triggered=self.save_script, shortcut="Ctrl+S", shortcut_context=Qt.ApplicationShortcut) action_save_script_as = create_action(self, "Save Script as...", on_triggered=self.save_script_as) action_generate_ws_script = create_action( self, "Generate Recovery Script", on_triggered=self.generate_script_from_workspaces) action_save_project = create_action(self, "Save Project", on_triggered=self.save_project) action_save_project_as = create_action( self, "Save Project as...", on_triggered=self.save_project_as) action_manage_directories = create_action( self, "Manage User Directories", on_triggered=self.open_manage_directories) action_script_repository = create_action( self, "Script Repository", on_triggered=self.open_script_repository) action_settings = create_action(self, "Settings", on_triggered=self.open_settings_window) action_quit = create_action(self, "&Quit", on_triggered=self.close, shortcut="Ctrl+Q", shortcut_context=Qt.ApplicationShortcut) self.file_menu_actions = [ action_open, action_load_project, None, action_save_script, action_save_script_as, action_generate_ws_script, None, action_save_project, action_save_project_as, None, action_settings, None, action_manage_directories, None, action_script_repository, None, action_quit ] # view menu action_restore_default = create_action( self, "Restore Default Layout", on_triggered=self.setup_default_layouts, shortcut="Shift+F10", shortcut_context=Qt.ApplicationShortcut) self.view_menu_layouts = self.view_menu.addMenu("&User Layouts") self.populate_layout_menu() self.view_menu_actions = [action_restore_default, None ] + self.create_widget_actions() # help menu action_mantid_help = create_action( self, "Mantid Help", on_triggered=self.open_mantid_help, shortcut='F1', shortcut_context=Qt.ApplicationShortcut) action_algorithm_descriptions = create_action( self, 'Algorithm Descriptions', on_triggered=self.open_algorithm_descriptions_help) action_mantid_concepts = create_action( self, "Mantid Concepts", on_triggered=self.open_mantid_concepts_help) action_mantid_homepage = create_action( self, "Mantid Homepage", on_triggered=self.open_mantid_homepage) action_mantid_forum = create_action( self, "Mantid Forum", on_triggered=self.open_mantid_forum) action_about = create_action(self, "About Mantid Workbench", on_triggered=self.open_about) self.help_menu_actions = [ action_mantid_help, action_mantid_concepts, action_algorithm_descriptions, None, action_mantid_homepage, action_mantid_forum, None, action_about ] def create_widget_actions(self): """ Creates menu actions to show/hide dockable widgets. This uses all widgets that are in self.widgets :return: A list of show/hide actions for all widgets """ widget_actions = [] for widget in self.widgets: action = widget.dockwidget.toggleViewAction() widget_actions.append(action) return widget_actions def populate_menus(self): # Link to menus add_actions(self.file_menu, self.file_menu_actions) add_actions(self.view_menu, self.view_menu_actions) add_actions(self.help_menu, self.help_menu_actions) self.populate_interfaces_menu() def launch_custom_python_gui(self, filename): self.interface_executor.execute(open(filename).read(), filename) def launch_custom_cpp_gui(self, interface_name, submenu=None): """Create a new interface window if one does not already exist, else show existing window""" object_name = 'custom-cpp-interface-' + interface_name window = find_window(object_name, QMainWindow) if window is None: interface = self.interface_manager.createSubWindow(interface_name) interface.setObjectName(object_name) interface.setAttribute(Qt.WA_DeleteOnClose, True) # make indirect interfaces children of workbench if submenu == "Indirect": interface.setParent(self, interface.windowFlags()) interface.show() else: if window.windowState() == Qt.WindowMinimized: window.setWindowState(Qt.WindowActive) else: window.raise_() def populate_interfaces_menu(self): """Populate then Interfaces menu with all Python and C++ interfaces""" self.interfaces_menu.clear() interface_dir = ConfigService['mantidqt.python_interfaces_directory'] self.interface_list = self._discover_python_interfaces(interface_dir) self._discover_cpp_interfaces(self.interface_list) hidden_interfaces = ConfigService[ 'interfaces.categories.hidden'].split(';') keys = list(self.interface_list.keys()) keys.sort() for key in keys: if key not in hidden_interfaces: submenu = self.interfaces_menu.addMenu(key) names = self.interface_list[key] names.sort() for name in names: if '.py' in name: action = submenu.addAction( name.replace('.py', '').replace('_', ' ')) script = os.path.join(interface_dir, name) action.triggered.connect( lambda checked_py, script=script: self. launch_custom_python_gui(script)) else: action = submenu.addAction(name) action.triggered.connect( lambda checked_cpp, name=name, key=key: self. launch_custom_cpp_gui(name, key)) def redirect_python_warnings(self): """By default the warnings module writes warnings to sys.stderr. stderr is assumed to be an error channel so we don't confuse warnings with errors this redirects warnings from the warnings module to mantid.logger.warning """ import warnings def to_mantid_warning(*args, **kwargs): logger.warning(warnings.formatwarning(*args, **kwargs)) warnings.showwarning = to_mantid_warning def _discover_python_interfaces(self, interface_dir): """Return a dictionary mapping a category to a set of named Python interfaces""" items = ConfigService['mantidqt.python_interfaces'].split() # list of custom interfaces that are not qt4/qt5 compatible GUI_BLACKLIST = ['Frequency_Domain_Analysis_Old.py'] # detect the python interfaces interfaces = {} for item in items: key, scriptname = item.split('/') if not os.path.exists(os.path.join(interface_dir, scriptname)): logger.warning('Failed to find script "{}" in "{}"'.format( scriptname, interface_dir)) continue if scriptname in GUI_BLACKLIST: logger.information('Not adding gui "{}"'.format(scriptname)) continue interfaces.setdefault(key, []).append(scriptname) return interfaces def _discover_cpp_interfaces(self, interfaces): """Return a dictionary mapping a category to a set of named C++ interfaces""" cpp_interface_factory = UserSubWindowFactory.Instance() interface_names = cpp_interface_factory.keys() for name in interface_names: categories = cpp_interface_factory.categories(name) if len(categories) == 0: categories = ["General"] for category in categories: if category in interfaces.keys(): interfaces[category].append(name) else: interfaces[category] = [name] return interfaces def add_dockwidget(self, plugin): """Create a dockwidget around a plugin and add the dock to window""" dockwidget, location = plugin.create_dockwidget() self.addDockWidget(location, dockwidget) # ----------------------- Layout --------------------------------- def populate_layout_menu(self): self.view_menu_layouts.clear() try: layout_dict = CONF.get("MainWindow/user_layouts") except KeyError: layout_dict = {} layout_keys = sorted(layout_dict.keys()) layout_options = [] for item in layout_keys: layout_options.append( self.create_load_layout_action(item, layout_dict[item])) layout_options.append(None) action_settings = create_action( self, "Settings", on_triggered=self.open_settings_layout_window) layout_options.append(action_settings) add_actions(self.view_menu_layouts, layout_options) def create_load_layout_action(self, layout_name, layout): action_load_layout = create_action( self, layout_name, on_triggered=lambda: self.restoreState(layout)) return action_load_layout def prep_window_for_reset(self): """Function to reset all dock widgets to a state where they can be ordered by setup_default_layout""" for widget in self.widgets: widget.dockwidget.setFloating( False) # Bring back any floating windows self.addDockWidget(Qt.LeftDockWidgetArea, widget.dockwidget) # Un-tabify all widgets widget.toggle_view(False) def setup_default_layouts(self): """Set the default layouts of the child widgets""" # layout definition logmessages = self.messagedisplay ipython = self.ipythonconsole workspacewidget = self.workspacewidget editor = self.editor algorithm_selector = self.algorithm_selector plot_selector = self.plot_selector default_layout = { 'widgets': [ # column 0 [[workspacewidget], [algorithm_selector, plot_selector]], # column 1 [[editor, ipython]], # column 2 [[logmessages]] ], 'width-fraction': [ 0.25, # column 0 width 0.50, # column 1 width 0.25 ], # column 2 width 'height-fraction': [ [0.5, 0.5], # column 0 row heights [1.0], # column 1 row heights [1.0] ] # column 2 row heights } size = self.size() # Preserve size on reset self.arrange_layout(default_layout) self.resize(size) def arrange_layout(self, layout): """Arrange the layout of the child widgets according to the supplied layout""" self.prep_window_for_reset() widgets_layout = layout['widgets'] with widget_updates_disabled(self): # flatten list widgets = [ item for column in widgets_layout for row in column for item in row ] # show everything for w in widgets: w.toggle_view(True) # split everything on the horizontal for i in range(len(widgets) - 1): first, second = widgets[i], widgets[i + 1] self.splitDockWidget(first.dockwidget, second.dockwidget, Qt.Horizontal) # now arrange the rows for column in widgets_layout: for i in range(len(column) - 1): first_row, second_row = column[i], column[i + 1] self.splitDockWidget(first_row[0].dockwidget, second_row[0].dockwidget, Qt.Vertical) # and finally tabify those in the same position for column in widgets_layout: for row in column: for i in range(len(row) - 1): first, second = row[i], row[i + 1] self.tabifyDockWidget(first.dockwidget, second.dockwidget) # Raise front widget per row row[0].dockwidget.show() row[0].dockwidget.raise_() # ----------------------- Events --------------------------------- def closeEvent(self, event): if self.project.is_saving or self.project.is_loading: event.ignore() self.project.inform_user_not_possible() return # Check whether or not to save project if not self.project.saved: # Offer save if self.project.offer_save(self): # Cancel has been clicked event.ignore() return # Close editors if self.editor.app_closing(): # write out any changes to the mantid config file ConfigService.saveConfig(ConfigService.getUserFilename()) # write current window information to global settings object self.writeSettings(CONF) # Close all open plots # We don't want this at module scope here import matplotlib.pyplot as plt # noqa plt.close('all') app = QApplication.instance() if app is not None: app.closeAllWindows() # Kill the project recovery thread and don't restart should a save be in progress and clear out current # recovery checkpoint as it is closing properly self.project_recovery.stop_recovery_thread() self.project_recovery.closing_workbench = True self.project_recovery.remove_current_pid_folder() self.interface_manager.closeHelpWindow() event.accept() else: # Cancel was pressed when closing an editor event.ignore() # ----------------------- Slots --------------------------------- def open_file(self): # todo: when more file types are added this should # live in its own type filepath, _ = QFileDialog.getOpenFileName(self, "Open File...", "", "Python (*.py)") if not filepath: return self.editor.open_file_in_new_tab(filepath) def save_script(self): self.editor.save_current_file() def save_script_as(self): self.editor.save_current_file_as() def generate_script_from_workspaces(self): task = BlockingAsyncTaskWithCallback( target=self._generate_script_from_workspaces, blocking_cb=QApplication.processEvents) task.start() def _generate_script_from_workspaces(self): script = "from mantid.simpleapi import *\n\n" + get_all_workspace_history_from_ads( ) QAppThreadCall(self.editor.open_script_in_new_tab)(script) def save_project(self): self.project.save() def save_project_as(self): self.project.save_as() def load_project(self): self.project.load() def open_manage_directories(self): manageuserdirectories.ManageUserDirectories.openManageUserDirectories() def open_script_repository(self): self.script_repository = ScriptRepositoryView(self) self.script_repository.loadScript.connect( self.editor.open_file_in_new_tab) self.script_repository.setAttribute(Qt.WA_DeleteOnClose, True) self.script_repository.show() def open_settings_window(self): settings = SettingsPresenter(self) settings.show() def open_settings_layout_window(self): settings = SettingsPresenter(self) settings.show() settings.general_settings.focus_layout_box() def config_updated(self): """ Updates the widgets that depend on settings from the Workbench Config. """ self.editor.load_settings_from_config(CONF) self.project.load_settings_from_config(CONF) self.algorithm_selector.refresh() self.populate_interfaces_menu() self.workspacewidget.refresh_workspaces() def open_algorithm_descriptions_help(self): self.interface_manager.showAlgorithmHelp('') def open_mantid_concepts_help(self): self.interface_manager.showConceptHelp('') def open_mantid_help(self): self.interface_manager.showHelpPage('') def open_mantid_homepage(self): self.interface_manager.showWebPage('https://www.mantidproject.org') def open_mantid_forum(self): self.interface_manager.showWebPage('https://forum.mantidproject.org/') def open_about(self): about = AboutPresenter(self) about.show() def readSettings(self, settings): qapp = QApplication.instance() qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(Qt, 'AA_EnableHighDpiScaling'): qapp.setAttribute(Qt.AA_EnableHighDpiScaling, settings.get('high_dpi_scaling')) # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) if settings.has('MainWindow/font'): font_string = settings.get('MainWindow/font').split(',') font = QFontDatabase().font(font_string[0], font_string[-1], int(font_string[1])) qapp.setFont(font) # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.screenGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): self.restoreState(settings.get('MainWindow/state')) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettings'): widget.readSettings(settings) def writeSettings(self, settings): settings.set('MainWindow/size', self.size()) # QSize settings.set('MainWindow/position', self.pos()) # QPoint settings.set('MainWindow/state', self.saveState()) # QByteArray # write out settings for children AlgorithmInputHistory().writeSettings(settings) for widget in self.widgets: if hasattr(widget, 'writeSettings'): widget.writeSettings(settings)
def action_open_release_notes(self): InterfaceManager().showHelpPage(release_notes_url())
def open_help_window(self): InterfaceManager().showCustomInterfaceHelp(self.doc)
def action_open_download_website(self): InterfaceManager().showWebPage('http://download.mantidproject.org')