class FigureBrowser(QWidget): """ Widget to browse the figures that were sent by the kernel to the IPython console to be plotted inline. """ sig_option_changed = Signal(str, object) sig_collapse = Signal() def __init__(self, parent=None, options_button=None, plugin_actions=[], background_color=None): super(FigureBrowser, self).__init__(parent) self.shellwidget = None self.is_visible = True self.figviewer = None self.setup_in_progress = False self.background_color = background_color # Options : self.mute_inline_plotting = None self.show_plot_outline = None self.auto_fit_plotting = None # Option actions : self.mute_inline_action = None self.show_plot_outline_action = None self.auto_fit_action = None self.options_button = options_button self.plugin_actions = plugin_actions self.shortcuts = self.create_shortcuts() def setup(self, mute_inline_plotting=None, show_plot_outline=None, auto_fit_plotting=None): """Setup the figure browser with provided settings.""" assert self.shellwidget is not None self.mute_inline_plotting = mute_inline_plotting self.show_plot_outline = show_plot_outline self.auto_fit_plotting = auto_fit_plotting if self.figviewer is not None: self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action.setChecked(show_plot_outline) self.auto_fit_action.setChecked(auto_fit_plotting) return self.figviewer = FigureViewer(background_color=self.background_color) self.figviewer.setStyleSheet("FigureViewer{" "border: 1px solid lightgrey;" "border-top-width: 0px;" "border-bottom-width: 0px;" "border-left-width: 0px;" "}") self.thumbnails_sb = ThumbnailScrollBar( self.figviewer, background_color=self.background_color) # Option actions : self.setup_option_actions(mute_inline_plotting, show_plot_outline, auto_fit_plotting) # Create the layout : main_widget = QSplitter() main_widget.addWidget(self.figviewer) main_widget.addWidget(self.thumbnails_sb) main_widget.setFrameStyle(QScrollArea().frameStyle()) self.tools_layout = QHBoxLayout() toolbar = self.setup_toolbar() for widget in toolbar: self.tools_layout.addWidget(widget) self.tools_layout.addStretch() self.setup_options_button() layout = create_plugin_layout(self.tools_layout, main_widget) self.setLayout(layout) def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton(self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton(self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton(self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton(self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut( 'plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton(self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut( 'plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(zoom_out_btn) layout.addWidget(zoom_in_btn) layout.addWidget(self.zoom_disp) return [ savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan ] def setup_option_actions(self, mute_inline_plotting, show_plot_outline, auto_fit_plotting): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.mute_inline_action = create_action( self, _("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda state: self.option_changed('mute_inline_plotting', state)) self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action = create_action( self, _("Show plot outline"), tip=_("Show the plot outline."), toggled=self.show_fig_outline_in_viewer) self.show_plot_outline_action.setChecked(show_plot_outline) self.auto_fit_action = create_action( self, _("Fit plots to window"), tip=_("Automatically fit plots to Plot pane size."), toggled=self.change_auto_fit_plotting) self.auto_fit_action.setChecked(auto_fit_plotting) self.actions = [ self.mute_inline_action, self.show_plot_outline_action, self.auto_fit_action ] self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: # When the FigureBowser widget is instatiated outside of the # plugin (for testing purpose for instance), we need to create # the options_button and set its menu. self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions self.options_menu = QMenu(self) add_actions(self.options_menu, actions) self.options_button.setMenu(self.options_menu) if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None: self.tools_layout.insertWidget(self.tools_layout.count() - 1, self.options_button) else: self.tools_layout.addWidget(self.options_button) def create_shortcuts(self): """Create shortcuts for this widget.""" # Configurable copyfig = config_shortcut(self.copy_figure, context='plots', name='copy', parent=self) prevfig = config_shortcut(self.go_previous_thumbnail, context='plots', name='previous figure', parent=self) nextfig = config_shortcut(self.go_next_thumbnail, context='plots', name='next figure', parent=self) return [copyfig, prevfig, nextfig] def get_shortcut_data(self): """ Return shortcut data, a list of tuples (shortcut, text, default). shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def option_changed(self, option, value): """Handle when the value of an option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() if self.setup_in_progress is False: self.sig_option_changed.emit(option, value) def show_fig_outline_in_viewer(self, state): """Draw a frame around the figure viewer if state is True.""" if state is True: self.figviewer.figcanvas.setStyleSheet( "FigureCanvas{border: 1px solid lightgrey;}") else: self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}") self.option_changed('show_plot_outline', state) def change_auto_fit_plotting(self, state): """Change the auto_fit_plotting option and scale images.""" self.option_changed('auto_fit_plotting', state) self.figviewer.auto_fit_plotting = state self.figviewer.scale_image() def set_shellwidget(self, shellwidget): """Bind the shellwidget instance to the figure browser""" self.shellwidget = shellwidget shellwidget.set_figurebrowser(self) shellwidget.sig_new_inline_figure.connect(self._handle_new_figure) def get_actions(self): """Get the actions of the widget.""" return self.actions def _handle_new_figure(self, fig, fmt): """ Handle when a new figure is sent to the IPython console by the kernel. """ self.thumbnails_sb.add_thumbnail(fig, fmt) # ---- Toolbar Handlers def zoom_in(self): """Zoom the figure in by a single step in the figure viewer.""" self.figviewer.zoom_in() def zoom_out(self): """Zoom the figure out by a single step in the figure viewer.""" self.figviewer.zoom_out() def go_previous_thumbnail(self): """ Select the thumbnail previous to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_previous_thumbnail() def go_next_thumbnail(self): """ Select the thumbnail next to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_next_thumbnail() def save_figure(self): """Save the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.save_current_figure_as() def save_all_figures(self): """Save all the figures in a selected directory.""" return self.thumbnails_sb.save_all_figures_as() def close_figure(self): """Close the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.remove_current_thumbnail() def close_all_figures(self): """Close all the figures in the thumbnail scrollbar.""" self.thumbnails_sb.remove_all_thumbnails() def copy_figure(self): """Copy figure from figviewer to clipboard.""" if self.figviewer and self.figviewer.figcanvas.fig: self.figviewer.figcanvas.copy_figure()
class FigureBrowser(QWidget): """ Widget to browse the figures that were sent by the kernel to the IPython console to be plotted inline. """ sig_option_changed = Signal(str, object) sig_collapse = Signal() def __init__(self, parent=None, options_button=None, plugin_actions=[], background_color=None): super(FigureBrowser, self).__init__(parent) self.shellwidget = None self.is_visible = True self.figviewer = None self.setup_in_progress = False self.background_color = background_color # Options : self.mute_inline_plotting = None self.show_plot_outline = None # Option actions : self.mute_inline_action = None self.show_plot_outline_action = None self.options_button = options_button self.plugin_actions = plugin_actions self.shortcuts = self.create_shortcuts() def setup(self, mute_inline_plotting=None, show_plot_outline=None): """Setup the figure browser with provided settings.""" assert self.shellwidget is not None self.mute_inline_plotting = mute_inline_plotting self.show_plot_outline = show_plot_outline if self.figviewer is not None: self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action.setChecked(show_plot_outline) return self.figviewer = FigureViewer(background_color=self.background_color) self.figviewer.setStyleSheet("FigureViewer{" "border: 1px solid lightgrey;" "border-top-width: 0px;" "border-bottom-width: 0px;" "border-left-width: 0px;" "}") self.thumbnails_sb = ThumbnailScrollBar( self.figviewer, background_color=self.background_color) # Option actions : self.setup_option_actions(mute_inline_plotting, show_plot_outline) # Create the layout : main_widget = QSplitter() main_widget.addWidget(self.figviewer) main_widget.addWidget(self.thumbnails_sb) main_widget.setFrameStyle(QScrollArea().frameStyle()) self.tools_layout = QHBoxLayout() toolbar = self.setup_toolbar() for widget in toolbar: self.tools_layout.addWidget(widget) self.tools_layout.addStretch() self.setup_options_button() layout = create_plugin_layout(self.tools_layout, main_widget) self.setLayout(layout) def setup_toolbar(self): """Setup the toolbar""" savefig_btn = create_toolbutton( self, icon=ima.icon('filesave'), tip=_("Save Image As..."), triggered=self.save_figure) saveall_btn = create_toolbutton( self, icon=ima.icon('save_all'), tip=_("Save All Images..."), triggered=self.save_all_figures) copyfig_btn = create_toolbutton( self, icon=ima.icon('editcopy'), tip=_("Copy plot to clipboard as image (%s)" % get_shortcut('plots', 'copy')), triggered=self.copy_figure) closefig_btn = create_toolbutton( self, icon=ima.icon('editclear'), tip=_("Remove image"), triggered=self.close_figure) closeall_btn = create_toolbutton( self, icon=ima.icon('filecloseall'), tip=_("Remove all images from the explorer"), triggered=self.close_all_figures) vsep1 = QFrame() vsep1.setFrameStyle(53) goback_btn = create_toolbutton( self, icon=ima.icon('ArrowBack'), tip=_("Previous Figure ({})".format( get_shortcut('plots', 'previous figure'))), triggered=self.go_previous_thumbnail) gonext_btn = create_toolbutton( self, icon=ima.icon('ArrowForward'), tip=_("Next Figure ({})".format( get_shortcut('plots', 'next figure'))), triggered=self.go_next_thumbnail) vsep2 = QFrame() vsep2.setFrameStyle(53) zoom_out_btn = create_toolbutton( self, icon=ima.icon('zoom_out'), tip=_("Zoom out (Ctrl + mouse-wheel-down)"), triggered=self.zoom_out) zoom_in_btn = create_toolbutton( self, icon=ima.icon('zoom_in'), tip=_("Zoom in (Ctrl + mouse-wheel-up)"), triggered=self.zoom_in) self.zoom_disp = QSpinBox() self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) self.figviewer.sig_zoom_changed.connect(self.zoom_disp.setValue) zoom_pan = QWidget() layout = QHBoxLayout(zoom_pan) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(zoom_out_btn) layout.addWidget(zoom_in_btn) layout.addWidget(self.zoom_disp) return [savefig_btn, saveall_btn, copyfig_btn, closefig_btn, closeall_btn, vsep1, goback_btn, gonext_btn, vsep2, zoom_pan] def setup_option_actions(self, mute_inline_plotting, show_plot_outline): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.mute_inline_action = create_action( self, _("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda state: self.option_changed('mute_inline_plotting', state) ) self.mute_inline_action.setChecked(mute_inline_plotting) self.show_plot_outline_action = create_action( self, _("Show plot outline"), tip=_("Show the plot outline."), toggled=self.show_fig_outline_in_viewer ) self.show_plot_outline_action.setChecked(show_plot_outline) self.actions = [self.mute_inline_action, self.show_plot_outline_action] self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: # When the FigureBowser widget is instatiated outside of the # plugin (for testing purpose for instance), we need to create # the options_button and set its menu. self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) actions = self.actions + [MENU_SEPARATOR] + self.plugin_actions self.options_menu = QMenu(self) add_actions(self.options_menu, actions) self.options_button.setMenu(self.options_menu) if self.tools_layout.itemAt(self.tools_layout.count() - 1) is None: self.tools_layout.insertWidget( self.tools_layout.count() - 1, self.options_button) else: self.tools_layout.addWidget(self.options_button) def create_shortcuts(self): """Create shortcuts for this widget.""" # Configurable copyfig = config_shortcut(self.copy_figure, context='plots', name='copy', parent=self) prevfig = config_shortcut(self.go_previous_thumbnail, context='plots', name='previous figure', parent=self) nextfig = config_shortcut(self.go_next_thumbnail, context='plots', name='next figure', parent=self) return [copyfig, prevfig, nextfig] def get_shortcut_data(self): """ Return shortcut data, a list of tuples (shortcut, text, default). shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def option_changed(self, option, value): """Handle when the value of an option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() if self.setup_in_progress is False: self.sig_option_changed.emit(option, value) def show_fig_outline_in_viewer(self, state): """Draw a frame around the figure viewer if state is True.""" if state is True: self.figviewer.figcanvas.setStyleSheet( "FigureCanvas{border: 1px solid lightgrey;}") else: self.figviewer.figcanvas.setStyleSheet("FigureCanvas{}") self.option_changed('show_plot_outline', state) def set_shellwidget(self, shellwidget): """Bind the shellwidget instance to the figure browser""" self.shellwidget = shellwidget shellwidget.set_figurebrowser(self) shellwidget.sig_new_inline_figure.connect(self._handle_new_figure) def get_actions(self): """Get the actions of the widget.""" return self.actions def _handle_new_figure(self, fig, fmt): """ Handle when a new figure is sent to the IPython console by the kernel. """ self.thumbnails_sb.add_thumbnail(fig, fmt) # ---- Toolbar Handlers def zoom_in(self): """Zoom the figure in by a single step in the figure viewer.""" self.figviewer.zoom_in() def zoom_out(self): """Zoom the figure out by a single step in the figure viewer.""" self.figviewer.zoom_out() def go_previous_thumbnail(self): """ Select the thumbnail previous to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_previous_thumbnail() def go_next_thumbnail(self): """ Select the thumbnail next to the currently selected one in the thumbnail scrollbar. """ self.thumbnails_sb.go_next_thumbnail() def save_figure(self): """Save the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.save_current_figure_as() def save_all_figures(self): """Save all the figures in a selected directory.""" return self.thumbnails_sb.save_all_figures_as() def close_figure(self): """Close the currently selected figure in the thumbnail scrollbar.""" self.thumbnails_sb.remove_current_thumbnail() def close_all_figures(self): """Close all the figures in the thumbnail scrollbar.""" self.thumbnails_sb.remove_all_thumbnails() def copy_figure(self): """Copy figure from figviewer to clipboard.""" if self.figviewer and self.figviewer.figcanvas.fig: self.figviewer.figcanvas.copy_figure()
class RotateLabelDlg(QDialog): rotateSelectedPolygon = QtCore.Signal(bool, int) def __init__(self, parent=None, rotation_connection=None, clockwise=True, angle=0): super(RotateLabelDlg, self).__init__(parent) self.setWindowTitle(self.tr("Rotate the selected polygon")) if rotation_connection is not None: self.rotateSelectedPolygon.connect(rotation_connection) self.clockwise = clockwise self.angle = angle self.setLayout(self.createLayout()) self.setFixedSize(400, 80) def resetRotateDlg(self, parent_topright): self.clockwise = True self.angle = 0 self.radio_clockwise.setChecked(self.clockwise) self.angle_editor.setValue(self.angle) self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) size = self.size() self.move(parent_topright.x() - size.width(), parent_topright.y()) def createLayout(self): hbox = QHBoxLayout() self.radio_clockwise = QRadioButton(self.tr("clockwise"), self) self.radio_clockwise.clockwise = True self.radio_clockwise.setChecked(self.clockwise) self.radio_clockwise.toggled.connect(self.rotationDirectionChanged) self.radio_anticlockwise = QRadioButton(self.tr("anticlockwise"), self) self.radio_anticlockwise.clockwise = False self.radio_anticlockwise.setChecked(not self.clockwise) self.radio_anticlockwise.toggled.connect(self.rotationDirectionChanged) self.angle_editor = QSpinBox(self) self.angle_editor.setButtonSymbols( QtWidgets.QAbstractSpinBox.NoButtons) self.angle_editor.setRange(0, 360) self.angle_editor.setSuffix(" °") self.angle_editor.setValue(self.angle) self.angle_editor.setToolTip("rotation angle") self.angle_editor.setStatusTip(self.toolTip()) self.angle_editor.setAlignment(QtCore.Qt.AlignCenter) self.angle_editor.valueChanged.connect(self.rotationAngleChanged) hbox.addWidget(self.radio_anticlockwise) hbox.addWidget(self.radio_clockwise) hbox.addWidget(self.angle_editor) self.rotate_bar = AngleBar(self, self.clockwise, self.angle, self.angleBarChanged) vbox = QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.rotate_bar) return vbox def rotationDirectionChanged(self, value): # radio button rbtn = self.sender() if rbtn.isChecked(): self.clockwise = rbtn.clockwise self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) self.appleRotateInfo() def rotationAngleChanged(self, value): # spinbox if value != self.angle: self.angle = value self.rotate_bar.updateRotationInfo(self.clockwise, self.angle) self.appleRotateInfo() def angleBarChanged(self, clockwise, angle): if self.clockwise == clockwise and self.angle == angle: return self.clockwise = clockwise self.angle = angle self.angle_editor.setValue(self.angle) if self.clockwise and not self.radio_clockwise.isChecked(): self.radio_clockwise.setChecked(True) elif not self.clockwise and not self.radio_anticlockwise.isChecked(): self.radio_anticlockwise.setChecked(True) else: self.appleRotateInfo() def appleRotateInfo(self): self.rotateSelectedPolygon.emit(self.clockwise, self.angle)
class PlotsWidget(PluginMainWidget): DEFAULT_OPTIONS = { 'auto_fit_plotting': True, 'mute_inline_plotting': True, 'show_plot_outline': True, 'save_dir': getcwd_or_home() } # Signals sig_option_changed = Signal(str, object) sig_figure_loaded = Signal() """This signal is emitted when a figure is loaded succesfully""" sig_redirect_stdio_requested = Signal(bool) """ This signal is emitted to request the main application to redirect standard output/error when using Open/Save/Browse dialogs within widgets. Parameters ---------- redirect: bool Start redirect (True) or stop redirect (False). """ def __init__(self, name=None, plugin=None, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent, options) # Widgets self._stack = PlotsStackedWidget(parent=self) self._shellwidgets = {} self.zoom_disp = QSpinBox(self) self._right_clicked_thumbnail = None # Widget setup self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) # Layout layout = QHBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._stack) self.setLayout(layout) # Signals self._stack.sig_figure_loaded.connect(self.sig_figure_loaded) self._stack.sig_figure_menu_requested.connect(self.show_figure_menu) self._stack.sig_thumbnail_menu_requested.connect( self.show_thumbnail_menu) self._stack.sig_zoom_changed.connect(self.zoom_disp.setValue) self._stack.sig_figure_loaded.connect(self.update_actions) self._stack.sig_save_dir_changed.connect( lambda val: self.set_option('save_dir', val)) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Plots') def get_focus_widget(self): widget = self.current_widget() if widget and widget.thumbnails_sb.current_thumbnail is not None: if widget.figviewer.figcanvas.fig: widget = widget.thumbnails_sb.scrollarea return widget def setup(self, options): # Menu actions self.mute_action = self.create_action( name=PlotsWidgetActions.ToggleMuteInlinePlotting, text=_("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=lambda val: self.set_option('mute_inline_plotting', val), initial=options['mute_inline_plotting'], ) self.outline_action = self.create_action( name=PlotsWidgetActions.ToggleShowPlotOutline, text=_("Show plot outline"), tip=_("Show the plot outline."), toggled=lambda val: self.set_option('show_plot_outline', val), initial=options['show_plot_outline'], ) self.fit_action = self.create_action( name=PlotsWidgetActions.ToggleAutoFitPlotting, text=_("Fit plots to window"), tip=_("Automatically fit plots to Plot pane size."), toggled=lambda val: self.set_option('auto_fit_plotting', val), initial=options['auto_fit_plotting'], ) # Toolbar actions save_action = self.create_action( name=PlotsWidgetActions.Save, text=_("Save plot as..."), icon=self.create_icon('filesave'), triggered=self.save_plot, register_shortcut=True, ) save_all_action = self.create_action( name=PlotsWidgetActions.SaveAll, text=_("Save all plots..."), icon=self.create_icon('save_all'), triggered=self.save_all_plots, register_shortcut=True, ) copy_action = self.create_action( name=PlotsWidgetActions.Copy, text=_("Copy Image"), icon=self.create_icon('editcopy'), triggered=self.copy_image, register_shortcut=True, ) remove_action = self.create_action( name=PlotsWidgetActions.Close, text=_("Remove plot"), icon=self.create_icon('editclear'), triggered=self.remove_plot, ) remove_all_action = self.create_action( name=PlotsWidgetActions.CloseAll, text=_("Remove all plots"), tip=_("Remove all plots"), icon=self.create_icon('filecloseall'), triggered=self.remove_all_plots, register_shortcut=True, ) previous_action = self.create_action( name=PlotsWidgetActions.MoveToPreviousFigure, text=_("Previous plot"), tip=_("Previous plot"), icon=self.create_icon('ArrowBack'), triggered=self.previous_plot, register_shortcut=True, ) next_action = self.create_action( name=PlotsWidgetActions.MoveToNextFigure, text=_("Next plot"), tip=_("Next plot"), icon=self.create_icon('ArrowForward'), triggered=self.next_plot, register_shortcut=True, ) zoom_in_action = self.create_action( name=PlotsWidgetActions.ZoomIn, text=_("Zoom in"), tip=_("Zoom in"), icon=self.create_icon('zoom_in'), triggered=self.zoom_in, register_shortcut=True, ) zoom_out_action = self.create_action( name=PlotsWidgetActions.ZoomOut, text=_("Zoom out"), tip=_("Zoom out"), icon=self.create_icon('zoom_out'), triggered=self.zoom_out, register_shortcut=True, ) # Options menu options_menu = self.get_options_menu() self.add_item_to_menu(self.mute_action, menu=options_menu) self.add_item_to_menu(self.outline_action, menu=options_menu) self.add_item_to_menu(self.fit_action, menu=options_menu) # Main toolbar main_toolbar = self.get_main_toolbar() for item in [ save_action, save_all_action, copy_action, remove_action, remove_all_action, previous_action, next_action, zoom_in_action, zoom_out_action, self.zoom_disp ]: self.add_item_to_toolbar( item, toolbar=main_toolbar, section=PlotsWidgetMainToolBarSections.Edit, ) # Context menu context_menu = self.create_menu(PluginMainWidgetMenus.Context) for item in [save_action, copy_action, remove_action]: self.add_item_to_menu(item, menu=context_menu) def update_actions(self): value = False widget = self.current_widget() figviewer = None if widget: figviewer = widget.figviewer thumbnails_sb = widget.thumbnails_sb value = figviewer.figcanvas.fig is not None for __, action in self.get_actions().items(): if action and action not in [ self.mute_action, self.outline_action, self.fit_action ]: action.setEnabled(value) # IMPORTANT: Since we are defining the main actions in here # and the context is WidgetWithChildrenShortcut we need to # assign the same actions to the children widgets in order # for shortcuts to work if figviewer: figviewer_actions = figviewer.actions() thumbnails_sb_actions = thumbnails_sb.actions() if action not in figviewer_actions: figviewer.addAction(action) if action not in thumbnails_sb_actions: thumbnails_sb.addAction(action) self.zoom_disp.setEnabled(value) # Disable zoom buttons if autofit if value: value = not self.get_option('auto_fit_plotting') self.get_action(PlotsWidgetActions.ZoomIn).setEnabled(value) self.get_action(PlotsWidgetActions.ZoomOut).setEnabled(value) self.zoom_disp.setEnabled(value) def on_option_update(self, option, value): for index in range(self.count()): widget = self._stack.widget(index) if widget: widget.setup({option: value}) self.update_actions() # --- Public API: # ------------------------------------------------------------------------ def set_current_widget(self, fig_browser): """ Set the current figure browser widget in the stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The widget to set. """ self._stack.setCurrentWidget(fig_browser) def current_widget(self): """ Return the current figure browser widget in the stack. Returns ------- spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The current widget. """ return self._stack.currentWidget() def count(self): """ Return the number of widgets in the stack. Returns ------- int The number of widgets in the stack. """ return self._stack.count() def remove_widget(self, fig_browser): """ Remove widget from stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The figure browser widget to remove. """ self._stack.removeWidget(fig_browser) def add_widget(self, fig_browser): """ Add widget to stack. Parameters ---------- fig_browser: spyder.plugins.plots.widgets.figurebrowser.FigureBrowser The figure browser widget to add. """ self._stack.addWidget(fig_browser) def add_shellwidget(self, shellwidget): """ Add a new shellwidget registered with the plots plugin. This function registers a new FigureBrowser for browsing the figures in the shell. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id not in self._shellwidgets: fig_browser = FigureBrowser(parent=self._stack, background_color=MAIN_BG_COLOR) fig_browser.set_shellwidget(shellwidget) fig_browser.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.add_widget(fig_browser) self._shellwidgets[shellwidget_id] = fig_browser self.set_shellwidget(shellwidget) return fig_browser def remove_shellwidget(self, shellwidget): """ Remove the shellwidget registered with the plots plugin. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id in self._shellwidgets: fig_browser = self._shellwidgets.pop(shellwidget_id) self.remove_widget(fig_browser) fig_browser.close() def set_shellwidget(self, shellwidget): """ Update the current shellwidget displayed with the plots plugin. Parameters ---------- shelwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget. """ shellwidget_id = id(shellwidget) if shellwidget_id in self._shellwidgets: fig_browser = self._shellwidgets[shellwidget_id] fig_browser.setup(self._options) self.set_current_widget(fig_browser) def show_figure_menu(self, qpoint): """ Show main figure menu and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = None widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def show_thumbnail_menu(self, qpoint, thumbnail): """ Show menu on a given `thumbnail` and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = thumbnail widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def save_plot(self): """ Save currently active plot or plot selected to be saved with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.save_current_figure_as() else: widget.thumbnails_sb.save_thumbnail_figure_as( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def save_all_plots(self): """Save all available plots.""" widget = self.current_widget() if widget: widget.thumbnails_sb.save_all_figures_as() def copy_image(self): """ Copy currently active plot or plot selected to be copied with context menu in the thumbnails scrollbar into the clipboard. """ widget = self.current_widget() if widget and widget.figviewer and widget.figviewer.figcanvas.fig: if self._right_clicked_thumbnail is None: widget.figviewer.figcanvas.copy_figure() else: self._right_clicked_thumbnail.canvas.copy_figure() # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def remove_plot(self): """ Remove currently active plot or plot selected to be removed with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.remove_current_thumbnail() else: widget.thumbnails_sb.remove_thumbnail( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None self.update_actions() def remove_all_plots(self): """Remove all available plots..""" widget = self.current_widget() if widget: widget.thumbnails_sb.remove_all_thumbnails() self.update_actions() def previous_plot(self): """Select the previous plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_previous_thumbnail() def next_plot(self): """Select the next plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_next_thumbnail() def zoom_in(self): """Perform a zoom in on the main figure.""" widget = self.current_widget() if widget: widget.zoom_in() def zoom_out(self): """Perform a zoom out on the main figure.""" widget = self.current_widget() if widget: widget.zoom_out()
class PlotsWidget(ShellConnectMainWidget): sig_figure_loaded = Signal() """This signal is emitted when a figure is loaded succesfully""" sig_redirect_stdio_requested = Signal(bool) """ This signal is emitted to request the main application to redirect standard output/error when using Open/Save/Browse dialogs within widgets. Parameters ---------- redirect: bool Start redirect (True) or stop redirect (False). """ def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent) # Widgets self.zoom_disp = QSpinBox(self) self.zoom_disp.ID = PlotsWidgetToolbarItems.ZoomSpinBox self._right_clicked_thumbnail = None # Widget setup self.zoom_disp.setAlignment(Qt.AlignCenter) self.zoom_disp.setButtonSymbols(QSpinBox.NoButtons) self.zoom_disp.setReadOnly(True) self.zoom_disp.setSuffix(' %') self.zoom_disp.setRange(0, 9999) self.zoom_disp.setValue(100) # Resize to a huge width to get the right size of the thumbnail # scrollbar at startup. self.resize(50000, self.height()) # ---- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Plots') def get_focus_widget(self): widget = self.current_widget() if widget and widget.thumbnails_sb.current_thumbnail is not None: if widget.figviewer.figcanvas.fig: widget = widget.thumbnails_sb.scrollarea return widget def setup(self): # Menu actions self.mute_action = self.create_action( name=PlotsWidgetActions.ToggleMuteInlinePlotting, text=_("Mute inline plotting"), tip=_("Mute inline plotting in the ipython console."), toggled=True, initial=self.get_conf('mute_inline_plotting'), option='mute_inline_plotting') self.outline_action = self.create_action( name=PlotsWidgetActions.ToggleShowPlotOutline, text=_("Show plot outline"), tip=_("Show the plot outline."), toggled=True, initial=self.get_conf('show_plot_outline'), option='show_plot_outline') self.fit_action = self.create_action( name=PlotsWidgetActions.ToggleAutoFitPlotting, text=_("Fit plots to window"), tip=_("Automatically fit plots to Plot pane size."), toggled=True, initial=self.get_conf('auto_fit_plotting'), option='auto_fit_plotting') # Toolbar actions save_action = self.create_action( name=PlotsWidgetActions.Save, text=_("Save plot as..."), tip=_("Save plot as..."), icon=self.create_icon('filesave'), triggered=self.save_plot, register_shortcut=True, ) save_all_action = self.create_action( name=PlotsWidgetActions.SaveAll, text=_("Save all plots..."), tip=_("Save all plots..."), icon=self.create_icon('save_all'), triggered=self.save_all_plots, register_shortcut=True, ) copy_action = self.create_action( name=PlotsWidgetActions.Copy, text=_("Copy image"), tip=_("Copy plot to clipboard as image"), icon=self.create_icon('editcopy'), triggered=self.copy_image, register_shortcut=True, ) remove_action = self.create_action( name=PlotsWidgetActions.Close, text=_("Remove plot"), icon=self.create_icon('editclear'), triggered=self.remove_plot, register_shortcut=True, ) remove_all_action = self.create_action( name=PlotsWidgetActions.CloseAll, text=_("Remove all plots"), tip=_("Remove all plots"), icon=self.create_icon('filecloseall'), triggered=self.remove_all_plots, register_shortcut=True, ) previous_action = self.create_action( name=PlotsWidgetActions.MoveToPreviousFigure, text=_("Previous plot"), tip=_("Previous plot"), icon=self.create_icon('previous'), triggered=self.previous_plot, register_shortcut=True, ) next_action = self.create_action( name=PlotsWidgetActions.MoveToNextFigure, text=_("Next plot"), tip=_("Next plot"), icon=self.create_icon('next'), triggered=self.next_plot, register_shortcut=True, ) zoom_in_action = self.create_action( name=PlotsWidgetActions.ZoomIn, text=_("Zoom in"), tip=_("Zoom in"), icon=self.create_icon('zoom_in'), triggered=self.zoom_in, register_shortcut=True, ) zoom_out_action = self.create_action( name=PlotsWidgetActions.ZoomOut, text=_("Zoom out"), tip=_("Zoom out"), icon=self.create_icon('zoom_out'), triggered=self.zoom_out, register_shortcut=True, ) # Options menu options_menu = self.get_options_menu() self.add_item_to_menu(self.mute_action, menu=options_menu) self.add_item_to_menu(self.outline_action, menu=options_menu) self.add_item_to_menu(self.fit_action, menu=options_menu) # Main toolbar main_toolbar = self.get_main_toolbar() for item in [ save_action, save_all_action, copy_action, remove_action, remove_all_action, previous_action, next_action, zoom_in_action, zoom_out_action, self.zoom_disp ]: self.add_item_to_toolbar( item, toolbar=main_toolbar, section=PlotsWidgetMainToolbarSections.Edit, ) # Context menu context_menu = self.create_menu(PluginMainWidgetMenus.Context) for item in [save_action, copy_action, remove_action]: self.add_item_to_menu(item, menu=context_menu) def update_actions(self): value = False widget = self.current_widget() figviewer = None if widget: figviewer = widget.figviewer thumbnails_sb = widget.thumbnails_sb value = figviewer.figcanvas.fig is not None for __, action in self.get_actions().items(): if action and action not in [ self.mute_action, self.outline_action, self.fit_action, self.undock_action, self.close_action, self.dock_action, self.toggle_view_action ]: action.setEnabled(value) # IMPORTANT: Since we are defining the main actions in here # and the context is WidgetWithChildrenShortcut we need to # assign the same actions to the children widgets in order # for shortcuts to work if figviewer: figviewer_actions = figviewer.actions() thumbnails_sb_actions = thumbnails_sb.actions() if action not in figviewer_actions: figviewer.addAction(action) if action not in thumbnails_sb_actions: thumbnails_sb.addAction(action) self.zoom_disp.setEnabled(value) # Disable zoom buttons if autofit if value: value = not self.get_conf('auto_fit_plotting') self.get_action(PlotsWidgetActions.ZoomIn).setEnabled(value) self.get_action(PlotsWidgetActions.ZoomOut).setEnabled(value) self.zoom_disp.setEnabled(value) @on_conf_change(option=[ 'auto_fit_plotting', 'mute_inline_plotting', 'show_plot_outline', 'save_dir' ]) def on_section_conf_change(self, option, value): for index in range(self.count()): widget = self._stack.widget(index) if widget: widget.setup({option: value}) self.update_actions() # ---- Public API: # ------------------------------------------------------------------------ def create_new_widget(self, shellwidget): fig_browser = FigureBrowser(parent=self, background_color=MAIN_BG_COLOR) fig_browser.update_splitter_widths(self.width()) fig_browser.set_shellwidget(shellwidget) fig_browser.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) fig_browser.sig_figure_menu_requested.connect(self.show_figure_menu) fig_browser.sig_thumbnail_menu_requested.connect( self.show_thumbnail_menu) fig_browser.sig_figure_loaded.connect(self.update_actions) fig_browser.sig_save_dir_changed.connect( lambda val: self.set_conf('save_dir', val)) fig_browser.sig_zoom_changed.connect(self.zoom_disp.setValue) return fig_browser def close_widget(self, fig_browser): fig_browser.close() def switch_widget(self, fig_browser, old_fig_browser): option_keys = [('auto_fit_plotting', True), ('mute_inline_plotting', True), ('show_plot_outline', True), ('save_dir', getcwd_or_home())] conf_values = {k: self.get_conf(k, d) for k, d in option_keys} fig_browser.setup(conf_values) def show_figure_menu(self, qpoint): """ Show main figure menu and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = None widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def show_thumbnail_menu(self, qpoint, thumbnail): """ Show menu on a given `thumbnail` and display on given `qpoint`. Parameters ---------- qpoint: QPoint The point to display the menu in global coordinated. """ self._right_clicked_thumbnail = thumbnail widget = self.current_widget() if widget: self.get_menu(PluginMainWidgetMenus.Context).popup(qpoint) def save_plot(self): """ Save currently active plot or plot selected to be saved with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.save_current_figure_as() else: widget.thumbnails_sb.save_thumbnail_figure_as( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def save_all_plots(self): """Save all available plots.""" widget = self.current_widget() if widget: widget.thumbnails_sb.save_all_figures_as() def copy_image(self): """ Copy currently active plot or plot selected to be copied with context menu in the thumbnails scrollbar into the clipboard. """ widget = self.current_widget() if widget and widget.figviewer and widget.figviewer.figcanvas.fig: if self._right_clicked_thumbnail is None: widget.figviewer.figcanvas.copy_figure() else: self._right_clicked_thumbnail.canvas.copy_figure() # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None def remove_plot(self): """ Remove currently active plot or plot selected to be removed with context menu in the thumbnails scrollbar. """ widget = self.current_widget() if widget: if self._right_clicked_thumbnail is None: widget.thumbnails_sb.remove_current_thumbnail() else: widget.thumbnails_sb.remove_thumbnail( self._right_clicked_thumbnail) # Reset the toolbar buttons to use the figviewer and not the thumbnail # selection self._right_clicked_thumbnail = None self.update_actions() def remove_all_plots(self): """Remove all available plots..""" widget = self.current_widget() if widget: widget.thumbnails_sb.remove_all_thumbnails() self.update_actions() def previous_plot(self): """Select the previous plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_previous_thumbnail() def next_plot(self): """Select the next plot in the thumbnails scrollbar.""" widget = self.current_widget() if widget: widget.thumbnails_sb.go_next_thumbnail() def zoom_in(self): """Perform a zoom in on the main figure.""" widget = self.current_widget() if widget: widget.zoom_in() def zoom_out(self): """Perform a zoom out on the main figure.""" widget = self.current_widget() if widget: widget.zoom_out()