class TabHeader(QWidget): def __init__(self): super(TabHeader, self).__init__() # QWidget.__init__(self) self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(4) self.setLayout(self.layout) # def __del__(self): # _log.debug("Finalize: {}".format(self)) def addTab(self, index, item): self.layout.insertWidget(index, item.tab) @property def tabs(self): return [ self.layout.itemAt(k).widget() for k in range(self.layout.count()) ] def getTab(self, dock): for k in range(self.layout.count()): w = self.layout.itemAt(k).widget() if w.uid == dock.uid: return w return None
class JumpList(QWidget): """List of buttons to jump to reactions on map""" def __init__(self, parent): QWidget.__init__(self) self.parent = parent self.layout = QHBoxLayout() self.layout.setAlignment(Qt.AlignLeft) def clear(self): for i in reversed(range(self.layout.count())): self.layout.itemAt(i).widget().setParent(None) def add(self, name: str): if self.layout.count() == 0: label = QLabel("Jump to reaction on map:") self.layout.addWidget(label) jb = JumpButton(self, name) policy = QSizePolicy() policy.ShrinkFlag = True jb.setSizePolicy(policy) self.layout.addWidget(jb) self.setLayout(self.layout) jb.jumpToMap.connect(self.parent.emit_jump_to_map) @ Slot(str) def emit_jump_to_map(self: JumpButton, name: str): self.parent.emit_jump_to_map(name) jumpToMap = Signal(str)
class MxToolBarMixin: """Mixin class for tool bar in stacked widgets""" sig_option_changed = Signal(str, object) def __init__(self, options_button=None, plugin_actions=None): self.options_button = options_button self.actions = [] if plugin_actions is None: self.plugin_actions = [] else: self.plugin_actions = plugin_actions # Setup toolbar layout. self.tools_layout = QHBoxLayout() toolbar = self.setup_toolbar() if toolbar: for widget in toolbar: self.tools_layout.addWidget(widget) else: self.tools_layout.addStretch() self.setup_option_actions() self.setup_options_button() def get_actions(self): """Get actions of the widget.""" return self.actions def setup_toolbar(self): """Setup toolbar. Override in subclass Return list of buttons to be displayed left-aligned. """ return [] def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: 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 setup_option_actions(self, *args, **kwargs): """Setup the actions to show in the cog menu.""" pass
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 LabelEditor(QWidget): """Widget for create label scheme.""" def __init__(self, settings: ViewSettings): super().__init__() self.settings = settings self.color_list = [] self.chosen = None self.prohibited_names = set(self.settings.label_color_dict.keys() ) # Prohibited name is added to reduce # probability of colormap cache collision self.color_picker = QColorDialog() self.color_picker.setWindowFlag(Qt.Widget) self.color_picker.setOptions(QColorDialog.DontUseNativeDialog | QColorDialog.NoButtons) self.add_color_btn = QPushButton("Add color") self.add_color_btn.clicked.connect(self.add_color) self.remove_color_btn = QPushButton("Remove last color") self.remove_color_btn.clicked.connect(self.remove_color) self.save_btn = QPushButton("Save") self.save_btn.clicked.connect(self.save) self.color_layout = QHBoxLayout() layout = QVBoxLayout() layout.addWidget(self.color_picker) btn_layout = QHBoxLayout() btn_layout.addWidget(self.add_color_btn) btn_layout.addWidget(self.remove_color_btn) btn_layout.addWidget(self.save_btn) layout.addLayout(btn_layout) layout.addLayout(self.color_layout) self.setLayout(layout) @Slot(list) def set_colors(self, colors: list): for _ in range(self.color_layout.count()): el = self.color_layout.takeAt(0) if el.widget(): el.widget().deleteLater() for color in colors: self.color_layout.addWidget(ColorShow(color, self)) def remove_color(self): if self.color_layout.count(): el = self.color_layout.takeAt(self.color_layout.count() - 1) el.widget().deleteLater() def add_color(self): color = self.color_picker.currentColor() self.color_layout.addWidget( ColorShow([color.red(), color.green(), color.blue()], self)) def get_colors(self): count = self.color_layout.count() return [ self.color_layout.itemAt(i).widget().color for i in range(count) ] def save(self): count = self.color_layout.count() if not count: return rand_name = custom_name_generate(self.prohibited_names, self.settings.label_color_dict) self.prohibited_names.add(rand_name) self.settings.label_color_dict[rand_name] = self.get_colors() def mousePressEvent(self, e: QMouseEvent): child = self.childAt(e.pos()) if not isinstance(child, ColorShow): self.chosen = None return self.chosen = child def mouseMoveEvent(self, e: QMouseEvent): if self.chosen is None: return index = self.color_layout.indexOf(self.chosen) index2 = int(e.x() / self.width() * self.color_layout.count() + 0.5) if index2 != index: self.color_layout.insertWidget(index2, self.chosen) def mouseReleaseEvent(self, e: QMouseEvent): self.chosen = None
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 NamespaceBrowser(QWidget): """Namespace browser (global variables explorer widget)""" sig_option_changed = Signal(str, object) sig_collapse = Signal() sig_free_memory = Signal() def __init__(self, parent, options_button=None, plugin_actions=[]): QWidget.__init__(self, parent) self.shellwidget = None self.is_visible = True self.setup_in_progress = None # Remote dict editor settings self.check_all = None self.exclude_private = None self.exclude_uppercase = None self.exclude_capitalized = None self.exclude_unsupported = None self.exclude_callables_and_modules = None self.excluded_names = None self.minmax = None # Other setting self.dataframe_format = None self.show_callable_attributes = None self.show_special_attributes = None self.editor = None self.exclude_private_action = None self.exclude_uppercase_action = None self.exclude_capitalized_action = None self.exclude_unsupported_action = None self.exclude_callables_and_modules_action = None self.finder = None self.options_button = options_button self.actions = None self.plugin_actions = plugin_actions self.filename = None def setup( self, check_all=None, exclude_private=None, exclude_uppercase=None, exclude_capitalized=None, exclude_unsupported=None, excluded_names=None, exclude_callables_and_modules=None, minmax=None, dataframe_format=None, show_callable_attributes=None, show_special_attributes=None, ): """ Setup the namespace browser with provided settings. Args: dataframe_format (string): default floating-point format for DataFrame editor """ assert self.shellwidget is not None self.check_all = check_all self.exclude_private = exclude_private self.exclude_uppercase = exclude_uppercase self.exclude_capitalized = exclude_capitalized self.exclude_unsupported = exclude_unsupported self.exclude_callables_and_modules = exclude_callables_and_modules self.excluded_names = excluded_names self.minmax = minmax self.dataframe_format = dataframe_format self.show_callable_attributes = show_callable_attributes self.show_special_attributes = show_special_attributes if self.editor is not None: self.editor.setup_menu(minmax) self.editor.set_dataframe_format(dataframe_format) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.exclude_callables_and_modules_action.setChecked( exclude_callables_and_modules) self.refresh_table() return self.editor = RemoteCollectionsEditorTableView( self, data=None, minmax=minmax, shellwidget=self.shellwidget, dataframe_format=dataframe_format, show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes) self.editor.sig_option_changed.connect(self.sig_option_changed.emit) self.editor.sig_files_dropped.connect(self.import_data) self.editor.sig_free_memory.connect(self.sig_free_memory.emit) self.setup_option_actions(exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported, exclude_callables_and_modules) # Setup toolbar layout. 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() # Setup layout. layout = create_plugin_layout(self.tools_layout, self.editor) # Fuzzy search layout finder_layout = QHBoxLayout() close_button = create_toolbutton(self, triggered=self.show_finder, icon=ima.icon('DialogCloseButton')) text_finder = NamespacesBrowserFinder(self.editor, callback=self.editor.set_regex, main=self, regex_base=VALID_VARIABLE_CHARS) self.editor.finder = text_finder finder_layout.addWidget(close_button) finder_layout.addWidget(text_finder) finder_layout.setContentsMargins(0, 0, 0, 0) self.finder = QWidget(self) self.finder.text_finder = text_finder self.finder.setLayout(finder_layout) self.finder.setVisible(False) layout.addWidget(self.finder) self.setLayout(layout) # Local shortcuts self.shortcuts = self.create_shortcuts() self.sig_option_changed.connect(self.option_changed) def create_shortcuts(self): """Create local shortcut for the nsbrowser.""" search = CONF.config_shortcut( lambda: self.show_finder(set_visible=True), context='variable_explorer', name='search', parent=self) refresh = CONF.config_shortcut(self.refresh_table, context='variable_explorer', name='refresh', parent=self) return [search, refresh] def get_shortcut_data(self): """ Returns 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 set_shellwidget(self, shellwidget): """Bind shellwidget instance to namespace browser""" self.shellwidget = shellwidget shellwidget.set_namespacebrowser(self) def get_actions(self): """Get actions of the widget.""" return self.actions def setup_toolbar(self): """Setup toolbar""" load_button = create_toolbutton(self, text=_('Import data'), icon=ima.icon('fileimport'), triggered=lambda: self.import_data()) self.save_button = create_toolbutton( self, text=_("Save data"), icon=ima.icon('filesave'), triggered=lambda: self.save_data(self.filename)) self.save_button.setEnabled(False) save_as_button = create_toolbutton(self, text=_("Save data as..."), icon=ima.icon('filesaveas'), triggered=self.save_data) reset_namespace_button = create_toolbutton( self, text=_("Remove all variables"), icon=ima.icon('editdelete'), triggered=self.reset_namespace) self.search_button = create_toolbutton( self, text=_("Search variable names and types"), icon=ima.icon('find'), toggled=self.show_finder) self.refresh_button = create_toolbutton( self, text=_("Refresh variables"), icon=ima.icon('refresh'), triggered=lambda: self.refresh_table(interrupt=True)) return [ load_button, self.save_button, save_as_button, reset_namespace_button, self.search_button, self.refresh_button ] def setup_option_actions(self, exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported, exclude_callables_and_modules): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.exclude_private_action = create_action( self, _("Exclude private references"), tip=_("Exclude references which name starts" " with an underscore"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_private', state)) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action = create_action( self, _("Exclude all-uppercase references"), tip=_("Exclude references which name is uppercase"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_uppercase', state)) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action = create_action( self, _("Exclude capitalized references"), tip=_("Exclude references which name starts with an " "uppercase character"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_capitalized', state)) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action = create_action( self, _("Exclude unsupported data types"), tip=_("Exclude references to data types that don't have " "an specialized viewer or can't be edited."), toggled=lambda state: self.sig_option_changed.emit( 'exclude_unsupported', state)) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.exclude_callables_and_modules_action = create_action( self, _("Exclude callables and modules"), tip=_("Exclude references to functions, modules and " "any other callable."), toggled=lambda state: self.sig_option_changed.emit( 'exclude_callables_and_modules', state)) self.exclude_callables_and_modules_action.setChecked( exclude_callables_and_modules) self.actions = [ self.exclude_private_action, self.exclude_uppercase_action, self.exclude_capitalized_action, self.exclude_unsupported_action, self.exclude_callables_and_modules_action ] if is_module_installed('numpy'): self.actions.extend([MENU_SEPARATOR, self.editor.minmax_action]) self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: 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 option_changed(self, option, value): """Option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() self.refresh_table() def get_view_settings(self): """Return dict editor view settings""" settings = {} for name in REMOTE_SETTINGS: settings[name] = getattr(self, name) return settings def show_finder(self, set_visible=False): """Handle showing/hiding the finder widget.""" self.finder.text_finder.setText('') self.finder.setVisible(set_visible) self.search_button.setChecked(set_visible) if self.finder.isVisible(): self.finder.text_finder.setFocus() else: self.editor.setFocus() def refresh_table(self, interrupt=False): """Refresh variable table""" if self.is_visible and self.isVisible(): self.shellwidget.refresh_namespacebrowser(interrupt=interrupt) try: self.editor.resizeRowToContents() except TypeError: pass def process_remote_view(self, remote_view): """Process remote view""" if remote_view is not None: self.set_data(remote_view) def set_var_properties(self, properties): """Set properties of variables""" if properties is not None: self.editor.var_properties = properties def set_data(self, data): """Set data.""" if data != self.editor.source_model.get_data(): self.editor.set_data(data) self.editor.adjust_columns() def collapse(self): """Collapse.""" self.sig_collapse.emit() @Slot(bool) @Slot(list) def import_data(self, filenames=None): """Import data from text file.""" title = _("Import data") if filenames is None: if self.filename is None: basedir = getcwd_or_home() else: basedir = osp.dirname(self.filename) filenames, _selfilter = getopenfilenames(self, title, basedir, iofunctions.load_filters) if not filenames: return elif is_text_string(filenames): filenames = [filenames] for filename in filenames: self.filename = to_text_string(filename) if os.name == "nt": self.filename = remove_backslashes(self.filename) ext = osp.splitext(self.filename)[1].lower() if ext not in iofunctions.load_funcs: buttons = QMessageBox.Yes | QMessageBox.Cancel answer = QMessageBox.question( self, title, _("<b>Unsupported file extension '%s'</b><br><br>" "Would you like to import it anyway " "(by selecting a known file format)?") % ext, buttons) if answer == QMessageBox.Cancel: return formats = list(iofunctions.load_extensions.keys()) item, ok = QInputDialog.getItem(self, title, _('Open file as:'), formats, 0, False) if ok: ext = iofunctions.load_extensions[to_text_string(item)] else: return load_func = iofunctions.load_funcs[ext] # 'import_wizard' (self.setup_io) if is_text_string(load_func): # Import data with import wizard error_message = None try: text, _encoding = encoding.read(self.filename) base_name = osp.basename(self.filename) editor = ImportWizard( self, text, title=base_name, varname=fix_reference_name(base_name)) if editor.exec_(): var_name, clip_data = editor.get_data() self.editor.new_value(var_name, clip_data) except Exception as error: error_message = str(error) else: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.load_data(self.filename, ext) QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical( self, title, _("<b>Unable to load '%s'</b>" "<br><br>" "The error message was:<br>%s") % (self.filename, error_message)) self.refresh_table() @Slot() def reset_namespace(self): warning = CONF.get('ipython_console', 'show_reset_namespace_warning') self.shellwidget.reset_namespace(warning=warning, message=True) self.editor.automatic_column_width = True @Slot() def save_data(self, filename=None): """Save data""" if filename is None: filename = self.filename if filename is None: filename = getcwd_or_home() filename, _selfilter = getsavefilename(self, _("Save data"), filename, iofunctions.save_filters) if filename: self.filename = filename else: return False QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.save_namespace(self.filename) QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: if 'Some objects could not be saved:' in error_message: save_data_message = ( _("<b>Some objects could not be saved:</b>") + "<br><br><code>{obj_list}</code>".format( obj_list=error_message.split(': ')[1])) else: save_data_message = _( "<b>Unable to save current workspace</b>" "<br><br>" "The error message was:<br>") + error_message QMessageBox.critical(self, _("Save data"), save_data_message) self.save_button.setEnabled(self.filename is not None)
class ConvertFluxUnitGUI(QDialog): """ GUI for unit conversions """ def __init__(self, controller, parent=None): super(ConvertFluxUnitGUI, self).__init__(parent=parent) self.setWindowFlags(self.windowFlags() | Qt.Tool) self.title = "Unit Conversion" self.setMinimumSize(400, 270) self.cubeviz_layout = controller.cubeviz_layout self._hub = self.cubeviz_layout.session.hub self.controller = controller self.data = controller.data self.controller_components = controller._components self.current_unit = None self._init_ui() def _init_ui(self): # LINE 1: Data component drop down self.component_prompt = QLabel("Data Component:") self.component_prompt.setWordWrap(True) # Add the data component labels to the drop down, with the ComponentID # set as the userData: if self.parent is not None and hasattr(self.parent, 'data_components'): self.label_data = [(str(cid), cid) for cid in self.parent.data_components] else: self.label_data = [(str(cid), cid) for cid in self.data.visible_components] default_index = 0 self.component_combo = QComboBox() self.component_combo.setFixedWidth(200) update_combobox(self.component_combo, self.label_data, default_index=default_index) self.component_combo.currentIndexChanged.connect(self.update_unit_layout) # hbl is short for Horizontal Box Layout hbl1 = QHBoxLayout() hbl1.addWidget(self.component_prompt) hbl1.addWidget(self.component_combo) hbl1.addStretch(1) # LINE 2: Unit conversion layout # This layout is filled by CubeVizUnit self.unit_layout = QHBoxLayout() # this is hbl2 # LINE 3: Message box self.message_box = QLabel("") hbl3 = QHBoxLayout() hbl3.addWidget(self.message_box) hbl3.addStretch(1) # Line 4: Buttons self.okButton = QPushButton("Convert Units") self.okButton.clicked.connect(self.call_main) self.okButton.setDefault(True) self.cancelButton = QPushButton("Cancel") self.cancelButton.clicked.connect(self.cancel) hbl4 = QHBoxLayout() hbl4.addStretch(1) hbl4.addWidget(self.cancelButton) hbl4.addWidget(self.okButton) vbl = QVBoxLayout() vbl.addLayout(hbl1) vbl.addLayout(self.unit_layout) vbl.addLayout(hbl3) vbl.addLayout(hbl4) self.setLayout(vbl) self.vbl = vbl self.update_unit_layout(default_index) self.show() def update_unit_layout(self, index): """ Call back for component selection drop down. """ component_id = str(self.component_combo.currentData()) # STEP1: Clean up widgets from last component widgets = (self.unit_layout.itemAt(i) for i in range(self.unit_layout.count())) for w in widgets: if isinstance(w, QSpacerItem): self.unit_layout.removeItem(w) continue elif isinstance(w, QWidgetItem): w = w.widget() if hasattr(w, "deleteLater"): w.deleteLater() self.message_box.setText("") if self.current_unit: self.current_unit.reset_widgets() # STEP2: Add now component and connect to CubeVizUnit # so that new widgets are populated. if component_id in self.controller_components: cubeviz_unit = self.controller_components[component_id] self.current_unit = cubeviz_unit cubeviz_unit.set_message_box(self.message_box) cubeviz_unit.populate_unit_layout(self.unit_layout, self) if cubeviz_unit.is_convertible: self.okButton.setEnabled(True) else: self.okButton.setEnabled(False) else: self.current_unit = None default_message = "CubeViz can not convert this unit." default_label = QLabel(default_message) self.unit_layout.addWidget(default_label) self.okButton.setEnabled(False) self.unit_layout.update() self.vbl.update() def call_main(self): """ Calls CubeVizUnit.change_units to finalize conversions. Updates plots with new units. :return: """ success = self.current_unit.change_units() if not success: # Todo: Warning should pop up return component_id = self.component_combo.currentData() self.data.get_component(component_id).units = self.current_unit.unit_string msg = FluxUnitsUpdateMessage(self, self.current_unit.unit, component_id) self._hub.broadcast(msg) self.close() def cancel(self): self.close()
class ConvertFluxUnitGUI(QDialog): """ GUI for unit conversions """ def __init__(self, controller, parent=None, convert_data=False): super(ConvertFluxUnitGUI, self).__init__(parent=parent) self.setWindowFlags(self.windowFlags() | Qt.Tool) self.title = "Unit Conversion" self.setMinimumSize(400, 270) self.convert_data = convert_data self.cubeviz_layout = controller.cubeviz_layout self._hub = self.cubeviz_layout.session.hub self.controller = controller self.data = controller.data self.controller_components = controller._components self.current_unit = None self.current_layout = None self._init_ui() def _init_ui(self): # LINE 1: Data component drop down self.component_prompt = QLabel("Data Component:") self.component_prompt.setWordWrap(True) # Add the data component labels to the drop down, with the ComponentID # set as the userData: if self.parent is not None and hasattr(self.parent, 'data_components'): self.label_data = [(str(cid), cid) for cid in self.parent.data_components] else: self.label_data = [(str(cid), cid) for cid in self.data.visible_components] default_index = 0 self.component_combo = QComboBox() self.component_combo.setFixedWidth(200) update_combobox(self.component_combo, self.label_data, default_index=default_index) self.component_combo.currentIndexChanged.connect(self.update_unit_layout) # hbl is short for Horizontal Box Layout hbl1 = QHBoxLayout() hbl1.addWidget(self.component_prompt) hbl1.addWidget(self.component_combo) hbl1.addStretch(1) # LINE 2: Unit conversion layout # This layout is filled by CubeVizUnit self.unit_layout = QHBoxLayout() # this is hbl2 # LINE 3: Message box self.message_box = QLabel("") hbl3 = QHBoxLayout() hbl3.addWidget(self.message_box) hbl3.addStretch(1) # Line 4: Buttons ok_text = "Convert Data" if self.convert_data else "Convert Displayed Units" ok_function = self.convert_data_units if self.convert_data else self.convert_displayed_units self.okButton = QPushButton(ok_text) self.okButton.clicked.connect(ok_function) self.okButton.setDefault(True) self.cancelButton = QPushButton("Cancel") self.cancelButton.clicked.connect(self.cancel) hbl4 = QHBoxLayout() hbl4.addStretch(1) hbl4.addWidget(self.cancelButton) hbl4.addWidget(self.okButton) vbl = QVBoxLayout() vbl.addLayout(hbl1) vbl.addLayout(self.unit_layout) vbl.addLayout(hbl3) vbl.addLayout(hbl4) self.setLayout(vbl) self.vbl = vbl self.update_unit_layout(default_index) self.show() def update_unit_layout(self, index): """ Call back for component selection drop down. """ component_id = self.component_combo.currentData() # STEP1: Clean up widgets from last component widgets = (self.unit_layout.itemAt(i) for i in range(self.unit_layout.count())) for w in widgets: if isinstance(w, QSpacerItem): self.unit_layout.removeItem(w) continue elif isinstance(w, QWidgetItem): w = w.widget() if hasattr(w, "deleteLater"): w.deleteLater() self.message_box.setText("") if self.current_layout: self.current_layout.reset_widgets() # STEP2: Add now component and connect to CubeVizUnit # so that new widgets are populated. if component_id in self.controller_components: cubeviz_unit = self.controller_components[component_id] self.current_unit = cubeviz_unit wave = self.controller.wave pixel_area = self.controller.pixel_area layout = assign_cubeviz_unit_layout(cubeviz_unit, wave=wave, pixel_area=pixel_area) layout.set_message_box(self.message_box) layout.populate_unit_layout(self.unit_layout, self) self.current_layout = layout if ASTROPY_CubeVizUnit == cubeviz_unit.type: self.okButton.setEnabled(True) else: self.okButton.setEnabled(False) else: self.current_unit = None default_message = "CubeViz can not convert this unit." default_label = QLabel(default_message) self.unit_layout.addWidget(default_label) self.okButton.setEnabled(False) self.unit_layout.update() self.vbl.update() def convert_displayed_units(self): """ Calls CubeVizUnit.change_units to finalize conversions. Updates plots with new units. :return: """ success = self.current_layout.change_units() if not success: info = QMessageBox.critical(self, "Error", "Conversion failed.") return new_unit = self.current_layout.new_unit self.current_unit.unit = new_unit self.current_unit.unit_string = str(new_unit) component_id = self.component_combo.currentData() self.data.get_component(component_id).units = self.current_unit.unit_string msg = FluxUnitsUpdateMessage(self, self.current_unit, component_id) self._hub.broadcast(msg) self.close() def convert_data_units(self): """ Calls CubeVizUnit.change_units to finalize conversions. Updates plots with new units. :return: """ success = self.current_layout.change_units() if not success: info = QMessageBox.critical(self, "Error", "Conversion failed.") return new_unit = self.current_layout.new_unit self.current_unit.unit = new_unit self.current_unit.unit_string = str(new_unit) component_id = self.component_combo.currentData() component = component_id.parent.get_component(component_id) old_array = component._data.copy() old_array.flags.writeable = True wavelengths = self.controller.construct_3d_wavelengths(old_array) new_array = self.current_unit.convert_value(old_array, wave=wavelengths) component._data = new_array self.current_unit = self.controller.add_component_unit(component_id, new_unit) component.units = self.current_unit.unit_string msg = FluxUnitsUpdateMessage(self, self.current_unit, component_id) self._hub.broadcast(msg) self.close() def cancel(self): self.close()
class NamespaceBrowser(QWidget): """Namespace browser (global variables explorer widget)""" sig_option_changed = Signal(str, object) sig_collapse = Signal() sig_free_memory = Signal() def __init__(self, parent, options_button=None, plugin_actions=[]): QWidget.__init__(self, parent) self.shellwidget = None self.is_visible = True self.setup_in_progress = None # Remote dict editor settings self.check_all = None self.exclude_private = None self.exclude_uppercase = None self.exclude_capitalized = None self.exclude_unsupported = None self.excluded_names = None self.minmax = None # Other setting self.dataframe_format = None self.editor = None self.exclude_private_action = None self.exclude_uppercase_action = None self.exclude_capitalized_action = None self.exclude_unsupported_action = None self.options_button = options_button self.actions = None self.plugin_actions = plugin_actions self.filename = None def setup(self, check_all=None, exclude_private=None, exclude_uppercase=None, exclude_capitalized=None, exclude_unsupported=None, excluded_names=None, minmax=None, dataframe_format=None): """ Setup the namespace browser with provided settings. Args: dataframe_format (string): default floating-point format for DataFrame editor """ assert self.shellwidget is not None self.check_all = check_all self.exclude_private = exclude_private self.exclude_uppercase = exclude_uppercase self.exclude_capitalized = exclude_capitalized self.exclude_unsupported = exclude_unsupported self.excluded_names = excluded_names self.minmax = minmax self.dataframe_format = dataframe_format if self.editor is not None: self.editor.setup_menu(minmax) self.editor.set_dataframe_format(dataframe_format) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.refresh_table() return self.editor = RemoteCollectionsEditorTableView( self, data=None, minmax=minmax, shellwidget=self.shellwidget, dataframe_format=dataframe_format) self.editor.sig_option_changed.connect(self.sig_option_changed.emit) self.editor.sig_files_dropped.connect(self.import_data) self.editor.sig_free_memory.connect(self.sig_free_memory.emit) self.setup_option_actions(exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported) # Setup toolbar layout. 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() # Setup layout. layout = create_plugin_layout(self.tools_layout, self.editor) self.setLayout(layout) self.sig_option_changed.connect(self.option_changed) def set_shellwidget(self, shellwidget): """Bind shellwidget instance to namespace browser""" self.shellwidget = shellwidget shellwidget.set_namespacebrowser(self) def get_actions(self): """Get actions of the widget.""" return self.actions def setup_toolbar(self): """Setup toolbar""" load_button = create_toolbutton(self, text=_('Import data'), icon=ima.icon('fileimport'), triggered=lambda: self.import_data()) self.save_button = create_toolbutton(self, text=_("Save data"), icon=ima.icon('filesave'), triggered=lambda: self.save_data(self.filename)) self.save_button.setEnabled(False) save_as_button = create_toolbutton(self, text=_("Save data as..."), icon=ima.icon('filesaveas'), triggered=self.save_data) reset_namespace_button = create_toolbutton( self, text=_("Remove all variables"), icon=ima.icon('editdelete'), triggered=self.reset_namespace) return [load_button, self.save_button, save_as_button, reset_namespace_button] def setup_option_actions(self, exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.exclude_private_action = create_action(self, _("Exclude private references"), tip=_("Exclude references which name starts" " with an underscore"), toggled=lambda state: self.sig_option_changed.emit('exclude_private', state)) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action = create_action(self, _("Exclude all-uppercase references"), tip=_("Exclude references which name is uppercase"), toggled=lambda state: self.sig_option_changed.emit('exclude_uppercase', state)) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action = create_action(self, _("Exclude capitalized references"), tip=_("Exclude references which name starts with an " "uppercase character"), toggled=lambda state: self.sig_option_changed.emit('exclude_capitalized', state)) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action = create_action(self, _("Exclude unsupported data types"), tip=_("Exclude references to unsupported data types" " (i.e. which won't be handled/saved correctly)"), toggled=lambda state: self.sig_option_changed.emit('exclude_unsupported', state)) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.actions = [ self.exclude_private_action, self.exclude_uppercase_action, self.exclude_capitalized_action, self.exclude_unsupported_action] if is_module_installed('numpy'): self.actions.extend([MENU_SEPARATOR, self.editor.minmax_action]) self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: 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 option_changed(self, option, value): """Option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() self.refresh_table() def get_view_settings(self): """Return dict editor view settings""" settings = {} for name in REMOTE_SETTINGS: settings[name] = getattr(self, name) return settings def refresh_table(self): """Refresh variable table""" if self.is_visible and self.isVisible(): self.shellwidget.refresh_namespacebrowser() try: self.editor.resizeRowToContents() except TypeError: pass def process_remote_view(self, remote_view): """Process remote view""" if remote_view is not None: self.set_data(remote_view) def set_var_properties(self, properties): """Set properties of variables""" if properties is not None: self.editor.var_properties = properties def set_data(self, data): """Set data.""" if data != self.editor.model.get_data(): self.editor.set_data(data) self.editor.adjust_columns() def collapse(self): """Collapse.""" self.sig_collapse.emit() @Slot(bool) @Slot(list) def import_data(self, filenames=None): """Import data from text file.""" title = _("Import data") if filenames is None: if self.filename is None: basedir = getcwd_or_home() else: basedir = osp.dirname(self.filename) filenames, _selfilter = getopenfilenames(self, title, basedir, iofunctions.load_filters) if not filenames: return elif is_text_string(filenames): filenames = [filenames] for filename in filenames: self.filename = to_text_string(filename) ext = osp.splitext(self.filename)[1].lower() if ext not in iofunctions.load_funcs: buttons = QMessageBox.Yes | QMessageBox.Cancel answer = QMessageBox.question(self, title, _("<b>Unsupported file extension '%s'</b><br><br>" "Would you like to import it anyway " "(by selecting a known file format)?" ) % ext, buttons) if answer == QMessageBox.Cancel: return formats = list(iofunctions.load_extensions.keys()) item, ok = QInputDialog.getItem(self, title, _('Open file as:'), formats, 0, False) if ok: ext = iofunctions.load_extensions[to_text_string(item)] else: return load_func = iofunctions.load_funcs[ext] # 'import_wizard' (self.setup_io) if is_text_string(load_func): # Import data with import wizard error_message = None try: text, _encoding = encoding.read(self.filename) base_name = osp.basename(self.filename) editor = ImportWizard(self, text, title=base_name, varname=fix_reference_name(base_name)) if editor.exec_(): var_name, clip_data = editor.get_data() self.editor.new_value(var_name, clip_data) except Exception as error: error_message = str(error) else: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.load_data(self.filename, ext) self.shellwidget._kernel_reply = None QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical(self, title, _("<b>Unable to load '%s'</b>" "<br><br>Error message:<br>%s" ) % (self.filename, error_message)) self.refresh_table() @Slot() def reset_namespace(self): warning = CONF.get('ipython_console', 'show_reset_namespace_warning') self.shellwidget.reset_namespace(warning=warning, message=True) @Slot() def save_data(self, filename=None): """Save data""" if filename is None: filename = self.filename if filename is None: filename = getcwd_or_home() filename, _selfilter = getsavefilename(self, _("Save data"), filename, iofunctions.save_filters) if filename: self.filename = filename else: return False QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.save_namespace(self.filename) self.shellwidget._kernel_reply = None QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical(self, _("Save data"), _("<b>Unable to save current workspace</b>" "<br><br>Error message:<br>%s") % error_message) self.save_button.setEnabled(self.filename is not None)
class NamespaceBrowser(QWidget): """Namespace browser (global variables explorer widget)""" sig_option_changed = Signal(str, object) sig_collapse = Signal() sig_free_memory = Signal() def __init__(self, parent, options_button=None, plugin_actions=[]): QWidget.__init__(self, parent) self.shellwidget = None self.is_visible = True self.setup_in_progress = None # Remote dict editor settings self.check_all = None self.exclude_private = None self.exclude_uppercase = None self.exclude_capitalized = None self.exclude_unsupported = None self.excluded_names = None self.minmax = None # Other setting self.dataframe_format = None self.editor = None self.exclude_private_action = None self.exclude_uppercase_action = None self.exclude_capitalized_action = None self.exclude_unsupported_action = None self.options_button = options_button self.actions = None self.plugin_actions = plugin_actions self.filename = None def setup(self, check_all=None, exclude_private=None, exclude_uppercase=None, exclude_capitalized=None, exclude_unsupported=None, excluded_names=None, minmax=None, dataframe_format=None): """ Setup the namespace browser with provided settings. Args: dataframe_format (string): default floating-point format for DataFrame editor """ assert self.shellwidget is not None self.check_all = check_all self.exclude_private = exclude_private self.exclude_uppercase = exclude_uppercase self.exclude_capitalized = exclude_capitalized self.exclude_unsupported = exclude_unsupported self.excluded_names = excluded_names self.minmax = minmax self.dataframe_format = dataframe_format if self.editor is not None: self.editor.setup_menu(minmax) self.editor.set_dataframe_format(dataframe_format) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.refresh_table() return self.editor = RemoteCollectionsEditorTableView( self, data=None, minmax=minmax, shellwidget=self.shellwidget, dataframe_format=dataframe_format) self.editor.sig_option_changed.connect(self.sig_option_changed.emit) self.editor.sig_files_dropped.connect(self.import_data) self.editor.sig_free_memory.connect(self.sig_free_memory.emit) self.setup_option_actions(exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported) # Setup toolbar layout. 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() # Setup layout. layout = create_plugin_layout(self.tools_layout, self.editor) self.setLayout(layout) self.sig_option_changed.connect(self.option_changed) def set_shellwidget(self, shellwidget): """Bind shellwidget instance to namespace browser""" self.shellwidget = shellwidget shellwidget.set_namespacebrowser(self) def get_actions(self): """Get actions of the widget.""" return self.actions def setup_toolbar(self): """Setup toolbar""" load_button = create_toolbutton(self, text=_('Import data'), icon=ima.icon('fileimport'), triggered=lambda: self.import_data()) self.save_button = create_toolbutton( self, text=_("Save data"), icon=ima.icon('filesave'), triggered=lambda: self.save_data(self.filename)) self.save_button.setEnabled(False) save_as_button = create_toolbutton(self, text=_("Save data as..."), icon=ima.icon('filesaveas'), triggered=self.save_data) reset_namespace_button = create_toolbutton( self, text=_("Remove all variables"), icon=ima.icon('editdelete'), triggered=self.reset_namespace) return [ load_button, self.save_button, save_as_button, reset_namespace_button ] def setup_option_actions(self, exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported): """Setup the actions to show in the cog menu.""" self.setup_in_progress = True self.exclude_private_action = create_action( self, _("Exclude private references"), tip=_("Exclude references which name starts" " with an underscore"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_private', state)) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action = create_action( self, _("Exclude all-uppercase references"), tip=_("Exclude references which name is uppercase"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_uppercase', state)) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action = create_action( self, _("Exclude capitalized references"), tip=_("Exclude references which name starts with an " "uppercase character"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_capitalized', state)) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action = create_action( self, _("Exclude unsupported data types"), tip=_("Exclude references to unsupported data types" " (i.e. which won't be handled/saved correctly)"), toggled=lambda state: self.sig_option_changed.emit( 'exclude_unsupported', state)) self.exclude_unsupported_action.setChecked(exclude_unsupported) self.actions = [ self.exclude_private_action, self.exclude_uppercase_action, self.exclude_capitalized_action, self.exclude_unsupported_action ] if is_module_installed('numpy'): self.actions.extend([MENU_SEPARATOR, self.editor.minmax_action]) self.setup_in_progress = False def setup_options_button(self): """Add the cog menu button to the toolbar.""" if not self.options_button: 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 option_changed(self, option, value): """Option has changed""" setattr(self, to_text_string(option), value) self.shellwidget.set_namespace_view_settings() self.refresh_table() def get_view_settings(self): """Return dict editor view settings""" settings = {} for name in REMOTE_SETTINGS: settings[name] = getattr(self, name) return settings def refresh_table(self): """Refresh variable table""" if self.is_visible and self.isVisible(): self.shellwidget.refresh_namespacebrowser() try: self.editor.resizeRowToContents() except TypeError: pass def process_remote_view(self, remote_view): """Process remote view""" if remote_view is not None: self.set_data(remote_view) def set_var_properties(self, properties): """Set properties of variables""" if properties is not None: self.editor.var_properties = properties def set_data(self, data): """Set data.""" if data != self.editor.model.get_data(): self.editor.set_data(data) self.editor.adjust_columns() def collapse(self): """Collapse.""" self.sig_collapse.emit() @Slot(bool) @Slot(list) def import_data(self, filenames=None): """Import data from text file.""" title = _("Import data") if filenames is None: if self.filename is None: basedir = getcwd_or_home() else: basedir = osp.dirname(self.filename) filenames, _selfilter = getopenfilenames(self, title, basedir, iofunctions.load_filters) if not filenames: return elif is_text_string(filenames): filenames = [filenames] for filename in filenames: self.filename = to_text_string(filename) ext = osp.splitext(self.filename)[1].lower() if ext not in iofunctions.load_funcs: buttons = QMessageBox.Yes | QMessageBox.Cancel answer = QMessageBox.question( self, title, _("<b>Unsupported file extension '%s'</b><br><br>" "Would you like to import it anyway " "(by selecting a known file format)?") % ext, buttons) if answer == QMessageBox.Cancel: return formats = list(iofunctions.load_extensions.keys()) item, ok = QInputDialog.getItem(self, title, _('Open file as:'), formats, 0, False) if ok: ext = iofunctions.load_extensions[to_text_string(item)] else: return load_func = iofunctions.load_funcs[ext] # 'import_wizard' (self.setup_io) if is_text_string(load_func): # Import data with import wizard error_message = None try: text, _encoding = encoding.read(self.filename) base_name = osp.basename(self.filename) editor = ImportWizard( self, text, title=base_name, varname=fix_reference_name(base_name)) if editor.exec_(): var_name, clip_data = editor.get_data() self.editor.new_value(var_name, clip_data) except Exception as error: error_message = str(error) else: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.load_data(self.filename, ext) self.shellwidget._kernel_reply = None QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical( self, title, _("<b>Unable to load '%s'</b>" "<br><br>Error message:<br>%s") % (self.filename, error_message)) self.refresh_table() @Slot() def reset_namespace(self): warning = CONF.get('ipython_console', 'show_reset_namespace_warning') self.shellwidget.reset_namespace(warning=warning, message=True) @Slot() def save_data(self, filename=None): """Save data""" if filename is None: filename = self.filename if filename is None: filename = getcwd_or_home() filename, _selfilter = getsavefilename(self, _("Save data"), filename, iofunctions.save_filters) if filename: self.filename = filename else: return False QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() error_message = self.shellwidget.save_namespace(self.filename) self.shellwidget._kernel_reply = None QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical( self, _("Save data"), _("<b>Unable to save current workspace</b>" "<br><br>Error message:<br>%s") % error_message) self.save_button.setEnabled(self.filename is not None)
class CodePreviewWidget(QWidget): def __init__(self, main_window, flow): super(CodePreviewWidget, self).__init__() self.codes = { # structure: # # <node>: { # <object>: { (the node instance or some widget instance) # 'title': str, (for faster widget building) # 'original': str, (original code comes from Node.__class_codes__) # 'edited': str(None), (initially None) # } # } } self.node = None # currently displayed node self.current_obj = None # reference to the currently shown/edited object self.radio_buttons = [] # the radio buttons to select a source flow.nodes_selection_changed.connect(self.set_selected_nodes) self.text_edit = CodeEditorWidget(main_window.theme) self.setup_ui() self.set_node(None) def setup_ui(self): secondary_layout = QHBoxLayout() # class radio buttons widget self.class_selection_layout = QHBoxLayout() # QGridLayout() secondary_layout.addLayout(self.class_selection_layout) self.class_selection_layout.setAlignment(Qt.AlignLeft) # secondary_layout.setAlignment(self.class_selection_layout, Qt.AlignLeft) # edit source code buttons self.edit_code_button = QPushButton('edit') self.edit_code_button.setProperty('class', 'small_button') self.edit_code_button.setMaximumWidth(100) self.edit_code_button.clicked.connect(self.edit_code_button_clicked) self.override_code_button = QPushButton('override') self.override_code_button.setProperty('class', 'small_button') self.override_code_button.setMaximumWidth(100) self.override_code_button.setEnabled(False) self.override_code_button.clicked.connect( self.override_code_button_clicked) self.reset_code_button = QPushButton('reset') self.reset_code_button.setProperty('class', 'small_button') self.reset_code_button.setMaximumWidth(206) self.reset_code_button.setEnabled(False) self.reset_code_button.clicked.connect(self.reset_code_button_clicked) edit_buttons_layout = QHBoxLayout() # edit_buttons_layout.addWidget(self.highlight_code_button) edit_buttons_layout.addWidget(self.edit_code_button) edit_buttons_layout.addWidget(self.override_code_button) edit_buttons_layout.addWidget(self.reset_code_button) # edit_buttons_layout.addWidget(self.highlight_code_button) secondary_layout.addLayout(edit_buttons_layout) edit_buttons_layout.setAlignment(Qt.AlignRight) main_layout = QVBoxLayout() main_layout.addLayout(secondary_layout) main_layout.addWidget(self.text_edit) self.setLayout(main_layout) def set_selected_nodes(self, nodes): if len(nodes) == 0: self.set_node(None) else: self.set_node(nodes[-1]) def set_node(self, node): self.node = node if node is None or node.__class_codes__ is None: # no node selected / only imported nodes have __class_codes__ # clear view self.text_edit.set_code('') self.edit_code_button.setEnabled(False) self.override_code_button.setEnabled(False) self.reset_code_button.setEnabled(False) self.clear_class_layout() return if node not in self.codes: # node not yet registered # save class sources for all objects (node, main_widget, custom input widgets) # saving it in this structure enables easy view updates and src code overrides node_objects = { node: { 'title': 'node', 'original cls': node.__class_codes__['node cls'], 'original mod': node.__class_codes__['node mod'], 'modified cls': None, }, } if node.main_widget(): node_objects[node.main_widget()] = { 'title': 'main widget', 'original cls': node.__class_codes__['main widget cls'], 'original mod': node.__class_codes__['main widget mod'], 'modified cls': None } for i in range(len(node.inputs)): iw = node.item.inputs[i].widget if iw: # find code code = '' for name, cls in node.input_widget_classes.items(): if cls == iw.__class__: code = node.__class_codes__[ 'custom input widgets'][name]['cls'] break else: # no break -> no custom widget (builtin widget) -> ignore continue node_objects[iw] = { 'title': f'inp {i}', 'original cls': code, 'modified cls': None, } self.codes[node] = node_objects self.rebuild_class_selection(node) self.update_edit_statuses() self.edit_code_button.setEnabled(True) self.update_code(node, node) def update_code(self, node, obj): """ Updates the 'modified' field in the nodes dict """ self.disable_editing() self.text_edit.disable_highlighting() self.current_obj = obj orig = self.codes[node][obj]['original cls'] modf = self.codes[node][obj]['modified cls'] if modf: code = modf self.reset_code_button.setEnabled(True) else: code = orig self.reset_code_button.setEnabled(False) self.text_edit.set_code(code) def rebuild_class_selection(self, node): self.clear_class_layout() self.radio_buttons.clear() for obj, d in self.codes[node].items(): rb = LinkedRadioButton(d['title'], node, obj) rb.toggled.connect(self.class_RB_toggled) self.class_selection_layout.addWidget(rb) self.radio_buttons.append(rb) self.radio_buttons[0].setChecked(True) def clear_class_layout(self): # clear layout for i in range(self.class_selection_layout.count()): item = self.class_selection_layout.itemAt(0) widget = item.widget() widget.hide() self.class_selection_layout.removeItem(item) def update_edit_statuses(self): """ Draws radio buttons referring to modified objects bold """ for b in self.radio_buttons: if self.codes[b.node][b.obj]['modified cls']: # o.setStyleSheet('color: #3B9CD9;') f = b.font() f.setBold(True) b.setFont(f) else: # o.setStyleSheet('color: white;') f = b.font() f.setBold(False) b.setFont(f) def class_RB_toggled(self, checked): if checked: self.update_code(self.sender().node, self.sender().obj) def edit_code_button_clicked(self): if not EditSrcCodeInfoDialog.dont_show_again: info_dialog = EditSrcCodeInfoDialog(self) accepted = info_dialog.exec_() if not accepted: return self.enable_editing() def enable_editing(self): self.text_edit.enable_editing() self.override_code_button.setEnabled(True) def disable_editing(self): self.text_edit.disable_editing() self.override_code_button.setEnabled(False) def override_code_button_clicked(self): new_code = self.text_edit.get_code() SrcCodeUpdater.override_code( obj=self.current_obj, new_class_src=new_code, ) self.codes[self.node][self.current_obj]['modified cls'] = new_code self.disable_editing() self.reset_code_button.setEnabled(True) self.update_edit_statuses() def reset_code_button_clicked(self): code = self.codes[self.node][self.current_obj]['original cls'] SrcCodeUpdater.override_code(obj=self.current_obj, new_class_src=code) self.codes[self.node][self.current_obj]['modified cls'] = None self.update_code(self.node, self.current_obj) self.update_edit_statuses()