示例#1
0
    def __init__(self, *args, **kwargs):
        self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp())
        self.build_dir = osp.join(self.sphinx_dir, '_build', 'html')
        super(UrlHelp, self).__init__(*args, **kwargs)

        self.error_msg = PyErrorMessage(self)
        if with_sphinx:
            self.sphinx_thread = SphinxThread(self.sphinx_dir)
            self.sphinx_thread.html_ready[str].connect(self.browse)
            self.sphinx_thread.html_error[str].connect(
                self.error_msg.showTraceback)
            self.sphinx_thread.html_error[str].connect(logger.debug)
        else:
            self.sphinx_thread = None

        #: menu button with different urls
        self.bt_url_menus = QToolButton(self)
        self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png')))
        self.bt_url_menus.setToolTip('Browse documentations')
        self.bt_url_menus.setPopupMode(QToolButton.InstantPopup)

        docu_menu = QMenu(self)
        for name, url in six.iteritems(self.doc_urls):
            def to_url(b, url=url):
                self.browse(url)
            action = QAction(name, self)
            action.triggered.connect(to_url)
            docu_menu.addAction(action)
        self.bt_url_menus.setMenu(docu_menu)

        self.button_box.addWidget(self.bt_url_menus)
    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
示例#3
0
    def __init__(self, *args, **kwargs):
        self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp())
        self.build_dir = osp.join(self.sphinx_dir, '_build', 'html')
        super(UrlHelp, self).__init__(*args, **kwargs)

        self.error_msg = PyErrorMessage(self)
        if with_sphinx:
            self.sphinx_thread = SphinxThread(self.sphinx_dir)
            self.sphinx_thread.html_ready[str].connect(self.browse)
            self.sphinx_thread.html_error[str].connect(
                self.error_msg.showTraceback)
            self.sphinx_thread.html_error[str].connect(logger.debug)
            rcParams.connect('help_explorer.render_docs_parallel',
                             self.reset_sphinx)
            rcParams.connect('help_explorer.use_intersphinx',
                             self.reset_sphinx)
            rcParams.connect('help_explorer.online', self.reset_sphinx)

        else:
            self.sphinx_thread = None

        self.bt_connect_console = QToolButton(self)
        self.bt_connect_console.setCheckable(True)
        if rcParams['console.connect_to_help']:
            self.bt_connect_console.setIcon(
                QIcon(get_icon('ipython_console.png')))
            self.bt_connect_console.click()
        else:
            self.bt_connect_console.setIcon(
                QIcon(get_icon('ipython_console_t.png')))
        self.bt_connect_console.clicked.connect(self.toogle_connect_console)
        rcParams.connect('console.connect_to_help',
                         self.update_connect_console)
        self.toogle_connect_console()

        # menu button with different urls
        self.bt_url_menus = QToolButton(self)
        self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png')))
        self.bt_url_menus.setToolTip('Browse documentations')
        self.bt_url_menus.setPopupMode(QToolButton.InstantPopup)

        docu_menu = QMenu(self)
        for name, url in six.iteritems(self.doc_urls):

            def to_url(b, url=url):
                self.browse(url)

            action = QAction(name, self)
            action.triggered.connect(to_url)
            docu_menu.addAction(action)
        self.bt_url_menus.setMenu(docu_menu)

        self.button_box.addWidget(self.bt_connect_console)
        self.button_box.addWidget(self.bt_url_menus)
        # toogle the lock again to set the bt_url_menus enabled state
        self.toogle_url_lock()
示例#4
0
    def __init__(self, *args, **kwargs):
        self._temp_dir = 'sphinx_dir' not in kwargs
        self.sphinx_dir = kwargs.pop('sphinx_dir', mkdtemp(prefix='psyplot_'))
        self.build_dir = osp.join(self.sphinx_dir, '_build', 'html')
        super(UrlHelp, self).__init__(*args, **kwargs)

        self.error_msg = PyErrorMessage(self)
        if with_sphinx:
            self.sphinx_thread = SphinxThread(self.sphinx_dir)
            self.sphinx_thread.html_ready[str].connect(self.browse)
            self.sphinx_thread.html_error[str].connect(
                self.error_msg.showTraceback)
            self.sphinx_thread.html_error[str].connect(logger.debug)
            rcParams.connect('help_explorer.render_docs_parallel',
                             self.reset_sphinx)
            rcParams.connect('help_explorer.use_intersphinx',
                             self.reset_sphinx)
            rcParams.connect('help_explorer.online',
                             self.reset_sphinx)

        self.bt_connect_console = QToolButton(self)
        self.bt_connect_console.setCheckable(True)
        if rcParams['console.connect_to_help']:
            self.bt_connect_console.setIcon(QIcon(get_icon(
                'ipython_console.png')))
            self.bt_connect_console.click()
        else:
            self.bt_connect_console.setIcon(QIcon(get_icon(
                'ipython_console_t.png')))
        self.bt_connect_console.clicked.connect(self.toogle_connect_console)
        rcParams.connect('console.connect_to_help',
                         self.update_connect_console)
        self.toogle_connect_console()

        # menu button with different urls
        self.bt_url_menus = QToolButton(self)
        self.bt_url_menus.setIcon(QIcon(get_icon('docu_button.png')))
        self.bt_url_menus.setToolTip('Browse documentations')
        self.bt_url_menus.setPopupMode(QToolButton.InstantPopup)

        docu_menu = QMenu(self)
        for name, url in six.iteritems(self.doc_urls):
            def to_url(b, url=url):
                self.browse(url)
            action = QAction(name, self)
            action.triggered.connect(to_url)
            docu_menu.addAction(action)
        self.bt_url_menus.setMenu(docu_menu)

        self.button_box.addWidget(self.bt_connect_console)
        self.button_box.addWidget(self.bt_url_menus)
        # toogle the lock again to set the bt_url_menus enabled state
        self.toogle_url_lock()
示例#5
0
 def __init__(self, parent, *fmtos):
     """
     Parameters
     ----------
     parent: psyplot_gui.fmt_widget.FormatoptionWidget
         The formatoption widget that contains the button
     ``*fmtos``
         Instances of the :class:`psyplot.plotter.Formatoption` for which
         the links should be created
     """
     QToolButton.__init__(self, parent=parent)
     self.setText('fmt')
     self.setPopupMode(QToolButton.InstantPopup)
     menu = QMenu()
     for fmto in fmtos:
         name = parent.get_name(fmto)
         menu.addAction(name, partial(parent.set_fmto, name))
     self.setMenu(menu)
示例#6
0
    def open_menu(self, position):
        """Open a menu to expand and collapse all items in the tree

        Parameters
        ----------
        position: QPosition
            The position where to open the menu"""
        menu = QMenu()
        expand_all_action = QAction('Expand all', self)
        expand_all_action.triggered.connect(self.expandAll)
        menu.addAction(expand_all_action)
        collapse_all_action = QAction('Collapse all', self)
        collapse_all_action.triggered.connect(self.collapseAll)
        menu.addAction(collapse_all_action)
        menu.exec_(self.viewport().mapToGlobal(position))
示例#7
0
    def open_menu(self, position):
        """Open a menu to expand and collapse all items in the tree

        Parameters
        ----------
        position: QPosition
            The position where to open the menu"""
        menu = QMenu()
        expand_all_action = QAction('Expand all', self)
        expand_all_action.triggered.connect(self.expandAll)
        menu.addAction(expand_all_action)
        collapse_all_action = QAction('Collapse all', self)
        collapse_all_action.triggered.connect(self.collapseAll)
        menu.addAction(collapse_all_action)
        menu.exec_(self.viewport().mapToGlobal(position))
示例#8
0
    def __init__(self, *args, **kwargs):
        super(RcParamsWidget, self).__init__(*args, **kwargs)
        self.vbox = vbox = QVBoxLayout()

        self.description = QLabel(
            '<p>Modify the rcParams for your need. Changes will not be applied'
            ' until you click the Apply or Ok button.</p>'
            '<p>Values must be entered in yaml syntax</p>',
            parent=self)
        vbox.addWidget(self.description)
        self.tree = tree = RcParamsTree(self.rc,
                                        getattr(self.rc, 'validate', None),
                                        getattr(self.rc, 'descriptions', None),
                                        parent=self)
        tree.setSelectionMode(QAbstractItemView.MultiSelection)
        vbox.addWidget(self.tree)

        self.bt_select_all = QPushButton('Select All', self)
        self.bt_select_changed = QPushButton('Select changes', self)
        self.bt_select_none = QPushButton('Clear Selection', self)
        self.bt_export = QToolButton(self)
        self.bt_export.setText('Export Selection...')
        self.bt_export.setToolTip('Export the selected rcParams to a file')
        self.bt_export.setPopupMode(QToolButton.InstantPopup)
        self.export_menu = export_menu = QMenu(self)
        export_menu.addAction(self.save_settings_action())
        export_menu.addAction(self.save_settings_action(True))
        self.bt_export.setMenu(export_menu)
        hbox = QHBoxLayout()
        hbox.addWidget(self.bt_select_all)
        hbox.addWidget(self.bt_select_changed)
        hbox.addWidget(self.bt_select_none)
        hbox.addStretch(1)
        hbox.addWidget(self.bt_export)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

        self.bt_select_all.clicked.connect(self.tree.selectAll)
        self.bt_select_none.clicked.connect(self.tree.clearSelection)
        self.bt_select_changed.clicked.connect(self.tree.select_changes)
示例#9
0
 def setup_menu(self):
     """Setup context menu"""
     menu = QMenu(self)
     self.insert_row_above_action = menu.addAction(
         'Insert row above', self.insert_row_above_selection)
     self.insert_row_below_action = menu.addAction(
         'Insert row below', self.insert_row_below_selection)
     menu.addSeparator()
     self.delete_selected_rows_action = menu.addAction(
         'Delete selected rows', self.delete_selected_rows)
     menu.addSeparator()
     self.fit2data_action = menu.addAction('Fit selected cells to the data',
                                           self.fit2data)
     self.zoom_to_selection_action = menu.addAction(
         'Zoom to selected cells and rows', self.zoom_to_selection)
     return menu
示例#10
0
    def setup_menu_actions(self, main):
        """Create the actions for the file menu

        Parameters
        ----------
        main: psyplot_gui.main.MainWindow
            The mainwindow whose menubar shall be adapted"""
        # load buttons
        self.open_menu = menu = QMenu('Open straditizer')
        main.open_project_menu.addMenu(self.open_menu)

        self.load_stradi_action = self._add_action(
            menu,
            'Project or image',
            self.open_straditizer,
            tooltip='Reload a digitization project or load a picture')

        self.load_clipboard_action = self._add_action(
            menu,
            'From clipboard',
            self.from_clipboard,
            tooltip='Load a picture from the clipboard')

        # save and export data buttons
        self.save_straditizer_action = self._add_action(
            main.save_project_menu,
            'Save straditizer',
            self.save_straditizer,
            tooltip='Save the digitization project')

        self.save_straditizer_as_action = self._add_action(
            main.save_project_as_menu,
            'Save straditizer as',
            self.save_straditizer_as,
            tooltip='Save the digitization project to a different file')

        self.export_data_menu = menu = QMenu('Straditizer data')
        main.export_project_menu.addMenu(menu)

        self.export_full_action = self._add_action(
            menu,
            'Full data',
            self.export_full,
            tooltip='Export the full digitized data')

        self.export_final_action = self._add_action(
            menu,
            'Samples',
            self.export_final,
            tooltip='Export the data at the sample locations')

        # close menu
        self.close_straditizer_action = self._add_action(
            main.close_project_menu,
            'Close straditizer',
            self.straditizer_widgets.close_straditizer,
            tooltip='Close the current straditize project')

        self.close_all_straditizer_action = self._add_action(
            main.close_project_menu,
            'Close all straditizers',
            self.straditizer_widgets.close_all_straditizers,
            tooltip='Close all open straditize projects')

        # export image buttons
        self.export_images_menu = menu = QMenu('Straditizer image(s)')
        menu.setToolTipsVisible(True)
        main.export_project_menu.addMenu(menu)

        self.export_full_image_action = self._add_action(
            menu,
            'Full image',
            self.save_full_image,
            tooltip='Save the full image to a file')

        self.export_data_image_action = self._add_action(
            menu,
            'Save data image',
            self.save_data_image,
            tooltip='Save the binary image that represents the data part')

        self.export_text_image_action = self._add_action(
            menu,
            'Save text image',
            self.save_text_image,
            tooltip='Save the image part with the rotated column descriptions')

        # import image buttons
        self.import_images_menu = menu = QMenu('Import straditizer image(s)')
        menu.setToolTipsVisible(True)

        self.import_full_image_action = self._add_action(
            menu,
            'Full image',
            self.import_full_image,
            tooltip='Import the diagram into the current project')

        self.import_data_image_action = self._add_action(
            menu,
            'Data image',
            self.import_data_image,
            tooltip='Import the data part image')

        self.import_binary_image_action = self._add_action(
            menu,
            'Binary data image',
            self.import_binary_image,
            tooltip='Import the binary image for the data part')

        self.import_text_image_action = self._add_action(
            menu,
            'Text image',
            self.import_text_image,
            tooltip='Import the image for the column names')

        self.window_layout_action = main.window_layouts_menu.addAction(
            'Straditizer layout',
            self.straditizer_widgets.switch_to_straditizer_layout)

        self.save_actions = [
            self.export_full_image_action, self.save_straditizer_action,
            self.import_full_image_action
        ]
        self.data_actions = [
            self.export_data_image_action, self.export_full_action,
            self.export_final_action, self.import_binary_image_action,
            self.import_data_image_action
        ]
        self.text_actions = [
            self.export_text_image_action, self.import_text_image_action
        ]

        self.widgets2disable = [
            self.load_stradi_action, self.load_clipboard_action
        ]

        self.refresh()
示例#11
0
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 &copy; 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()
示例#12
0
 def setup_menu(self):
     """Setup context menu"""
     menu = QMenu(self)
     menu.addAction('Copy', self.copy, QtGui.QKeySequence.Copy)
     menu.addSeparator()
     functions = (("To bool", bool), ("To complex", complex),
                  ("To int", int), ("To float", float),
                  ("To str", str))
     self.dtype_actions = {
         name: menu.addAction(name, partial(self.change_type, func))
         for name, func in functions}
     menu.addSeparator()
     self.insert_row_above_action = menu.addAction(
         'Insert rows above', self.insert_row_above_selection)
     self.insert_row_below_action = menu.addAction(
         'Insert rows below', self.insert_row_below_selection)
     menu.addSeparator()
     self.set_index_action = menu.addAction(
         'Set as index', partial(self.set_index, False))
     self.append_index_action = menu.addAction(
         'Append to as index', partial(self.set_index, True))
     return menu
示例#13
0
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 &copy; 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()
示例#14
0
 def setup_menu(self):
     """Setup context menu"""
     menu = QMenu(self)
     menu.addAction('Copy', self.copy, QtGui.QKeySequence.Copy)
     menu.addSeparator()
     functions = (("To bool", bool), ("To complex", complex),
                  ("To int", int), ("To float", float),
                  ("To str", six.text_type))
     self.dtype_actions = {
         name: menu.addAction(name, partial(self.change_type, func))
         for name, func in functions}
     menu.addSeparator()
     self.insert_row_above_action = menu.addAction(
         'Insert rows above', self.insert_row_above_selection)
     self.insert_row_below_action = menu.addAction(
         'Insert rows below', self.insert_row_below_selection)
     menu.addSeparator()
     self.set_index_action = menu.addAction(
         'Set as index', partial(self.set_index, False))
     self.append_index_action = menu.addAction(
         'Append to as index', partial(self.set_index, True))
     return menu
示例#15
0
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)})
示例#16
0
    def create_actions(self):
        """Define the actions for the toolbar and set everything up"""
        # Reader toolbar
        self.combo = QComboBox()
        self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.addWidget(self.combo)

        select_group = QActionGroup(self)

        # select action
        self._actions['select'] = a = self.addAction(
            QIcon(get_icon('select.png')), 'select', self.toggle_selection)
        a.setToolTip('Select pixels within a rectangle')
        a.setCheckable(True)
        select_group.addAction(a)

        # select menu
        select_menu = QMenu(self)
        self._select_actions['rect_select'] = menu_a = select_menu.addAction(
            QIcon(get_icon('select.png')), 'rectangle',
            self.set_rect_select_mode)
        menu_a.setToolTip('Select a rectangle')
        a.setToolTip(menu_a.toolTip())

        self._select_actions['poly_select'] = menu_a = select_menu.addAction(
            QIcon(get_icon('poly_select.png')), 'polygon',
            self.set_poly_select_mode)
        menu_a.setToolTip('Select a rectangle')
        a.setToolTip(menu_a.toolTip())

        a.setMenu(select_menu)

        # wand_select action
        self._actions['wand_select'] = a = self.addAction(
            QIcon(get_icon('wand_select.png')), 'select',
            self.toggle_selection)
        a.setCheckable(True)
        select_group.addAction(a)

        # wand menu
        tool_menu = QMenu(self)
        self._wand_actions['wand_select'] = menu_a = tool_menu.addAction(
            QIcon(get_icon('wand_select.png')), 'wand',
            self.set_label_wand_mode)
        menu_a.setToolTip('Select labels within a rectangle')
        a.setToolTip(menu_a.toolTip())

        self._wand_actions['color_select'] = menu_a = tool_menu.addAction(
            QIcon(get_icon('color_select.png')), 'color wand',
            self.set_color_wand_mode)
        menu_a.setToolTip('Select colors')

        self._wand_actions['row_select'] = menu_a = tool_menu.addAction(
            QIcon(get_icon('row_select.png')), 'row selection',
            self.set_row_wand_mode)
        menu_a.setToolTip('Select pixel rows')

        self._wand_actions['col_select'] = menu_a = tool_menu.addAction(
            QIcon(get_icon('col_select.png')), 'column selection',
            self.set_col_wand_mode)
        menu_a.setToolTip('Select pixel columns')

        a.setMenu(tool_menu)

        # color_wand widgets
        self.distance_slider = slider = QSlider(Qt.Horizontal)
        slider.setMinimum(0)
        slider.setMaximum(255)
        slider.setValue(30)
        slider.setSingleStep(1)

        self.lbl_slider = QLabel('30')
        slider.valueChanged.connect(lambda i: self.lbl_slider.setText(str(i)))
        slider.setMaximumWidth(self.combo.sizeHint().width())

        self.cb_whole_fig = QCheckBox('Whole plot')
        self.cb_whole_fig.setToolTip('Select the colors on the entire plot')

        self.cb_use_alpha = QCheckBox('Use alpha')
        self.cb_use_alpha.setToolTip('Use the alpha channel, i.e. the '
                                     'transparency of the RGBA image.')

        self.color_wand_actions = [
                self.addWidget(slider), self.addWidget(self.lbl_slider),
                self.addWidget(self.cb_whole_fig),
                self.addWidget(self.cb_use_alpha)]

        self.set_label_wand_mode()

        self.addSeparator()
        type_group = QActionGroup(self)

        self._type_actions = {}

        # new selection action
        self._type_actions['new_select'] = a = self.addAction(
            QIcon(get_icon('new_selection.png')), 'Create a new selection')
        a.setToolTip('Select pixels within a rectangle and ignore the current '
                     'selection')
        a.setCheckable(True)
        type_group.addAction(a)

        # add to selection action
        self._type_actions['add_select'] = a = self.addAction(
            QIcon(get_icon('add_select.png')), 'Add to selection')
        a.setToolTip('Select pixels within a rectangle and add them to the '
                     'current selection')
        a.setCheckable(True)
        type_group.addAction(a)

        # remove action
        self._type_actions['remove_select'] = a = self.addAction(
            QIcon(get_icon('remove_select.png')), 'Remove from selection')
        a.setToolTip('Select pixels within a rectangle and remove them from '
                     'the current selection')
        a.setCheckable(True)
        type_group.addAction(a)

        # info button
        self.addSeparator()
        self.info_button = InfoButton(self, 'selection_toolbar.rst')
        self.addWidget(self.info_button)

        # selection appearence options
        self.addSeparator()
        self.sl_alpha = slider = QSlider(Qt.Horizontal)
        self._appearance_actions['alpha'] = self.addWidget(slider)
        slider.setMinimum(0)
        slider.setMaximum(100)
        slider.setValue(100)
        slider.setSingleStep(1)

        self.lbl_alpha_slider = QLabel('100 %')
        slider.valueChanged.connect(
            lambda i: self.lbl_alpha_slider.setText(str(i) + ' %'))
        slider.valueChanged.connect(self.update_alpha)
        slider.setMaximumWidth(self.combo.sizeHint().width())

        # Select all and invert selection buttons
        self.addSeparator()
        self._actions['select_all'] = a = self.addAction(
            QIcon(get_icon('select_all.png')), 'all', self.select_all)
        a.setToolTip('Select all labels')

        self._actions['expand_select'] = a = self.addAction(
            QIcon(get_icon('expand_select.png')), 'expand',
            self.expand_selection)
        a.setToolTip('Expand the selected areas to select the entire feature')

        self._actions['invert_select'] = a = self.addAction(
            QIcon(get_icon('invert_select.png')), 'invert',
            self.invert_selection)
        a.setToolTip('Invert selection')

        self._actions['clear_select'] = a = self.addAction(
            QIcon(get_icon('clear_select.png')), 'clear',
            self.clear_selection)
        a.setToolTip('Clear selection')

        self._actions['select_right'] = a = self.addAction(
            QIcon(get_icon('select_right.png')), 'right',
            self.select_everything_to_the_right)
        a.setToolTip('Select everything to the right of each column')

        self._actions['select_pattern'] = a = self.addAction(
            QIcon(get_icon('pattern.png')), 'pattern',
            self.start_pattern_selection)
        a.setCheckable(True)
        a.setToolTip(
            'Select a binary pattern/hatch within the current selection')

        # wand menu
        pattern_menu = QMenu(self)
        self._pattern_actions['binary'] = menu_a = pattern_menu.addAction(
            QIcon(get_icon('pattern.png')), 'Binary',
            self.set_binary_pattern_mode)
        menu_a.setToolTip(
            'Select a binary pattern/hatch within the current selection')
        a.setToolTip(menu_a.toolTip())

        self._pattern_actions['grey'] = menu_a = pattern_menu.addAction(
            QIcon(get_icon('pattern_grey.png')), 'Greyscale',
            self.set_grey_pattern_mode)
        menu_a.setToolTip(
            'Select a pattern/hatch within the current selection based on '
            'grey scale colors')

        a.setMenu(pattern_menu)

        self.new_select_action.setChecked(True)
        for a in self._type_actions.values():
            a.toggled.connect(self.add_or_remove_pattern)

        self.refresh()
示例#17
0
    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)
示例#18
0
    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
示例#19
0
    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