Пример #1
0
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
Пример #2
0
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)
Пример #3
0
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
Пример #4
0
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()
Пример #5
0
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
Пример #6
0
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()
Пример #7
0
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)
Пример #8
0
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()
Пример #9
0
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()
Пример #10
0
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)
Пример #11
0
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)
Пример #12
0
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()