def open_menu(self, pos): menu = QMenu() item = self.itemAt(pos) parent, item_type = self._get_toplevel_item(item) # ---- Refresh the selected item action refresh_action = QAction('Refresh', self) refresh_action.setToolTip(self.tooltips['Refresh']) refresh_action.triggered.connect(lambda: self.refresh_items(parent)) # ---- Refresh all items action refresh_all_action = QAction('Refresh all', self) refresh_all_action.setToolTip(self.tooltips['Refresh all']) refresh_all_action.triggered.connect(lambda: self.refresh_items()) # ---- add refresh actions menu.addActions([refresh_action, refresh_all_action]) # ---- add plot option if item_type == 'variable': add2p_action = QAction('Add to project', self) add2p_action.setToolTip(self.tooltips['Add to project']) add2p_action.triggered.connect( lambda: self.make_plot(parent.ds(), item.text(0), True)) menu.addSeparator() menu.addAction(add2p_action) # ---- show menu menu.exec_(self.mapToGlobal(pos)) return menu
class MainWindow(QMainWindow): open_external = QtCore.pyqtSignal(list) def __init__(self): super(MainWindow, self).__init__() #: list of figures from the psyplot backend self.figures = [] self.error_msg = PyErrorMessage(self) self.setDockOptions( QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks) #: Inprocess console self.console = ConsoleWidget(parent=self) self.project_actions = {} # --------------------------------------------------------------------- # ----------------------------- Menus --------------------------------- # --------------------------------------------------------------------- # ######################## File menu ################################## # --------------------------- New plot -------------------------------- self.file_menu = QMenu('File', parent=self) self.new_plot_action = QAction('New plot', self) self.new_plot_action.setStatusTip( 'Use an existing dataset (or open a new one) to create one or ' 'more plots') self.new_plot_action.setShortcut(QKeySequence.New) self.new_plot_action.triggered.connect(self.new_plots) self.file_menu.addAction(self.new_plot_action) # --------------------------- Open project ---------------------------- self.open_project_menu = QMenu('Open project', self) self.file_menu.addMenu(self.open_project_menu) self.open_mp_action = QAction('New main project', self) self.open_mp_action.setShortcut(QKeySequence.Open) self.open_mp_action.setStatusTip('Open a new main project') self.open_mp_action.triggered.connect(self.open_mp) self.open_project_menu.addAction(self.open_mp_action) self.open_sp_action = QAction('Add to current', self) self.open_sp_action.setShortcut(QKeySequence( 'Ctrl+Shift+O', QKeySequence.NativeText)) self.open_sp_action.setStatusTip( 'Load a project as a sub project and add it to the current main ' 'project') self.open_sp_action.triggered.connect(self.open_sp) self.open_project_menu.addAction(self.open_sp_action) # ----------------------- Save project -------------------------------- self.save_project_menu = QMenu('Save project', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_mp_action = QAction('All', self) self.save_mp_action.setStatusTip( 'Save the entire project into a pickle file') self.save_mp_action.setShortcut(QKeySequence.Save) self.save_mp_action.triggered.connect(self.save_mp) self.save_project_menu.addAction(self.save_mp_action) self.save_sp_action = QAction('Selected', self) self.save_sp_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_action.triggered.connect(self.save_sp) self.save_project_menu.addAction(self.save_sp_action) # ------------------------ Save project as ---------------------------- self.save_project_menu = QMenu('Save project', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_project_as_menu = QMenu('Save project as', parent=self) self.file_menu.addMenu(self.save_project_as_menu) self.save_mp_as_action = QAction('All', self) self.save_mp_as_action.setStatusTip( 'Save the entire project into a pickle file') self.save_mp_as_action.setShortcut(QKeySequence.SaveAs) self.save_mp_as_action.triggered.connect( partial(self.save_mp, new_name=True)) self.save_project_as_menu.addAction(self.save_mp_as_action) self.save_sp_as_action = QAction('Selected', self) self.save_sp_as_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_as_action.triggered.connect( partial(self.save_sp, new_name=True)) self.save_project_as_menu.addAction(self.save_sp_as_action) # -------------------------- Pack project ----------------------------- self.pack_project_menu = QMenu('Zip project files', parent=self) self.file_menu.addMenu(self.pack_project_menu) self.pack_mp_action = QAction('All', self) self.pack_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.pack_mp_action.triggered.connect(partial(self.save_mp, pack=True)) self.pack_project_menu.addAction(self.pack_mp_action) self.pack_sp_action = QAction('Selected', self) self.pack_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.pack_sp_action.triggered.connect(partial(self.save_sp, pack=True)) self.pack_project_menu.addAction(self.pack_sp_action) # ------------------------ Export figures ----------------------------- self.export_project_menu = QMenu('Export figures', parent=self) self.file_menu.addMenu(self.export_project_menu) self.export_mp_action = QAction('All', self) self.export_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.export_mp_action.triggered.connect(self.export_mp) self.export_mp_action.setShortcut(QKeySequence( 'Ctrl+E', QKeySequence.NativeText)) self.export_project_menu.addAction(self.export_mp_action) self.export_sp_action = QAction('Selected', self) self.export_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.export_sp_action.setShortcut(QKeySequence( 'Ctrl+Shift+E', QKeySequence.NativeText)) self.export_sp_action.triggered.connect(self.export_sp) self.export_project_menu.addAction(self.export_sp_action) # ------------------------ Close project ------------------------------ self.file_menu.addSeparator() self.close_project_menu = QMenu('Close project', parent=self) self.file_menu.addMenu(self.close_project_menu) self.close_mp_action = QAction('Main project', self) self.close_mp_action.setStatusTip( 'Close the main project and delete all data and plots out of ' 'memory') self.close_mp_action.setShortcut(QKeySequence.Close) self.close_mp_action.triggered.connect( lambda: psy.close(psy.gcp(True).num)) self.close_project_menu.addAction(self.close_mp_action) self.close_sp_action = QAction('Only selected', self) self.close_sp_action.setStatusTip( 'Close the selected arrays project and delete all data and plots ' 'out of memory') self.close_sp_action.setShortcut(QKeySequence( 'Ctrl+Shift+W', QKeySequence.NativeText)) self.close_sp_action.triggered.connect( lambda: psy.gcp().close(True, True)) self.close_project_menu.addAction(self.close_sp_action) # ------------------------ Quit ------------------------------ if sys.platform != 'darwin': # mac os makes this anyway self.quit_action = QAction('Quit', self) self.quit_action.triggered.connect( QtCore.QCoreApplication.instance().quit) self.quit_action.setShortcut(QKeySequence.Quit) self.file_menu.addAction(self.quit_action) self.menuBar().addMenu(self.file_menu) # ######################## Console menu ############################### self.console_menu = QMenu('Console', self) self.console_menu.addActions(self.console.actions()) self.menuBar().addMenu(self.console_menu) # ######################## Windows menu ############################### self.windows_menu = QMenu('Windows', self) self.menuBar().addMenu(self.windows_menu) # --------------------------------------------------------------------- # -------------------------- Dock windows ----------------------------- # --------------------------------------------------------------------- #: tab widget displaying the arrays in current main and sub project self.project_content = ProjectContentWidget(parent=self) self.addDockWidget(Qt.LeftDockWidgetArea, self.project_content.to_dock('Plot objects', self), 'pane') #: tree widget displaying the open datasets self.ds_tree = DatasetTree(parent=self) self.addDockWidget(Qt.LeftDockWidgetArea, self.ds_tree.to_dock( 'Datasets', self), 'pane') #: tree widget displaying the open figures self.figures_tree = FiguresTree(parent=self) self.addDockWidget(Qt.LeftDockWidgetArea, self.figures_tree.to_dock( 'Figures', self), 'pane') #: help explorer self.help_explorer = help_explorer = HelpExplorer(parent=self) self.addDockWidget(Qt.RightDockWidgetArea, help_explorer.to_dock( 'Help explorer', self), 'pane') #: general formatoptions widget self.fmt_widget = FormatoptionWidget( parent=self, help_explorer=help_explorer, shell=self.console.kernel_client.kernel.shell) self.addDockWidget(Qt.BottomDockWidgetArea, self.fmt_widget.to_dock( 'Formatoptions', self), 'pane') self.windows_menu.addSeparator() self.add_mp_to_menu() psy.Project.oncpchange.connect(self.eventually_add_mp_to_menu) # --------------------------------------------------------------------- # -------------------------- connections ------------------------------ # --------------------------------------------------------------------- self.console.help_explorer = help_explorer psyp.default_print_func = partial(help_explorer.show_rst, oname='formatoption_docs') psy._PlotterInterface._print_func = psyp.default_print_func self.setCentralWidget(self.console) # make sure that the plots are shown between the project content and # the help explorer widget self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) # make sure that the formatoption widgets are shown between the # project content and the help explorer widget self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # Server to open external files on a single instance self.open_files_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) self.showMaximized() self._file_thread = Thread(target=self.start_open_files_server) self._file_thread.setDaemon(True) self._file_thread.start() self.open_external.connect(self.open_external_files) # --------------------------------------------------------------------- # ------------------------------ closure ------------------------------ # --------------------------------------------------------------------- self.help_explorer.show_intro(self.console.intro_msg) def _save_project(self, p, new_fname=False, *args, **kwargs): if new_fname or 'project_file' not in p.attrs: fname = QFileDialog.getSaveFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) if not fname: return else: fname = p.attrs['project_file'] try: p.save_project(fname, *args, **kwargs) except: self.error_msg.showTraceback('<b>Could not save the project!</b>') else: p.attrs['project_file'] = fname if p.is_main: self.update_project_action(p.num) def open_mp(self, *args, **kwargs): """Open a new main project""" self._open_project(main=True) def open_sp(self, *args, **kwargs): """Open a subproject and add it to the current main project""" self._open_project(main=False) def _open_project(self, *args, **kwargs): fname = QFileDialog.getOpenFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) p = psy.Project.load_project(fname, *args, **kwargs) p.attrs['project_file'] = fname self.update_project_action(p.num) def save_mp(self, *args, **kwargs): """Save the current main project""" self._save_project(psy.gcp(True), **kwargs) def save_sp(self, *args, **kwargs): """Save the current sub project""" self._save_project(psy.gcp(), **kwargs) def _export_project(self, p, *args, **kwargs): fname = QFileDialog.getSaveFileName( self, 'Picture destination', os.getcwd(), 'PDF files (*.pdf);;' 'Postscript file (*.ps);;' 'PNG image (*.png);;' 'JPG image (*.jpg *.jpeg);;' 'TIFF image (*.tif *.tiff);;' 'GIF image (*.gif);;' 'All files (*)' ) if not fname: return try: p.export(fname, *args, **kwargs) except: self.error_msg.showTraceback( '<b>Could not export the figures!</b>') def export_mp(self, *args, **kwargs): self._export_project(psy.gcp(True), **kwargs) def export_sp(self, *args, **kwargs): self._export_project(psy.gcp(), **kwargs) def new_plots(self): if hasattr(self, 'plot_creator'): self.plot_creator.close() self.plot_creator = PlotCreator( self.console.get_obj, help_explorer=self.help_explorer) available_width = QDesktopWidget().availableGeometry().width() / 3. width = self.plot_creator.sizeHint().width() height = self.plot_creator.sizeHint().height() # The plot creator window shoul cover at least one third of the screen self.plot_creator.resize(max(available_width, width), height) self.plot_creator.show() def add_mp_to_menu(self): mp = psy.gcp(True) action = QAction(os.path.basename(mp.attrs.get( 'project_file', 'Untitled %s*' % mp.num)), self) action.setStatusTip( 'Make project %s the current project' % mp.num) action.triggered.connect(lambda: psy.scp(psy.project(mp.num))) self.project_actions[mp.num] = action self.windows_menu.addAction(action) def update_project_action(self, num): action = self.project_actions.get(num) p = psy.project(num) if action: action.setText(os.path.basename(p.attrs.get( 'project_file', 'Untitled %s*' % num))) def eventually_add_mp_to_menu(self, p): for num in set(self.project_actions).difference( psy.get_project_nums()): self.windows_menu.removeAction(self.project_actions.pop(num)) if p is None or not p.is_main: return if p.num not in self.project_actions: self.add_mp_to_menu() def addDockWidget(self, area, dockwidget, docktype=None, *args, **kwargs): """Reimplemented to add widgets to the windows menu""" ret = super(MainWindow, self).addDockWidget(area, dockwidget, *args, **kwargs) if docktype == 'pane': self.windows_menu.addAction(dockwidget.toggleViewAction()) return ret def start_open_files_server(self): """This method listens to the open_files_port and opens the plot creator for new files This method is inspired and to most parts copied from spyder""" self.open_files_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) port = rcParams['main.open_files_port'] self.open_files_server.bind(('127.0.0.1', port)) self.open_files_server.listen(20) while 1: # 1 is faster than True try: req, dummy = self.open_files_server.accept() except socket.error as e: # See Issue 1275 for details on why errno EINTR is # silently ignored here. eintr = errno.EINTR # To avoid a traceback after closing on Windows if e.args[0] == eintr: continue raise l = pickle.loads(req.recv(1024)) self.open_external.emit(l) req.sendall(b' ') def open_external_files(self, l): fnames, project, engine, plot_method, name, dims = l if project is not None: fnames = [s.split(',') for s in fnames] single_files = (l[0] for l in fnames if len(l) == 1) alternative_paths = defaultdict(lambda: next(single_files, None)) alternative_paths.update(list(l for l in fnames if len(l) == 2)) psy.Project.load_project( project, alternative_paths=alternative_paths, engine=engine, main=False) else: self.new_plots() self.plot_creator.open_dataset(fnames, engine=engine) self.plot_creator.insert_array(name) if dims is not None: self.plot_creator.array_table.selectAll() self.plot_creator.array_table.update_selected( dims={key: ', '.join( map(str, val)) for key, val in six.iteritems( dims)})
class MainWindow(QMainWindow): #: A signal that is emmitted when the a signal is received through the #: open_files_server open_external = QtCore.pyqtSignal(list) #: The server to open external files open_files_server = None #: Inprocess console console = None #: tree widget displaying the open datasets ds_tree = None #: list of figures from the psyplot backend figures = [] #: tree widget displaying the open figures figures_tree = None #: general formatoptions widget fmt_widget = None #: help explorer help_explorer = None #: the DataFrameEditor widgets, a widget to show and edit data frames dataframeeditors = None #: tab widget displaying the arrays in current main and sub project project_content = None #: The dockwidgets of this instance dockwidgets = [] #: default widths of the dock widgets default_widths = {} _is_open = False #: The keyboard shortcuts of the default layout default_shortcuts = [] #: The current keyboard shortcuts current_shortcuts = [] #: The key for the central widget for the main window in the #: :attr:`plugins` dictionary central_widget_key = 'console' @property def logger(self): """The logger of this instance""" return logging.getLogger( '%s.%s' % (self.__class__.__module__, self.__class__.__name__)) @docstrings.get_sections(base='MainWindow') @docstrings.dedent def __init__(self, show=True): """ Parameters ---------- show: bool If True, the created mainwindow is show """ if sys.stdout is None: sys.stdout = StreamToLogger(self.logger) if sys.stderr is None: sys.stderr = StreamToLogger(self.logger) super(MainWindow, self).__init__() self.setWindowIcon(QIcon(get_icon('logo.png'))) #: list of figures from the psyplot backend self.figures = [] self.error_msg = PyErrorMessage(self) self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks) #: Inprocess console self.console = ConsoleWidget(self) self.project_actions = {} self.config_pages = [] self.open_file_options = OrderedDict([ ('new psyplot plot from dataset', self.open_external_files), ('new psyplot project', partial(self.open_external_files, [])), ]) # --------------------------------------------------------------------- # ----------------------------- Menus --------------------------------- # --------------------------------------------------------------------- # ######################## File menu ################################## # --------------------------- New plot -------------------------------- self.file_menu = QMenu('File', parent=self) self.new_plot_action = QAction('New plot', self) self.new_plot_action.setStatusTip( 'Use an existing dataset (or open a new one) to create one or ' 'more plots') self.register_shortcut(self.new_plot_action, QKeySequence.New) self.new_plot_action.triggered.connect(lambda: self.new_plots(True)) self.file_menu.addAction(self.new_plot_action) # --------------------------- Open project ---------------------------- self.open_project_menu = QMenu('Open project', self) self.file_menu.addMenu(self.open_project_menu) self.open_mp_action = QAction('New main project', self) self.register_shortcut(self.open_mp_action, QKeySequence.Open) self.open_mp_action.setStatusTip('Open a new main project') self.open_mp_action.triggered.connect(self.open_mp) self.open_project_menu.addAction(self.open_mp_action) self.open_sp_action = QAction('Add to current', self) self.register_shortcut( self.open_sp_action, QKeySequence('Ctrl+Shift+O', QKeySequence.NativeText)) self.open_sp_action.setStatusTip( 'Load a project as a sub project and add it to the current main ' 'project') self.open_sp_action.triggered.connect(self.open_sp) self.open_project_menu.addAction(self.open_sp_action) # ---------------------- load preset menu ----------------------------- self.load_preset_menu = QMenu('Load preset', parent=self) self.file_menu.addMenu(self.load_preset_menu) self.load_sp_preset_action = self.load_preset_menu.addAction( "For selection", self.load_sp_preset) self.load_sp_preset_action.setStatusTip( "Load a preset for the selected project") self.load_mp_preset_action = self.load_preset_menu.addAction( "For full project", self.load_mp_preset) self.load_sp_preset_action.setStatusTip( "Load a preset for the full project") # ----------------------- Save project -------------------------------- self.save_project_menu = QMenu('Save', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_mp_action = QAction('Full psyplot project', self) self.save_mp_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_action, QKeySequence.Save) self.save_mp_action.triggered.connect(self.save_mp) self.save_project_menu.addAction(self.save_mp_action) self.save_sp_action = QAction('Selected psyplot project', self) self.save_sp_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_action.triggered.connect(self.save_sp) self.save_project_menu.addAction(self.save_sp_action) # ------------------------ Save project as ---------------------------- self.save_project_as_menu = QMenu('Save as', parent=self) self.file_menu.addMenu(self.save_project_as_menu) self.save_mp_as_action = QAction('Full psyplot project', self) self.save_mp_as_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_as_action, QKeySequence.SaveAs) self.save_mp_as_action.triggered.connect( partial(self.save_mp, new_fname=True)) self.save_project_as_menu.addAction(self.save_mp_as_action) self.save_sp_as_action = QAction('Selected psyplot project', self) self.save_sp_as_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_as_action.triggered.connect( partial(self.save_sp, new_fname=True)) self.save_project_as_menu.addAction(self.save_sp_as_action) # ------------------------ Save preset -------------------------------- self.save_preset_menu = QMenu('Save preset', parent=self) self.file_menu.addMenu(self.save_preset_menu) self.save_sp_preset_action = self.save_preset_menu.addAction( "Selection", self.save_sp_preset) self.save_sp_preset_action.setStatusTip( "Save the formatoptions of the selected project as a preset") self.save_mp_preset_action = self.save_preset_menu.addAction( "Full project", self.save_mp_preset) self.save_sp_preset_action.setStatusTip( "Save the formatoptions of the full project as a preset") # -------------------------- Pack project ----------------------------- self.pack_project_menu = QMenu('Zip project files', parent=self) self.file_menu.addMenu(self.pack_project_menu) self.pack_mp_action = QAction('Full psyplot project', self) self.pack_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.pack_mp_action.triggered.connect(partial(self.save_mp, pack=True)) self.pack_project_menu.addAction(self.pack_mp_action) self.pack_sp_action = QAction('Selected psyplot project', self) self.pack_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.pack_sp_action.triggered.connect(partial(self.save_sp, pack=True)) self.pack_project_menu.addAction(self.pack_sp_action) # ------------------------ Export figures ----------------------------- self.export_project_menu = QMenu('Export figures', parent=self) self.file_menu.addMenu(self.export_project_menu) self.export_mp_action = QAction('Full psyplot project', self) self.export_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.export_mp_action.triggered.connect(self.export_mp) self.register_shortcut(self.export_mp_action, QKeySequence('Ctrl+E', QKeySequence.NativeText)) self.export_project_menu.addAction(self.export_mp_action) self.export_sp_action = QAction('Selected psyplot project', self) self.export_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.register_shortcut( self.export_sp_action, QKeySequence('Ctrl+Shift+E', QKeySequence.NativeText)) self.export_sp_action.triggered.connect(self.export_sp) self.export_project_menu.addAction(self.export_sp_action) # ------------------------ Close project ------------------------------ self.file_menu.addSeparator() self.close_project_menu = QMenu('Close project', parent=self) self.file_menu.addMenu(self.close_project_menu) self.close_mp_action = QAction('Full psyplot project', self) self.register_shortcut( self.close_mp_action, QKeySequence('Ctrl+Shift+W', QKeySequence.NativeText)) self.close_mp_action.setStatusTip( 'Close the main project and delete all data and plots out of ' 'memory') self.close_mp_action.triggered.connect( lambda: psy.close(psy.gcp(True).num)) self.close_project_menu.addAction(self.close_mp_action) self.close_sp_action = QAction('Selected psyplot project', self) self.close_sp_action.setStatusTip( 'Close the selected arrays project and delete all data and plots ' 'out of memory') self.register_shortcut(self.close_sp_action, QKeySequence.Close) self.close_sp_action.triggered.connect( lambda: psy.gcp().close(True, True)) self.close_project_menu.addAction(self.close_sp_action) # ----------------------------- Quit ---------------------------------- if sys.platform != 'darwin': # mac os makes this anyway self.quit_action = QAction('Quit', self) self.quit_action.triggered.connect(self.close) self.quit_action.triggered.connect( QtCore.QCoreApplication.instance().quit) self.register_shortcut(self.quit_action, QKeySequence.Quit) self.file_menu.addAction(self.quit_action) self.menuBar().addMenu(self.file_menu) # ######################## Console menu ############################### self.console_menu = QMenu('Console', self) self.console_menu.addActions(self.console.actions()) self.menuBar().addMenu(self.console_menu) # ######################## Windows menu ############################### self.windows_menu = QMenu('Windows', self) self.menuBar().addMenu(self.windows_menu) # ############################ Help menu ############################## self.help_menu = QMenu('Help', parent=self) self.menuBar().addMenu(self.help_menu) # -------------------------- Preferences ------------------------------ self.help_action = QAction('Preferences', self) self.help_action.triggered.connect(lambda: self.edit_preferences(True)) self.register_shortcut(self.help_action, QKeySequence.Preferences) self.help_menu.addAction(self.help_action) # ---------------------------- About ---------------------------------- self.about_action = QAction('About', self) self.about_action.triggered.connect(self.about) self.help_menu.addAction(self.about_action) # ---------------------------- Dependencies --------------------------- self.dependencies_action = QAction('Dependencies', self) self.dependencies_action.triggered.connect( lambda: self.show_dependencies(True)) self.help_menu.addAction(self.dependencies_action) self.dockwidgets = [] # --------------------------------------------------------------------- # -------------------------- Dock windows ----------------------------- # --------------------------------------------------------------------- #: tab widget displaying the arrays in current main and sub project #: tree widget displaying the open datasets self.project_content = ProjectContentWidget(parent=self) self.ds_tree = DatasetTree(parent=self) #: tree widget displaying the open figures self.figures_tree = FiguresTree(parent=self) #: help explorer self.help_explorer = help_explorer = HelpExplorer(parent=self) if 'HTML help' in help_explorer.viewers and help_explorer.viewers[ 'HTML help'].sphinx_thread is not None: help_explorer.viewers[ 'HTML help'].sphinx_thread.html_ready.connect( self.focus_on_console) #: the DataFrameEditor widgets self.dataframeeditors = [] #: general formatoptions widget self.fmt_widget = FormatoptionWidget(parent=self, help_explorer=help_explorer, console=self.console) # load plugin widgets self.plugins = plugins = OrderedDict([ ('console', self.console), ('project_content', self.project_content), ('ds_tree', self.ds_tree), ('figures_tree', self.figures_tree), ('help_explorer', self.help_explorer), ('fmt_widget', self.fmt_widget), ]) self.default_plugins = list(plugins) for plugin_name, w_class in six.iteritems(rcParams.load_plugins()): plugins[plugin_name] = w_class(parent=self) self.add_mp_to_menu() psy.Project.oncpchange.connect(self.eventually_add_mp_to_menu) self.windows_menu.addSeparator() self.window_layouts_menu = QMenu('Window layouts', self) self.restore_layout_action = QAction('Restore default layout', self) self.restore_layout_action.triggered.connect(self.setup_default_layout) self.window_layouts_menu.addAction(self.restore_layout_action) self.windows_menu.addMenu(self.window_layouts_menu) self.panes_menu = QMenu('Panes', self) self.windows_menu.addMenu(self.panes_menu) self.dataframe_menu = QMenu('DataFrame editors', self) self.dataframe_menu.addAction( 'New Editor', partial(self.new_data_frame_editor, None, 'DataFrame Editor')) self.dataframe_menu.addSeparator() self.windows_menu.addMenu(self.dataframe_menu) self.central_widgets_menu = menu = QMenu('Central widget', self) self.windows_menu.addMenu(menu) self.central_widgets_actions = group = QActionGroup(self) group.setExclusive(True) # --------------------------------------------------------------------- # -------------------------- connections ------------------------------ # --------------------------------------------------------------------- self.console.help_explorer = help_explorer psyp.default_print_func = partial(help_explorer.show_rst, oname='formatoption_docs') psy.PlotterInterface._print_func = psyp.default_print_func self.setCentralWidget(self.console) # make sure that the plots are shown between the project content and # the help explorer widget self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) # make sure that the formatoption widgets are shown between the # project content and the help explorer widget self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # --------------------------------------------------------------------- # ------------------------------ closure ------------------------------ # --------------------------------------------------------------------- if show: self.help_explorer.show_intro(self.console.intro_msg) # --------------------------------------------------------------------- # ------------------------- open_files_server ------------------------- # --------------------------------------------------------------------- self.callbacks = { 'new_plot': self.open_external.emit, 'change_cwd': self._change_cwd, 'run_script': self.console.run_script.emit, 'command': self.console.run_command.emit, } # Server to open external files on a single instance self.open_files_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if rcParams['main.listen_to_port']: self._file_thread = Thread(target=self.start_open_files_server) self._file_thread.setDaemon(True) self._file_thread.start() self.open_external.connect(self._open_external_files) self.config_pages.extend([GuiRcParamsWidget, PsyRcParamsWidget]) # display the statusBar statusbar = self.statusBar() self.figures_label = QLabel() statusbar.addWidget(self.figures_label) self.plugin_label = QLabel() statusbar.addWidget(self.plugin_label) self.default_widths = {} self.setup_default_layout() if show: self.showMaximized() # save the default widths after they have been shown for w in self.plugins.values(): if w.dock is not None: self.default_widths[w] = w.dock.size().width() # hide plugin widgets that should be hidden at startup. Although this # has been executed by :meth:`setup_default_layout`, we have to execute # it again after the call of showMaximized for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() else: w.create_central_widget_action(self).setChecked(True) self._is_open = True def focus_on_console(self, *args, **kwargs): """Put focus on the ipython console""" self.console._control.setFocus() def new_data_frame_editor(self, df=None, title='DataFrame Editor'): """Open a new dataframe editor Parameters ---------- df: pandas.DataFrame The dataframe to display title: str The title of the dock window Returns ------- psyplot_gui.dataframeeditor.DataFrameEditor The newly created editor""" editor = DataFrameEditor() self.dataframeeditors.append(editor) editor.to_dock(self, title, Qt.RightDockWidgetArea, docktype='df') if df is not None: editor.set_df(df) editor.show_plugin() editor.maybe_tabify() editor.raise_() return editor def setup_default_layout(self): """Set up the default window layout""" self.project_content.to_dock(self, 'Plot objects', Qt.LeftDockWidgetArea) self.ds_tree.to_dock(self, 'Datasets', Qt.LeftDockWidgetArea) self.figures_tree.to_dock(self, 'Figures', Qt.LeftDockWidgetArea) self.help_explorer.to_dock(self, 'Help explorer', Qt.RightDockWidgetArea) self.fmt_widget.to_dock(self, 'Formatoptions', Qt.BottomDockWidgetArea) modify_widths = bool(self.default_widths) for w in map(self.plugins.__getitem__, self.default_plugins): if w.dock is not None: w.show_plugin() if modify_widths and with_qt5: self.resizeDocks([w.dock], [self.default_widths[w]], Qt.Horizontal) # hide plugin widgets that should be hidden at startup for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() action2shortcut = defaultdict(list) for s, a in self.default_shortcuts: action2shortcut[a].append(s) for a, s in action2shortcut.items(): self.register_shortcut(a, s) def set_central_widget(self, name): """Set the central widget Parameters ---------- name: str or QWidget The key or the plugin widget in the :attr:`plugins` dictionary""" from PyQt5.QtCore import QTimer self.setUpdatesEnabled(False) current = self.centralWidget() if isinstance(name, six.string_types): new = self.plugins[name] else: new = name name = next(key for key, val in self.plugins.items() if val is new) if new is not current: self._dock_widths = dock_widths = OrderedDict() self._dock_heights = dock_heights = OrderedDict() for key, w in self.plugins.items(): if w.dock is not None and w.is_shown: s = w.dock.size() dock_widths[w] = s.width() if w is not new: dock_heights[w] = s.height() new_pos = self.dockWidgetArea(new.dock) self.removeDockWidget(new.dock) new.dock.close() self.panes_menu.removeAction(new._view_action) self.dataframe_menu.removeAction(new._view_action) new.dock = new._view_action = None self.central_widget_key = name current.to_dock(self) self.setCentralWidget(new) new._set_central_action.setChecked(True) current.show_plugin() current.to_dock(self) new_width = dock_widths.pop(new, None) if current.hidden: current.hide_plugin() else: current_pos = self.dockWidgetArea(current.dock) if current_pos == new_pos and new_width: dock_widths[current] = new_width self._custom_layout_timer = QTimer(self) self._custom_layout_timer.timeout.connect(self._reset_dock_widths) self._custom_layout_timer.setSingleShot(True) self._custom_layout_timer.start(5000) def _reset_dock_widths(self): # resize the plugins if with_qt5: for w, width in self._dock_widths.items(): if w.dock is not None: self.resizeDocks([w.dock], [width], Qt.Horizontal) for w, height in self._dock_heights.items(): if w.dock is not None: self.resizeDocks([w.dock], [height], Qt.Vertical) self.setUpdatesEnabled(True) def _save_project(self, p, new_fname=False, *args, **kwargs): if new_fname or 'project_file' not in p.attrs: fname = QFileDialog.getSaveFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return else: fname = p.attrs['project_file'] try: p.save_project(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback('<b>Could not save the project!</b>') else: p.attrs['project_file'] = fname if p.is_main: self.update_project_action(p.num) def load_mp_preset(self): self._load_preset(psy.gcp(True)) def load_sp_preset(self): self._load_preset(psy.gcp()) def _load_preset(self, project, *args, **kwargs): fname, ok = QFileDialog.getOpenFileName( self, 'Load preset', os.path.join(get_configdir(), "presets"), 'YAML files (*.yml *.yaml);;' 'All files (*)') if ok: project.load_preset(fname, *args, **kwargs) def open_mp(self, *args, **kwargs): """Open a new main project""" self._open_project(main=True) def open_sp(self, *args, **kwargs): """Open a subproject and add it to the current main project""" self._open_project(main=False) def _open_project(self, *args, **kwargs): fname = QFileDialog.getOpenFileName( self, 'Project file', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return p = psy.Project.load_project(fname, *args, **kwargs) p.attrs['project_file'] = fname self.update_project_action(p.num) def save_mp(self, *args, **kwargs): """Save the current main project.""" self._save_project(psy.gcp(True), **kwargs) def save_sp(self, *args, **kwargs): """Save the current sub project.""" self._save_project(psy.gcp(), **kwargs) def save_sp_preset(self): self._save_preset(psy.gcp()) def save_mp_preset(self): self._save_preset(psy.gcp(True)) def _save_preset(self, project, *args, **kwargs): fname, ok = QFileDialog.getSaveFileName( self, 'Save preset', os.path.join(get_configdir(), 'presets'), 'YAML file (*.yml *.yaml);;' 'All files (*)') if ok: project.save_preset(fname, *args, **kwargs) def _export_project(self, p, *args, **kwargs): fname = QFileDialog.getSaveFileName( self, 'Picture destination', os.getcwd(), 'PDF files (*.pdf);;' 'Postscript file (*.ps);;' 'PNG image (*.png);;' 'JPG image (*.jpg *.jpeg);;' 'TIFF image (*.tif *.tiff);;' 'GIF image (*.gif);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return try: p.export(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback( '<b>Could not export the figures!</b>') def export_mp(self, *args, **kwargs): self._export_project(psy.gcp(True), **kwargs) def export_sp(self, *args, **kwargs): self._export_project(psy.gcp(), **kwargs) def new_plots(self, exec_=None): if hasattr(self, 'plot_creator'): try: self.plot_creator.close() except RuntimeError: pass self.plot_creator = PlotCreator(help_explorer=self.help_explorer, parent=self) available_width = QDesktopWidget().availableGeometry().width() / 3. width = self.plot_creator.sizeHint().width() height = self.plot_creator.sizeHint().height() # The plot creator window should cover at least one third of the screen self.plot_creator.resize(max(available_width, width), height) if exec_: self.plot_creator.exec_() def excepthook(self, type, value, traceback): """A method to replace the sys.excepthook""" self.error_msg.excepthook(type, value, traceback) def edit_preferences(self, exec_=None): """Edit Spyder preferences""" if hasattr(self, 'preferences'): try: self.preferences.close() except RuntimeError: pass self.preferences = dlg = Prefences(self) for PrefPageClass in self.config_pages: widget = PrefPageClass(dlg) widget.initialize() dlg.add_page(widget) available_width = int(0.667 * QDesktopWidget().availableGeometry().width()) width = dlg.sizeHint().width() height = dlg.sizeHint().height() # The preferences window should cover at least one third of the screen dlg.resize(max(available_width, width), height) if exec_: dlg.exec_() def about(self): """About the tool""" versions = { key: d['version'] for key, d in psyplot.get_versions(False).items() } versions.update(psyplot_gui.get_versions()['requirements']) versions.update(psyplot._get_versions()['requirements']) versions['github'] = 'https://github.com/psyplot/psyplot' versions['author'] = psyplot.__author__ QMessageBox.about( self, "About psyplot", u"""<b>psyplot: Interactive data visualization with python</b> <br>Copyright © 2017- Philipp Sommer <br>Licensed under the terms of the GNU General Public License v2 (GPLv2) <p>Created by %(author)s</p> <p>Most of the icons come from the <a href="https://www.iconfinder.com/"> iconfinder</a>.</p> <p>For bug reports and feature requests, please go to our <a href="%(github)s">Github website</a> or contact the author via mail.</p> <p>This package uses (besides others) the following packages:<br> <ul> <li>psyplot %(psyplot)s</li> <li>Python %(python)s </li> <li>numpy %(numpy)s</li> <li>xarray %(xarray)s</li> <li>pandas %(pandas)s</li> <li>psyplot_gui %(psyplot_gui)s</li> <li>Qt %(qt)s</li> <li>PyQt %(pyqt)s</li> <li>qtconsole %(qtconsole)s</li> </ul></p> <p>For a full list of requirements see the <em>dependencies</em> in the <em>Help</em> menu.</p> <p>This software is provided "as is", without warranty or support of any kind.</p>""" % versions) def show_dependencies(self, exec_=None): """Open a dialog that shows the dependencies""" if hasattr(self, 'dependencies'): try: self.dependencies.close() except RuntimeError: pass self.dependencies = dlg = DependenciesDialog(psyplot.get_versions(), parent=self) dlg.resize(630, 420) if exec_: dlg.exec_() def reset_rcParams(self): rcParams.update_from_defaultParams() psy.rcParams.update_from_defaultParams() def add_mp_to_menu(self): mp = psy.gcp(True) action = QAction( os.path.basename( mp.attrs.get('project_file', 'Untitled %s*' % mp.num)), self) action.setStatusTip('Make project %s the current project' % mp.num) action.triggered.connect(lambda: psy.scp(psy.project(mp.num))) self.project_actions[mp.num] = action self.windows_menu.addAction(action) def update_project_action(self, num): action = self.project_actions.get(num) p = psy.project(num) if action: action.setText( os.path.basename( p.attrs.get('project_file', 'Untitled %s*' % num))) def eventually_add_mp_to_menu(self, p): for num in set(self.project_actions).difference( psy.get_project_nums()): self.windows_menu.removeAction(self.project_actions.pop(num)) if p is None or not p.is_main: return if p.num not in self.project_actions: self.add_mp_to_menu() def start_open_files_server(self): """This method listens to the open_files_port and opens the plot creator for new files This method is inspired and to most parts copied from spyder""" self.open_files_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) port = rcParams['main.open_files_port'] try: self.open_files_server.bind(('127.0.0.1', port)) except Exception: return self.open_files_server.listen(20) while 1: # 1 is faster than True try: req, dummy = self.open_files_server.accept() except socket.error as e: # See Issue 1275 for details on why errno EINTR is # silently ignored here. eintr = errno.EINTR # To avoid a traceback after closing on Windows if e.args[0] == eintr: continue # handle a connection abort on close error enotsock = (errno.WSAENOTSOCK if os.name == 'nt' else errno.ENOTSOCK) if e.args[0] in [errno.ECONNABORTED, enotsock]: return raise args = pickle.loads(req.recv(1024)) callback = args[0] func = self.callbacks[callback] self.logger.debug('Emitting %s callback %s', callback, func) func(args[1:]) req.sendall(b' ') def change_cwd(self, path): """Change the current working directory""" import os os.chdir(path) def _change_cwd(self, args): path = args[0][0] self.change_cwd(path) docstrings.keep_params('make_plot.parameters', 'fnames', 'project', 'engine', 'plot_method', 'name', 'dims', 'encoding', 'enable_post', 'seaborn_style', 'concat_dim', 'chname', 'preset') def open_files(self, fnames): """Open a file and ask the user how""" fnames_s = ', '.join(map(os.path.basename, fnames)) if len(fnames_s) > 30: fnames_s = fnames_s[:27] + '...' item, ok = QInputDialog.getItem(self, 'Open file...', 'Open %s as...' % fnames_s, list(self.open_file_options), current=0, editable=False) if ok: return self.open_file_options[item](fnames) @docstrings.get_sections(base='MainWindow.open_external_files') @docstrings.dedent def open_external_files(self, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value( xr.open_mfdataset, 'concat_dim'), chname={}, preset=None): """ Open external files Parameters ---------- %(make_plot.parameters.fnames|project|engine|plot_method|name|dims|encoding|enable_post|seaborn_style|concat_dim|chname|preset)s """ if seaborn_style is not None: import seaborn as sns sns.set_style(seaborn_style) if project is not None: fnames = [s.split(',') for s in fnames] if not isinstance(project, dict): project = psyd.safe_list(project)[0] single_files = (l[0] for l in fnames if len(l) == 1) alternative_paths = defaultdict(lambda: next(single_files, None)) alternative_paths.update(list(l for l in fnames if len(l) == 2)) p = psy.Project.load_project(project, alternative_paths=alternative_paths, engine=engine, main=not psy.gcp(), encoding=encoding, enable_post=enable_post, chname=chname) if preset: p.load_preset(preset) if isinstance(project, six.string_types): p.attrs.setdefault('project_file', project) return True else: self.new_plots(False) self.plot_creator.open_dataset(fnames, engine=engine, concat_dim=concat_dim) if name == 'all': ds = self.plot_creator.get_ds() name = sorted(set(ds.variables) - set(ds.coords)) self.plot_creator.insert_array( list(filter(None, psy.safe_list(name)))) if dims is not None: ds = self.plot_creator.get_ds() dims = { key: ', '.join(map(str, val)) for key, val in six.iteritems(dims) } for i, vname in enumerate( self.plot_creator.array_table.vnames): self.plot_creator.array_table.selectRow(i) self.plot_creator.array_table.update_selected() self.plot_creator.array_table.selectAll() var = ds[vname[0]] self.plot_creator.array_table.update_selected( dims=var.psy.decoder.correct_dims(var, dims.copy())) if preset: self.plot_creator.set_preset(preset) if plot_method: self.plot_creator.pm_combo.setCurrentIndex( self.plot_creator.pm_combo.findText(plot_method)) self.plot_creator.exec_() return True def _open_external_files(self, args): self.open_external_files(*args) @classmethod @docstrings.get_sections(base='MainWindow.run') @docstrings.dedent def run(cls, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value(xr.open_mfdataset, 'concat_dim'), chname={}, preset=None, show=True): """ Create a mainwindow and open the given files or project This class method creates a new mainwindow instance and sets the global :attr:`mainwindow` variable. Parameters ---------- %(MainWindow.open_external_files.parameters)s %(MainWindow.parameters)s Notes ----- - There can be only one mainwindow at the time - This method does not create a QApplication instance! See :meth:`run_app` See Also -------- run_app """ mainwindow = cls(show=show) _set_mainwindow(mainwindow) if fnames or project: mainwindow.open_external_files(fnames, project, engine, plot_method, name, dims, encoding, enable_post, seaborn_style, concat_dim, chname, preset) psyplot.with_gui = True return mainwindow def register_shortcut(self, action, shortcut, context=Qt.ApplicationShortcut): """Register an action for a shortcut""" shortcuts = psy.safe_list(shortcut) for j, shortcut in enumerate(shortcuts): found = False for i, (s, a) in enumerate(self.current_shortcuts): if s == shortcut: new_shortcuts = [ sc for sc in self.current_shortcuts[i][1].shortcuts() if sc != s ] a.setShortcut(QKeySequence()) if new_shortcuts: a.setShortcuts(new_shortcuts) self.current_shortcuts[i][1] = action found = True break if not found: self.default_shortcuts.append([shortcut, action]) self.current_shortcuts.append([shortcut, action]) action.setShortcuts(shortcuts) action.setShortcutContext(context) @classmethod @docstrings.dedent def run_app(cls, *args, **kwargs): """ Create a QApplication, open the given files or project and enter the mainloop Parameters ---------- %(MainWindow.run.parameters)s See Also -------- run """ app = QApplication(sys.argv) cls.run(*args, **kwargs) sys.exit(app.exec_()) def closeEvent(self, event): """closeEvent reimplementation""" if not self._is_open or (self._is_open and self.close()): self._is_open = False event.accept() def close(self): _set_mainwindow(None) if self.open_files_server is not None: self.open_files_server.close() del self.open_files_server for widget in self.plugins.values(): widget.close() self.plugins.clear() return super(MainWindow, self).close()
class MainWindow(QMainWindow): #: A signal that is emmitted when the a signal is received through the #: open_files_server open_external = QtCore.pyqtSignal(list) #: The server to open external files open_files_server = None #: Inprocess console console = None #: tree widget displaying the open datasets ds_tree = None #: list of figures from the psyplot backend figures = [] #: tree widget displaying the open figures figures_tree = None #: general formatoptions widget fmt_widget = None #: help explorer help_explorer = None #: the DataFrameEditor widgets, a widget to show and edit data frames dataframeeditors = None #: tab widget displaying the arrays in current main and sub project project_content = None #: The dockwidgets of this instance dockwidgets = [] #: default widths of the dock widgets default_widths = {} _is_open = False #: The keyboard shortcuts of the default layout default_shortcuts = [] #: The current keyboard shortcuts current_shortcuts = [] #: The key for the central widget for the main window in the #: :attr:`plugins` dictionary central_widget_key = 'console' @property def logger(self): """The logger of this instance""" return logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__name__)) @docstrings.get_sectionsf('MainWindow') @docstrings.dedent def __init__(self, show=True): """ Parameters ---------- show: bool If True, the created mainwindow is show """ if sys.stdout is None: sys.stdout = StreamToLogger(self.logger) if sys.stderr is None: sys.stderr = StreamToLogger(self.logger) super(MainWindow, self).__init__() self.setWindowIcon(QIcon(get_icon('logo.png'))) #: list of figures from the psyplot backend self.figures = [] self.error_msg = PyErrorMessage(self) self.setDockOptions( QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks) #: Inprocess console self.console = ConsoleWidget(self) self.project_actions = {} self.config_pages = [] self.open_file_options = OrderedDict([ ('new psyplot plot from dataset', self.open_external_files), ('new psyplot project', partial(self.open_external_files, [])), ]) # --------------------------------------------------------------------- # ----------------------------- Menus --------------------------------- # --------------------------------------------------------------------- # ######################## File menu ################################## # --------------------------- New plot -------------------------------- self.file_menu = QMenu('File', parent=self) self.new_plot_action = QAction('New plot', self) self.new_plot_action.setStatusTip( 'Use an existing dataset (or open a new one) to create one or ' 'more plots') self.register_shortcut(self.new_plot_action, QKeySequence.New) self.new_plot_action.triggered.connect(lambda: self.new_plots(True)) self.file_menu.addAction(self.new_plot_action) # --------------------------- Open project ---------------------------- self.open_project_menu = QMenu('Open project', self) self.file_menu.addMenu(self.open_project_menu) self.open_mp_action = QAction('New main project', self) self.register_shortcut(self.open_mp_action, QKeySequence.Open) self.open_mp_action.setStatusTip('Open a new main project') self.open_mp_action.triggered.connect(self.open_mp) self.open_project_menu.addAction(self.open_mp_action) self.open_sp_action = QAction('Add to current', self) self.register_shortcut( self.open_sp_action, QKeySequence( 'Ctrl+Shift+O', QKeySequence.NativeText)) self.open_sp_action.setStatusTip( 'Load a project as a sub project and add it to the current main ' 'project') self.open_sp_action.triggered.connect(self.open_sp) self.open_project_menu.addAction(self.open_sp_action) # ----------------------- Save project -------------------------------- self.save_project_menu = QMenu('Save', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_mp_action = QAction('Full psyplot project', self) self.save_mp_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_action, QKeySequence.Save) self.save_mp_action.triggered.connect(self.save_mp) self.save_project_menu.addAction(self.save_mp_action) self.save_sp_action = QAction('Selected psyplot project', self) self.save_sp_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_action.triggered.connect(self.save_sp) self.save_project_menu.addAction(self.save_sp_action) # ------------------------ Save project as ---------------------------- self.save_project_as_menu = QMenu('Save as', parent=self) self.file_menu.addMenu(self.save_project_as_menu) self.save_mp_as_action = QAction('Full psyplot project', self) self.save_mp_as_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_as_action, QKeySequence.SaveAs) self.save_mp_as_action.triggered.connect( partial(self.save_mp, new_fname=True)) self.save_project_as_menu.addAction(self.save_mp_as_action) self.save_sp_as_action = QAction('Selected psyplot project', self) self.save_sp_as_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_as_action.triggered.connect( partial(self.save_sp, new_fname=True)) self.save_project_as_menu.addAction(self.save_sp_as_action) # -------------------------- Pack project ----------------------------- self.pack_project_menu = QMenu('Zip project files', parent=self) self.file_menu.addMenu(self.pack_project_menu) self.pack_mp_action = QAction('Full psyplot project', self) self.pack_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.pack_mp_action.triggered.connect(partial(self.save_mp, pack=True)) self.pack_project_menu.addAction(self.pack_mp_action) self.pack_sp_action = QAction('Selected psyplot project', self) self.pack_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.pack_sp_action.triggered.connect(partial(self.save_sp, pack=True)) self.pack_project_menu.addAction(self.pack_sp_action) # ------------------------ Export figures ----------------------------- self.export_project_menu = QMenu('Export figures', parent=self) self.file_menu.addMenu(self.export_project_menu) self.export_mp_action = QAction('Full psyplot project', self) self.export_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.export_mp_action.triggered.connect(self.export_mp) self.register_shortcut( self.export_mp_action, QKeySequence( 'Ctrl+E', QKeySequence.NativeText)) self.export_project_menu.addAction(self.export_mp_action) self.export_sp_action = QAction('Selected psyplot project', self) self.export_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.register_shortcut( self.export_sp_action, QKeySequence( 'Ctrl+Shift+E', QKeySequence.NativeText)) self.export_sp_action.triggered.connect(self.export_sp) self.export_project_menu.addAction(self.export_sp_action) # ------------------------ Close project ------------------------------ self.file_menu.addSeparator() self.close_project_menu = QMenu('Close project', parent=self) self.file_menu.addMenu(self.close_project_menu) self.close_mp_action = QAction('Full psyplot project', self) self.register_shortcut( self.close_mp_action, QKeySequence( 'Ctrl+Shift+W', QKeySequence.NativeText)) self.close_mp_action.setStatusTip( 'Close the main project and delete all data and plots out of ' 'memory') self.close_mp_action.triggered.connect( lambda: psy.close(psy.gcp(True).num)) self.close_project_menu.addAction(self.close_mp_action) self.close_sp_action = QAction('Selected psyplot project', self) self.close_sp_action.setStatusTip( 'Close the selected arrays project and delete all data and plots ' 'out of memory') self.register_shortcut(self.close_sp_action, QKeySequence.Close) self.close_sp_action.triggered.connect( lambda: psy.gcp().close(True, True)) self.close_project_menu.addAction(self.close_sp_action) # ----------------------------- Quit ---------------------------------- if sys.platform != 'darwin': # mac os makes this anyway self.quit_action = QAction('Quit', self) self.quit_action.triggered.connect(self.close) self.quit_action.triggered.connect( QtCore.QCoreApplication.instance().quit) self.register_shortcut( self.quit_action, QKeySequence.Quit) self.file_menu.addAction(self.quit_action) self.menuBar().addMenu(self.file_menu) # ######################## Console menu ############################### self.console_menu = QMenu('Console', self) self.console_menu.addActions(self.console.actions()) self.menuBar().addMenu(self.console_menu) # ######################## Windows menu ############################### self.windows_menu = QMenu('Windows', self) self.menuBar().addMenu(self.windows_menu) # ############################ Help menu ############################## self.help_menu = QMenu('Help', parent=self) self.menuBar().addMenu(self.help_menu) # -------------------------- Preferences ------------------------------ self.help_action = QAction('Preferences', self) self.help_action.triggered.connect(lambda: self.edit_preferences(True)) self.register_shortcut(self.help_action, QKeySequence.Preferences) self.help_menu.addAction(self.help_action) # ---------------------------- About ---------------------------------- self.about_action = QAction('About', self) self.about_action.triggered.connect(self.about) self.help_menu.addAction(self.about_action) # ---------------------------- Dependencies --------------------------- self.dependencies_action = QAction('Dependencies', self) self.dependencies_action.triggered.connect( lambda: self.show_dependencies(True)) self.help_menu.addAction(self.dependencies_action) self.dockwidgets = [] # --------------------------------------------------------------------- # -------------------------- Dock windows ----------------------------- # --------------------------------------------------------------------- #: tab widget displaying the arrays in current main and sub project #: tree widget displaying the open datasets self.project_content = ProjectContentWidget(parent=self) self.ds_tree = DatasetTree(parent=self) #: tree widget displaying the open figures self.figures_tree = FiguresTree(parent=self) #: help explorer self.help_explorer = help_explorer = HelpExplorer(parent=self) if help_explorer.viewers['HTML help'].sphinx_thread is not None: help_explorer.viewers[ 'HTML help'].sphinx_thread.html_ready.connect( self.focus_on_console) #: the DataFrameEditor widgets self.dataframeeditors = [] #: general formatoptions widget self.fmt_widget = FormatoptionWidget( parent=self, help_explorer=help_explorer, console=self.console) # load plugin widgets self.plugins = plugins = OrderedDict([ ('console', self.console), ('project_content', self.project_content), ('ds_tree', self.ds_tree), ('figures_tree', self.figures_tree), ('help_explorer', self.help_explorer), ('fmt_widget', self.fmt_widget), ]) self.default_plugins = list(plugins) for plugin_name, w_class in six.iteritems(rcParams.load_plugins()): plugins[plugin_name] = w_class(parent=self) self.add_mp_to_menu() psy.Project.oncpchange.connect(self.eventually_add_mp_to_menu) self.windows_menu.addSeparator() self.window_layouts_menu = QMenu('Window layouts', self) self.restore_layout_action = QAction('Restore default layout', self) self.restore_layout_action.triggered.connect(self.setup_default_layout) self.window_layouts_menu.addAction(self.restore_layout_action) self.windows_menu.addMenu(self.window_layouts_menu) self.panes_menu = QMenu('Panes', self) self.windows_menu.addMenu(self.panes_menu) self.dataframe_menu = QMenu('DataFrame editors', self) self.dataframe_menu.addAction( 'New Editor', partial(self.new_data_frame_editor, None, 'DataFrame Editor')) self.dataframe_menu.addSeparator() self.windows_menu.addMenu(self.dataframe_menu) self.central_widgets_menu = menu = QMenu('Central widget', self) self.windows_menu.addMenu(menu) self.central_widgets_actions = group = QActionGroup(self) group.setExclusive(True) # --------------------------------------------------------------------- # -------------------------- connections ------------------------------ # --------------------------------------------------------------------- self.console.help_explorer = help_explorer psyp.default_print_func = partial(help_explorer.show_rst, oname='formatoption_docs') psy.PlotterInterface._print_func = psyp.default_print_func self.setCentralWidget(self.console) # make sure that the plots are shown between the project content and # the help explorer widget self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) # make sure that the formatoption widgets are shown between the # project content and the help explorer widget self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # --------------------------------------------------------------------- # ------------------------------ closure ------------------------------ # --------------------------------------------------------------------- if show: self.help_explorer.show_intro(self.console.intro_msg) # --------------------------------------------------------------------- # ------------------------- open_files_server ------------------------- # --------------------------------------------------------------------- self.callbacks = {'new_plot': self.open_external.emit, 'change_cwd': self._change_cwd, 'run_script': self.console.run_script.emit, 'command': self.console.run_command.emit, } # Server to open external files on a single instance self.open_files_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if rcParams['main.listen_to_port']: self._file_thread = Thread(target=self.start_open_files_server) self._file_thread.setDaemon(True) self._file_thread.start() self.open_external.connect(self._open_external_files) self.config_pages.extend([GuiRcParamsWidget, PsyRcParamsWidget]) # display the statusBar statusbar = self.statusBar() self.figures_label = QLabel() statusbar.addWidget(self.figures_label) self.plugin_label = QLabel() statusbar.addWidget(self.plugin_label) self.default_widths = {} self.setup_default_layout() if show: self.showMaximized() # save the default widths after they have been shown for w in self.plugins.values(): if w.dock is not None: self.default_widths[w] = w.dock.size().width() # hide plugin widgets that should be hidden at startup. Although this # has been executed by :meth:`setup_default_layout`, we have to execute # it again after the call of showMaximized for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() else: w.create_central_widget_action(self).setChecked(True) self._is_open = True def focus_on_console(self, *args, **kwargs): """Put focus on the ipython console""" self.console._control.setFocus() def new_data_frame_editor(self, df=None, title='DataFrame Editor'): """Open a new dataframe editor Parameters ---------- df: pandas.DataFrame The dataframe to display title: str The title of the dock window Returns ------- psyplot_gui.dataframeeditor.DataFrameEditor The newly created editor""" editor = DataFrameEditor() self.dataframeeditors.append(editor) editor.to_dock(self, title, Qt.RightDockWidgetArea, docktype='df') if df is not None: editor.set_df(df) editor.show_plugin() editor.maybe_tabify() editor.raise_() return editor def setup_default_layout(self): """Set up the default window layout""" self.project_content.to_dock(self, 'Plot objects', Qt.LeftDockWidgetArea) self.ds_tree.to_dock(self, 'Datasets', Qt.LeftDockWidgetArea) self.figures_tree.to_dock(self, 'Figures', Qt.LeftDockWidgetArea) self.help_explorer.to_dock(self, 'Help explorer', Qt.RightDockWidgetArea) self.fmt_widget.to_dock(self, 'Formatoptions', Qt.BottomDockWidgetArea) modify_widths = bool(self.default_widths) for w in map(self.plugins.__getitem__, self.default_plugins): if w.dock is not None: w.show_plugin() if modify_widths and with_qt5: self.resizeDocks([w.dock], [self.default_widths[w]], Qt.Horizontal) # hide plugin widgets that should be hidden at startup for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() action2shortcut = defaultdict(list) for s, a in self.default_shortcuts: action2shortcut[a].append(s) for a, s in action2shortcut.items(): self.register_shortcut(a, s) def set_central_widget(self, name): """Set the central widget Parameters ---------- name: str or QWidget The key or the plugin widget in the :attr:`plugins` dictionary""" from PyQt5.QtCore import QTimer self.setUpdatesEnabled(False) current = self.centralWidget() if isinstance(name, six.string_types): new = self.plugins[name] else: new = name name = next(key for key, val in self.plugins.items() if val is new) if new is not current: self._dock_widths = dock_widths = OrderedDict() self._dock_heights = dock_heights = OrderedDict() for key, w in self.plugins.items(): if w.dock is not None and w.is_shown: s = w.dock.size() dock_widths[w] = s.width() if w is not new: dock_heights[w] = s.height() new_pos = self.dockWidgetArea(new.dock) self.removeDockWidget(new.dock) new.dock.close() self.panes_menu.removeAction(new._view_action) self.dataframe_menu.removeAction(new._view_action) new.dock = new._view_action = None self.central_widget_key = name current.to_dock(self) self.setCentralWidget(new) new._set_central_action.setChecked(True) current.show_plugin() current.to_dock(self) new_width = dock_widths.pop(new, None) if current.hidden: current.hide_plugin() else: current_pos = self.dockWidgetArea(current.dock) if current_pos == new_pos and new_width: dock_widths[current] = new_width self._custom_layout_timer = QTimer(self) self._custom_layout_timer.timeout.connect(self._reset_dock_widths) self._custom_layout_timer.setSingleShot(True) self._custom_layout_timer.start(5000) def _reset_dock_widths(self): # resize the plugins if with_qt5: for w, width in self._dock_widths.items(): if w.dock is not None: self.resizeDocks([w.dock], [width], Qt.Horizontal) for w, height in self._dock_heights.items(): if w.dock is not None: self.resizeDocks([w.dock], [height], Qt.Vertical) self.setUpdatesEnabled(True) def _save_project(self, p, new_fname=False, *args, **kwargs): if new_fname or 'project_file' not in p.attrs: fname = QFileDialog.getSaveFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return else: fname = p.attrs['project_file'] try: p.save_project(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback('<b>Could not save the project!</b>') else: p.attrs['project_file'] = fname if p.is_main: self.update_project_action(p.num) def open_mp(self, *args, **kwargs): """Open a new main project""" self._open_project(main=True) def open_sp(self, *args, **kwargs): """Open a subproject and add it to the current main project""" self._open_project(main=False) def _open_project(self, *args, **kwargs): fname = QFileDialog.getOpenFileName( self, 'Project file', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return p = psy.Project.load_project(fname, *args, **kwargs) p.attrs['project_file'] = fname self.update_project_action(p.num) def save_mp(self, *args, **kwargs): """Save the current main project""" self._save_project(psy.gcp(True), **kwargs) def save_sp(self, *args, **kwargs): """Save the current sub project""" self._save_project(psy.gcp(), **kwargs) def _export_project(self, p, *args, **kwargs): fname = QFileDialog.getSaveFileName( self, 'Picture destination', os.getcwd(), 'PDF files (*.pdf);;' 'Postscript file (*.ps);;' 'PNG image (*.png);;' 'JPG image (*.jpg *.jpeg);;' 'TIFF image (*.tif *.tiff);;' 'GIF image (*.gif);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return try: p.export(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback( '<b>Could not export the figures!</b>') def export_mp(self, *args, **kwargs): self._export_project(psy.gcp(True), **kwargs) def export_sp(self, *args, **kwargs): self._export_project(psy.gcp(), **kwargs) def new_plots(self, exec_=None): if hasattr(self, 'plot_creator'): try: self.plot_creator.close() except RuntimeError: pass self.plot_creator = PlotCreator( help_explorer=self.help_explorer, parent=self) available_width = QDesktopWidget().availableGeometry().width() / 3. width = self.plot_creator.sizeHint().width() height = self.plot_creator.sizeHint().height() # The plot creator window should cover at least one third of the screen self.plot_creator.resize(max(available_width, width), height) if exec_: self.plot_creator.exec_() def excepthook(self, type, value, traceback): """A method to replace the sys.excepthook""" self.error_msg.excepthook(type, value, traceback) def edit_preferences(self, exec_=None): """Edit Spyder preferences""" if hasattr(self, 'preferences'): try: self.preferences.close() except RuntimeError: pass self.preferences = dlg = Prefences(self) for PrefPageClass in self.config_pages: widget = PrefPageClass(dlg) widget.initialize() dlg.add_page(widget) available_width = 0.667 * QDesktopWidget().availableGeometry().width() width = dlg.sizeHint().width() height = dlg.sizeHint().height() # The preferences window should cover at least one third of the screen dlg.resize(max(available_width, width), height) if exec_: dlg.exec_() def about(self): """About the tool""" versions = { key: d['version'] for key, d in psyplot.get_versions(False).items() } versions.update(psyplot_gui.get_versions()['requirements']) versions.update(psyplot._get_versions()['requirements']) versions['github'] = 'https://github.com/Chilipp/psyplot' versions['author'] = psyplot.__author__ QMessageBox.about( self, "About psyplot", u"""<b>psyplot: Interactive data visualization with python</b> <br>Copyright © 2017- Philipp Sommer <br>Licensed under the terms of the GNU General Public License v2 (GPLv2) <p>Created by %(author)s</p> <p>Most of the icons come from the <a href="https://www.iconfinder.com/"> iconfinder</a>.</p> <p>For bug reports and feature requests, please go to our <a href="%(github)s">Github website</a> or contact the author via mail.</p> <p>This package uses (besides others) the following packages:<br> <ul> <li>psyplot %(psyplot)s</li> <li>Python %(python)s </li> <li>numpy %(numpy)s</li> <li>xarray %(xarray)s</li> <li>pandas %(pandas)s</li> <li>psyplot_gui %(psyplot_gui)s</li> <li>Qt %(qt)s</li> <li>PyQt %(pyqt)s</li> <li>qtconsole %(qtconsole)s</li> </ul></p> <p>For a full list of requirements see the <em>dependencies</em> in the <em>Help</em> menu.</p> <p>This software is provided "as is", without warranty or support of any kind.</p>""" % versions) def show_dependencies(self, exec_=None): """Open a dialog that shows the dependencies""" if hasattr(self, 'dependencies'): try: self.dependencies.close() except RuntimeError: pass self.dependencies = dlg = DependenciesDialog(psyplot.get_versions(), parent=self) dlg.resize(630, 420) if exec_: dlg.exec_() def reset_rcParams(self): rcParams.update_from_defaultParams() psy.rcParams.update_from_defaultParams() def add_mp_to_menu(self): mp = psy.gcp(True) action = QAction(os.path.basename(mp.attrs.get( 'project_file', 'Untitled %s*' % mp.num)), self) action.setStatusTip( 'Make project %s the current project' % mp.num) action.triggered.connect(lambda: psy.scp(psy.project(mp.num))) self.project_actions[mp.num] = action self.windows_menu.addAction(action) def update_project_action(self, num): action = self.project_actions.get(num) p = psy.project(num) if action: action.setText(os.path.basename(p.attrs.get( 'project_file', 'Untitled %s*' % num))) def eventually_add_mp_to_menu(self, p): for num in set(self.project_actions).difference( psy.get_project_nums()): self.windows_menu.removeAction(self.project_actions.pop(num)) if p is None or not p.is_main: return if p.num not in self.project_actions: self.add_mp_to_menu() def start_open_files_server(self): """This method listens to the open_files_port and opens the plot creator for new files This method is inspired and to most parts copied from spyder""" self.open_files_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) port = rcParams['main.open_files_port'] try: self.open_files_server.bind(('127.0.0.1', port)) except Exception: return self.open_files_server.listen(20) while 1: # 1 is faster than True try: req, dummy = self.open_files_server.accept() except socket.error as e: # See Issue 1275 for details on why errno EINTR is # silently ignored here. eintr = errno.EINTR # To avoid a traceback after closing on Windows if e.args[0] == eintr: continue # handle a connection abort on close error enotsock = (errno.WSAENOTSOCK if os.name == 'nt' else errno.ENOTSOCK) if e.args[0] in [errno.ECONNABORTED, enotsock]: return raise args = pickle.loads(req.recv(1024)) callback = args[0] func = self.callbacks[callback] self.logger.debug('Emitting %s callback %s', callback, func) func(args[1:]) req.sendall(b' ') def change_cwd(self, path): """Change the current working directory""" import os os.chdir(path) def _change_cwd(self, args): path = args[0][0] self.change_cwd(path) docstrings.keep_params( 'make_plot.parameters', 'fnames', 'project', 'engine', 'plot_method', 'name', 'dims', 'encoding', 'enable_post', 'seaborn_style', 'concat_dim', 'chname') def open_files(self, fnames): """Open a file and ask the user how""" fnames_s = ', '.join(map(os.path.basename, fnames)) if len(fnames_s) > 30: fnames_s = fnames_s[:27] + '...' item, ok = QInputDialog.getItem( self, 'Open file...', 'Open %s as...' % fnames_s, list(self.open_file_options), current=0, editable=False) if ok: return self.open_file_options[item](fnames) @docstrings.get_sectionsf('MainWindow.open_external_files') @docstrings.dedent def open_external_files(self, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value( xr.open_mfdataset, 'concat_dim'), chname={}): """ Open external files Parameters ---------- %(make_plot.parameters.fnames|project|engine|plot_method|name|dims|encoding|enable_post|seaborn_style|concat_dim|chname)s """ if seaborn_style is not None: import seaborn as sns sns.set_style(seaborn_style) if project is not None: fnames = [s.split(',') for s in fnames] if not isinstance(project, dict): project = psyd.safe_list(project)[0] single_files = (l[0] for l in fnames if len(l) == 1) alternative_paths = defaultdict(lambda: next(single_files, None)) alternative_paths.update(list(l for l in fnames if len(l) == 2)) p = psy.Project.load_project( project, alternative_paths=alternative_paths, engine=engine, main=not psy.gcp(), encoding=encoding, enable_post=enable_post, chname=chname) if isinstance(project, six.string_types): p.attrs.setdefault('project_file', project) return True else: self.new_plots(False) self.plot_creator.open_dataset(fnames, engine=engine, concat_dim=concat_dim) if name == 'all': ds = self.plot_creator.get_ds() name = sorted(set(ds.variables) - set(ds.coords)) self.plot_creator.insert_array( list(filter(None, psy.safe_list(name)))) if dims is not None: ds = self.plot_creator.get_ds() dims = {key: ', '.join( map(str, val)) for key, val in six.iteritems( dims)} for i, vname in enumerate( self.plot_creator.array_table.vnames): self.plot_creator.array_table.selectRow(i) self.plot_creator.array_table.update_selected( ) self.plot_creator.array_table.selectAll() var = ds[vname[0]] self.plot_creator.array_table.update_selected( dims=var.psy.decoder.correct_dims(var, dims.copy())) if plot_method: self.plot_creator.pm_combo.setCurrentIndex( self.plot_creator.pm_combo.findText(plot_method)) self.plot_creator.exec_() return True def _open_external_files(self, args): self.open_external_files(*args) @classmethod @docstrings.get_sectionsf('MainWindow.run') @docstrings.dedent def run(cls, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value(xr.open_mfdataset, 'concat_dim'), chname={}, show=True): """ Create a mainwindow and open the given files or project This class method creates a new mainwindow instance and sets the global :attr:`mainwindow` variable. Parameters ---------- %(MainWindow.open_external_files.parameters)s %(MainWindow.parameters)s Notes ----- - There can be only one mainwindow at the time - This method does not create a QApplication instance! See :meth:`run_app` See Also -------- run_app """ mainwindow = cls(show=show) _set_mainwindow(mainwindow) if fnames or project: mainwindow.open_external_files( fnames, project, engine, plot_method, name, dims, encoding, enable_post, seaborn_style, concat_dim, chname) psyplot.with_gui = True return mainwindow def register_shortcut(self, action, shortcut, context=Qt.ApplicationShortcut): """Register an action for a shortcut""" shortcuts = psy.safe_list(shortcut) for j, shortcut in enumerate(shortcuts): found = False for i, (s, a) in enumerate(self.current_shortcuts): if s == shortcut: new_shortcuts = [ sc for sc in self.current_shortcuts[i][1].shortcuts() if sc != s] a.setShortcut(QKeySequence()) if new_shortcuts: a.setShortcuts(new_shortcuts) self.current_shortcuts[i][1] = action found = True break if not found: self.default_shortcuts.append([shortcut, action]) self.current_shortcuts.append([shortcut, action]) action.setShortcuts(shortcuts) action.setShortcutContext(context) @classmethod @docstrings.dedent def run_app(cls, *args, **kwargs): """ Create a QApplication, open the given files or project and enter the mainloop Parameters ---------- %(MainWindow.run.parameters)s See Also -------- run """ app = QApplication(sys.argv) cls.run(*args, **kwargs) sys.exit(app.exec_()) def closeEvent(self, event): """closeEvent reimplementation""" if not self._is_open or (self._is_open and self.close()): self._is_open = False event.accept() def close(self): _set_mainwindow(None) if self.open_files_server is not None: self.open_files_server.close() del self.open_files_server for widget in self.plugins.values(): widget.close() self.plugins.clear() return super(MainWindow, self).close()