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()
def __init__(self, straditizer, axes=None, *args, **kwargs): """ Parameters ---------- straditizer: weakref.ref The reference to the straditizer axes: matplotlib.axes.Axes The matplotlib axes corresponding to the marks """ super(MultiCrossMarksEditor, self).__init__(*args, **kwargs) self.straditizer = straditizer straditizer = straditizer() self.error_msg = PyErrorMessage(self) #: Plot the reconstructed data self.cb_plot_lines = QCheckBox('Plot reconstruction') self.cb_plot_lines.setChecked(True) # A Checkbox to automatically zoom to the selection self.cb_zoom_to_selection = QCheckBox('Zoom to selection') # A Checkbox to automaticall hide the other marks self.cb_selection_only = QCheckBox('Selection only') # A Checkbox to automatically fit the selected cells to the selected # data self.cb_fit2selection = QCheckBox( 'Fit selected cells to selected data') self.cb_fit2selection.setToolTip( 'If checked, select cells from the table and click on one of the ' 'plots to update the table with the data at the selected position.' ) # The table to display the DataFrame self.table = self.create_view(axes=axes) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) self.btn_save = QPushButton('Save') self.btn_save.setToolTip('Save the samples and continue editing') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_zoom_to_selection) hbox.addWidget(self.cb_selection_only) hbox.addWidget(self.cb_fit2selection) hbox.addWidget(self.cb_plot_lines) hbox.addStretch(0) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_save) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_save.clicked.connect(self.save_samples) straditizer.mark_added.connect(self.table.model().load_new_marks) straditizer.mark_removed.connect(self.table.model().remove_mark) self.table.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_zoom_to_selection) self.table.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.table.frozen_table_view.selectionModel().selectionChanged.connect( self.maybe_show_selection_only) self.cb_zoom_to_selection.stateChanged.connect( self.toggle_cb_zoom_to_selection) self.cb_selection_only.stateChanged.connect( self.toggle_cb_selection_only) self.cb_fit2selection.stateChanged.connect(self.toggle_fit2selection) self.cb_plot_lines.stateChanged.connect(self.toggle_plot_lines) self.toggle_plot_lines()
def __init__(self, *args, **kwargs): from straditize.widgets.menu_actions import StraditizerMenuActions from straditize.widgets.progress_widget import ProgressWidget from straditize.widgets.data import DigitizingControl from straditize.widgets.selection_toolbar import SelectionToolbar from straditize.widgets.marker_control import MarkerControl from straditize.widgets.plots import PlotControl from straditize.widgets.axes_translations import AxesTranslations from straditize.widgets.image_correction import (ImageRotator, ImageRescaler) from straditize.widgets.colnames import ColumnNamesManager self._straditizers = [] super(StraditizerWidgets, self).__init__(*args, **kwargs) self.tree = QTreeWidget(parent=self) self.tree.setSelectionMode(QTreeWidget.NoSelection) self.refresh_button = QToolButton(self) self.refresh_button.setIcon(QIcon(get_psy_icon('refresh.png'))) self.refresh_button.setToolTip('Refresh from the straditizer') self.apply_button = EnableButton('Apply', parent=self) self.cancel_button = EnableButton('Cancel', parent=self) self.attrs_button = QPushButton('Attributes', parent=self) self.tutorial_button = QPushButton('Tutorial', parent=self) self.tutorial_button.setCheckable(True) self.error_msg = PyErrorMessage(self) self.stradi_combo = QComboBox() self.btn_open_stradi = QToolButton() self.btn_open_stradi.setIcon(QIcon(get_psy_icon('run_arrow.png'))) self.btn_close_stradi = QToolButton() self.btn_close_stradi.setIcon(QIcon(get_psy_icon('invalid.png'))) self.btn_reload_autosaved = QPushButton("Reload") self.btn_reload_autosaved.setToolTip( "Close the straditizer and reload the last autosaved project") # --------------------------------------------------------------------- # --------------------------- Tree widgets ---------------------------- # --------------------------------------------------------------------- self.tree.setHeaderLabels(['', '']) self.tree.setColumnCount(2) self.progress_item = QTreeWidgetItem(0) self.progress_item.setText(0, 'ToDo list') self.progress_widget = ProgressWidget(self, self.progress_item) self.menu_actions_item = QTreeWidgetItem(0) self.menu_actions_item.setText(0, 'Images import/export') self.tree.addTopLevelItem(self.menu_actions_item) self.menu_actions = StraditizerMenuActions(self) self.digitizer_item = item = QTreeWidgetItem(0) item.setText(0, 'Digitization control') self.digitizer = DigitizingControl(self, item) self.col_names_item = item = QTreeWidgetItem(0) item.setText(0, 'Column names') self.colnames_manager = ColumnNamesManager(self, item) self.add_info_button(item, 'column_names.rst') self.axes_translations_item = item = QTreeWidgetItem(0) item.setText(0, 'Axes translations') self.axes_translations = AxesTranslations(self, item) self.image_transform_item = item = QTreeWidgetItem(0) item.setText(0, 'Transform source image') self.image_rescaler = ImageRescaler(self, item) self.image_rotator_item = item = QTreeWidgetItem(0) item.setText(0, 'Rotate image') self.image_rotator = ImageRotator(self) self.image_transform_item.addChild(item) self.image_rotator.setup_children(item) self.plot_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Plot control') self.plot_control = PlotControl(self, item) self.add_info_button(item, 'plot_control.rst') self.marker_control_item = item = QTreeWidgetItem(0) item.setText(0, 'Marker control') self.marker_control = MarkerControl(self, item) self.add_info_button(item, 'marker_control.rst') # --------------------------------------------------------------------- # ----------------------------- Toolbars ------------------------------ # --------------------------------------------------------------------- self.selection_toolbar = SelectionToolbar(self, 'Selection toolbar') # --------------------------------------------------------------------- # ----------------------------- InfoButton ---------------------------- # --------------------------------------------------------------------- self.info_button = InfoButton(self, get_doc_file('straditize.rst')) # --------------------------------------------------------------------- # --------------------------- Layouts --------------------------------- # --------------------------------------------------------------------- stradi_box = QHBoxLayout() stradi_box.addWidget(self.stradi_combo, 1) stradi_box.addWidget(self.btn_open_stradi) stradi_box.addWidget(self.btn_close_stradi) attrs_box = QHBoxLayout() attrs_box.addWidget(self.attrs_button) attrs_box.addStretch(0) attrs_box.addWidget(self.tutorial_button) btn_box = QHBoxLayout() btn_box.addWidget(self.refresh_button) btn_box.addWidget(self.info_button) btn_box.addStretch(0) btn_box.addWidget(self.apply_button) btn_box.addWidget(self.cancel_button) reload_box = QHBoxLayout() reload_box.addWidget(self.btn_reload_autosaved) reload_box.addStretch(0) vbox = QVBoxLayout() vbox.addLayout(stradi_box) vbox.addWidget(self.tree) vbox.addLayout(attrs_box) vbox.addLayout(btn_box) vbox.addLayout(reload_box) self.setLayout(vbox) self.apply_button.setEnabled(False) self.cancel_button.setEnabled(False) self.tree.expandItem(self.progress_item) self.tree.expandItem(self.digitizer_item) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.stradi_combo.currentIndexChanged.connect(self.set_current_stradi) self.refresh_button.clicked.connect(self.refresh) self.attrs_button.clicked.connect(self.edit_attrs) self.tutorial_button.clicked.connect(self.start_tutorial) self.open_external.connect(self._create_straditizer_from_args) self.btn_open_stradi.clicked.connect( self.menu_actions.open_straditizer) self.btn_close_stradi.clicked.connect(self.close_straditizer) self.btn_reload_autosaved.clicked.connect(self.reload_autosaved) self.refresh() header = self.tree.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch)
def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled)
class DataFrameEditor(DockMixin, QWidget): """An editor for data frames""" dock_cls = DataFrameDock #: A signal that is emitted, if the table is cleared cleared = QtCore.pyqtSignal() #: A signal that is emitted when a cell has been changed. The argument #: is a tuple of two integers and one float: #: the row index, the column index and the new value cell_edited = QtCore.pyqtSignal(int, int, object, object) #: A signal that is emitted, if rows have been inserted into the dataframe. #: The first value is the integer of the (original) position of the row, #: the second one is the number of rows rows_inserted = QtCore.pyqtSignal(int, int) @property def hidden(self): return not self.table.filled def __init__(self, *args, **kwargs): super(DataFrameEditor, self).__init__(*args, **kwargs) self.error_msg = PyErrorMessage(self) # Label for displaying the DataFrame size self.lbl_size = QLabel() # A Checkbox for enabling and disabling the editability of the index self.cb_index_editable = QCheckBox('Index editable') # A checkbox for enabling and disabling the change of data types self.cb_dtypes_changeable = QCheckBox('Datatypes changeable') # A checkbox for enabling and disabling sorting self.cb_enable_sort = QCheckBox('Enable sorting') # A button to open a dataframe from the file self.btn_open_df = QToolButton(parent=self) self.btn_open_df.setIcon(QIcon(get_icon('run_arrow.png'))) self.btn_open_df.setToolTip('Open a DataFrame from your disk') self.btn_from_console = LoadFromConsoleButton(pd.DataFrame) self.btn_from_console.setToolTip('Show a DataFrame from the console') # The table to display the DataFrame self.table = DataFrameView(pd.DataFrame(), self) # format line edit self.format_editor = QLineEdit() self.format_editor.setText(self.table.model()._format) # format update button self.btn_change_format = QPushButton('Update') self.btn_change_format.setEnabled(False) # table clearing button self.btn_clear = QPushButton('Clear') self.btn_clear.setToolTip( 'Clear the table and disconnect from the DataFrame') # refresh button self.btn_refresh = QToolButton() self.btn_refresh.setIcon(QIcon(get_icon('refresh.png'))) self.btn_refresh.setToolTip('Refresh the table') # close button self.btn_close = QPushButton('Close') self.btn_close.setToolTip('Close this widget permanentely') # --------------------------------------------------------------------- # ------------------------ layout -------------------------------- # --------------------------------------------------------------------- vbox = QVBoxLayout() self.top_hbox = hbox = QHBoxLayout() hbox.addWidget(self.cb_index_editable) hbox.addWidget(self.cb_dtypes_changeable) hbox.addWidget(self.cb_enable_sort) hbox.addWidget(self.lbl_size) hbox.addStretch(0) hbox.addWidget(self.btn_open_df) hbox.addWidget(self.btn_from_console) vbox.addLayout(hbox) vbox.addWidget(self.table) self.bottom_hbox = hbox = QHBoxLayout() hbox.addWidget(self.format_editor) hbox.addWidget(self.btn_change_format) hbox.addStretch(0) hbox.addWidget(self.btn_clear) hbox.addWidget(self.btn_close) hbox.addWidget(self.btn_refresh) vbox.addLayout(hbox) self.setLayout(vbox) # --------------------------------------------------------------------- # ------------------------ Connections -------------------------------- # --------------------------------------------------------------------- self.cb_dtypes_changeable.stateChanged.connect( self.set_dtypes_changeable) self.cb_index_editable.stateChanged.connect(self.set_index_editable) self.btn_from_console.object_loaded.connect(self._open_ds_from_console) self.rows_inserted.connect(lambda i, n: self.set_lbl_size_text()) self.format_editor.textChanged.connect(self.toggle_fmt_button) self.btn_change_format.clicked.connect(self.update_format) self.btn_clear.clicked.connect(self.clear_table) self.btn_close.clicked.connect(self.clear_table) self.btn_close.clicked.connect(lambda: self.close()) self.btn_refresh.clicked.connect(self.table.reset_model) self.btn_open_df.clicked.connect(self._open_dataframe) self.table.set_index_action.triggered.connect( self.update_index_editable) self.table.append_index_action.triggered.connect( self.update_index_editable) self.cb_enable_sort.stateChanged.connect( self.table.setSortingEnabled) def update_index_editable(self): model = self.table.model() if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) self.cb_index_editable.setChecked(model.index_editable) def set_lbl_size_text(self, nrows=None, ncols=None): """Set the text of the :attr:`lbl_size` label to display the size""" model = self.table.model() nrows = nrows if nrows is not None else model.rowCount() ncols = ncols if ncols is not None else model.columnCount() if not nrows and not ncols: self.lbl_size.setText('') else: self.lbl_size.setText('Rows: %i, Columns: %i' % (nrows, ncols)) def clear_table(self): """Clear the table and emit the :attr:`cleared` signal""" df = pd.DataFrame() self.set_df(df, show=False) def _open_ds_from_console(self, oname, df): self.set_df(df) @docstrings.dedent def set_df(self, df, *args, **kwargs): """ Fill the table from a :class:`~pandas.DataFrame` Parameters ---------- %(DataFrameModel.parameters.no_parent)s show: bool If True (default), show and raise_ the editor """ show = kwargs.pop('show', True) self.table.set_df(df, *args, **kwargs) self.set_lbl_size_text(*df.shape) model = self.table.model() self.cb_dtypes_changeable.setChecked(model.dtypes_changeable) if len(model.df.index.names) > 1: model.index_editable = False self.cb_index_editable.setEnabled(False) else: self.cb_index_editable.setEnabled(True) self.cb_index_editable.setChecked(model.index_editable) self.cleared.emit() if show: self.show_plugin() self.dock.raise_() def set_index_editable(self, state): """Set the :attr:`DataFrameModel.index_editable` attribute""" self.table.model().index_editable = state == Qt.Checked def set_dtypes_changeable(self, state): """Set the :attr:`DataFrameModel.dtypes_changeable` attribute""" self.table.model().dtypes_changeable = state == Qt.Checked def toggle_fmt_button(self, text): try: text % 1.1 except (TypeError, ValueError): self.btn_change_format.setEnabled(False) else: self.btn_change_format.setEnabled( text.strip() != self.table.model()._format) def update_format(self): """Update the format of the table""" self.table.model().set_format(self.format_editor.text().strip()) def to_dock(self, main, *args, **kwargs): connect = self.dock is None super(DataFrameEditor, self).to_dock(main, *args, **kwargs) if connect: self.dock.toggleViewAction().triggered.connect(self.maybe_tabify) def maybe_tabify(self): main = self.dock.parent() if self.is_shown and main.dockWidgetArea( main.help_explorer.dock) == main.dockWidgetArea(self.dock): main.tabifyDockWidget(main.help_explorer.dock, self.dock) def _open_dataframe(self): self.open_dataframe() def open_dataframe(self, fname=None, *args, **kwargs): """Opens a file dialog and the dataset that has been inserted""" if fname is None: fname = QFileDialog.getOpenFileName( self, 'Open dataset', os.getcwd(), 'Comma separated files (*.csv);;' 'Excel files (*.xls *.xlsx);;' 'JSON files (*.json);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if isinstance(fname, pd.DataFrame): self.set_df(fname) elif not fname: return else: ext = osp.splitext(fname)[1] open_funcs = { '.xls': pd.read_excel, '.xlsx': pd.read_excel, '.json': pd.read_json, '.tab': partial(pd.read_csv, delimiter='\t'), '.dat': partial(pd.read_csv, delim_whitespace=True), } open_func = open_funcs.get(ext, pd.read_csv) try: df = open_func(fname) except Exception: self.error_msg.showTraceback( '<b>Could not open DataFrame %s with %s</b>' % ( fname, open_func)) return self.set_df(df) def close(self, *args, **kwargs): if self.dock is not None: self.dock.close(*args, **kwargs) # removes the dock window del self.dock return super(DataFrameEditor, self).close(*args, **kwargs)
class MainWindow(QMainWindow): #: A signal that is emmitted when the a signal is received through the #: open_files_server open_external = QtCore.pyqtSignal(list) #: The server to open external files open_files_server = None #: Inprocess console console = None #: tree widget displaying the open datasets ds_tree = None #: list of figures from the psyplot backend figures = [] #: tree widget displaying the open figures figures_tree = None #: general formatoptions widget fmt_widget = None #: help explorer help_explorer = None #: the DataFrameEditor widgets, a widget to show and edit data frames dataframeeditors = None #: tab widget displaying the arrays in current main and sub project project_content = None #: The dockwidgets of this instance dockwidgets = [] #: default widths of the dock widgets default_widths = {} _is_open = False #: The keyboard shortcuts of the default layout default_shortcuts = [] #: The current keyboard shortcuts current_shortcuts = [] #: The key for the central widget for the main window in the #: :attr:`plugins` dictionary central_widget_key = 'console' @property def logger(self): """The logger of this instance""" return logging.getLogger( '%s.%s' % (self.__class__.__module__, self.__class__.__name__)) @docstrings.get_sections(base='MainWindow') @docstrings.dedent def __init__(self, show=True): """ Parameters ---------- show: bool If True, the created mainwindow is show """ if sys.stdout is None: sys.stdout = StreamToLogger(self.logger) if sys.stderr is None: sys.stderr = StreamToLogger(self.logger) super(MainWindow, self).__init__() self.setWindowIcon(QIcon(get_icon('logo.png'))) #: list of figures from the psyplot backend self.figures = [] self.error_msg = PyErrorMessage(self) self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks) #: Inprocess console self.console = ConsoleWidget(self) self.project_actions = {} self.config_pages = [] self.open_file_options = OrderedDict([ ('new psyplot plot from dataset', self.open_external_files), ('new psyplot project', partial(self.open_external_files, [])), ]) # --------------------------------------------------------------------- # ----------------------------- Menus --------------------------------- # --------------------------------------------------------------------- # ######################## File menu ################################## # --------------------------- New plot -------------------------------- self.file_menu = QMenu('File', parent=self) self.new_plot_action = QAction('New plot', self) self.new_plot_action.setStatusTip( 'Use an existing dataset (or open a new one) to create one or ' 'more plots') self.register_shortcut(self.new_plot_action, QKeySequence.New) self.new_plot_action.triggered.connect(lambda: self.new_plots(True)) self.file_menu.addAction(self.new_plot_action) # --------------------------- Open project ---------------------------- self.open_project_menu = QMenu('Open project', self) self.file_menu.addMenu(self.open_project_menu) self.open_mp_action = QAction('New main project', self) self.register_shortcut(self.open_mp_action, QKeySequence.Open) self.open_mp_action.setStatusTip('Open a new main project') self.open_mp_action.triggered.connect(self.open_mp) self.open_project_menu.addAction(self.open_mp_action) self.open_sp_action = QAction('Add to current', self) self.register_shortcut( self.open_sp_action, QKeySequence('Ctrl+Shift+O', QKeySequence.NativeText)) self.open_sp_action.setStatusTip( 'Load a project as a sub project and add it to the current main ' 'project') self.open_sp_action.triggered.connect(self.open_sp) self.open_project_menu.addAction(self.open_sp_action) # ---------------------- load preset menu ----------------------------- self.load_preset_menu = QMenu('Load preset', parent=self) self.file_menu.addMenu(self.load_preset_menu) self.load_sp_preset_action = self.load_preset_menu.addAction( "For selection", self.load_sp_preset) self.load_sp_preset_action.setStatusTip( "Load a preset for the selected project") self.load_mp_preset_action = self.load_preset_menu.addAction( "For full project", self.load_mp_preset) self.load_sp_preset_action.setStatusTip( "Load a preset for the full project") # ----------------------- Save project -------------------------------- self.save_project_menu = QMenu('Save', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_mp_action = QAction('Full psyplot project', self) self.save_mp_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_action, QKeySequence.Save) self.save_mp_action.triggered.connect(self.save_mp) self.save_project_menu.addAction(self.save_mp_action) self.save_sp_action = QAction('Selected psyplot project', self) self.save_sp_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_action.triggered.connect(self.save_sp) self.save_project_menu.addAction(self.save_sp_action) # ------------------------ Save project as ---------------------------- self.save_project_as_menu = QMenu('Save as', parent=self) self.file_menu.addMenu(self.save_project_as_menu) self.save_mp_as_action = QAction('Full psyplot project', self) self.save_mp_as_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_as_action, QKeySequence.SaveAs) self.save_mp_as_action.triggered.connect( partial(self.save_mp, new_fname=True)) self.save_project_as_menu.addAction(self.save_mp_as_action) self.save_sp_as_action = QAction('Selected psyplot project', self) self.save_sp_as_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_as_action.triggered.connect( partial(self.save_sp, new_fname=True)) self.save_project_as_menu.addAction(self.save_sp_as_action) # ------------------------ Save preset -------------------------------- self.save_preset_menu = QMenu('Save preset', parent=self) self.file_menu.addMenu(self.save_preset_menu) self.save_sp_preset_action = self.save_preset_menu.addAction( "Selection", self.save_sp_preset) self.save_sp_preset_action.setStatusTip( "Save the formatoptions of the selected project as a preset") self.save_mp_preset_action = self.save_preset_menu.addAction( "Full project", self.save_mp_preset) self.save_sp_preset_action.setStatusTip( "Save the formatoptions of the full project as a preset") # -------------------------- Pack project ----------------------------- self.pack_project_menu = QMenu('Zip project files', parent=self) self.file_menu.addMenu(self.pack_project_menu) self.pack_mp_action = QAction('Full psyplot project', self) self.pack_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.pack_mp_action.triggered.connect(partial(self.save_mp, pack=True)) self.pack_project_menu.addAction(self.pack_mp_action) self.pack_sp_action = QAction('Selected psyplot project', self) self.pack_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.pack_sp_action.triggered.connect(partial(self.save_sp, pack=True)) self.pack_project_menu.addAction(self.pack_sp_action) # ------------------------ Export figures ----------------------------- self.export_project_menu = QMenu('Export figures', parent=self) self.file_menu.addMenu(self.export_project_menu) self.export_mp_action = QAction('Full psyplot project', self) self.export_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.export_mp_action.triggered.connect(self.export_mp) self.register_shortcut(self.export_mp_action, QKeySequence('Ctrl+E', QKeySequence.NativeText)) self.export_project_menu.addAction(self.export_mp_action) self.export_sp_action = QAction('Selected psyplot project', self) self.export_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.register_shortcut( self.export_sp_action, QKeySequence('Ctrl+Shift+E', QKeySequence.NativeText)) self.export_sp_action.triggered.connect(self.export_sp) self.export_project_menu.addAction(self.export_sp_action) # ------------------------ Close project ------------------------------ self.file_menu.addSeparator() self.close_project_menu = QMenu('Close project', parent=self) self.file_menu.addMenu(self.close_project_menu) self.close_mp_action = QAction('Full psyplot project', self) self.register_shortcut( self.close_mp_action, QKeySequence('Ctrl+Shift+W', QKeySequence.NativeText)) self.close_mp_action.setStatusTip( 'Close the main project and delete all data and plots out of ' 'memory') self.close_mp_action.triggered.connect( lambda: psy.close(psy.gcp(True).num)) self.close_project_menu.addAction(self.close_mp_action) self.close_sp_action = QAction('Selected psyplot project', self) self.close_sp_action.setStatusTip( 'Close the selected arrays project and delete all data and plots ' 'out of memory') self.register_shortcut(self.close_sp_action, QKeySequence.Close) self.close_sp_action.triggered.connect( lambda: psy.gcp().close(True, True)) self.close_project_menu.addAction(self.close_sp_action) # ----------------------------- Quit ---------------------------------- if sys.platform != 'darwin': # mac os makes this anyway self.quit_action = QAction('Quit', self) self.quit_action.triggered.connect(self.close) self.quit_action.triggered.connect( QtCore.QCoreApplication.instance().quit) self.register_shortcut(self.quit_action, QKeySequence.Quit) self.file_menu.addAction(self.quit_action) self.menuBar().addMenu(self.file_menu) # ######################## Console menu ############################### self.console_menu = QMenu('Console', self) self.console_menu.addActions(self.console.actions()) self.menuBar().addMenu(self.console_menu) # ######################## Windows menu ############################### self.windows_menu = QMenu('Windows', self) self.menuBar().addMenu(self.windows_menu) # ############################ Help menu ############################## self.help_menu = QMenu('Help', parent=self) self.menuBar().addMenu(self.help_menu) # -------------------------- Preferences ------------------------------ self.help_action = QAction('Preferences', self) self.help_action.triggered.connect(lambda: self.edit_preferences(True)) self.register_shortcut(self.help_action, QKeySequence.Preferences) self.help_menu.addAction(self.help_action) # ---------------------------- About ---------------------------------- self.about_action = QAction('About', self) self.about_action.triggered.connect(self.about) self.help_menu.addAction(self.about_action) # ---------------------------- Dependencies --------------------------- self.dependencies_action = QAction('Dependencies', self) self.dependencies_action.triggered.connect( lambda: self.show_dependencies(True)) self.help_menu.addAction(self.dependencies_action) self.dockwidgets = [] # --------------------------------------------------------------------- # -------------------------- Dock windows ----------------------------- # --------------------------------------------------------------------- #: tab widget displaying the arrays in current main and sub project #: tree widget displaying the open datasets self.project_content = ProjectContentWidget(parent=self) self.ds_tree = DatasetTree(parent=self) #: tree widget displaying the open figures self.figures_tree = FiguresTree(parent=self) #: help explorer self.help_explorer = help_explorer = HelpExplorer(parent=self) if 'HTML help' in help_explorer.viewers and help_explorer.viewers[ 'HTML help'].sphinx_thread is not None: help_explorer.viewers[ 'HTML help'].sphinx_thread.html_ready.connect( self.focus_on_console) #: the DataFrameEditor widgets self.dataframeeditors = [] #: general formatoptions widget self.fmt_widget = FormatoptionWidget(parent=self, help_explorer=help_explorer, console=self.console) # load plugin widgets self.plugins = plugins = OrderedDict([ ('console', self.console), ('project_content', self.project_content), ('ds_tree', self.ds_tree), ('figures_tree', self.figures_tree), ('help_explorer', self.help_explorer), ('fmt_widget', self.fmt_widget), ]) self.default_plugins = list(plugins) for plugin_name, w_class in six.iteritems(rcParams.load_plugins()): plugins[plugin_name] = w_class(parent=self) self.add_mp_to_menu() psy.Project.oncpchange.connect(self.eventually_add_mp_to_menu) self.windows_menu.addSeparator() self.window_layouts_menu = QMenu('Window layouts', self) self.restore_layout_action = QAction('Restore default layout', self) self.restore_layout_action.triggered.connect(self.setup_default_layout) self.window_layouts_menu.addAction(self.restore_layout_action) self.windows_menu.addMenu(self.window_layouts_menu) self.panes_menu = QMenu('Panes', self) self.windows_menu.addMenu(self.panes_menu) self.dataframe_menu = QMenu('DataFrame editors', self) self.dataframe_menu.addAction( 'New Editor', partial(self.new_data_frame_editor, None, 'DataFrame Editor')) self.dataframe_menu.addSeparator() self.windows_menu.addMenu(self.dataframe_menu) self.central_widgets_menu = menu = QMenu('Central widget', self) self.windows_menu.addMenu(menu) self.central_widgets_actions = group = QActionGroup(self) group.setExclusive(True) # --------------------------------------------------------------------- # -------------------------- connections ------------------------------ # --------------------------------------------------------------------- self.console.help_explorer = help_explorer psyp.default_print_func = partial(help_explorer.show_rst, oname='formatoption_docs') psy.PlotterInterface._print_func = psyp.default_print_func self.setCentralWidget(self.console) # make sure that the plots are shown between the project content and # the help explorer widget self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) # make sure that the formatoption widgets are shown between the # project content and the help explorer widget self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # --------------------------------------------------------------------- # ------------------------------ closure ------------------------------ # --------------------------------------------------------------------- if show: self.help_explorer.show_intro(self.console.intro_msg) # --------------------------------------------------------------------- # ------------------------- open_files_server ------------------------- # --------------------------------------------------------------------- self.callbacks = { 'new_plot': self.open_external.emit, 'change_cwd': self._change_cwd, 'run_script': self.console.run_script.emit, 'command': self.console.run_command.emit, } # Server to open external files on a single instance self.open_files_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if rcParams['main.listen_to_port']: self._file_thread = Thread(target=self.start_open_files_server) self._file_thread.setDaemon(True) self._file_thread.start() self.open_external.connect(self._open_external_files) self.config_pages.extend([GuiRcParamsWidget, PsyRcParamsWidget]) # display the statusBar statusbar = self.statusBar() self.figures_label = QLabel() statusbar.addWidget(self.figures_label) self.plugin_label = QLabel() statusbar.addWidget(self.plugin_label) self.default_widths = {} self.setup_default_layout() if show: self.showMaximized() # save the default widths after they have been shown for w in self.plugins.values(): if w.dock is not None: self.default_widths[w] = w.dock.size().width() # hide plugin widgets that should be hidden at startup. Although this # has been executed by :meth:`setup_default_layout`, we have to execute # it again after the call of showMaximized for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() else: w.create_central_widget_action(self).setChecked(True) self._is_open = True def focus_on_console(self, *args, **kwargs): """Put focus on the ipython console""" self.console._control.setFocus() def new_data_frame_editor(self, df=None, title='DataFrame Editor'): """Open a new dataframe editor Parameters ---------- df: pandas.DataFrame The dataframe to display title: str The title of the dock window Returns ------- psyplot_gui.dataframeeditor.DataFrameEditor The newly created editor""" editor = DataFrameEditor() self.dataframeeditors.append(editor) editor.to_dock(self, title, Qt.RightDockWidgetArea, docktype='df') if df is not None: editor.set_df(df) editor.show_plugin() editor.maybe_tabify() editor.raise_() return editor def setup_default_layout(self): """Set up the default window layout""" self.project_content.to_dock(self, 'Plot objects', Qt.LeftDockWidgetArea) self.ds_tree.to_dock(self, 'Datasets', Qt.LeftDockWidgetArea) self.figures_tree.to_dock(self, 'Figures', Qt.LeftDockWidgetArea) self.help_explorer.to_dock(self, 'Help explorer', Qt.RightDockWidgetArea) self.fmt_widget.to_dock(self, 'Formatoptions', Qt.BottomDockWidgetArea) modify_widths = bool(self.default_widths) for w in map(self.plugins.__getitem__, self.default_plugins): if w.dock is not None: w.show_plugin() if modify_widths and with_qt5: self.resizeDocks([w.dock], [self.default_widths[w]], Qt.Horizontal) # hide plugin widgets that should be hidden at startup for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() action2shortcut = defaultdict(list) for s, a in self.default_shortcuts: action2shortcut[a].append(s) for a, s in action2shortcut.items(): self.register_shortcut(a, s) def set_central_widget(self, name): """Set the central widget Parameters ---------- name: str or QWidget The key or the plugin widget in the :attr:`plugins` dictionary""" from PyQt5.QtCore import QTimer self.setUpdatesEnabled(False) current = self.centralWidget() if isinstance(name, six.string_types): new = self.plugins[name] else: new = name name = next(key for key, val in self.plugins.items() if val is new) if new is not current: self._dock_widths = dock_widths = OrderedDict() self._dock_heights = dock_heights = OrderedDict() for key, w in self.plugins.items(): if w.dock is not None and w.is_shown: s = w.dock.size() dock_widths[w] = s.width() if w is not new: dock_heights[w] = s.height() new_pos = self.dockWidgetArea(new.dock) self.removeDockWidget(new.dock) new.dock.close() self.panes_menu.removeAction(new._view_action) self.dataframe_menu.removeAction(new._view_action) new.dock = new._view_action = None self.central_widget_key = name current.to_dock(self) self.setCentralWidget(new) new._set_central_action.setChecked(True) current.show_plugin() current.to_dock(self) new_width = dock_widths.pop(new, None) if current.hidden: current.hide_plugin() else: current_pos = self.dockWidgetArea(current.dock) if current_pos == new_pos and new_width: dock_widths[current] = new_width self._custom_layout_timer = QTimer(self) self._custom_layout_timer.timeout.connect(self._reset_dock_widths) self._custom_layout_timer.setSingleShot(True) self._custom_layout_timer.start(5000) def _reset_dock_widths(self): # resize the plugins if with_qt5: for w, width in self._dock_widths.items(): if w.dock is not None: self.resizeDocks([w.dock], [width], Qt.Horizontal) for w, height in self._dock_heights.items(): if w.dock is not None: self.resizeDocks([w.dock], [height], Qt.Vertical) self.setUpdatesEnabled(True) def _save_project(self, p, new_fname=False, *args, **kwargs): if new_fname or 'project_file' not in p.attrs: fname = QFileDialog.getSaveFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return else: fname = p.attrs['project_file'] try: p.save_project(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback('<b>Could not save the project!</b>') else: p.attrs['project_file'] = fname if p.is_main: self.update_project_action(p.num) def load_mp_preset(self): self._load_preset(psy.gcp(True)) def load_sp_preset(self): self._load_preset(psy.gcp()) def _load_preset(self, project, *args, **kwargs): fname, ok = QFileDialog.getOpenFileName( self, 'Load preset', os.path.join(get_configdir(), "presets"), 'YAML files (*.yml *.yaml);;' 'All files (*)') if ok: project.load_preset(fname, *args, **kwargs) def open_mp(self, *args, **kwargs): """Open a new main project""" self._open_project(main=True) def open_sp(self, *args, **kwargs): """Open a subproject and add it to the current main project""" self._open_project(main=False) def _open_project(self, *args, **kwargs): fname = QFileDialog.getOpenFileName( self, 'Project file', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return p = psy.Project.load_project(fname, *args, **kwargs) p.attrs['project_file'] = fname self.update_project_action(p.num) def save_mp(self, *args, **kwargs): """Save the current main project.""" self._save_project(psy.gcp(True), **kwargs) def save_sp(self, *args, **kwargs): """Save the current sub project.""" self._save_project(psy.gcp(), **kwargs) def save_sp_preset(self): self._save_preset(psy.gcp()) def save_mp_preset(self): self._save_preset(psy.gcp(True)) def _save_preset(self, project, *args, **kwargs): fname, ok = QFileDialog.getSaveFileName( self, 'Save preset', os.path.join(get_configdir(), 'presets'), 'YAML file (*.yml *.yaml);;' 'All files (*)') if ok: project.save_preset(fname, *args, **kwargs) def _export_project(self, p, *args, **kwargs): fname = QFileDialog.getSaveFileName( self, 'Picture destination', os.getcwd(), 'PDF files (*.pdf);;' 'Postscript file (*.ps);;' 'PNG image (*.png);;' 'JPG image (*.jpg *.jpeg);;' 'TIFF image (*.tif *.tiff);;' 'GIF image (*.gif);;' 'All files (*)') if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return try: p.export(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback( '<b>Could not export the figures!</b>') def export_mp(self, *args, **kwargs): self._export_project(psy.gcp(True), **kwargs) def export_sp(self, *args, **kwargs): self._export_project(psy.gcp(), **kwargs) def new_plots(self, exec_=None): if hasattr(self, 'plot_creator'): try: self.plot_creator.close() except RuntimeError: pass self.plot_creator = PlotCreator(help_explorer=self.help_explorer, parent=self) available_width = QDesktopWidget().availableGeometry().width() / 3. width = self.plot_creator.sizeHint().width() height = self.plot_creator.sizeHint().height() # The plot creator window should cover at least one third of the screen self.plot_creator.resize(max(available_width, width), height) if exec_: self.plot_creator.exec_() def excepthook(self, type, value, traceback): """A method to replace the sys.excepthook""" self.error_msg.excepthook(type, value, traceback) def edit_preferences(self, exec_=None): """Edit Spyder preferences""" if hasattr(self, 'preferences'): try: self.preferences.close() except RuntimeError: pass self.preferences = dlg = Prefences(self) for PrefPageClass in self.config_pages: widget = PrefPageClass(dlg) widget.initialize() dlg.add_page(widget) available_width = int(0.667 * QDesktopWidget().availableGeometry().width()) width = dlg.sizeHint().width() height = dlg.sizeHint().height() # The preferences window should cover at least one third of the screen dlg.resize(max(available_width, width), height) if exec_: dlg.exec_() def about(self): """About the tool""" versions = { key: d['version'] for key, d in psyplot.get_versions(False).items() } versions.update(psyplot_gui.get_versions()['requirements']) versions.update(psyplot._get_versions()['requirements']) versions['github'] = 'https://github.com/psyplot/psyplot' versions['author'] = psyplot.__author__ QMessageBox.about( self, "About psyplot", u"""<b>psyplot: Interactive data visualization with python</b> <br>Copyright © 2017- Philipp Sommer <br>Licensed under the terms of the GNU General Public License v2 (GPLv2) <p>Created by %(author)s</p> <p>Most of the icons come from the <a href="https://www.iconfinder.com/"> iconfinder</a>.</p> <p>For bug reports and feature requests, please go to our <a href="%(github)s">Github website</a> or contact the author via mail.</p> <p>This package uses (besides others) the following packages:<br> <ul> <li>psyplot %(psyplot)s</li> <li>Python %(python)s </li> <li>numpy %(numpy)s</li> <li>xarray %(xarray)s</li> <li>pandas %(pandas)s</li> <li>psyplot_gui %(psyplot_gui)s</li> <li>Qt %(qt)s</li> <li>PyQt %(pyqt)s</li> <li>qtconsole %(qtconsole)s</li> </ul></p> <p>For a full list of requirements see the <em>dependencies</em> in the <em>Help</em> menu.</p> <p>This software is provided "as is", without warranty or support of any kind.</p>""" % versions) def show_dependencies(self, exec_=None): """Open a dialog that shows the dependencies""" if hasattr(self, 'dependencies'): try: self.dependencies.close() except RuntimeError: pass self.dependencies = dlg = DependenciesDialog(psyplot.get_versions(), parent=self) dlg.resize(630, 420) if exec_: dlg.exec_() def reset_rcParams(self): rcParams.update_from_defaultParams() psy.rcParams.update_from_defaultParams() def add_mp_to_menu(self): mp = psy.gcp(True) action = QAction( os.path.basename( mp.attrs.get('project_file', 'Untitled %s*' % mp.num)), self) action.setStatusTip('Make project %s the current project' % mp.num) action.triggered.connect(lambda: psy.scp(psy.project(mp.num))) self.project_actions[mp.num] = action self.windows_menu.addAction(action) def update_project_action(self, num): action = self.project_actions.get(num) p = psy.project(num) if action: action.setText( os.path.basename( p.attrs.get('project_file', 'Untitled %s*' % num))) def eventually_add_mp_to_menu(self, p): for num in set(self.project_actions).difference( psy.get_project_nums()): self.windows_menu.removeAction(self.project_actions.pop(num)) if p is None or not p.is_main: return if p.num not in self.project_actions: self.add_mp_to_menu() def start_open_files_server(self): """This method listens to the open_files_port and opens the plot creator for new files This method is inspired and to most parts copied from spyder""" self.open_files_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) port = rcParams['main.open_files_port'] try: self.open_files_server.bind(('127.0.0.1', port)) except Exception: return self.open_files_server.listen(20) while 1: # 1 is faster than True try: req, dummy = self.open_files_server.accept() except socket.error as e: # See Issue 1275 for details on why errno EINTR is # silently ignored here. eintr = errno.EINTR # To avoid a traceback after closing on Windows if e.args[0] == eintr: continue # handle a connection abort on close error enotsock = (errno.WSAENOTSOCK if os.name == 'nt' else errno.ENOTSOCK) if e.args[0] in [errno.ECONNABORTED, enotsock]: return raise args = pickle.loads(req.recv(1024)) callback = args[0] func = self.callbacks[callback] self.logger.debug('Emitting %s callback %s', callback, func) func(args[1:]) req.sendall(b' ') def change_cwd(self, path): """Change the current working directory""" import os os.chdir(path) def _change_cwd(self, args): path = args[0][0] self.change_cwd(path) docstrings.keep_params('make_plot.parameters', 'fnames', 'project', 'engine', 'plot_method', 'name', 'dims', 'encoding', 'enable_post', 'seaborn_style', 'concat_dim', 'chname', 'preset') def open_files(self, fnames): """Open a file and ask the user how""" fnames_s = ', '.join(map(os.path.basename, fnames)) if len(fnames_s) > 30: fnames_s = fnames_s[:27] + '...' item, ok = QInputDialog.getItem(self, 'Open file...', 'Open %s as...' % fnames_s, list(self.open_file_options), current=0, editable=False) if ok: return self.open_file_options[item](fnames) @docstrings.get_sections(base='MainWindow.open_external_files') @docstrings.dedent def open_external_files(self, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value( xr.open_mfdataset, 'concat_dim'), chname={}, preset=None): """ Open external files Parameters ---------- %(make_plot.parameters.fnames|project|engine|plot_method|name|dims|encoding|enable_post|seaborn_style|concat_dim|chname|preset)s """ if seaborn_style is not None: import seaborn as sns sns.set_style(seaborn_style) if project is not None: fnames = [s.split(',') for s in fnames] if not isinstance(project, dict): project = psyd.safe_list(project)[0] single_files = (l[0] for l in fnames if len(l) == 1) alternative_paths = defaultdict(lambda: next(single_files, None)) alternative_paths.update(list(l for l in fnames if len(l) == 2)) p = psy.Project.load_project(project, alternative_paths=alternative_paths, engine=engine, main=not psy.gcp(), encoding=encoding, enable_post=enable_post, chname=chname) if preset: p.load_preset(preset) if isinstance(project, six.string_types): p.attrs.setdefault('project_file', project) return True else: self.new_plots(False) self.plot_creator.open_dataset(fnames, engine=engine, concat_dim=concat_dim) if name == 'all': ds = self.plot_creator.get_ds() name = sorted(set(ds.variables) - set(ds.coords)) self.plot_creator.insert_array( list(filter(None, psy.safe_list(name)))) if dims is not None: ds = self.plot_creator.get_ds() dims = { key: ', '.join(map(str, val)) for key, val in six.iteritems(dims) } for i, vname in enumerate( self.plot_creator.array_table.vnames): self.plot_creator.array_table.selectRow(i) self.plot_creator.array_table.update_selected() self.plot_creator.array_table.selectAll() var = ds[vname[0]] self.plot_creator.array_table.update_selected( dims=var.psy.decoder.correct_dims(var, dims.copy())) if preset: self.plot_creator.set_preset(preset) if plot_method: self.plot_creator.pm_combo.setCurrentIndex( self.plot_creator.pm_combo.findText(plot_method)) self.plot_creator.exec_() return True def _open_external_files(self, args): self.open_external_files(*args) @classmethod @docstrings.get_sections(base='MainWindow.run') @docstrings.dedent def run(cls, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value(xr.open_mfdataset, 'concat_dim'), chname={}, preset=None, show=True): """ Create a mainwindow and open the given files or project This class method creates a new mainwindow instance and sets the global :attr:`mainwindow` variable. Parameters ---------- %(MainWindow.open_external_files.parameters)s %(MainWindow.parameters)s Notes ----- - There can be only one mainwindow at the time - This method does not create a QApplication instance! See :meth:`run_app` See Also -------- run_app """ mainwindow = cls(show=show) _set_mainwindow(mainwindow) if fnames or project: mainwindow.open_external_files(fnames, project, engine, plot_method, name, dims, encoding, enable_post, seaborn_style, concat_dim, chname, preset) psyplot.with_gui = True return mainwindow def register_shortcut(self, action, shortcut, context=Qt.ApplicationShortcut): """Register an action for a shortcut""" shortcuts = psy.safe_list(shortcut) for j, shortcut in enumerate(shortcuts): found = False for i, (s, a) in enumerate(self.current_shortcuts): if s == shortcut: new_shortcuts = [ sc for sc in self.current_shortcuts[i][1].shortcuts() if sc != s ] a.setShortcut(QKeySequence()) if new_shortcuts: a.setShortcuts(new_shortcuts) self.current_shortcuts[i][1] = action found = True break if not found: self.default_shortcuts.append([shortcut, action]) self.current_shortcuts.append([shortcut, action]) action.setShortcuts(shortcuts) action.setShortcutContext(context) @classmethod @docstrings.dedent def run_app(cls, *args, **kwargs): """ Create a QApplication, open the given files or project and enter the mainloop Parameters ---------- %(MainWindow.run.parameters)s See Also -------- run """ app = QApplication(sys.argv) cls.run(*args, **kwargs) sys.exit(app.exec_()) def closeEvent(self, event): """closeEvent reimplementation""" if not self._is_open or (self._is_open and self.close()): self._is_open = False event.accept() def close(self): _set_mainwindow(None) if self.open_files_server is not None: self.open_files_server.close() del self.open_files_server for widget in self.plugins.values(): widget.close() self.plugins.clear() return super(MainWindow, self).close()
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 __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode(QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc)
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)})
def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode( QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc)
class FormatoptionWidget(QWidget, DockMixin): """ Widget to update the formatoptions of the current project This widget, mainly made out of a combobox for the formatoption group, a combobox for the formatoption, and a text editor, is designed for updating the selected formatoptions for the current subproject. The widget is connected to the :attr:`psyplot.project.Project.oncpchange` signal and refills the comboboxes if the current subproject changes. The text editor either accepts python code that will be executed by the given `console`, or yaml code. """ no_fmtos_update = _temp_bool_prop( 'no_fmtos_update', """update the fmto combo box or not""") #: The combobox for the formatoption groups group_combo = None #: The combobox for the formatoptions fmt_combo = None #: The help_explorer to display the documentation of the formatoptions help_explorer = None #: The formatoption specific widget that is loaded from the formatoption fmt_widget = None #: A line edit for updating the formatoptions line_edit = None #: A multiline text editor for updating the formatoptions text_edit = None #: A button to switch between :attr:`line_edit` and :attr:`text_edit` multiline_button = None @property def shell(self): """The shell to execute the update of the formatoptions in the current project""" return self.console.kernel_manager.kernel.shell def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode( QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc) def refill_from_rc(self, sort_by_key): from psyplot.project import gcp self.fill_combos_from_project(gcp()) def fill_combos_from_project(self, project): """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project Parameters ---------- project: psyplot.project.Project The project to use""" if rcParams['fmt.sort_by_key']: def sorter(fmto): return fmto.key else: sorter = self.get_name current_text = self.group_combo.currentText() with self.no_fmtos_update: self.group_combo.clear() if project is None or project.is_main or not len(project): self.fmt_combo.clear() self.groups = [] self.fmtos = [] self.line_edit.setEnabled(False) return self.line_edit.setEnabled(True) # get dimensions it_vars = chain.from_iterable( arr.psy.iter_base_variables for arr in project.arrays) dims = next(it_vars).dims sdims = set(dims) for var in it_vars: sdims.intersection_update(var.dims) coords = [d for d in dims if d in sdims] coords_name = [COORDSGROUP] if coords else [] coords_verbose = ['Dimensions'] if coords else [] coords = [coords] if coords else [] if len(project.plotters): # get formatoptions and group them alphabetically grouped_fmts = defaultdict(list) for fmto in project._fmtos: grouped_fmts[fmto.group].append(fmto) for val in six.itervalues(grouped_fmts): val.sort(key=sorter) grouped_fmts = OrderedDict( sorted(six.iteritems(grouped_fmts), key=lambda t: psyp.groups.get(t[0], t[0]))) fmt_groups = list(grouped_fmts.keys()) # save original names self.groups = coords_name + [ALLGROUP] + fmt_groups # save verbose group names (which are used in the combo box) self.groupnames = ( coords_verbose + ['All formatoptions'] + list( map(lambda s: psyp.groups.get(s, s), fmt_groups))) # save formatoptions fmtos = list(grouped_fmts.values()) self.fmtos = coords + [sorted( chain(*fmtos), key=sorter)] + fmtos else: self.groups = coords_name self.groupnames = coords_verbose self.fmtos = coords self.group_combo.addItems(self.groupnames) ind = self.group_combo.findText(current_text) self.group_combo.setCurrentIndex(ind if ind >= 0 else 0) self.fill_fmt_combo(self.group_combo.currentIndex()) def get_name(self, fmto): """Get the name of a :class:`psyplot.plotter.Formatoption` instance""" if isinstance(fmto, six.string_types): return fmto return '%s (%s)' % (fmto.name, fmto.key) if fmto.name else fmto.key @property def fmto(self): return self.fmtos[self.group_combo.currentIndex()][ self.fmt_combo.currentIndex()] @fmto.setter def fmto(self, value): name = self.get_name(value) for i, fmtos in enumerate(self.fmtos): if i == 1: # all formatoptions continue if name in map(self.get_name, fmtos): with self.no_fmtos_update: self.group_combo.setCurrentIndex(i) self.fill_fmt_combo(i, name) return def toggle_line_edit(self): """Switch between the :attr:`line_edit` and :attr:`text_edit` This method is called when the :attr:`multiline_button` is clicked and switches between the single line :attr:``line_edit` and the multiline :attr:`text_edit` """ # switch to multiline text edit if (self.multiline_button.isChecked() and not self.text_edit.isVisible()): self.line_edit.setVisible(False) self.text_edit.setVisible(True) self.text_edit.setPlainText(self.line_edit.text()) elif (not self.multiline_button.isChecked() and not self.line_edit.isVisible()): self.line_edit.setVisible(True) self.text_edit.setVisible(False) self.line_edit.setText(self.text_edit.toPlainText()) def fill_fmt_combo(self, i, current_text=None): """Fill the :attr:`fmt_combo` combobox based on the current group name """ if not self.no_fmtos_update: with self.no_fmtos_update: if current_text is None: current_text = self.fmt_combo.currentText() self.fmt_combo.clear() self.fmt_combo.addItems( list(map(self.get_name, self.fmtos[i]))) ind = self.fmt_combo.findText(current_text) self.fmt_combo.setCurrentIndex(ind if ind >= 0 else 0) # update completer model self.setup_fmt_completion_model() idx = self.fmt_combo.currentIndex() self.show_fmt_info(idx) self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def set_fmto(self, name): self.fmto = name def setup_fmt_completion_model(self): fmtos = list(unique_everseen(map( self.get_name, chain.from_iterable(self.fmtos)))) model = self.fmto_completer.model() model.setRowCount(len(fmtos)) for i, name in enumerate(fmtos): model.setItem(i, QStandardItem(name)) def load_fmt_widget(self, i): """Load the formatoption specific widget This method loads the formatoption specific widget from the :meth:`psyplot.plotter.Formatoption.get_fmt_widget` method and displays it above the :attr:`line_edit` Parameters ---------- i: int The index of the current formatoption""" self.remove_fmt_widget() group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: from psyplot.project import gcp if self.groups[group_ind] == COORDSGROUP: dim = self.fmtos[group_ind][i] self.fmt_widget = self.dim_widget self.dim_widget.set_dim(dim) self.dim_widget.set_single_selection( dim not in gcp()[0].dims) self.dim_widget.setVisible(True) else: fmto = self.fmtos[group_ind][i] self.fmt_widget = fmto.get_fmt_widget(self, gcp()) if self.fmt_widget is not None: self.vbox.insertWidget(2, self.fmt_widget) def reset_fmt_widget(self): idx = self.fmt_combo.currentIndex() self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def remove_fmt_widget(self): if self.fmt_widget is not None: self.fmt_widget.hide() if self.fmt_widget is self.dim_widget: self.fmt_widget.reset_combobox() else: self.vbox.removeWidget(self.fmt_widget) self.fmt_widget.close() del self.fmt_widget def set_current_fmt_value(self, i): """Add the value of the current formatoption to the line text""" group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: if self.groups[group_ind] == COORDSGROUP: from psyplot.project import gcp dim = self.fmtos[group_ind][i] self.set_obj(gcp().arrays[0].psy.idims[dim]) else: fmto = self.fmtos[group_ind][i] self.set_obj(fmto.value) def show_fmt_info(self, i): """Show the documentation of the formatoption in the help explorer """ group_ind = self.group_combo.currentIndex() if (not self.no_fmtos_update and self.groups[group_ind] != COORDSGROUP): fmto = self.fmtos[self.group_combo.currentIndex()][i] fmto.plotter.show_docs( fmto.key, include_links=self.include_links_cb.isChecked()) def run_code(self): """Run the update of the project inside the :attr:`shell`""" if self.line_edit.isVisible(): text = str(self.line_edit.text()) else: text = str(self.text_edit.toPlainText()) if not text or not self.fmtos: return group_ind = self.group_combo.currentIndex() if self.groups[group_ind] == COORDSGROUP: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()] param = 'dims' else: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()].key param = 'fmt' if self.yaml_cb.isChecked(): import psyplot.project as psy psy.gcp().update(**{key: yaml.load(text)}) else: code = "psy.gcp().update(%s={'%s': %s})" % (param, key, text) if ExecutionInfo is not None: info = ExecutionInfo(raw_cell=code, store_history=False, silent=True, shell_futures=False) e = ExecutionResult(info) else: e = ExecutionResult() self.console.run_command_in_shell(code, e) try: e.raise_error() except Exception: # reset the console and clear the error message raise finally: self.console.reset() def get_text(self): """Get the current update text""" if self.line_edit.isVisible(): return self.line_edit.text() else: return self.text_edit.toPlainText() def get_obj(self): """Get the current update text""" if self.line_edit.isVisible(): txt = self.line_edit.text() else: txt = self.text_edit.toPlainText() try: obj = yaml.load(txt) except Exception: self.error_msg.showTraceback("Could not load %s" % txt) else: return obj def insert_obj(self, obj): """Add a string to the formatoption widget""" current = self.get_text() use_yaml = self.yaml_cb.isChecked() use_line_edit = self.line_edit.isVisible() # strings are treated separately such that we consider quotation marks # at the borders if isstring(obj) and current: if use_line_edit: pos = self.line_edit.cursorPosition() else: pos = self.text_edit.textCursor().position() if pos not in [0, len(current)]: s = obj else: if current[0] in ['"', "'"]: current = current[1:-1] self.clear_text() if pos == 0: s = '"' + obj + current + '"' else: s = '"' + current + obj + '"' current = '' elif isstring(obj): # add quotation marks s = '"' + obj + '"' elif not use_yaml: s = repr(obj) else: s = yaml.dump(obj).strip() if s.endswith('\n...'): s = s[:-4] if use_line_edit: self.line_edit.insert(s) else: self.text_edit.insertPlainText(s) def clear_text(self): if self.line_edit.isVisible(): self.line_edit.clear() else: self.text_edit.clear() def set_obj(self, obj): self.clear_text() self.insert_obj(obj) def show_all_fmt_info(self, what): """Show the keys, summaries or docs of the formatoptions Calling this function let's the help browser show the documentation etc. of all docs or only the selected group determined by the state of the :attr:`grouped_cb` and :attr:`all_groups_cb` checkboxes Parameters ---------- what: {'keys', 'summaries', 'docs'} Determines what to show""" if not self.fmtos: return if (self.all_groups_cb.isChecked() or self.group_combo.currentIndex() < 2): fmtos = list(chain.from_iterable( fmto_group for i, fmto_group in enumerate(self.fmtos) if self.groups[i] not in [ALLGROUP, COORDSGROUP])) else: fmtos = self.fmtos[self.group_combo.currentIndex()] plotter = fmtos[0].plotter getattr(plotter, 'show_' + what)( [fmto.key for fmto in fmtos], grouped=self.grouped_cb.isChecked(), include_links=self.include_links_cb.isChecked())
class MainWindow(QMainWindow): #: A signal that is emmitted when the a signal is received through the #: open_files_server open_external = QtCore.pyqtSignal(list) #: The server to open external files open_files_server = None #: Inprocess console console = None #: tree widget displaying the open datasets ds_tree = None #: list of figures from the psyplot backend figures = [] #: tree widget displaying the open figures figures_tree = None #: general formatoptions widget fmt_widget = None #: help explorer help_explorer = None #: the DataFrameEditor widgets, a widget to show and edit data frames dataframeeditors = None #: tab widget displaying the arrays in current main and sub project project_content = None #: The dockwidgets of this instance dockwidgets = [] #: default widths of the dock widgets default_widths = {} _is_open = False #: The keyboard shortcuts of the default layout default_shortcuts = [] #: The current keyboard shortcuts current_shortcuts = [] #: The key for the central widget for the main window in the #: :attr:`plugins` dictionary central_widget_key = 'console' @property def logger(self): """The logger of this instance""" return logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__name__)) @docstrings.get_sectionsf('MainWindow') @docstrings.dedent def __init__(self, show=True): """ Parameters ---------- show: bool If True, the created mainwindow is show """ if sys.stdout is None: sys.stdout = StreamToLogger(self.logger) if sys.stderr is None: sys.stderr = StreamToLogger(self.logger) super(MainWindow, self).__init__() self.setWindowIcon(QIcon(get_icon('logo.png'))) #: list of figures from the psyplot backend self.figures = [] self.error_msg = PyErrorMessage(self) self.setDockOptions( QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks) #: Inprocess console self.console = ConsoleWidget(self) self.project_actions = {} self.config_pages = [] self.open_file_options = OrderedDict([ ('new psyplot plot from dataset', self.open_external_files), ('new psyplot project', partial(self.open_external_files, [])), ]) # --------------------------------------------------------------------- # ----------------------------- Menus --------------------------------- # --------------------------------------------------------------------- # ######################## File menu ################################## # --------------------------- New plot -------------------------------- self.file_menu = QMenu('File', parent=self) self.new_plot_action = QAction('New plot', self) self.new_plot_action.setStatusTip( 'Use an existing dataset (or open a new one) to create one or ' 'more plots') self.register_shortcut(self.new_plot_action, QKeySequence.New) self.new_plot_action.triggered.connect(lambda: self.new_plots(True)) self.file_menu.addAction(self.new_plot_action) # --------------------------- Open project ---------------------------- self.open_project_menu = QMenu('Open project', self) self.file_menu.addMenu(self.open_project_menu) self.open_mp_action = QAction('New main project', self) self.register_shortcut(self.open_mp_action, QKeySequence.Open) self.open_mp_action.setStatusTip('Open a new main project') self.open_mp_action.triggered.connect(self.open_mp) self.open_project_menu.addAction(self.open_mp_action) self.open_sp_action = QAction('Add to current', self) self.register_shortcut( self.open_sp_action, QKeySequence( 'Ctrl+Shift+O', QKeySequence.NativeText)) self.open_sp_action.setStatusTip( 'Load a project as a sub project and add it to the current main ' 'project') self.open_sp_action.triggered.connect(self.open_sp) self.open_project_menu.addAction(self.open_sp_action) # ----------------------- Save project -------------------------------- self.save_project_menu = QMenu('Save', parent=self) self.file_menu.addMenu(self.save_project_menu) self.save_mp_action = QAction('Full psyplot project', self) self.save_mp_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_action, QKeySequence.Save) self.save_mp_action.triggered.connect(self.save_mp) self.save_project_menu.addAction(self.save_mp_action) self.save_sp_action = QAction('Selected psyplot project', self) self.save_sp_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_action.triggered.connect(self.save_sp) self.save_project_menu.addAction(self.save_sp_action) # ------------------------ Save project as ---------------------------- self.save_project_as_menu = QMenu('Save as', parent=self) self.file_menu.addMenu(self.save_project_as_menu) self.save_mp_as_action = QAction('Full psyplot project', self) self.save_mp_as_action.setStatusTip( 'Save the entire project into a pickle file') self.register_shortcut(self.save_mp_as_action, QKeySequence.SaveAs) self.save_mp_as_action.triggered.connect( partial(self.save_mp, new_fname=True)) self.save_project_as_menu.addAction(self.save_mp_as_action) self.save_sp_as_action = QAction('Selected psyplot project', self) self.save_sp_as_action.setStatusTip( 'Save the selected sub project into a pickle file') self.save_sp_as_action.triggered.connect( partial(self.save_sp, new_fname=True)) self.save_project_as_menu.addAction(self.save_sp_as_action) # -------------------------- Pack project ----------------------------- self.pack_project_menu = QMenu('Zip project files', parent=self) self.file_menu.addMenu(self.pack_project_menu) self.pack_mp_action = QAction('Full psyplot project', self) self.pack_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.pack_mp_action.triggered.connect(partial(self.save_mp, pack=True)) self.pack_project_menu.addAction(self.pack_mp_action) self.pack_sp_action = QAction('Selected psyplot project', self) self.pack_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.pack_sp_action.triggered.connect(partial(self.save_sp, pack=True)) self.pack_project_menu.addAction(self.pack_sp_action) # ------------------------ Export figures ----------------------------- self.export_project_menu = QMenu('Export figures', parent=self) self.file_menu.addMenu(self.export_project_menu) self.export_mp_action = QAction('Full psyplot project', self) self.export_mp_action.setStatusTip( 'Pack all the data of the main project into one folder') self.export_mp_action.triggered.connect(self.export_mp) self.register_shortcut( self.export_mp_action, QKeySequence( 'Ctrl+E', QKeySequence.NativeText)) self.export_project_menu.addAction(self.export_mp_action) self.export_sp_action = QAction('Selected psyplot project', self) self.export_sp_action.setStatusTip( 'Pack all the data of the current sub project into one folder') self.register_shortcut( self.export_sp_action, QKeySequence( 'Ctrl+Shift+E', QKeySequence.NativeText)) self.export_sp_action.triggered.connect(self.export_sp) self.export_project_menu.addAction(self.export_sp_action) # ------------------------ Close project ------------------------------ self.file_menu.addSeparator() self.close_project_menu = QMenu('Close project', parent=self) self.file_menu.addMenu(self.close_project_menu) self.close_mp_action = QAction('Full psyplot project', self) self.register_shortcut( self.close_mp_action, QKeySequence( 'Ctrl+Shift+W', QKeySequence.NativeText)) self.close_mp_action.setStatusTip( 'Close the main project and delete all data and plots out of ' 'memory') self.close_mp_action.triggered.connect( lambda: psy.close(psy.gcp(True).num)) self.close_project_menu.addAction(self.close_mp_action) self.close_sp_action = QAction('Selected psyplot project', self) self.close_sp_action.setStatusTip( 'Close the selected arrays project and delete all data and plots ' 'out of memory') self.register_shortcut(self.close_sp_action, QKeySequence.Close) self.close_sp_action.triggered.connect( lambda: psy.gcp().close(True, True)) self.close_project_menu.addAction(self.close_sp_action) # ----------------------------- Quit ---------------------------------- if sys.platform != 'darwin': # mac os makes this anyway self.quit_action = QAction('Quit', self) self.quit_action.triggered.connect(self.close) self.quit_action.triggered.connect( QtCore.QCoreApplication.instance().quit) self.register_shortcut( self.quit_action, QKeySequence.Quit) self.file_menu.addAction(self.quit_action) self.menuBar().addMenu(self.file_menu) # ######################## Console menu ############################### self.console_menu = QMenu('Console', self) self.console_menu.addActions(self.console.actions()) self.menuBar().addMenu(self.console_menu) # ######################## Windows menu ############################### self.windows_menu = QMenu('Windows', self) self.menuBar().addMenu(self.windows_menu) # ############################ Help menu ############################## self.help_menu = QMenu('Help', parent=self) self.menuBar().addMenu(self.help_menu) # -------------------------- Preferences ------------------------------ self.help_action = QAction('Preferences', self) self.help_action.triggered.connect(lambda: self.edit_preferences(True)) self.register_shortcut(self.help_action, QKeySequence.Preferences) self.help_menu.addAction(self.help_action) # ---------------------------- About ---------------------------------- self.about_action = QAction('About', self) self.about_action.triggered.connect(self.about) self.help_menu.addAction(self.about_action) # ---------------------------- Dependencies --------------------------- self.dependencies_action = QAction('Dependencies', self) self.dependencies_action.triggered.connect( lambda: self.show_dependencies(True)) self.help_menu.addAction(self.dependencies_action) self.dockwidgets = [] # --------------------------------------------------------------------- # -------------------------- Dock windows ----------------------------- # --------------------------------------------------------------------- #: tab widget displaying the arrays in current main and sub project #: tree widget displaying the open datasets self.project_content = ProjectContentWidget(parent=self) self.ds_tree = DatasetTree(parent=self) #: tree widget displaying the open figures self.figures_tree = FiguresTree(parent=self) #: help explorer self.help_explorer = help_explorer = HelpExplorer(parent=self) if help_explorer.viewers['HTML help'].sphinx_thread is not None: help_explorer.viewers[ 'HTML help'].sphinx_thread.html_ready.connect( self.focus_on_console) #: the DataFrameEditor widgets self.dataframeeditors = [] #: general formatoptions widget self.fmt_widget = FormatoptionWidget( parent=self, help_explorer=help_explorer, console=self.console) # load plugin widgets self.plugins = plugins = OrderedDict([ ('console', self.console), ('project_content', self.project_content), ('ds_tree', self.ds_tree), ('figures_tree', self.figures_tree), ('help_explorer', self.help_explorer), ('fmt_widget', self.fmt_widget), ]) self.default_plugins = list(plugins) for plugin_name, w_class in six.iteritems(rcParams.load_plugins()): plugins[plugin_name] = w_class(parent=self) self.add_mp_to_menu() psy.Project.oncpchange.connect(self.eventually_add_mp_to_menu) self.windows_menu.addSeparator() self.window_layouts_menu = QMenu('Window layouts', self) self.restore_layout_action = QAction('Restore default layout', self) self.restore_layout_action.triggered.connect(self.setup_default_layout) self.window_layouts_menu.addAction(self.restore_layout_action) self.windows_menu.addMenu(self.window_layouts_menu) self.panes_menu = QMenu('Panes', self) self.windows_menu.addMenu(self.panes_menu) self.dataframe_menu = QMenu('DataFrame editors', self) self.dataframe_menu.addAction( 'New Editor', partial(self.new_data_frame_editor, None, 'DataFrame Editor')) self.dataframe_menu.addSeparator() self.windows_menu.addMenu(self.dataframe_menu) self.central_widgets_menu = menu = QMenu('Central widget', self) self.windows_menu.addMenu(menu) self.central_widgets_actions = group = QActionGroup(self) group.setExclusive(True) # --------------------------------------------------------------------- # -------------------------- connections ------------------------------ # --------------------------------------------------------------------- self.console.help_explorer = help_explorer psyp.default_print_func = partial(help_explorer.show_rst, oname='formatoption_docs') psy.PlotterInterface._print_func = psyp.default_print_func self.setCentralWidget(self.console) # make sure that the plots are shown between the project content and # the help explorer widget self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) # make sure that the formatoption widgets are shown between the # project content and the help explorer widget self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # --------------------------------------------------------------------- # ------------------------------ closure ------------------------------ # --------------------------------------------------------------------- if show: self.help_explorer.show_intro(self.console.intro_msg) # --------------------------------------------------------------------- # ------------------------- open_files_server ------------------------- # --------------------------------------------------------------------- self.callbacks = {'new_plot': self.open_external.emit, 'change_cwd': self._change_cwd, 'run_script': self.console.run_script.emit, 'command': self.console.run_command.emit, } # Server to open external files on a single instance self.open_files_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if rcParams['main.listen_to_port']: self._file_thread = Thread(target=self.start_open_files_server) self._file_thread.setDaemon(True) self._file_thread.start() self.open_external.connect(self._open_external_files) self.config_pages.extend([GuiRcParamsWidget, PsyRcParamsWidget]) # display the statusBar statusbar = self.statusBar() self.figures_label = QLabel() statusbar.addWidget(self.figures_label) self.plugin_label = QLabel() statusbar.addWidget(self.plugin_label) self.default_widths = {} self.setup_default_layout() if show: self.showMaximized() # save the default widths after they have been shown for w in self.plugins.values(): if w.dock is not None: self.default_widths[w] = w.dock.size().width() # hide plugin widgets that should be hidden at startup. Although this # has been executed by :meth:`setup_default_layout`, we have to execute # it again after the call of showMaximized for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() else: w.create_central_widget_action(self).setChecked(True) self._is_open = True def focus_on_console(self, *args, **kwargs): """Put focus on the ipython console""" self.console._control.setFocus() def new_data_frame_editor(self, df=None, title='DataFrame Editor'): """Open a new dataframe editor Parameters ---------- df: pandas.DataFrame The dataframe to display title: str The title of the dock window Returns ------- psyplot_gui.dataframeeditor.DataFrameEditor The newly created editor""" editor = DataFrameEditor() self.dataframeeditors.append(editor) editor.to_dock(self, title, Qt.RightDockWidgetArea, docktype='df') if df is not None: editor.set_df(df) editor.show_plugin() editor.maybe_tabify() editor.raise_() return editor def setup_default_layout(self): """Set up the default window layout""" self.project_content.to_dock(self, 'Plot objects', Qt.LeftDockWidgetArea) self.ds_tree.to_dock(self, 'Datasets', Qt.LeftDockWidgetArea) self.figures_tree.to_dock(self, 'Figures', Qt.LeftDockWidgetArea) self.help_explorer.to_dock(self, 'Help explorer', Qt.RightDockWidgetArea) self.fmt_widget.to_dock(self, 'Formatoptions', Qt.BottomDockWidgetArea) modify_widths = bool(self.default_widths) for w in map(self.plugins.__getitem__, self.default_plugins): if w.dock is not None: w.show_plugin() if modify_widths and with_qt5: self.resizeDocks([w.dock], [self.default_widths[w]], Qt.Horizontal) # hide plugin widgets that should be hidden at startup for name, w in self.plugins.items(): if name != self.central_widget_key: w.to_dock(self) if w.hidden: w.hide_plugin() action2shortcut = defaultdict(list) for s, a in self.default_shortcuts: action2shortcut[a].append(s) for a, s in action2shortcut.items(): self.register_shortcut(a, s) def set_central_widget(self, name): """Set the central widget Parameters ---------- name: str or QWidget The key or the plugin widget in the :attr:`plugins` dictionary""" from PyQt5.QtCore import QTimer self.setUpdatesEnabled(False) current = self.centralWidget() if isinstance(name, six.string_types): new = self.plugins[name] else: new = name name = next(key for key, val in self.plugins.items() if val is new) if new is not current: self._dock_widths = dock_widths = OrderedDict() self._dock_heights = dock_heights = OrderedDict() for key, w in self.plugins.items(): if w.dock is not None and w.is_shown: s = w.dock.size() dock_widths[w] = s.width() if w is not new: dock_heights[w] = s.height() new_pos = self.dockWidgetArea(new.dock) self.removeDockWidget(new.dock) new.dock.close() self.panes_menu.removeAction(new._view_action) self.dataframe_menu.removeAction(new._view_action) new.dock = new._view_action = None self.central_widget_key = name current.to_dock(self) self.setCentralWidget(new) new._set_central_action.setChecked(True) current.show_plugin() current.to_dock(self) new_width = dock_widths.pop(new, None) if current.hidden: current.hide_plugin() else: current_pos = self.dockWidgetArea(current.dock) if current_pos == new_pos and new_width: dock_widths[current] = new_width self._custom_layout_timer = QTimer(self) self._custom_layout_timer.timeout.connect(self._reset_dock_widths) self._custom_layout_timer.setSingleShot(True) self._custom_layout_timer.start(5000) def _reset_dock_widths(self): # resize the plugins if with_qt5: for w, width in self._dock_widths.items(): if w.dock is not None: self.resizeDocks([w.dock], [width], Qt.Horizontal) for w, height in self._dock_heights.items(): if w.dock is not None: self.resizeDocks([w.dock], [height], Qt.Vertical) self.setUpdatesEnabled(True) def _save_project(self, p, new_fname=False, *args, **kwargs): if new_fname or 'project_file' not in p.attrs: fname = QFileDialog.getSaveFileName( self, 'Project destination', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return else: fname = p.attrs['project_file'] try: p.save_project(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback('<b>Could not save the project!</b>') else: p.attrs['project_file'] = fname if p.is_main: self.update_project_action(p.num) def open_mp(self, *args, **kwargs): """Open a new main project""" self._open_project(main=True) def open_sp(self, *args, **kwargs): """Open a subproject and add it to the current main project""" self._open_project(main=False) def _open_project(self, *args, **kwargs): fname = QFileDialog.getOpenFileName( self, 'Project file', os.getcwd(), 'Pickle files (*.pkl);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return p = psy.Project.load_project(fname, *args, **kwargs) p.attrs['project_file'] = fname self.update_project_action(p.num) def save_mp(self, *args, **kwargs): """Save the current main project""" self._save_project(psy.gcp(True), **kwargs) def save_sp(self, *args, **kwargs): """Save the current sub project""" self._save_project(psy.gcp(), **kwargs) def _export_project(self, p, *args, **kwargs): fname = QFileDialog.getSaveFileName( self, 'Picture destination', os.getcwd(), 'PDF files (*.pdf);;' 'Postscript file (*.ps);;' 'PNG image (*.png);;' 'JPG image (*.jpg *.jpeg);;' 'TIFF image (*.tif *.tiff);;' 'GIF image (*.gif);;' 'All files (*)' ) if with_qt5: # the filter is passed as well fname = fname[0] if not fname: return try: p.export(fname, *args, **kwargs) except Exception: self.error_msg.showTraceback( '<b>Could not export the figures!</b>') def export_mp(self, *args, **kwargs): self._export_project(psy.gcp(True), **kwargs) def export_sp(self, *args, **kwargs): self._export_project(psy.gcp(), **kwargs) def new_plots(self, exec_=None): if hasattr(self, 'plot_creator'): try: self.plot_creator.close() except RuntimeError: pass self.plot_creator = PlotCreator( help_explorer=self.help_explorer, parent=self) available_width = QDesktopWidget().availableGeometry().width() / 3. width = self.plot_creator.sizeHint().width() height = self.plot_creator.sizeHint().height() # The plot creator window should cover at least one third of the screen self.plot_creator.resize(max(available_width, width), height) if exec_: self.plot_creator.exec_() def excepthook(self, type, value, traceback): """A method to replace the sys.excepthook""" self.error_msg.excepthook(type, value, traceback) def edit_preferences(self, exec_=None): """Edit Spyder preferences""" if hasattr(self, 'preferences'): try: self.preferences.close() except RuntimeError: pass self.preferences = dlg = Prefences(self) for PrefPageClass in self.config_pages: widget = PrefPageClass(dlg) widget.initialize() dlg.add_page(widget) available_width = 0.667 * QDesktopWidget().availableGeometry().width() width = dlg.sizeHint().width() height = dlg.sizeHint().height() # The preferences window should cover at least one third of the screen dlg.resize(max(available_width, width), height) if exec_: dlg.exec_() def about(self): """About the tool""" versions = { key: d['version'] for key, d in psyplot.get_versions(False).items() } versions.update(psyplot_gui.get_versions()['requirements']) versions.update(psyplot._get_versions()['requirements']) versions['github'] = 'https://github.com/Chilipp/psyplot' versions['author'] = psyplot.__author__ QMessageBox.about( self, "About psyplot", u"""<b>psyplot: Interactive data visualization with python</b> <br>Copyright © 2017- Philipp Sommer <br>Licensed under the terms of the GNU General Public License v2 (GPLv2) <p>Created by %(author)s</p> <p>Most of the icons come from the <a href="https://www.iconfinder.com/"> iconfinder</a>.</p> <p>For bug reports and feature requests, please go to our <a href="%(github)s">Github website</a> or contact the author via mail.</p> <p>This package uses (besides others) the following packages:<br> <ul> <li>psyplot %(psyplot)s</li> <li>Python %(python)s </li> <li>numpy %(numpy)s</li> <li>xarray %(xarray)s</li> <li>pandas %(pandas)s</li> <li>psyplot_gui %(psyplot_gui)s</li> <li>Qt %(qt)s</li> <li>PyQt %(pyqt)s</li> <li>qtconsole %(qtconsole)s</li> </ul></p> <p>For a full list of requirements see the <em>dependencies</em> in the <em>Help</em> menu.</p> <p>This software is provided "as is", without warranty or support of any kind.</p>""" % versions) def show_dependencies(self, exec_=None): """Open a dialog that shows the dependencies""" if hasattr(self, 'dependencies'): try: self.dependencies.close() except RuntimeError: pass self.dependencies = dlg = DependenciesDialog(psyplot.get_versions(), parent=self) dlg.resize(630, 420) if exec_: dlg.exec_() def reset_rcParams(self): rcParams.update_from_defaultParams() psy.rcParams.update_from_defaultParams() def add_mp_to_menu(self): mp = psy.gcp(True) action = QAction(os.path.basename(mp.attrs.get( 'project_file', 'Untitled %s*' % mp.num)), self) action.setStatusTip( 'Make project %s the current project' % mp.num) action.triggered.connect(lambda: psy.scp(psy.project(mp.num))) self.project_actions[mp.num] = action self.windows_menu.addAction(action) def update_project_action(self, num): action = self.project_actions.get(num) p = psy.project(num) if action: action.setText(os.path.basename(p.attrs.get( 'project_file', 'Untitled %s*' % num))) def eventually_add_mp_to_menu(self, p): for num in set(self.project_actions).difference( psy.get_project_nums()): self.windows_menu.removeAction(self.project_actions.pop(num)) if p is None or not p.is_main: return if p.num not in self.project_actions: self.add_mp_to_menu() def start_open_files_server(self): """This method listens to the open_files_port and opens the plot creator for new files This method is inspired and to most parts copied from spyder""" self.open_files_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) port = rcParams['main.open_files_port'] try: self.open_files_server.bind(('127.0.0.1', port)) except Exception: return self.open_files_server.listen(20) while 1: # 1 is faster than True try: req, dummy = self.open_files_server.accept() except socket.error as e: # See Issue 1275 for details on why errno EINTR is # silently ignored here. eintr = errno.EINTR # To avoid a traceback after closing on Windows if e.args[0] == eintr: continue # handle a connection abort on close error enotsock = (errno.WSAENOTSOCK if os.name == 'nt' else errno.ENOTSOCK) if e.args[0] in [errno.ECONNABORTED, enotsock]: return raise args = pickle.loads(req.recv(1024)) callback = args[0] func = self.callbacks[callback] self.logger.debug('Emitting %s callback %s', callback, func) func(args[1:]) req.sendall(b' ') def change_cwd(self, path): """Change the current working directory""" import os os.chdir(path) def _change_cwd(self, args): path = args[0][0] self.change_cwd(path) docstrings.keep_params( 'make_plot.parameters', 'fnames', 'project', 'engine', 'plot_method', 'name', 'dims', 'encoding', 'enable_post', 'seaborn_style', 'concat_dim', 'chname') def open_files(self, fnames): """Open a file and ask the user how""" fnames_s = ', '.join(map(os.path.basename, fnames)) if len(fnames_s) > 30: fnames_s = fnames_s[:27] + '...' item, ok = QInputDialog.getItem( self, 'Open file...', 'Open %s as...' % fnames_s, list(self.open_file_options), current=0, editable=False) if ok: return self.open_file_options[item](fnames) @docstrings.get_sectionsf('MainWindow.open_external_files') @docstrings.dedent def open_external_files(self, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value( xr.open_mfdataset, 'concat_dim'), chname={}): """ Open external files Parameters ---------- %(make_plot.parameters.fnames|project|engine|plot_method|name|dims|encoding|enable_post|seaborn_style|concat_dim|chname)s """ if seaborn_style is not None: import seaborn as sns sns.set_style(seaborn_style) if project is not None: fnames = [s.split(',') for s in fnames] if not isinstance(project, dict): project = psyd.safe_list(project)[0] single_files = (l[0] for l in fnames if len(l) == 1) alternative_paths = defaultdict(lambda: next(single_files, None)) alternative_paths.update(list(l for l in fnames if len(l) == 2)) p = psy.Project.load_project( project, alternative_paths=alternative_paths, engine=engine, main=not psy.gcp(), encoding=encoding, enable_post=enable_post, chname=chname) if isinstance(project, six.string_types): p.attrs.setdefault('project_file', project) return True else: self.new_plots(False) self.plot_creator.open_dataset(fnames, engine=engine, concat_dim=concat_dim) if name == 'all': ds = self.plot_creator.get_ds() name = sorted(set(ds.variables) - set(ds.coords)) self.plot_creator.insert_array( list(filter(None, psy.safe_list(name)))) if dims is not None: ds = self.plot_creator.get_ds() dims = {key: ', '.join( map(str, val)) for key, val in six.iteritems( dims)} for i, vname in enumerate( self.plot_creator.array_table.vnames): self.plot_creator.array_table.selectRow(i) self.plot_creator.array_table.update_selected( ) self.plot_creator.array_table.selectAll() var = ds[vname[0]] self.plot_creator.array_table.update_selected( dims=var.psy.decoder.correct_dims(var, dims.copy())) if plot_method: self.plot_creator.pm_combo.setCurrentIndex( self.plot_creator.pm_combo.findText(plot_method)) self.plot_creator.exec_() return True def _open_external_files(self, args): self.open_external_files(*args) @classmethod @docstrings.get_sectionsf('MainWindow.run') @docstrings.dedent def run(cls, fnames=[], project=None, engine=None, plot_method=None, name=None, dims=None, encoding=None, enable_post=False, seaborn_style=None, concat_dim=get_default_value(xr.open_mfdataset, 'concat_dim'), chname={}, show=True): """ Create a mainwindow and open the given files or project This class method creates a new mainwindow instance and sets the global :attr:`mainwindow` variable. Parameters ---------- %(MainWindow.open_external_files.parameters)s %(MainWindow.parameters)s Notes ----- - There can be only one mainwindow at the time - This method does not create a QApplication instance! See :meth:`run_app` See Also -------- run_app """ mainwindow = cls(show=show) _set_mainwindow(mainwindow) if fnames or project: mainwindow.open_external_files( fnames, project, engine, plot_method, name, dims, encoding, enable_post, seaborn_style, concat_dim, chname) psyplot.with_gui = True return mainwindow def register_shortcut(self, action, shortcut, context=Qt.ApplicationShortcut): """Register an action for a shortcut""" shortcuts = psy.safe_list(shortcut) for j, shortcut in enumerate(shortcuts): found = False for i, (s, a) in enumerate(self.current_shortcuts): if s == shortcut: new_shortcuts = [ sc for sc in self.current_shortcuts[i][1].shortcuts() if sc != s] a.setShortcut(QKeySequence()) if new_shortcuts: a.setShortcuts(new_shortcuts) self.current_shortcuts[i][1] = action found = True break if not found: self.default_shortcuts.append([shortcut, action]) self.current_shortcuts.append([shortcut, action]) action.setShortcuts(shortcuts) action.setShortcutContext(context) @classmethod @docstrings.dedent def run_app(cls, *args, **kwargs): """ Create a QApplication, open the given files or project and enter the mainloop Parameters ---------- %(MainWindow.run.parameters)s See Also -------- run """ app = QApplication(sys.argv) cls.run(*args, **kwargs) sys.exit(app.exec_()) def closeEvent(self, event): """closeEvent reimplementation""" if not self._is_open or (self._is_open and self.close()): self._is_open = False event.accept() def close(self): _set_mainwindow(None) if self.open_files_server is not None: self.open_files_server.close() del self.open_files_server for widget in self.plugins.values(): widget.close() self.plugins.clear() return super(MainWindow, self).close()
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 __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)
class FormatoptionWidget(QWidget, DockMixin): """ Widget to update the formatoptions of the current project This widget, mainly made out of a combobox for the formatoption group, a combobox for the formatoption, and a text editor, is designed for updating the selected formatoptions for the current subproject. The widget is connected to the :attr:`psyplot.project.Project.oncpchange` signal and refills the comboboxes if the current subproject changes. The text editor either accepts python code that will be executed by the given `console`, or yaml code. """ no_fmtos_update = _temp_bool_prop('no_fmtos_update', """update the fmto combo box or not""") #: The combobox for the formatoption groups group_combo = None #: The combobox for the formatoptions fmt_combo = None #: The help_explorer to display the documentation of the formatoptions help_explorer = None #: The formatoption specific widget that is loaded from the formatoption fmt_widget = None #: A line edit for updating the formatoptions line_edit = None #: A multiline text editor for updating the formatoptions text_edit = None #: A button to switch between :attr:`line_edit` and :attr:`text_edit` multiline_button = None @property def shell(self): """The shell to execute the update of the formatoptions in the current project""" return self.console.kernel_manager.kernel.shell def __init__(self, *args, **kwargs): """ Parameters ---------- help_explorer: psyplot_gui.help_explorer.HelpExplorer The help explorer to show the documentation of one formatoption console: psyplot_gui.console.ConsoleWidget The console that can be used to update the current subproject via:: psy.gcp().update(**kwargs) where ``**kwargs`` is defined through the selected formatoption in the :attr:`fmt_combo` combobox and the value in the :attr:`line_edit` editor ``*args, **kwargs`` Any other keyword for the QWidget class """ help_explorer = kwargs.pop('help_explorer', None) console = kwargs.pop('console', None) super(FormatoptionWidget, self).__init__(*args, **kwargs) self.help_explorer = help_explorer self.console = console self.error_msg = PyErrorMessage(self) # --------------------------------------------------------------------- # -------------------------- Child widgets ---------------------------- # --------------------------------------------------------------------- self.group_combo = QComboBox(parent=self) self.fmt_combo = QComboBox(parent=self) self.line_edit = QLineEdit(parent=self) self.text_edit = QTextEdit(parent=self) self.run_button = QToolButton(parent=self) # completer for the fmto widget self.fmt_combo.setEditable(True) self.fmt_combo.setInsertPolicy(QComboBox.NoInsert) self.fmto_completer = completer = QCompleter( ['time', 'lat', 'lon', 'lev']) completer.setCompletionMode(QCompleter.PopupCompletion) completer.activated[str].connect(self.set_fmto) if with_qt5: completer.setFilterMode(Qt.MatchContains) completer.setModel(QStandardItemModel()) self.fmt_combo.setCompleter(completer) self.dim_widget = DimensionsWidget(parent=self) self.dim_widget.setVisible(False) self.multiline_button = QPushButton('Multiline', parent=self) self.multiline_button.setCheckable(True) self.yaml_cb = QCheckBox('Yaml syntax') self.yaml_cb.setChecked(True) self.keys_button = QPushButton('Keys', parent=self) self.summaries_button = QPushButton('Summaries', parent=self) self.docs_button = QPushButton('Docs', parent=self) self.grouped_cb = QCheckBox('grouped', parent=self) self.all_groups_cb = QCheckBox('all groups', parent=self) self.include_links_cb = QCheckBox('include links', parent=self) self.text_edit.setVisible(False) # --------------------------------------------------------------------- # -------------------------- Descriptions ----------------------------- # --------------------------------------------------------------------- self.group_combo.setToolTip('Select the formatoption group') self.fmt_combo.setToolTip('Select the formatoption to update') self.line_edit.setToolTip( 'Insert the value which what you want to update the selected ' 'formatoption and hit right button. The code is executed in the ' 'main console.') self.yaml_cb.setToolTip( "Use the yaml syntax for the values inserted in the above cell. " "Otherwise the content there is evaluated as a python expression " "in the terminal") self.text_edit.setToolTip(self.line_edit.toolTip()) self.run_button.setIcon(QIcon(get_icon('run_arrow.png'))) self.run_button.setToolTip('Update the selected formatoption') self.multiline_button.setToolTip( 'Allow linebreaks in the text editor line above.') self.keys_button.setToolTip( 'Show the formatoption keys in this group (or in all ' 'groups) in the help explorer') self.summaries_button.setToolTip( 'Show the formatoption summaries in this group (or in all ' 'groups) in the help explorer') self.docs_button.setToolTip( 'Show the formatoption documentations in this group (or in all ' 'groups) in the help explorer') self.grouped_cb.setToolTip( 'Group the formatoptions before displaying them in the help ' 'explorer') self.all_groups_cb.setToolTip('Use all groups when displaying the ' 'keys, docs or summaries') self.include_links_cb.setToolTip( 'Include links to remote documentations when showing the ' 'keys, docs and summaries in the help explorer (requires ' 'intersphinx)') # --------------------------------------------------------------------- # -------------------------- Connections ------------------------------ # --------------------------------------------------------------------- self.group_combo.currentIndexChanged[int].connect(self.fill_fmt_combo) self.fmt_combo.currentIndexChanged[int].connect(self.show_fmt_info) self.fmt_combo.currentIndexChanged[int].connect(self.load_fmt_widget) self.fmt_combo.currentIndexChanged[int].connect( self.set_current_fmt_value) self.run_button.clicked.connect(self.run_code) self.line_edit.returnPressed.connect(self.run_button.click) self.multiline_button.clicked.connect(self.toggle_line_edit) self.keys_button.clicked.connect( partial(self.show_all_fmt_info, 'keys')) self.summaries_button.clicked.connect( partial(self.show_all_fmt_info, 'summaries')) self.docs_button.clicked.connect( partial(self.show_all_fmt_info, 'docs')) # --------------------------------------------------------------------- # ------------------------------ Layouts ------------------------------ # --------------------------------------------------------------------- self.combos = QHBoxLayout() self.combos.addWidget(self.group_combo) self.combos.addWidget(self.fmt_combo) self.execs = QHBoxLayout() self.execs.addWidget(self.line_edit) self.execs.addWidget(self.text_edit) self.execs.addWidget(self.run_button) self.info_box = QHBoxLayout() self.info_box.addWidget(self.multiline_button) self.info_box.addWidget(self.yaml_cb) self.info_box.addStretch(0) for w in [ self.keys_button, self.summaries_button, self.docs_button, self.all_groups_cb, self.grouped_cb, self.include_links_cb ]: self.info_box.addWidget(w) self.vbox = QVBoxLayout() self.vbox.addLayout(self.combos) self.vbox.addWidget(self.dim_widget) self.vbox.addLayout(self.execs) self.vbox.addLayout(self.info_box) self.vbox.setSpacing(0) self.setLayout(self.vbox) # fill with content self.fill_combos_from_project(psy.gcp()) psy.Project.oncpchange.connect(self.fill_combos_from_project) rcParams.connect('fmt.sort_by_key', self.refill_from_rc) def refill_from_rc(self, sort_by_key): from psyplot.project import gcp self.fill_combos_from_project(gcp()) def fill_combos_from_project(self, project): """Fill :attr:`group_combo` and :attr:`fmt_combo` from a project Parameters ---------- project: psyplot.project.Project The project to use""" if rcParams['fmt.sort_by_key']: def sorter(fmto): return fmto.key else: sorter = self.get_name current_text = self.group_combo.currentText() with self.no_fmtos_update: self.group_combo.clear() if project is None or project.is_main or not len(project): self.fmt_combo.clear() self.groups = [] self.fmtos = [] self.line_edit.setEnabled(False) return self.line_edit.setEnabled(True) # get dimensions it_vars = chain.from_iterable(arr.psy.iter_base_variables for arr in project.arrays) dims = next(it_vars).dims sdims = set(dims) for var in it_vars: sdims.intersection_update(var.dims) coords = [d for d in dims if d in sdims] coords_name = [COORDSGROUP] if coords else [] coords_verbose = ['Dimensions'] if coords else [] coords = [coords] if coords else [] if len(project.plotters): # get formatoptions and group them alphabetically grouped_fmts = defaultdict(list) for fmto in project._fmtos: grouped_fmts[fmto.group].append(fmto) for val in six.itervalues(grouped_fmts): val.sort(key=sorter) grouped_fmts = OrderedDict( sorted(six.iteritems(grouped_fmts), key=lambda t: psyp.groups.get(t[0], t[0]))) fmt_groups = list(grouped_fmts.keys()) # save original names self.groups = coords_name + [ALLGROUP] + fmt_groups # save verbose group names (which are used in the combo box) self.groupnames = ( coords_verbose + ['All formatoptions'] + list(map(lambda s: psyp.groups.get(s, s), fmt_groups))) # save formatoptions fmtos = list(grouped_fmts.values()) self.fmtos = coords + [sorted(chain(*fmtos), key=sorter) ] + fmtos else: self.groups = coords_name self.groupnames = coords_verbose self.fmtos = coords self.group_combo.addItems(self.groupnames) ind = self.group_combo.findText(current_text) self.group_combo.setCurrentIndex(ind if ind >= 0 else 0) self.fill_fmt_combo(self.group_combo.currentIndex()) def get_name(self, fmto): """Get the name of a :class:`psyplot.plotter.Formatoption` instance""" if isinstance(fmto, six.string_types): return fmto return '%s (%s)' % (fmto.name, fmto.key) if fmto.name else fmto.key @property def fmto(self): return self.fmtos[self.group_combo.currentIndex()][ self.fmt_combo.currentIndex()] @fmto.setter def fmto(self, value): name = self.get_name(value) for i, fmtos in enumerate(self.fmtos): if i == 1: # all formatoptions continue if name in map(self.get_name, fmtos): with self.no_fmtos_update: self.group_combo.setCurrentIndex(i) self.fill_fmt_combo(i, name) return def toggle_line_edit(self): """Switch between the :attr:`line_edit` and :attr:`text_edit` This method is called when the :attr:`multiline_button` is clicked and switches between the single line :attr:``line_edit` and the multiline :attr:`text_edit` """ # switch to multiline text edit if (self.multiline_button.isChecked() and not self.text_edit.isVisible()): self.line_edit.setVisible(False) self.text_edit.setVisible(True) self.text_edit.setPlainText(self.line_edit.text()) elif (not self.multiline_button.isChecked() and not self.line_edit.isVisible()): self.line_edit.setVisible(True) self.text_edit.setVisible(False) self.line_edit.setText(self.text_edit.toPlainText()) def fill_fmt_combo(self, i, current_text=None): """Fill the :attr:`fmt_combo` combobox based on the current group name """ if not self.no_fmtos_update: with self.no_fmtos_update: if current_text is None: current_text = self.fmt_combo.currentText() self.fmt_combo.clear() self.fmt_combo.addItems(list(map(self.get_name, self.fmtos[i]))) ind = self.fmt_combo.findText(current_text) self.fmt_combo.setCurrentIndex(ind if ind >= 0 else 0) # update completer model self.setup_fmt_completion_model() idx = self.fmt_combo.currentIndex() self.show_fmt_info(idx) self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def set_fmto(self, name): self.fmto = name def setup_fmt_completion_model(self): fmtos = list( unique_everseen(map(self.get_name, chain.from_iterable(self.fmtos)))) model = self.fmto_completer.model() model.setRowCount(len(fmtos)) for i, name in enumerate(fmtos): model.setItem(i, QStandardItem(name)) def load_fmt_widget(self, i): """Load the formatoption specific widget This method loads the formatoption specific widget from the :meth:`psyplot.plotter.Formatoption.get_fmt_widget` method and displays it above the :attr:`line_edit` Parameters ---------- i: int The index of the current formatoption""" self.remove_fmt_widget() group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: from psyplot.project import gcp if self.groups[group_ind] == COORDSGROUP: dim = self.fmtos[group_ind][i] self.fmt_widget = self.dim_widget self.dim_widget.set_dim(dim) self.dim_widget.set_single_selection(dim not in gcp()[0].dims) self.dim_widget.setVisible(True) else: fmto = self.fmtos[group_ind][i] self.fmt_widget = fmto.get_fmt_widget(self, gcp()) if self.fmt_widget is not None: self.vbox.insertWidget(2, self.fmt_widget) def reset_fmt_widget(self): idx = self.fmt_combo.currentIndex() self.load_fmt_widget(idx) self.set_current_fmt_value(idx) def remove_fmt_widget(self): if self.fmt_widget is not None: self.fmt_widget.hide() if self.fmt_widget is self.dim_widget: self.fmt_widget.reset_combobox() else: self.vbox.removeWidget(self.fmt_widget) self.fmt_widget.close() del self.fmt_widget def set_current_fmt_value(self, i): """Add the value of the current formatoption to the line text""" group_ind = self.group_combo.currentIndex() if not self.no_fmtos_update: if self.groups[group_ind] == COORDSGROUP: from psyplot.project import gcp dim = self.fmtos[group_ind][i] self.set_obj(gcp().arrays[0].psy.idims[dim]) else: fmto = self.fmtos[group_ind][i] self.set_obj(fmto.value) def show_fmt_info(self, i): """Show the documentation of the formatoption in the help explorer """ group_ind = self.group_combo.currentIndex() if (not self.no_fmtos_update and self.groups[group_ind] != COORDSGROUP): fmto = self.fmtos[self.group_combo.currentIndex()][i] fmto.plotter.show_docs( fmto.key, include_links=self.include_links_cb.isChecked()) def run_code(self): """Run the update of the project inside the :attr:`shell`""" if self.line_edit.isVisible(): text = str(self.line_edit.text()) else: text = str(self.text_edit.toPlainText()) if not text or not self.fmtos: return group_ind = self.group_combo.currentIndex() if self.groups[group_ind] == COORDSGROUP: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()] param = 'dims' else: key = self.fmtos[group_ind][self.fmt_combo.currentIndex()].key param = 'fmt' if self.yaml_cb.isChecked(): import psyplot.project as psy psy.gcp().update(**{key: yaml.load(text, Loader=yaml.Loader)}) else: code = "psy.gcp().update(%s={'%s': %s})" % (param, key, text) if ExecutionInfo is not None: info = ExecutionInfo(raw_cell=code, store_history=False, silent=True, shell_futures=False) e = ExecutionResult(info) else: e = ExecutionResult() self.console.run_command_in_shell(code, e) try: e.raise_error() except Exception: # reset the console and clear the error message raise finally: self.console.reset() def get_text(self): """Get the current update text""" if self.line_edit.isVisible(): return self.line_edit.text() else: return self.text_edit.toPlainText() def get_obj(self): """Get the current update text""" if self.line_edit.isVisible(): txt = self.line_edit.text() else: txt = self.text_edit.toPlainText() try: obj = yaml.load(txt, Loader=yaml.Loader) except Exception: self.error_msg.showTraceback("Could not load %s" % txt) else: return obj def insert_obj(self, obj): """Add a string to the formatoption widget""" current = self.get_text() use_yaml = self.yaml_cb.isChecked() use_line_edit = self.line_edit.isVisible() # strings are treated separately such that we consider quotation marks # at the borders if isstring(obj) and current: if use_line_edit: pos = self.line_edit.cursorPosition() else: pos = self.text_edit.textCursor().position() if pos not in [0, len(current)]: s = obj else: if current[0] in ['"', "'"]: current = current[1:-1] self.clear_text() if pos == 0: s = '"' + obj + current + '"' else: s = '"' + current + obj + '"' current = '' elif isstring(obj): # add quotation marks s = '"' + obj + '"' elif not use_yaml: s = repr(obj) else: s = yaml.dump(obj, default_flow_style=True).strip() if s.endswith('\n...'): s = s[:-4] if use_line_edit: self.line_edit.insert(s) else: self.text_edit.insertPlainText(s) def clear_text(self): if self.line_edit.isVisible(): self.line_edit.clear() else: self.text_edit.clear() def set_obj(self, obj): self.clear_text() self.insert_obj(obj) def show_all_fmt_info(self, what): """Show the keys, summaries or docs of the formatoptions Calling this function let's the help browser show the documentation etc. of all docs or only the selected group determined by the state of the :attr:`grouped_cb` and :attr:`all_groups_cb` checkboxes Parameters ---------- what: {'keys', 'summaries', 'docs'} Determines what to show""" if not self.fmtos: return if (self.all_groups_cb.isChecked() or self.group_combo.currentIndex() < 2): fmtos = list( chain.from_iterable( fmto_group for i, fmto_group in enumerate(self.fmtos) if self.groups[i] not in [ALLGROUP, COORDSGROUP])) else: fmtos = self.fmtos[self.group_combo.currentIndex()] plotter = fmtos[0].plotter getattr(plotter, 'show_' + what)([fmto.key for fmto in fmtos], grouped=self.grouped_cb.isChecked(), include_links=self.include_links_cb.isChecked())