Beispiel #1
0
    def __init__(self, parent):
        QTableView.__init__(self, parent)
        self._model = None

        # Setting up actions
        self.date_dayfirst_action = create_action(self, "dayfirst",
            triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=True))
        self.date_monthfirst_action = create_action(self, "monthfirst",
            triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=False))
        self.perc_action = create_action(self, "perc",
            triggered=ft_partial(self.parse_to_type, atype="perc"))
        self.acc_action = create_action(self, "account",
            triggered=ft_partial(self.parse_to_type, atype="account"))
        self.str_action = create_action(self, "unicode",
            triggered=ft_partial(self.parse_to_type, atype="unicode"))
        self.int_action = create_action(self, "int",
            triggered=ft_partial(self.parse_to_type, atype="int"))
        self.float_action = create_action(self, "float",
            triggered=ft_partial(self.parse_to_type, atype="float"))

        # Setting up menus
        self.date_menu = QMenu()
        self.date_menu.setTitle("Date")
        add_actions( self.date_menu, (self.date_dayfirst_action,
                                      self.date_monthfirst_action))
        self.parse_menu = QMenu(self)
        self.parse_menu.addMenu(self.date_menu)
        add_actions( self.parse_menu, (self.perc_action, self.acc_action))
        self.parse_menu.setTitle("String to")
        self.opt_menu = QMenu(self)
        self.opt_menu.addMenu(self.parse_menu)
        add_actions( self.opt_menu, (self.str_action, self.int_action,
                                     self.float_action))
Beispiel #2
0
    def __init__(self, parent, actions=None, menu=None,
                 corner_widgets=None, menu_use_tooltips=False):
        QTabWidget.__init__(self, parent)
        self.setUsesScrollButtons(True)

        # To style tabs on Mac
        if sys.platform == 'darwin':
            self.setObjectName('plugin-tab')

        self.corner_widgets = {}
        self.menu_use_tooltips = menu_use_tooltips
        
        if menu is None:
            self.menu = QMenu(self)
            if actions:
                add_actions(self.menu, actions)
        else:
            self.menu = menu
            
        # Corner widgets
        if corner_widgets is None:
            corner_widgets = {}
        corner_widgets.setdefault(Qt.TopLeftCorner, [])
        corner_widgets.setdefault(Qt.TopRightCorner, [])
        self.browse_button = create_toolbutton(self,
                                          icon=ima.icon('browse_tab'),
                                          tip=_("Browse tabs"))
        self.browse_tabs_menu = QMenu(self)
        self.browse_button.setMenu(self.browse_tabs_menu)
        self.browse_button.setPopupMode(self.browse_button.InstantPopup)
        self.browse_tabs_menu.aboutToShow.connect(self.update_browse_tabs_menu)
        corner_widgets[Qt.TopLeftCorner] += [self.browse_button]

        self.set_corner_widgets(corner_widgets)
Beispiel #3
0
 def get_plugin_actions(self):
     """Return a list of actions related to plugin"""
     quit_action = create_action(self, _("&Quit"),
                                 icon=ima.icon('exit'), 
                                 tip=_("Quit"),
                                 triggered=self.quit)
     self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q")
     run_action = create_action(self, _("&Run..."), None,
                         ima.icon('run_small'),
                         _("Run a Python script"),
                         triggered=self.run_script)
     environ_action = create_action(self,
                         _("Environment variables..."),
                         icon=ima.icon('environ'),
                         tip=_("Show and edit environment variables"
                                     " (for current session)"),
                         triggered=self.show_env)
     syspath_action = create_action(self,
                         _("Show sys.path contents..."),
                         icon=ima.icon('syspath'),
                         tip=_("Show (read-only) sys.path"),
                         triggered=self.show_syspath)
     buffer_action = create_action(self,
                         _("Buffer..."), None,
                         tip=_("Set maximum line count"),
                         triggered=self.change_max_line_count)
     exteditor_action = create_action(self,
                         _("External editor path..."), None, None,
                         _("Set external editor executable path"),
                         triggered=self.change_exteditor)
     wrap_action = create_action(self,
                         _("Wrap lines"),
                         toggled=self.toggle_wrap_mode)
     wrap_action.setChecked(self.get_option('wrap'))
     calltips_action = create_action(self, _("Display balloon tips"),
         toggled=self.toggle_calltips)
     calltips_action.setChecked(self.get_option('calltips'))
     codecompletion_action = create_action(self,
                                       _("Automatic code completion"),
                                       toggled=self.toggle_codecompletion)
     codecompletion_action.setChecked(self.get_option('codecompletion/auto'))
     codecompenter_action = create_action(self,
                                 _("Enter key selects completion"),
                                 toggled=self.toggle_codecompletion_enter)
     codecompenter_action.setChecked(self.get_option(
                                                 'codecompletion/enter_key'))
     
     option_menu = QMenu(_('Internal console settings'), self)
     option_menu.setIcon(ima.icon('tooloptions'))
     add_actions(option_menu, (buffer_action, wrap_action,
                               calltips_action, codecompletion_action,
                               codecompenter_action, exteditor_action))
                 
     plugin_actions = [None, run_action, environ_action, syspath_action,
                       option_menu, None, quit_action]
     
     # Add actions to context menu
     add_actions(self.shell.menu, plugin_actions)
     
     return plugin_actions
Beispiel #4
0
 def tableMenu(self, event):
     """Right click menu for table, can plot or print selected logs"""
     menu = QMenu(self)
     plotAction = menu.addAction("Plot selected")
     plotAction.triggered.connect(self.presenter.new_plot_logs)
     plotAction = menu.addAction("Print selected")
     plotAction.triggered.connect(self.presenter.print_selected_logs)
     menu.exec_(event.globalPos())
Beispiel #5
0
 def context_menu_requested(self, event):
     """Popup context menu."""
     if self.fig:
         pos = QPoint(event.x(), event.y())
         context_menu = QMenu(self)
         context_menu.addAction(ima.icon('editcopy'), "Copy Image",
                                self.copy_figure,
                                QKeySequence(
                                    get_shortcut('plots', 'copy')))
         context_menu.popup(self.mapToGlobal(pos))
Beispiel #6
0
    def _display_roi_context_menu(self, roi_index):

        def delete_roi(event):
            self._dc.remove_subset_group(self._dc.subset_groups[roi_index])

        context_menu = QMenu()
        action = QAction("Delete ROI", context_menu)
        action.triggered.connect(delete_roi)
        context_menu.addAction(action)
        pos = self._viewer.mapToParent(QCursor().pos())
        context_menu.exec_(pos)
 def _add_axes_scale_menu(self, menu):
     """Add the Axes scale options menu to the given menu"""
     axes_menu = QMenu("Axes", menu)
     axes_actions = QActionGroup(axes_menu)
     current_scale_types = self._get_axes_scale_types()
     for label, scale_types in iteritems(AXES_SCALE_MENU_OPTS):
         action = axes_menu.addAction(label, partial(self._quick_change_axes, scale_types))
         if current_scale_types == scale_types:
             action.setCheckable(True)
             action.setChecked(True)
         axes_actions.addAction(action)
     menu.addMenu(axes_menu)
Beispiel #8
0
 def _make_export_button(self):
     """
     Makes the export button menu, containing a list of export
     types
     :return: The export button menu
     """
     export_button = QPushButton("Export")
     export_menu = QMenu()
     for text, extension in EXPORT_TYPES:
         export_menu.addAction(text, lambda ext=extension: self.presenter.export_plots_called(ext))
     export_button.setMenu(export_menu)
     return export_button
Beispiel #9
0
    def _init_ui(self):
        loadUi(os.path.abspath(
            os.path.join(os.path.dirname(__file__),
                         ".", "model_editor.ui")), self)

        # Populate the add mode button with a dropdown containing available
        # fittable model objects
        self.add_model_button.setPopupMode(QToolButton.InstantPopup)
        models_menu = QMenu(self.add_model_button)
        self.add_model_button.setMenu(models_menu)

        for k, v in MODELS.items():
            action = QAction(k, models_menu)
            action.triggered.connect(lambda x, m=v: self._add_fittable_model(m))
            models_menu.addAction(action)

        self.fit_model_thread = None

        # Initially hide the model editor tools until user has selected an
        # editable model spectrum object
        self.editor_holder_widget.setHidden(True)
        self.setup_holder_widget.setHidden(False)

        self.equation_edit_button.clicked.connect(
            self._on_equation_edit_button_clicked)
        self.new_model_button.clicked.connect(self._on_create_new_model)
        self.remove_model_button.clicked.connect(self._on_remove_model)

        self.advanced_settings_button.clicked.connect(
            lambda: ModelAdvancedSettingsDialog(self, self).exec())

        self.save_model_button.clicked.connect(self._on_save_model)
        self.load_model_button.clicked.connect(self._on_load_from_file)

        self._data_item_proxy_model = DataItemProxyModel()
        self._data_item_proxy_model.setSourceModel(self.hub.model)
        self.data_selection_combo.setModel(self._data_item_proxy_model)
        self.data_selection_combo.currentIndexChanged.connect(self._redraw_model)

        # When a plot data item is select, get its model editor model
        # representation
        self.hub.workspace.current_selected_changed.connect(
            self._on_plot_item_selected)

        # When the plot window changes, reset model editor
        self.hub.workspace.mdi_area.subWindowActivated.connect(self._on_new_plot_activated)

        # Listen for when data items are added to internal model
        self.hub.model.data_added.connect(self._on_data_item_added)

        # Connect the fit model button
        self.fit_button.clicked.connect(self._on_fit_clicked)
Beispiel #10
0
 def openWidgetMenu(self,position):
     indexes = self.ui.treeWidget_2.selectedIndexes()
     item = self.ui.treeWidget_2.itemAt(position)
     if item == None:
         return
     #item = self.ui.listWidget.itemAt(position)
     if len(indexes) > 0:
         menu = QMenu()
         menu.addAction(QAction("Delete", menu,checkable = True))#This function is perhaps useless
         #menu.triggered.connect(self.eraseItem)
         item = self.ui.treeWidget_2.itemAt(position)
         #collec = str(item.text())
         menu.triggered.connect(lambda action: self.ListMethodSelected(action, item))
     menu.exec_(self.ui.treeWidget_2.viewport().mapToGlobal(position))
Beispiel #11
0
    def context_menu_requested(self, event):
        """ """
        pos = QPoint(event.x(), event.y())
        menu = QMenu(self)

        actions = []
        action_title = create_action(self, _('Go to step: '), icon=QIcon())
        action_title.setDisabled(True)
        actions.append(action_title)
#        actions.append(create_action(self, _(': '), icon=QIcon()))

        add_actions(menu, actions)

        menu.popup(self.mapToGlobal(pos))
Beispiel #12
0
 def contextMenuEvent(self, event):
     menu = QMenu(self)
     actions = [self.pageAction(QWebEnginePage.Back),
                self.pageAction(QWebEnginePage.Forward), None,
                self.pageAction(QWebEnginePage.SelectAll),
                self.pageAction(QWebEnginePage.Copy), None,
                self.zoom_in_action, self.zoom_out_action]
     if DEV and not WEBENGINE:
         settings = self.page().settings()
         settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True)
         actions += [None, self.pageAction(QWebEnginePage.InspectElement)]
     add_actions(menu, actions)
     menu.popup(event.globalPos())
     event.accept()
Beispiel #13
0
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.new_project_action = create_action(self, _("New Project..."), triggered=self.create_new_project)
        self.open_project_action = create_action(self, _("Open Project..."), triggered=lambda v: self.open_project())
        self.close_project_action = create_action(self, _("Close Project"), triggered=self.close_project)
        self.clear_recent_projects_action = create_action(
            self, _("Clear this list"), triggered=self.clear_recent_projects
        )
        self.edit_project_preferences_action = create_action(
            self, _("Project Preferences"), triggered=self.edit_project_preferences
        )
        self.recent_project_menu = QMenu(_("Recent Projects"), self)
        explorer_action = self.toggle_view_action

        self.main.projects_menu_actions += [
            self.new_project_action,
            None,
            self.open_project_action,
            self.close_project_action,
            None,
            self.recent_project_menu,
            explorer_action,
        ]

        self.setup_menu_actions()
        return []
Beispiel #14
0
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.new_project_action = create_action(self,
                                    _("New Project..."),
                                    triggered=self.create_new_project)
        self.open_project_action = create_action(self,
                                    _("Open Project..."),
                                    triggered=lambda v: self.open_project())
        self.close_project_action = create_action(self,
                                    _("Close Project"),
                                    triggered=self.close_project)
        self.delete_project_action = create_action(self,
                                    _("Delete Project"),
                                    triggered=self._delete_project)
        self.clear_recent_projects_action =\
            create_action(self, _("Clear this list"),
                          triggered=self.clear_recent_projects)
        self.edit_project_preferences_action =\
            create_action(self, _("Project Preferences"),
                          triggered=self.edit_project_preferences)
        self.recent_project_menu = QMenu(_("Recent Projects"), self)

        if self.main is not None:
            self.main.projects_menu_actions += [self.new_project_action,
                                                MENU_SEPARATOR,
                                                self.open_project_action,
                                                self.close_project_action,
                                                self.delete_project_action,
                                                MENU_SEPARATOR,
                                                self.recent_project_menu,
                                                self.toggle_view_action]

        self.setup_menu_actions()
        return []
Beispiel #15
0
 def setup_context_menu(self):
     """Setup shell context menu"""
     self.menu = QMenu(self)
     self.cut_action = create_action(self, _("Cut"),
                                     shortcut=keybinding('Cut'),
                                     icon=ima.icon('editcut'),
                                     triggered=self.cut)
     self.copy_action = create_action(self, _("Copy"),
                                      shortcut=keybinding('Copy'),
                                      icon=ima.icon('editcopy'),
                                      triggered=self.copy)
     paste_action = create_action(self, _("Paste"),
                                  shortcut=keybinding('Paste'),
                                  icon=ima.icon('editpaste'),
                                  triggered=self.paste)
     save_action = create_action(self, _("Save history log..."),
                                 icon=ima.icon('filesave'),
                                 tip=_("Save current history log (i.e. all "
                                       "inputs and outputs) in a text file"),
                                 triggered=self.save_historylog)
     self.delete_action = create_action(self, _("Delete"),
                                 shortcut=keybinding('Delete'),
                                 icon=ima.icon('editdelete'),
                                 triggered=self.delete)
     selectall_action = create_action(self, _("Select All"),
                                 shortcut=keybinding('SelectAll'),
                                 icon=ima.icon('selectall'),
                                 triggered=self.selectAll)
     add_actions(self.menu, (self.cut_action, self.copy_action,
                             paste_action, self.delete_action, None,
                             selectall_action, None, save_action) )
Beispiel #16
0
    def _make_context_menu(self):
        """
        Makes the context menu with options relating to plots
        :return: The context menu, and export sub-menu with a list of
                 export types
        """
        context_menu = QMenu()
        context_menu.addAction("Show", self.presenter.show_multiple_selected)
        context_menu.addAction("Hide", self.presenter.hide_selected_plots)
        context_menu.addAction("Delete", self.presenter.close_action_called)
        context_menu.addAction("Rename", self.rename_selected_in_context_menu)

        export_menu = context_menu.addMenu("Export")
        for text, extension in EXPORT_TYPES:
            export_menu.addAction(text, lambda ext=extension: self.presenter.export_plots_called(ext))

        return context_menu, export_menu
Beispiel #17
0
 def build_context_menu(self, index):
     """Build context menu for test item that given index points to."""
     contextMenu = QMenu(self)
     if self.isExpanded(index):
         menuItem = create_action(self, _('Collapse'),
                                  triggered=lambda: self.collapse(index))
     else:
         menuItem = create_action(self, _('Expand'),
                                  triggered=lambda: self.expand(index))
         menuItem.setEnabled(self.model().hasChildren(index))
     contextMenu.addAction(menuItem)
     menuItem = create_action(
             self, _('Go to definition'),
             triggered=lambda: self.go_to_test_definition(index))
     test_location = self.model().data(index, Qt.UserRole)
     menuItem.setEnabled(test_location[0] is not None)
     contextMenu.addAction(menuItem)
     return contextMenu
Beispiel #18
0
    def setup(self, name_filters=['*.py', '*.pyw'], show_all=False):
        """Setup tree widget"""
        self.setup_view()

        self.set_name_filters(name_filters)
        self.show_all = show_all
        
        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()
Beispiel #19
0
    def context_menu_requested(self, event):
        """ Custom context menu"""
        index = self.current_index
        model_index = self.proxy_model.mapToSource(index)
        row = self.source_model.row(model_index.row())

        name, license_ = row[const.NAME], row[const.LICENSE]
        pos = QPoint(event.x(), event.y())
        self._menu = QMenu(self)

        metadata = self._parent.get_package_metadata(name)
        pypi = metadata["pypi"]
        home = metadata["home"]
        dev = metadata["dev"]
        docs = metadata["docs"]

        q_pypi = QIcon(get_image_path("python.png"))
        q_home = QIcon(get_image_path("home.png"))
        q_docs = QIcon(get_image_path("conda_docs.png"))

        if "git" in dev:
            q_dev = QIcon(get_image_path("conda_github.png"))
        elif "bitbucket" in dev:
            q_dev = QIcon(get_image_path("conda_bitbucket.png"))
        else:
            q_dev = QIcon()

        if "mit" in license_.lower():
            lic = "http://opensource.org/licenses/MIT"
        elif "bsd" == license_.lower():
            lic = "http://opensource.org/licenses/BSD-3-Clause"
        else:
            lic = None

        actions = []

        if license_ != "":
            actions.append(
                create_action(self, _("License: " + license_), icon=QIcon(), triggered=lambda: self.open_url(lic))
            )
            actions.append(None)

        if pypi != "":
            actions.append(
                create_action(self, _("Python Package Index"), icon=q_pypi, triggered=lambda: self.open_url(pypi))
            )
        if home != "":
            actions.append(create_action(self, _("Homepage"), icon=q_home, triggered=lambda: self.open_url(home)))
        if docs != "":
            actions.append(create_action(self, _("Documentation"), icon=q_docs, triggered=lambda: self.open_url(docs)))
        if dev != "":
            actions.append(create_action(self, _("Development"), icon=q_dev, triggered=lambda: self.open_url(dev)))
        if len(actions):
            add_actions(self._menu, actions)
            self._menu.popup(self.viewport().mapToGlobal(pos))
Beispiel #20
0
    def _dict_to_menu(self, menu_dict,  menu_widget=None):
        '''Stolen shamelessly from specviz. Thanks!'''
        if not menu_widget:
            menu_widget = QMenu()
        for k, v in menu_dict.items():
            if isinstance(v, dict):
                new_menu = menu_widget.addMenu(k)
                self._dict_to_menu(v, menu_widget=new_menu)
            else:
                act = QAction(k, menu_widget)

                if isinstance(v, list):
                    if v[0] == 'checkable':
                        v = v[1]
                        act.setCheckable(True)
                        act.setChecked(False)

                act.triggered.connect(v)
                menu_widget.addAction(act)
        return menu_widget
    def _show_context_menu(self, event):
        """Display a relevant context menu on the canvas
        :param event: The MouseEvent that generated this call
        """
        if not event.inaxes:
            # the current context menus are ony relevant for axes
            return

        fig_type = figure_type(self.canvas.figure)
        if fig_type == FigureType.Empty or fig_type == FigureType.Image:
            # Fitting or changing scale types does not make sense in
            # these cases
            return

        menu = QMenu()
        if self.fit_browser.tool is not None:
            self.fit_browser.add_to_menu(menu)
            menu.addSeparator()
        self._add_axes_scale_menu(menu)
        menu.exec_(QCursor.pos())
Beispiel #22
0
    def on_mouse_press_event(self, event):
        """
        Event handling for mouse press action
        Args:
            event:

        Returns:

        """
        # get the button and position information.
        curr_x = event.xdata
        curr_y = event.ydata
        if curr_x is None or curr_y is None:
            # outside of canvas
            return

        button = event.button
        if button == 1:
            # left button: no operation
            pass

        elif button == 3:
            # right button:
            # Pop-out menu
            self.menu = QMenu(self)

            if self.get_canvas().is_legend_on:
                # figure has legend: remove legend
                action1 = QAction('Hide legend', self)
                action1.triggered.connect(self._myCanvas.hide_legend)

                action2 = QAction('Legend font larger', self)
                action2.triggered.connect(self._myCanvas.increase_legend_font_size)

                action3 = QAction('Legend font smaller', self)
                action3.triggered.connect(self._myCanvas.decrease_legend_font_size)

                self.menu.addAction(action2)
                self.menu.addAction(action3)

            else:
                # figure does not have legend: add legend
                action1 = QAction('Show legend', self)
                action1.triggered.connect(self._myCanvas.show_legend)

            self.menu.addAction(action1)

            # pop up menu
            self.menu.popup(QCursor.pos())
        # END-IF-ELSE

        return
Beispiel #23
0
    def __init__(self, parent=None, init_channel=None):
        QLineEdit.__init__(self, parent)
        PyDMWritableWidget.__init__(self, init_channel=init_channel)
        self.app = QApplication.instance()
        self._display = None
        self._scale = 1

        self.returnPressed.connect(self.send_value)
        self.unitMenu = QMenu('Convert Units', self)
        self.create_unit_options()
        self._display_format_type = self.DisplayFormat.Default
        self._string_encoding = "utf_8"
        if utilities.is_pydm_app():
            self._string_encoding = self.app.get_string_encoding()
Beispiel #24
0
 def context_menu(self):
     try:
         menu = super(PyDMRelatedDisplayButton, self).context_menu()
     except:
         menu = QMenu(self)
     if len(menu.findChildren(QAction)) > 0:
         menu.addSeparator()
     menu.addAction(self.open_in_new_window_action)
     return menu
Beispiel #25
0
 def _rebuild_menu(self):
     if not any(self._filenames):
         self._filenames = []
     if not any(self._titles):
         self._titles = []
     if len(self._filenames) == 0:
         self.setEnabled(False)
     if len(self._filenames) <= 1:
         self.setMenu(None)
         self._menu_needs_rebuild = False
         return
     menu = QMenu(self)
     for i, filename in enumerate(self._filenames):
         if i >= len(self._titles):
             title = filename
         else:
             title = self._titles[i]
         action = menu.addAction(title)
         macros = ""
         if i < len(self._macros):
             macros = self._macros[i]
         action.triggered.connect(partial(self.open_display, filename, macros, target=None))
     self.setMenu(menu)
     self._menu_needs_rebuild = False
Beispiel #26
0
    def __init__(self, main=None):
        """Bind widget to a QMainWindow instance."""
        super(PluginWidget, self).__init__(main)
        assert self.CONF_SECTION is not None

        self.dockwidget = None
        self.undocked_window = None

        # Check compatibility
        check_compatibility, message = self.check_compatibility()
        if not check_compatibility:
            self.show_compatibility_message(message)

        self.PLUGIN_PATH = os.path.dirname(inspect.getfile(self.__class__))
        self.main = main
        self.default_margins = None
        self.plugin_actions = None
        self.ismaximized = False
        self.isvisible = False

        # Options button and menu
        self.options_button = create_toolbutton(self, text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)
        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")
        self.options_menu = QMenu(self)

        # NOTE: Don't use the default option of CONF.get to assign a
        # None shortcut to plugins that don't have one. That will mess
        # the creation of our Keyboard Shortcuts prefs page
        try:
            self.shortcut = CONF.get('shortcuts', '_/switch to %s' %
                                     self.CONF_SECTION)
        except configparser.NoOptionError:
            pass

        # We decided to create our own toggle action instead of using
        # the one that comes with dockwidget because it's not possible
        # to raise and focus the plugin with it.
        self.toggle_view_action = None
    def setup_window(self):
        """ """
        self.close_action = create_action(self, _("&Quit"),
                                          triggered=self.close)
        self.file_menu_actions.append(self.close_action)
        self.file_menu = self.menuBar().addMenu(_("&File"))
        add_actions(self.file_menu, self.file_menu_actions)

        # Environments
        self.add_env_action = create_action(self, _("&Add"),
                                            triggered=self.add_env)
        self.clone_env_action = create_action(self, _("&Clone"),
                                              triggered=self.clone_env)
        self.remove_env_action = create_action(self, _("&Remove"),
                                               triggered=self.remove_env)
        self.envs_list_menu = QMenu(_('Environments'))
        self.envs_menu_actions = [self.add_env_action, self.clone_env_action,
                                  self.remove_env_action, None,
                                  self.envs_list_menu]
        self.envs_menu = self.menuBar().addMenu(_("&Environments"))
        add_actions(self.envs_menu, self.envs_menu_actions)
        self.update_env_menu()

        # Channels
        self.envs_menu = self.menuBar().addMenu(_("&Channels"))

        # Tools
        self.preferences_action = create_action(self,
                                                _("&Preferences"),
                                                triggered=self.preferences)
        self.tools_menu_actions.append(self.preferences_action)
        self.tools_menu = self.menuBar().addMenu(_("&Tools"))
        add_actions(self.tools_menu, self.tools_menu_actions)

        # Help
        self.report_action = create_action(self,
                                           _("&Report issue"),
                                           triggered=self.report_issue)
        self.about_action = create_action(self, _("&About"),
                                          triggered=self.about)
        self.help_menu_actions.append(self.report_action)
        self.help_menu_actions.append(self.about_action)
        self.help_menu = self.menuBar().addMenu(_("&Help"))
        add_actions(self.help_menu, self.help_menu_actions)

        self.setWindowIcon(get_icon('condapackages.png'))
Beispiel #28
0
    def __init__(self, parent):
        QTreeWidget.__init__(self, parent)
        self.setItemsExpandable(True)
        self.setColumnCount(1)
        self.itemActivated.connect(self.activated)
        self.itemClicked.connect(self.clicked)
        # Setup context menu
        self.menu = QMenu(self)
        self.collapse_all_action = None
        self.collapse_selection_action = None
        self.expand_all_action = None
        self.expand_selection_action = None
        self.common_actions = self.setup_common_actions()
        
        self.__expanded_state = None

        self.itemSelectionChanged.connect(self.item_selection_changed)
        self.item_selection_changed()
Beispiel #29
0
    def contextMenuEvent(self, event):
        index_clicked = self.indexAt(event.pos())
        actions = []
        self.popup_menu = QMenu(self)
        clear_all_breakpoints_action = create_action(self, 
            _("Clear breakpoints in all files"),
            triggered=lambda: self.clear_all_breakpoints.emit())
        actions.append(clear_all_breakpoints_action)
        if self.model.breakpoints:
            filename = self.model.breakpoints[index_clicked.row()][0]
            lineno = int(self.model.breakpoints[index_clicked.row()][1])
            # QAction.triggered works differently for PySide and PyQt
            if not API == 'pyside':
                clear_slot = lambda _checked, filename=filename, lineno=lineno: \
                    self.clear_breakpoint.emit(filename, lineno)
                edit_slot = lambda _checked, filename=filename, lineno=lineno: \
                    (self.edit_goto.emit(filename, lineno, ''),
                     self.set_or_edit_conditional_breakpoint.emit())
            else:
                clear_slot = lambda filename=filename, lineno=lineno: \
                    self.clear_breakpoint.emit(filename, lineno)
                edit_slot = lambda filename=filename, lineno=lineno: \
                    (self.edit_goto.emit(filename, lineno, ''),
                     self.set_or_edit_conditional_breakpoint.emit())

            clear_breakpoint_action = create_action(self,
                    _("Clear this breakpoint"),
                    triggered=clear_slot)
            actions.insert(0,clear_breakpoint_action)

            edit_breakpoint_action = create_action(self,
                    _("Edit this breakpoint"),
                    triggered=edit_slot)
            actions.append(edit_breakpoint_action)
        add_actions(self.popup_menu, actions)        
        self.popup_menu.popup(event.globalPos())
        event.accept()
Beispiel #30
0
 def open_header_menu(self, position):
     menu = QMenu(self)
     customize_header = menu.addAction("Customize header")
     customize_header.triggered.connect(self.open_header_dialog)
     menu.popup(self.table_header_view.viewport().mapToGlobal(position))
Beispiel #31
0
class GofRView(MplGraphicsView):
    """
    Graphics view for G(R)
    """

    def __init__(self, parent):
        """
        Initialization
        """
        MplGraphicsView.__init__(self, parent)

        # class variable containers
        self._grDict = dict()

        self._colorList = ['black', 'red', 'blue', 'green', 'brown', 'orange']
        self._colorIndex = 0

        # define the event handlers to the mouse actions
        self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event)
        # self._myCanvas.mpl_connect('button_release_event', self.on_mouse_release_event)
        # self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion)

        # class variable
        self._minY = None
        self._maxY = None

        # variable
        self._isLegendOn = False

        return

    def on_mouse_press_event(self, event):
        """
        Event handling for mouse press action
        Args:
            event:

        Returns:

        """
        # get the button and position information.
        curr_x = event.xdata
        curr_y = event.ydata
        if curr_x is None or curr_y is None:
            # outside of canvas
            return

        button = event.button
        if button == 1:
            # left button: no operation
            pass

        elif button == 3:
            # right button:
            # Pop-out menu
            self.menu = QMenu(self)

            if self.get_canvas().is_legend_on:
                # figure has legend: remove legend
                action1 = QAction('Hide legend', self)
                action1.triggered.connect(self._myCanvas.hide_legend)

                action2 = QAction('Legend font larger', self)
                action2.triggered.connect(self._myCanvas.increase_legend_font_size)

                action3 = QAction('Legend font smaller', self)
                action3.triggered.connect(self._myCanvas.decrease_legend_font_size)

                self.menu.addAction(action2)
                self.menu.addAction(action3)

            else:
                # figure does not have legend: add legend
                action1 = QAction('Show legend', self)
                action1.triggered.connect(self._myCanvas.show_legend)

            self.menu.addAction(action1)

            # pop up menu
            self.menu.popup(QCursor.pos())
        # END-IF-ELSE

        return

    def plot_gr(self, plot_key, ws_name, plotError=False, color='black', style='.', marker=None,
                alpha=1., label=None):
        """
        Plot G(r)
        :param plot_key: a key to the current plot
        :param vec_r: numpy array for R
        :param vec_g: numpy array for G(r)
        :param vec_e: numpy array for G(r) error
        :param plot_error:
        :param color:
        :param style:
        :param marker:
        :param alpha:
        :param label: label for the line to plot
        :return:
        """
        # q_min = 10., q_max = 50.
        # alpha = 1. - (q_now - q_min)/(q_max - q_min)
        if not label:
            label = str(plot_key)

        line_id = self.add_plot_1d(ws_name, wkspindex=0, marker=marker,
                                   color=color, line_style=style, alpha=alpha,
                                   label=label, x_label=r'r ($\AA$)', plotError=plotError)
        self._colorIndex += 1
        self._grDict[str(plot_key)] = line_id

        # check the low/max
        self.auto_scale_y()

    def _reset_y_range(self, vec_gr):
        """
        reset the Y range
        :param vec_gr:
        :return:
        """
        this_min = min(vec_gr)
        this_max = max(vec_gr)

        if self._minY is None or this_min < self._minY:
            self._minY = this_min

        if self._maxY is None or this_max > self._maxY:
            self._maxY = this_max

        return

    def _auto_rescale_y(self):
        """

        :return:
        """
        if self._minY is None or self._maxY is None:
            return

        delta_y = self._maxY - self._minY

        lower_boundary = self._minY - delta_y * 0.05
        upper_boundary = self._maxY + delta_y * 0.05

        self.setXYLimit(ymin=lower_boundary, ymax=upper_boundary)

        return

    def has_gr(self, gr_ws_name):
        """Check whether a plot of G(r) exists on the canvas
        :param gr_ws_name:
        :return:
        """
        return gr_ws_name in self._grDict

    def get_current_grs(self):
        """
        list all the G(r) plotted on the figure now
        :return:
        """
        return list(self._grDict.keys())

    def remove_gr(self, plot_key):
        """Remove a plotted G(r) from canvas
        :param plot_key: key to locate the 1-D plot on canvas
        :return: boolean, string (as error message)
        """
        # check
        assert isinstance(plot_key, str), 'Key for the plot must be a string but not %s.' % str(type(plot_key))
        if plot_key not in self._grDict:
            return False, 'Workspace %s cannot be found in GofR dictionary of canvas' % plot_key

        # get line ID
        line_id = self._grDict[plot_key]

        # remove from plot
        self.remove_line(line_id)

        # clean G(r) plot
        del self._grDict[plot_key]

        # reset min and max
        self._minY = None
        self._maxY = None

        return

    def reset_color(self):
        """Reset color scheme
        :return:
        """
        self._colorIndex = 0

    def reset(self):
        """
        Reset the canvas by deleting all lines and clean the dictionary
        Returns:
        """
        # remove all lines and reset marker/color default sequence
        self.clear_all_lines()
        self.reset_line_color_marker_index()
        self._colorIndex = 0

        # clean dictionary
        self._grDict.clear()

        return

    def update_gr(self, plot_key, ws_name, plotError=False):
        """update the value of an existing G(r)
        :param plot_key:
        :param vec_r:
        :param vec_g:
        :param vec_ge:
        :return:
        """
        # check existence
        if plot_key not in self._grDict:
            raise RuntimeError('Plot with key/workspace name {0} does not exist on plot.  Current plots are '
                               '{1}'.format(plot_key, list(self._grDict.keys())))

        # update
        line_key = self._grDict[plot_key]
        self.updateLine(ikey=line_key, wkspname=ws_name)

        # update range
        self.auto_scale_y()

        return
Beispiel #32
0
class Projects(SpyderPluginWidget):
    """Projects plugin."""

    CONF_SECTION = 'project_explorer'
    sig_pythonpath_changed = Signal()
    sig_project_created = Signal(object, object, object)
    sig_project_loaded = Signal(object)
    sig_project_closed = Signal(object)

    def __init__(self, parent=None):
        """Initialization."""
        SpyderPluginWidget.__init__(self, parent)

        self.explorer = ProjectExplorerWidget(
                            self,
                            name_filters=self.get_option('name_filters'),
                            show_all=self.get_option('show_all'),
                            show_hscrollbar=self.get_option('show_hscrollbar'),
                            options_button=self.options_button)

        layout = QVBoxLayout()
        layout.addWidget(self.explorer)
        self.setLayout(layout)

        self.recent_projects = self.get_option('recent_projects', default=[])
        self.current_active_project = None
        self.latest_project = None

        # Initialize plugin
        self.initialize_plugin()
        self.explorer.setup_project(self.get_active_project_path())

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _("Project explorer")

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.explorer.treewidget

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.new_project_action = create_action(self,
                                    _("New Project..."),
                                    triggered=self.create_new_project)
        self.open_project_action = create_action(self,
                                    _("Open Project..."),
                                    triggered=lambda v: self.open_project())
        self.close_project_action = create_action(self,
                                    _("Close Project"),
                                    triggered=self.close_project)
        self.delete_project_action = create_action(self,
                                    _("Delete Project"),
                                    triggered=self._delete_project)
        self.clear_recent_projects_action =\
            create_action(self, _("Clear this list"),
                          triggered=self.clear_recent_projects)
        self.edit_project_preferences_action =\
            create_action(self, _("Project Preferences"),
                          triggered=self.edit_project_preferences)
        self.recent_project_menu = QMenu(_("Recent Projects"), self)

        if self.main is not None:
            self.main.projects_menu_actions += [self.new_project_action,
                                                MENU_SEPARATOR,
                                                self.open_project_action,
                                                self.close_project_action,
                                                self.delete_project_action,
                                                MENU_SEPARATOR,
                                                self.recent_project_menu,
                                                self.toggle_view_action]

        self.setup_menu_actions()
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        ipyconsole = self.main.ipyconsole
        treewidget = self.explorer.treewidget
        lspmgr = self.main.lspmanager

        self.main.add_dockwidget(self)
        self.explorer.sig_open_file.connect(self.main.open_file)

        treewidget.sig_edit.connect(self.main.editor.load)
        treewidget.sig_removed.connect(self.main.editor.removed)
        treewidget.sig_removed_tree.connect(self.main.editor.removed_tree)
        treewidget.sig_renamed.connect(self.main.editor.renamed)
        treewidget.sig_renamed_tree.connect(self.main.editor.renamed_tree)
        treewidget.sig_create_module.connect(self.main.editor.new)
        treewidget.sig_new_file.connect(
            lambda t: self.main.editor.new(text=t))
        treewidget.sig_open_interpreter.connect(
            ipyconsole.create_client_from_path)
        treewidget.redirect_stdio.connect(
            self.main.redirect_internalshell_stdio)
        treewidget.sig_run.connect(
            lambda fname:
            ipyconsole.run_script(fname, osp.dirname(fname), '', False, False,
                                  False, True))

        # New project connections. Order matters!
        self.sig_project_loaded.connect(
            lambda v: self.main.workingdirectory.chdir(v))
        self.sig_project_loaded.connect(
            lambda v: self.main.set_window_title())
        self.sig_project_loaded.connect(lspmgr.reinit_all_lsp_clients)
        self.sig_project_loaded.connect(
            lambda v: self.main.editor.setup_open_files())
        self.sig_project_loaded.connect(self.update_explorer)
        self.sig_project_closed[object].connect(
            lambda v: self.main.workingdirectory.chdir(
                self.get_last_working_dir()))
        self.sig_project_closed.connect(
            lambda v: self.main.set_window_title())
        self.sig_project_closed.connect(lspmgr.reinit_all_lsp_clients)
        self.sig_project_closed.connect(
            lambda v: self.main.editor.setup_open_files())
        self.recent_project_menu.aboutToShow.connect(self.setup_menu_actions)

        self.main.pythonpath_changed()
        self.main.restore_scrollbar_position.connect(
                                               self.restore_scrollbar_position)
        self.sig_pythonpath_changed.connect(self.main.pythonpath_changed)
        self.main.editor.set_projects(self)

    def refresh_plugin(self):
        """Refresh project explorer widget"""
        pass

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.save_config()
        self.explorer.closing_widget()
        return True

    def switch_to_plugin(self):
        """Switch to plugin."""
        # Unmaxizime currently maximized plugin
        if (self.main.last_plugin is not None and
                self.main.last_plugin.ismaximized and
                self.main.last_plugin is not self):
            self.main.maximize_dockwidget()

        # Show plugin only if it was already visible
        if self.get_option('visible_if_project_open'):
            if not self.toggle_view_action.isChecked():
                self.toggle_view_action.setChecked(True)
            self.visibility_changed(True)

    # ------ Public API -------------------------------------------------------
    def setup_menu_actions(self):
        """Setup and update the menu actions."""
        self.recent_project_menu.clear()
        self.recent_projects_actions = []
        if self.recent_projects:
            for project in self.recent_projects:
                if self.is_valid_project(project):
                    name = project.replace(get_home_dir(), '~')
                    action = create_action(
                        self,
                        name,
                        icon=ima.icon('project'),
                        triggered=(
                            lambda _, p=project: self.open_project(path=p))
                        )
                    self.recent_projects_actions.append(action)
                else:
                    self.recent_projects.remove(project)
            self.recent_projects_actions += [None,
                                             self.clear_recent_projects_action]
        else:
            self.recent_projects_actions = [self.clear_recent_projects_action]
        add_actions(self.recent_project_menu, self.recent_projects_actions)
        self.update_project_actions()

    def update_project_actions(self):
        """Update actions of the Projects menu"""
        if self.recent_projects:
            self.clear_recent_projects_action.setEnabled(True)
        else:
            self.clear_recent_projects_action.setEnabled(False)

        active = bool(self.get_active_project_path())
        self.close_project_action.setEnabled(active)
        self.delete_project_action.setEnabled(active)
        self.edit_project_preferences_action.setEnabled(active)

    def edit_project_preferences(self):
        """Edit Spyder active project preferences"""
        from spyder.plugins.projects.confpage import ProjectPreferences
        if self.project_active:
            active_project = self.project_list[0]
            dlg = ProjectPreferences(self, active_project)
#            dlg.size_change.connect(self.set_project_prefs_size)
#            if self.projects_prefs_dialog_size is not None:
#                dlg.resize(self.projects_prefs_dialog_size)
            dlg.show()
#        dlg.check_all_settings()
#        dlg.pages_widget.currentChanged.connect(self.__preference_page_changed)
            dlg.exec_()

    @Slot()
    def create_new_project(self):
        """Create new project"""
        self.switch_to_plugin()
        active_project = self.current_active_project
        dlg = ProjectDialog(self)
        dlg.sig_project_creation_requested.connect(self._create_project)
        dlg.sig_project_creation_requested.connect(self.sig_project_created)
        if dlg.exec_():
            if (active_project is None
                    and self.get_option('visible_if_project_open')):
                self.show_explorer()
            self.sig_pythonpath_changed.emit()
            self.restart_consoles()

    def _create_project(self, path):
        """Create a new project."""
        self.open_project(path=path)
        self.setup_menu_actions()
        self.add_to_recent(path)

    def open_project(self, path=None, restart_consoles=True,
                     save_previous_files=True):
        """Open the project located in `path`"""
        self.switch_to_plugin()
        if path is None:
            basedir = get_home_dir()
            path = getexistingdirectory(parent=self,
                                        caption=_("Open project"),
                                        basedir=basedir)
            path = encoding.to_unicode_from_fs(path)
            if not self.is_valid_project(path):
                if path:
                    QMessageBox.critical(self, _('Error'),
                                _("<b>%s</b> is not a Spyder project!") % path)
                return
        else:
            path = encoding.to_unicode_from_fs(path)

        self.add_to_recent(path)

        # A project was not open before
        if self.current_active_project is None:
            if save_previous_files and self.main.editor is not None:
                self.main.editor.save_open_files()
            if self.main.editor is not None:
                self.main.editor.set_option('last_working_dir',
                                            getcwd_or_home())
            if self.get_option('visible_if_project_open'):
                self.show_explorer()
        else:
            # We are switching projects
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())

        self.current_active_project = EmptyProject(path)
        self.latest_project = EmptyProject(path)
        self.set_option('current_project_path', self.get_active_project_path())

        self.setup_menu_actions()
        self.sig_project_loaded.emit(path)
        self.sig_pythonpath_changed.emit()

        if restart_consoles:
            self.restart_consoles()

    def close_project(self):
        """
        Close current project and return to a window without an active
        project
        """
        if self.current_active_project:
            self.switch_to_plugin()
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())
            path = self.current_active_project.root_path
            self.current_active_project = None
            self.set_option('current_project_path', None)
            self.setup_menu_actions()

            self.sig_project_closed.emit(path)
            self.sig_pythonpath_changed.emit()

            if self.dockwidget is not None:
                self.set_option('visible_if_project_open',
                                self.dockwidget.isVisible())
                self.dockwidget.close()

            self.explorer.clear()
            self.restart_consoles()

    def _delete_project(self):
        """Delete current project."""
        if self.current_active_project:
            self.switch_to_plugin()
            self.explorer.delete_project()

    def clear_recent_projects(self):
        """Clear the list of recent projects"""
        self.recent_projects = []
        self.setup_menu_actions()

    def get_active_project(self):
        """Get the active project"""
        return self.current_active_project

    def reopen_last_project(self):
        """
        Reopen the active project when Spyder was closed last time, if any
        """
        current_project_path = self.get_option('current_project_path',
                                               default=None)

        # Needs a safer test of project existence!
        if current_project_path and \
          self.is_valid_project(current_project_path):
            self.open_project(path=current_project_path,
                              restart_consoles=False,
                              save_previous_files=False)
            self.load_config()

    def get_project_filenames(self):
        """Get the list of recent filenames of a project"""
        recent_files = []
        if self.current_active_project:
            recent_files = self.current_active_project.get_recent_files()
        elif self.latest_project:
            recent_files = self.latest_project.get_recent_files()
        return recent_files

    def set_project_filenames(self, recent_files):
        """Set the list of open file names in a project"""
        if (self.current_active_project
                and self.is_valid_project(
                        self.current_active_project.root_path)):
            self.current_active_project.set_recent_files(recent_files)

    def get_active_project_path(self):
        """Get path of the active project"""
        active_project_path = None
        if self.current_active_project:
            active_project_path = self.current_active_project.root_path
        return active_project_path

    def get_pythonpath(self, at_start=False):
        """Get project path as a list to be added to PYTHONPATH"""
        if at_start:
            current_path = self.get_option('current_project_path',
                                           default=None)
        else:
            current_path = self.get_active_project_path()
        if current_path is None:
            return []
        else:
            return [current_path]

    def get_last_working_dir(self):
        """Get the path of the last working directory"""
        return self.main.editor.get_option('last_working_dir',
                                           default=getcwd_or_home())

    def save_config(self):
        """
        Save configuration: opened projects & tree widget state.

        Also save whether dock widget is visible if a project is open.
        """
        self.set_option('recent_projects', self.recent_projects)
        self.set_option('expanded_state',
                        self.explorer.treewidget.get_expanded_state())
        self.set_option('scrollbar_position',
                        self.explorer.treewidget.get_scrollbar_position())
        if self.current_active_project and self.dockwidget:
            self.set_option('visible_if_project_open',
                            self.dockwidget.isVisible())

    def load_config(self):
        """Load configuration: opened projects & tree widget state"""
        expanded_state = self.get_option('expanded_state', None)
        # Sometimes the expanded state option may be truncated in .ini file
        # (for an unknown reason), in this case it would be converted to a
        # string by 'userconfig':
        if is_text_string(expanded_state):
            expanded_state = None
        if expanded_state is not None:
            self.explorer.treewidget.set_expanded_state(expanded_state)

    def restore_scrollbar_position(self):
        """Restoring scrollbar position after main window is visible"""
        scrollbar_pos = self.get_option('scrollbar_position', None)
        if scrollbar_pos is not None:
            self.explorer.treewidget.set_scrollbar_position(scrollbar_pos)

    def update_explorer(self):
        """Update explorer tree"""
        self.explorer.setup_project(self.get_active_project_path())

    def show_explorer(self):
        """Show the explorer"""
        if self.dockwidget is not None:
            if self.dockwidget.isHidden():
                self.dockwidget.show()
            self.dockwidget.raise_()
            self.dockwidget.update()

    def restart_consoles(self):
        """Restart consoles when closing, opening and switching projects"""
        if self.main.ipyconsole is not None:
            self.main.ipyconsole.restart()

    def is_valid_project(self, path):
        """Check if a directory is a valid Spyder project"""
        spy_project_dir = osp.join(path, '.spyproject')
        if osp.isdir(path) and osp.isdir(spy_project_dir):
            return True
        else:
            return False

    def add_to_recent(self, project):
        """
        Add an entry to recent projetcs

        We only maintain the list of the 10 most recent projects
        """
        if project not in self.recent_projects:
            self.recent_projects.insert(0, project)
            self.recent_projects = self.recent_projects[:10]
Beispiel #33
0
    def __init__(self,
                 parent=None,
                 name_filters=['*.py', '*.pyw'],
                 show_all=False,
                 show_cd_only=None,
                 show_icontext=True):
        QWidget.__init__(self, parent)

        # Widgets
        self.treewidget = ExplorerTreeWidget(self, show_cd_only=show_cd_only)
        button_previous = QToolButton(self)
        button_next = QToolButton(self)
        button_parent = QToolButton(self)
        self.button_menu = QToolButton(self)
        menu = QMenu(self)

        self.action_widgets = [
            button_previous, button_next, button_parent, self.button_menu
        ]

        # Actions
        icontext_action = create_action(self,
                                        _("Show icons and text"),
                                        toggled=self.toggle_icontext)
        previous_action = create_action(
            self,
            text=_("Previous"),
            icon=ima.icon('ArrowBack'),
            triggered=self.treewidget.go_to_previous_directory)
        next_action = create_action(
            self,
            text=_("Next"),
            icon=ima.icon('ArrowForward'),
            triggered=self.treewidget.go_to_next_directory)
        parent_action = create_action(
            self,
            text=_("Parent"),
            icon=ima.icon('ArrowUp'),
            triggered=self.treewidget.go_to_parent_directory)
        options_action = create_action(self, text='', tip=_('Options'))

        # Setup widgets
        self.treewidget.setup(name_filters=name_filters, show_all=show_all)
        self.treewidget.chdir(getcwd())
        self.treewidget.common_actions += [None, icontext_action]

        button_previous.setDefaultAction(previous_action)
        previous_action.setEnabled(False)

        button_next.setDefaultAction(next_action)
        next_action.setEnabled(False)

        button_parent.setDefaultAction(parent_action)

        self.button_menu.setIcon(ima.icon('tooloptions'))
        self.button_menu.setPopupMode(QToolButton.InstantPopup)
        self.button_menu.setMenu(menu)
        add_actions(menu, self.treewidget.common_actions)
        options_action.setMenu(menu)

        self.toggle_icontext(show_icontext)
        icontext_action.setChecked(show_icontext)

        for widget in self.action_widgets:
            widget.setAutoRaise(True)
            widget.setIconSize(QSize(16, 16))

        # Layouts
        blayout = QHBoxLayout()
        blayout.addWidget(button_previous)
        blayout.addWidget(button_next)
        blayout.addWidget(button_parent)
        blayout.addStretch()
        blayout.addWidget(self.button_menu)

        layout = QVBoxLayout()
        layout.addLayout(blayout)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        # Signals and slots
        self.treewidget.set_previous_enabled.connect(
            previous_action.setEnabled)
        self.treewidget.set_next_enabled.connect(next_action.setEnabled)
Beispiel #34
0
class DirView(QTreeView):
    """Base file/directory tree view"""
    def __init__(self, parent=None):
        super(DirView, self).__init__(parent)
        self.name_filters = ['*.py']
        self.parent_widget = parent
        self.show_all = None
        self.menu = None
        self.common_actions = None
        self.__expanded_state = None
        self._to_be_loaded = None
        self.fsmodel = None
        self.setup_fs_model()
        self._scrollbar_positions = None

    #---- Model
    def setup_fs_model(self):
        """Setup filesystem model"""
        filters = QDir.AllDirs | QDir.Files | QDir.Drives | QDir.NoDotAndDotDot
        self.fsmodel = QFileSystemModel(self)
        self.fsmodel.setFilter(filters)
        self.fsmodel.setNameFilterDisables(False)

    def install_model(self):
        """Install filesystem model"""
        self.setModel(self.fsmodel)

    def setup_view(self):
        """Setup view"""
        self.install_model()
        if not is_pyqt46:
            self.fsmodel.directoryLoaded.connect(
                lambda: self.resizeColumnToContents(0))
        self.setAnimated(False)
        self.setSortingEnabled(True)
        self.sortByColumn(0, Qt.AscendingOrder)

    def set_name_filters(self, name_filters):
        """Set name filters"""
        self.name_filters = name_filters
        self.fsmodel.setNameFilters(name_filters)

    def set_show_all(self, state):
        """Toggle 'show all files' state"""
        if state:
            self.fsmodel.setNameFilters([])
        else:
            self.fsmodel.setNameFilters(self.name_filters)

    def get_filename(self, index):
        """Return filename associated with *index*"""
        if index:
            return osp.normpath(to_text_string(self.fsmodel.filePath(index)))

    def get_index(self, filename):
        """Return index associated with filename"""
        return self.fsmodel.index(filename)

    def get_selected_filenames(self):
        """Return selected filenames"""
        if self.selectionMode() == self.ExtendedSelection:
            return [self.get_filename(idx) for idx in self.selectedIndexes()]
        else:
            return [self.get_filename(self.currentIndex())]

    def get_dirname(self, index):
        """Return dirname associated with *index*"""
        fname = self.get_filename(index)
        if fname:
            if osp.isdir(fname):
                return fname
            else:
                return osp.dirname(fname)

    #---- Tree view widget
    def setup(self, name_filters=['*.py', '*.pyw'], show_all=False):
        """Setup tree widget"""
        self.setup_view()

        self.set_name_filters(name_filters)
        self.show_all = show_all

        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()

    #---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        # Filters
        filters_action = create_action(self,
                                       _("Edit filename filters..."),
                                       None,
                                       ima.icon('filter'),
                                       triggered=self.edit_filter)
        # Show all files
        all_action = create_action(self,
                                   _("Show all files"),
                                   toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)

        return [filters_action, all_action]

    @Slot()
    def edit_filter(self):
        """Edit name filters"""
        filters, valid = QInputDialog.getText(self, _('Edit filename filters'),
                                              _('Name filters:'),
                                              QLineEdit.Normal,
                                              ", ".join(self.name_filters))
        if valid:
            filters = [f.strip() for f in to_text_string(filters).split(',')]
            self.parent_widget.sig_option_changed.emit('name_filters', filters)
            self.set_name_filters(filters)

    @Slot(bool)
    def toggle_all(self, checked):
        """Toggle all files mode"""
        self.parent_widget.sig_option_changed.emit('show_all', checked)
        self.show_all = checked
        self.set_show_all(checked)

    def create_file_new_actions(self, fnames):
        """Return actions for submenu 'New...'"""
        if not fnames:
            return []
        new_file_act = create_action(
            self,
            _("File..."),
            icon=ima.icon('filenew'),
            triggered=lambda: self.new_file(fnames[-1]))
        new_module_act = create_action(
            self,
            _("Module..."),
            icon=ima.icon('spyder'),
            triggered=lambda: self.new_module(fnames[-1]))
        new_folder_act = create_action(
            self,
            _("Folder..."),
            icon=ima.icon('folder_new'),
            triggered=lambda: self.new_folder(fnames[-1]))
        new_package_act = create_action(
            self,
            _("Package..."),
            icon=ima.icon('package_new'),
            triggered=lambda: self.new_package(fnames[-1]))
        return [
            new_file_act, new_folder_act, None, new_module_act, new_package_act
        ]

    def create_file_import_actions(self, fnames):
        """Return actions for submenu 'Import...'"""
        return []

    def create_file_manage_actions(self, fnames):
        """Return file management actions"""
        only_files = all([osp.isfile(_fn) for _fn in fnames])
        only_modules = all([
            osp.splitext(_fn)[1] in ('.py', '.pyw', '.ipy') for _fn in fnames
        ])
        only_notebooks = all(
            [osp.splitext(_fn)[1] == '.ipynb' for _fn in fnames])
        only_valid = all([encoding.is_text_file(_fn) for _fn in fnames])
        run_action = create_action(self,
                                   _("Run"),
                                   icon=ima.icon('run'),
                                   triggered=self.run)
        edit_action = create_action(self,
                                    _("Edit"),
                                    icon=ima.icon('edit'),
                                    triggered=self.clicked)
        move_action = create_action(self,
                                    _("Move..."),
                                    icon="move.png",
                                    triggered=self.move)
        delete_action = create_action(self,
                                      _("Delete..."),
                                      icon=ima.icon('editdelete'),
                                      triggered=self.delete)
        rename_action = create_action(self,
                                      _("Rename..."),
                                      icon=ima.icon('rename'),
                                      triggered=self.rename)
        open_action = create_action(self, _("Open"), triggered=self.open)
        ipynb_convert_action = create_action(self,
                                             _("Convert to Python script"),
                                             icon=ima.icon('python'),
                                             triggered=self.convert_notebooks)

        actions = []
        if only_modules:
            actions.append(run_action)
        if only_valid and only_files:
            actions.append(edit_action)
        else:
            actions.append(open_action)
        actions += [delete_action, rename_action]
        basedir = fixpath(osp.dirname(fnames[0]))
        if all([fixpath(osp.dirname(_fn)) == basedir for _fn in fnames]):
            actions.append(move_action)
        actions += [None]
        if only_notebooks and nbexporter is not None:
            actions.append(ipynb_convert_action)

        # VCS support is quite limited for now, so we are enabling the VCS
        # related actions only when a single file/folder is selected:
        dirname = fnames[0] if osp.isdir(fnames[0]) else osp.dirname(fnames[0])
        if len(fnames) == 1 and vcs.is_vcs_repository(dirname):
            # QAction.triggered works differently for PySide and PyQt
            if not API == 'pyside':
                commit_slot = lambda _checked, fnames=[dirname]:\
                                    self.vcs_command(fnames, 'commit')
                browse_slot = lambda _checked, fnames=[dirname]:\
                                    self.vcs_command(fnames, 'browse')
            else:
                commit_slot = lambda fnames=[dirname]:\
                                    self.vcs_command(fnames, 'commit')
                browse_slot = lambda fnames=[dirname]:\
                                    self.vcs_command(fnames, 'browse')
            vcs_ci = create_action(self,
                                   _("Commit"),
                                   icon=ima.icon('vcs_commit'),
                                   triggered=commit_slot)
            vcs_log = create_action(self,
                                    _("Browse repository"),
                                    icon=ima.icon('vcs_browse'),
                                    triggered=browse_slot)
            actions += [None, vcs_ci, vcs_log]

        return actions

    def create_folder_manage_actions(self, fnames):
        """Return folder management actions"""
        actions = []
        if os.name == 'nt':
            _title = _("Open command prompt here")
        else:
            _title = _("Open terminal here")
        action = create_action(self,
                               _title,
                               icon=ima.icon('cmdprompt'),
                               triggered=lambda: self.open_terminal(fnames))
        actions.append(action)
        _title = _("Open Python console here")
        action = create_action(self,
                               _title,
                               icon=ima.icon('python'),
                               triggered=lambda: self.open_interpreter(fnames))
        actions.append(action)
        return actions

    def create_context_menu_actions(self):
        """Create context menu actions"""
        actions = []
        fnames = self.get_selected_filenames()
        new_actions = self.create_file_new_actions(fnames)
        if len(new_actions) > 1:
            # Creating a submenu only if there is more than one entry
            new_act_menu = QMenu(_('New'), self)
            add_actions(new_act_menu, new_actions)
            actions.append(new_act_menu)
        else:
            actions += new_actions
        import_actions = self.create_file_import_actions(fnames)
        if len(import_actions) > 1:
            # Creating a submenu only if there is more than one entry
            import_act_menu = QMenu(_('Import'), self)
            add_actions(import_act_menu, import_actions)
            actions.append(import_act_menu)
        else:
            actions += import_actions
        if actions:
            actions.append(None)
        if fnames:
            actions += self.create_file_manage_actions(fnames)
        if actions:
            actions.append(None)
        if fnames and all([osp.isdir(_fn) for _fn in fnames]):
            actions += self.create_folder_manage_actions(fnames)
        if actions:
            actions.append(None)
        actions += self.common_actions
        return actions

    def update_menu(self):
        """Update context menu"""
        self.menu.clear()
        add_actions(self.menu, self.create_context_menu_actions())

    #---- Events
    def viewportEvent(self, event):
        """Reimplement Qt method"""

        # Prevent Qt from crashing or showing warnings like:
        # "QSortFilterProxyModel: index from wrong model passed to
        # mapFromSource", probably due to the fact that the file system model
        # is being built. See Issue 1250.
        #
        # This workaround was inspired by the following KDE bug:
        # https://bugs.kde.org/show_bug.cgi?id=172198
        #
        # Apparently, this is a bug from Qt itself.
        self.executeDelayedItemsLayout()

        return QTreeView.viewportEvent(self, event)

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
        elif event.key() == Qt.Key_F2:
            self.rename()
        elif event.key() == Qt.Key_Delete:
            self.delete()
        elif event.key() == Qt.Key_Backspace:
            self.go_to_parent_directory()
        else:
            QTreeView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()

    @Slot()
    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed"""
        fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isdir(fname):
                self.directory_clicked(fname)
            else:
                self.open([fname])

    def directory_clicked(self, dirname):
        """Directory was just clicked"""
        pass

    #---- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event"""
        data = QMimeData()
        data.setUrls([QUrl(fname) for fname in self.get_selected_filenames()])
        drag = QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

    #---- File/Directory actions
    @Slot()
    def open(self, fnames=None):
        """Open files with the appropriate application"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isfile(fname) and encoding.is_text_file(fname):
                self.parent_widget.sig_open_file.emit(fname)
            else:
                self.open_outside_spyder([fname])

    def open_outside_spyder(self, fnames):
        """Open file outside Spyder with the appropriate application
        If this does not work, opening unknown file in Spyder, as text file"""
        for path in sorted(fnames):
            path = file_uri(path)
            ok = programs.start_file(path)
            if not ok:
                self.parent_widget.edit.emit(path)

    def open_terminal(self, fnames):
        """Open terminal"""
        for path in sorted(fnames):
            self.parent_widget.open_terminal.emit(path)

    def open_interpreter(self, fnames):
        """Open interpreter"""
        for path in sorted(fnames):
            self.parent_widget.open_interpreter.emit(path)

    @Slot()
    def run(self, fnames=None):
        """Run Python scripts"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            self.parent_widget.run.emit(fname)

    def remove_tree(self, dirname):
        """Remove whole directory tree
        Reimplemented in project explorer widget"""
        shutil.rmtree(dirname, onerror=misc.onerror)

    def delete_file(self, fname, multiple, yes_to_all):
        """Delete file"""
        if multiple:
            buttons = QMessageBox.Yes|QMessageBox.YesAll| \
                      QMessageBox.No|QMessageBox.Cancel
        else:
            buttons = QMessageBox.Yes | QMessageBox.No
        if yes_to_all is None:
            answer = QMessageBox.warning(
                self, _("Delete"),
                _("Do you really want "
                  "to delete <b>%s</b>?") % osp.basename(fname), buttons)
            if answer == QMessageBox.No:
                return yes_to_all
            elif answer == QMessageBox.Cancel:
                return False
            elif answer == QMessageBox.YesAll:
                yes_to_all = True
        try:
            if osp.isfile(fname):
                misc.remove_file(fname)
                self.parent_widget.removed.emit(fname)
            else:
                self.remove_tree(fname)
                self.parent_widget.removed_tree.emit(fname)
            return yes_to_all
        except EnvironmentError as error:
            action_str = _('delete')
            QMessageBox.critical(
                self, _("Project Explorer"),
                _("<b>Unable to %s <i>%s</i></b>"
                  "<br><br>Error message:<br>%s") %
                (action_str, fname, to_text_string(error)))
        return False

    @Slot()
    def delete(self, fnames=None):
        """Delete files"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        multiple = len(fnames) > 1
        yes_to_all = None
        for fname in fnames:
            yes_to_all = self.delete_file(fname, multiple, yes_to_all)
            if yes_to_all is not None and not yes_to_all:
                # Canceled
                return

    def convert_notebook(self, fname):
        """Convert an IPython notebook to a Python script in editor"""
        try:
            script = nbexporter().from_filename(fname)[0]
        except Exception as e:
            QMessageBox.critical(self, _('Conversion error'),
                                 _("It was not possible to convert this "
                                 "notebook. The error is:\n\n") + \
                                 to_text_string(e))
            return
        self.parent_widget.sig_new_file.emit(script)

    @Slot()
    def convert_notebooks(self):
        """Convert IPython notebooks to Python scripts in editor"""
        fnames = self.get_selected_filenames()
        if not isinstance(fnames, (tuple, list)):
            fnames = [fnames]
        for fname in fnames:
            self.convert_notebook(fname)

    def rename_file(self, fname):
        """Rename file"""
        path, valid = QInputDialog.getText(self, _('Rename'),
                                           _('New name:'), QLineEdit.Normal,
                                           osp.basename(fname))
        if valid:
            path = osp.join(osp.dirname(fname), to_text_string(path))
            if path == fname:
                return
            if osp.exists(path):
                if QMessageBox.warning(
                        self, _("Rename"),
                        _("Do you really want to rename <b>%s</b> and "
                          "overwrite the existing file <b>%s</b>?") %
                    (osp.basename(fname), osp.basename(path)),
                        QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
                    return
            try:
                misc.rename_file(fname, path)
                self.parent_widget.renamed.emit(fname, path)
                return path
            except EnvironmentError as error:
                QMessageBox.critical(
                    self, _("Rename"),
                    _("<b>Unable to rename file <i>%s</i></b>"
                      "<br><br>Error message:<br>%s") %
                    (osp.basename(fname), to_text_string(error)))

    @Slot()
    def rename(self, fnames=None):
        """Rename files"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        if not isinstance(fnames, (tuple, list)):
            fnames = [fnames]
        for fname in fnames:
            self.rename_file(fname)

    @Slot()
    def move(self, fnames=None):
        """Move files/directories"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        orig = fixpath(osp.dirname(fnames[0]))
        while True:
            self.parent_widget.redirect_stdio.emit(False)
            folder = getexistingdirectory(self, _("Select directory"), orig)
            self.parent_widget.redirect_stdio.emit(True)
            if folder:
                folder = fixpath(folder)
                if folder != orig:
                    break
            else:
                return
        for fname in fnames:
            basename = osp.basename(fname)
            try:
                misc.move_file(fname, osp.join(folder, basename))
            except EnvironmentError as error:
                QMessageBox.critical(
                    self, _("Error"),
                    _("<b>Unable to move <i>%s</i></b>"
                      "<br><br>Error message:<br>%s") %
                    (basename, to_text_string(error)))

    def create_new_folder(self, current_path, title, subtitle, is_package):
        """Create new folder"""
        if current_path is None:
            current_path = ''
        if osp.isfile(current_path):
            current_path = osp.dirname(current_path)
        name, valid = QInputDialog.getText(self, title, subtitle,
                                           QLineEdit.Normal, "")
        if valid:
            dirname = osp.join(current_path, to_text_string(name))
            try:
                os.mkdir(dirname)
            except EnvironmentError as error:
                QMessageBox.critical(
                    self, title,
                    _("<b>Unable "
                      "to create folder <i>%s</i></b>"
                      "<br><br>Error message:<br>%s") %
                    (dirname, to_text_string(error)))
            finally:
                if is_package:
                    fname = osp.join(dirname, '__init__.py')
                    try:
                        with open(fname, 'wb') as f:
                            f.write(to_binary_string('#'))
                        return dirname
                    except EnvironmentError as error:
                        QMessageBox.critical(
                            self, title,
                            _("<b>Unable "
                              "to create file <i>%s</i></b>"
                              "<br><br>Error message:<br>%s") %
                            (fname, to_text_string(error)))

    def new_folder(self, basedir):
        """New folder"""
        title = _('New folder')
        subtitle = _('Folder name:')
        self.create_new_folder(basedir, title, subtitle, is_package=False)

    def new_package(self, basedir):
        """New package"""
        title = _('New package')
        subtitle = _('Package name:')
        self.create_new_folder(basedir, title, subtitle, is_package=True)

    def create_new_file(self, current_path, title, filters, create_func):
        """Create new file
        Returns True if successful"""
        if current_path is None:
            current_path = ''
        if osp.isfile(current_path):
            current_path = osp.dirname(current_path)
        self.parent_widget.redirect_stdio.emit(False)
        fname, _selfilter = getsavefilename(self, title, current_path, filters)
        self.parent_widget.redirect_stdio.emit(True)
        if fname:
            try:
                create_func(fname)
                return fname
            except EnvironmentError as error:
                QMessageBox.critical(
                    self, _("New file"),
                    _("<b>Unable to create file <i>%s</i>"
                      "</b><br><br>Error message:<br>%s") %
                    (fname, to_text_string(error)))

    def new_file(self, basedir):
        """New file"""
        title = _("New file")
        filters = _("All files") + " (*)"

        def create_func(fname):
            """File creation callback"""
            if osp.splitext(fname)[1] in ('.py', '.pyw', '.ipy'):
                create_script(fname)
            else:
                with open(fname, 'wb') as f:
                    f.write(to_binary_string(''))

        fname = self.create_new_file(basedir, title, filters, create_func)
        if fname is not None:
            self.open([fname])

    def new_module(self, basedir):
        """New module"""
        title = _("New module")
        filters = _("Python scripts") + " (*.py *.pyw *.ipy)"
        create_func = lambda fname: self.parent_widget.create_module.emit(fname
                                                                          )
        self.create_new_file(basedir, title, filters, create_func)

    #----- VCS actions
    def vcs_command(self, fnames, action):
        """VCS action (commit, browse)"""
        try:
            for path in sorted(fnames):
                vcs.run_vcs_tool(path, action)
        except vcs.ActionToolNotFound as error:
            msg = _("For %s support, please install one of the<br/> "
                    "following tools:<br/><br/>  %s")\
                        % (error.vcsname, ', '.join(error.tools))
            QMessageBox.critical(
                self, _("Error"),
                _("""<b>Unable to find external program.</b><br><br>%s""") %
                to_text_string(msg))

    #----- Settings
    def get_scrollbar_position(self):
        """Return scrollbar positions"""
        return (self.horizontalScrollBar().value(),
                self.verticalScrollBar().value())

    def set_scrollbar_position(self, position):
        """Set scrollbar positions"""
        # Scrollbars will be restored after the expanded state
        self._scrollbar_positions = position
        if self._to_be_loaded is not None and len(self._to_be_loaded) == 0:
            self.restore_scrollbar_positions()

    def restore_scrollbar_positions(self):
        """Restore scrollbar positions once tree is loaded"""
        hor, ver = self._scrollbar_positions
        self.horizontalScrollBar().setValue(hor)
        self.verticalScrollBar().setValue(ver)

    def get_expanded_state(self):
        """Return expanded state"""
        self.save_expanded_state()
        return self.__expanded_state

    def set_expanded_state(self, state):
        """Set expanded state"""
        self.__expanded_state = state
        self.restore_expanded_state()

    def save_expanded_state(self):
        """Save all items expanded state"""
        model = self.model()
        # If model is not installed, 'model' will be None: this happens when
        # using the Project Explorer without having selected a workspace yet
        if model is not None:
            self.__expanded_state = []
            for idx in model.persistentIndexList():
                if self.isExpanded(idx):
                    self.__expanded_state.append(self.get_filename(idx))

    def restore_directory_state(self, fname):
        """Restore directory expanded state"""
        root = osp.normpath(to_text_string(fname))
        if not osp.exists(root):
            # Directory has been (re)moved outside Spyder
            return
        for basename in os.listdir(root):
            path = osp.normpath(osp.join(root, basename))
            if osp.isdir(path) and path in self.__expanded_state:
                self.__expanded_state.pop(self.__expanded_state.index(path))
                if self._to_be_loaded is None:
                    self._to_be_loaded = []
                self._to_be_loaded.append(path)
                self.setExpanded(self.get_index(path), True)
        if not self.__expanded_state and not is_pyqt46:
            self.fsmodel.directoryLoaded.disconnect(
                self.restore_directory_state)

    def follow_directories_loaded(self, fname):
        """Follow directories loaded during startup"""
        if self._to_be_loaded is None:
            return
        path = osp.normpath(to_text_string(fname))
        if path in self._to_be_loaded:
            self._to_be_loaded.remove(path)
        if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 \
          and not is_pyqt46:
            self.fsmodel.directoryLoaded.disconnect(
                self.follow_directories_loaded)
            if self._scrollbar_positions is not None:
                # The tree view need some time to render branches:
                QTimer.singleShot(50, self.restore_scrollbar_positions)

    def restore_expanded_state(self):
        """Restore all items expanded state"""
        if self.__expanded_state is not None:
            # In the old project explorer, the expanded state was a dictionnary:
            if isinstance(self.__expanded_state, list) and not is_pyqt46:
                self.fsmodel.directoryLoaded.connect(
                    self.restore_directory_state)
                self.fsmodel.directoryLoaded.connect(
                    self.follow_directories_loaded)
Beispiel #35
0
class Projects(SpyderPluginWidget):
    """Projects plugin."""

    CONF_SECTION = 'project_explorer'
    CONF_FILE = False

    # Signals
    sig_project_created = Signal(str, str, object)
    """
    This signal is emitted to request the Projects plugin the creation of a
    project.

    Parameters
    ----------
    project_path: str
        Location of project.
    project_type: str	
        Type of project as defined by project types.	
    project_packages: object	
        Package to install. Currently not in use.	
    """

    sig_project_loaded = Signal(object)
    sig_project_closed = Signal(object)
    sig_pythonpath_changed = Signal()

    def __init__(self, parent=None):
        """Initialization."""
        SpyderPluginWidget.__init__(self, parent)

        self.explorer = ProjectExplorerWidget(
            self,
            name_filters=self.get_option('name_filters'),
            show_hscrollbar=self.get_option('show_hscrollbar'),
            options_button=self.options_button,
            single_click_to_open=CONF.get('explorer', 'single_click_to_open'),
        )

        layout = QVBoxLayout()
        layout.addWidget(self.explorer)
        self.setLayout(layout)

        self.recent_projects = self.get_option('recent_projects', default=[])
        self.current_active_project = None
        self.latest_project = None
        self.watcher = WorkspaceWatcher(self)
        self.completions_available = False
        self.explorer.setup_project(self.get_active_project_path())
        self.watcher.connect_signals(self)
        self._project_types = OrderedDict()

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _("Project")

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.explorer.treewidget

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.new_project_action = create_action(self,
                                    _("New Project..."),
                                    triggered=self.create_new_project)
        self.open_project_action = create_action(self,
                                    _("Open Project..."),
                                    triggered=lambda v: self.open_project())
        self.close_project_action = create_action(self,
                                    _("Close Project"),
                                    triggered=self.close_project)
        self.delete_project_action = create_action(self,
                                    _("Delete Project"),
                                    triggered=self.delete_project)
        self.clear_recent_projects_action = create_action(
            self,
            _("Clear this list"),
            triggered=self.clear_recent_projects)
        self.recent_project_menu = QMenu(_("Recent Projects"), self)

        self.max_recent_action = create_action(
            self,
            _("Maximum number of recent projects..."),
            triggered=self.change_max_recent_projects)

        if self.main is not None:
            self.main.projects_menu_actions += [self.new_project_action,
                                                MENU_SEPARATOR,
                                                self.open_project_action,
                                                self.close_project_action,
                                                self.delete_project_action,
                                                MENU_SEPARATOR,
                                                self.recent_project_menu,
                                                self._toggle_view_action]

        self.setup_menu_actions()
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        ipyconsole = self.main.ipyconsole
        treewidget = self.explorer.treewidget
        lspmgr = self.main.completions

        self.add_dockwidget()
        self.explorer.sig_open_file.connect(self.main.open_file)
        self.register_widget_shortcuts(treewidget)

        treewidget.sig_delete_project.connect(self.delete_project)
        treewidget.sig_edit.connect(self.main.editor.load)
        treewidget.sig_removed.connect(self.main.editor.removed)
        treewidget.sig_removed_tree.connect(self.main.editor.removed_tree)
        treewidget.sig_renamed.connect(self.main.editor.renamed)
        treewidget.sig_renamed_tree.connect(self.main.editor.renamed_tree)
        treewidget.sig_create_module.connect(self.main.editor.new)
        treewidget.sig_new_file.connect(
            lambda t: self.main.editor.new(text=t))
        treewidget.sig_open_interpreter.connect(
            ipyconsole.create_client_from_path)
        treewidget.redirect_stdio.connect(
            self.main.redirect_internalshell_stdio)
        treewidget.sig_run.connect(
            lambda fname:
            ipyconsole.run_script(fname, osp.dirname(fname), '', False, False,
                                  False, True, False))

        # New project connections. Order matters!
        self.sig_project_loaded.connect(
            lambda path:
            self.main.workingdirectory.chdir(
                directory=path,
                sender_plugin=self
            )
        )
        self.sig_project_loaded.connect(
            lambda v: self.main.set_window_title())
        self.sig_project_loaded.connect(
            functools.partial(lspmgr.project_path_update,
                              update_kind=WorkspaceUpdateKind.ADDITION))
        self.sig_project_loaded.connect(
            lambda v: self.main.editor.setup_open_files())
        self.sig_project_loaded.connect(self.update_explorer)
        self.sig_project_loaded.connect(
            lambda v: self.main.outlineexplorer.update_all_editors())
        self.sig_project_closed[object].connect(
            lambda path:
            self.main.workingdirectory.chdir(
                directory=self.get_last_working_dir(),
                sender_plugin=self
            )
        )
        self.sig_project_closed.connect(
            lambda v: self.main.set_window_title())
        self.sig_project_closed.connect(
            functools.partial(lspmgr.project_path_update,
                              update_kind=WorkspaceUpdateKind.DELETION))
        self.sig_project_closed.connect(
            lambda v: self.main.editor.setup_open_files())
        self.sig_project_closed.connect(
            lambda v: self.main.outlineexplorer.update_all_editors())
        self.recent_project_menu.aboutToShow.connect(self.setup_menu_actions)

        self.main.restore_scrollbar_position.connect(
                                               self.restore_scrollbar_position)
        self.sig_pythonpath_changed.connect(self.main.pythonpath_changed)
        self.main.editor.set_projects(self)

        self.sig_project_loaded.connect(
            lambda v: self.main.editor.set_current_project_path(v))
        self.sig_project_closed.connect(
            lambda v: self.main.editor.set_current_project_path())

        # Connect to file explorer to keep single click to open files in sync
        self.main.explorer.fileexplorer.sig_option_changed.connect(
            self.set_single_click_to_open
        )

        self.register_project_type(self, EmptyProject)

    def set_single_click_to_open(self, option, value):
        """Set single click to open files and directories."""
        if option == 'single_click_to_open':
            self.explorer.treewidget.set_single_click_to_open(value)

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.save_config()
        self.explorer.closing_widget()
        return True

    def unmaximize(self):
        """Unmaximize the currently maximized plugin, if not self."""
        if (self.main.last_plugin is not None and
                self.main.last_plugin._ismaximized and
                self.main.last_plugin is not self):
            self.main.maximize_dockwidget()

    def build_opener(self, project):
        """Build function opening passed project"""
        def opener(*args, **kwargs):
            self.open_project(path=project)
        return opener

    # ------ Public API -------------------------------------------------------
    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        # TODO: Uncomment for Spyder 5
        # self.tabify(self.main.explorer)

    def setup_menu_actions(self):
        """Setup and update the menu actions."""
        self.recent_project_menu.clear()
        self.recent_projects_actions = []
        if self.recent_projects:
            for project in self.recent_projects:
                if self.is_valid_project(project):
                    name = project.replace(get_home_dir(), '~')
                    action = create_action(
                        self,
                        name,
                        icon=ima.icon('project'),
                        triggered=self.build_opener(project),
                    )
                    self.recent_projects_actions.append(action)
                else:
                    self.recent_projects.remove(project)
            self.recent_projects_actions += [
                None,
                self.clear_recent_projects_action,
                self.max_recent_action
            ]
        else:
            self.recent_projects_actions = [self.clear_recent_projects_action,
                                            self.max_recent_action]
        add_actions(self.recent_project_menu, self.recent_projects_actions)
        self.update_project_actions()

    def update_project_actions(self):
        """Update actions of the Projects menu"""
        if self.recent_projects:
            self.clear_recent_projects_action.setEnabled(True)
        else:
            self.clear_recent_projects_action.setEnabled(False)

        active = bool(self.get_active_project_path())
        self.close_project_action.setEnabled(active)
        self.delete_project_action.setEnabled(active)

    @Slot()
    def create_new_project(self):
        """Create new project."""
        self.unmaximize()
        active_project = self.current_active_project
        dlg = ProjectDialog(self, project_types=self.get_project_types())
        result = dlg.exec_()
        data = dlg.project_data
        root_path = data.get("root_path", None)
        project_type = data.get("project_type", EmptyProject.ID)

        if result:
            # A project was not open before
            if active_project is None:
                if self.get_option('visible_if_project_open'):
                    self.show_explorer()
            else:
                # We are switching projects.
                # TODO: Don't emit sig_project_closed when we support
                # multiple workspaces.
                self.sig_project_closed.emit(active_project.root_path)

            self._create_project(root_path, project_type_id=project_type)
            self.sig_pythonpath_changed.emit()
            self.restart_consoles()
            dlg.close()

    def _create_project(self, root_path, project_type_id=EmptyProject.ID,
                        packages=None):
        """Create a new project."""
        project_types = self.get_project_types()
        if project_type_id in project_types:
            project_type_class = project_types[project_type_id]
            project = project_type_class(
                root_path=root_path,
                parent_plugin=project_type_class._PARENT_PLUGIN,
            )

            created_succesfully, message = project.create_project()
            if not created_succesfully:
                QMessageBox.warning(self, "Project creation", message)
                shutil.rmtree(root_path, ignore_errors=True)
                return

            # TODO: In a subsequent PR return a value and emit based on that
            self.sig_project_created.emit(root_path, project_type_id, packages)
            self.open_project(path=root_path, project=project)
        else:
            if not running_under_pytest():
                QMessageBox.critical(
                    self,
                    _('Error'),
                    _("<b>{}</b> is not a registered Spyder project "
                      "type!").format(project_type)
                )

    def open_project(self, path=None, project=None, restart_consoles=True,
                     save_previous_files=True, workdir=None):
        """Open the project located in `path`."""
        self.unmaximize()
        if path is None:
            basedir = get_home_dir()
            path = getexistingdirectory(parent=self,
                                        caption=_("Open project"),
                                        basedir=basedir)
            path = encoding.to_unicode_from_fs(path)
            if not self.is_valid_project(path):
                if path:
                    QMessageBox.critical(
                        self,
                        _('Error'),
                        _("<b>%s</b> is not a Spyder project!") % path,
                    )
                return
        else:
            path = encoding.to_unicode_from_fs(path)
        if project is None:
            project_type_class = self._load_project_type_class(path)
            project = project_type_class(
                root_path=path,
                parent_plugin=project_type_class._PARENT_PLUGIN,
            )

        # A project was not open before
        if self.current_active_project is None:
            if save_previous_files and self.main.editor is not None:
                self.main.editor.save_open_files()

            if self.main.editor is not None:
                self.main.editor.set_option('last_working_dir',
                                            getcwd_or_home())

            if self.get_option('visible_if_project_open'):
                self.show_explorer()
        else:
            # We are switching projects
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())

            # TODO: Don't emit sig_project_closed when we support
            # multiple workspaces.
            self.sig_project_closed.emit(
                self.current_active_project.root_path)

        self.current_active_project = project
        self.latest_project = project
        self.add_to_recent(path)

        self.set_option('current_project_path', self.get_active_project_path())

        self.setup_menu_actions()
        if workdir and osp.isdir(workdir):
            self.sig_project_loaded.emit(workdir)
        else:
            self.sig_project_loaded.emit(path)
        self.sig_pythonpath_changed.emit()
        self.watcher.start(path)

        if restart_consoles:
            self.restart_consoles()

        open_successfully, message = project.open_project()
        if not open_successfully:
            QMessageBox.warning(self, "Project open", message)

    def close_project(self):
        """
        Close current project and return to a window without an active
        project
        """
        if self.current_active_project:
            self.unmaximize()
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())
            path = self.current_active_project.root_path
            closed_sucessfully, message = (
                self.current_active_project.close_project())
            if not closed_sucessfully:
                QMessageBox.warning(self, "Project close", message)

            self.current_active_project = None
            self.set_option('current_project_path', None)
            self.setup_menu_actions()

            self.sig_project_closed.emit(path)
            self.sig_pythonpath_changed.emit()

            if self.dockwidget is not None:
                self.set_option('visible_if_project_open',
                                self.dockwidget.isVisible())
                self.dockwidget.close()

            self.explorer.clear()
            self.restart_consoles()
            self.watcher.stop()

    def delete_project(self):
        """
        Delete the current project without deleting the files in the directory.
        """
        if self.current_active_project:
            self.unmaximize()
            path = self.current_active_project.root_path
            buttons = QMessageBox.Yes | QMessageBox.No
            answer = QMessageBox.warning(
                self,
                _("Delete"),
                _("Do you really want to delete <b>{filename}</b>?<br><br>"
                  "<b>Note:</b> This action will only delete the project. "
                  "Its files are going to be preserved on disk."
                  ).format(filename=osp.basename(path)),
                buttons)
            if answer == QMessageBox.Yes:
                try:
                    self.close_project()
                    shutil.rmtree(osp.join(path, '.spyproject'))
                except EnvironmentError as error:
                    QMessageBox.critical(
                        self,
                        _("Project Explorer"),
                        _("<b>Unable to delete <i>{varpath}</i></b>"
                          "<br><br>The error message was:<br>{error}"
                          ).format(varpath=path, error=to_text_string(error)))

    def clear_recent_projects(self):
        """Clear the list of recent projects"""
        self.recent_projects = []
        self.setup_menu_actions()

    def change_max_recent_projects(self):
        """Change max recent projects entries."""

        mrf, valid = QInputDialog.getInt(
            self,
            _('Projects'),
            _('Maximum number of recent projects'),
            self.get_option('max_recent_projects'),
            1,
            35)

        if valid:
            self.set_option('max_recent_projects', mrf)

    def get_active_project(self):
        """Get the active project"""
        return self.current_active_project

    def reopen_last_project(self):
        """
        Reopen the active project when Spyder was closed last time, if any
        """
        current_project_path = self.get_option('current_project_path',
                                               default=None)

        # Needs a safer test of project existence!
        if (current_project_path and
                self.is_valid_project(current_project_path)):
            self.open_project(path=current_project_path,
                              restart_consoles=False,
                              save_previous_files=False)
            self.load_config()

    def get_project_filenames(self):
        """Get the list of recent filenames of a project"""
        recent_files = []
        if self.current_active_project:
            recent_files = self.current_active_project.get_recent_files()
        elif self.latest_project:
            recent_files = self.latest_project.get_recent_files()
        return recent_files

    def set_project_filenames(self, recent_files):
        """Set the list of open file names in a project"""
        if (self.current_active_project
                and self.is_valid_project(
                        self.current_active_project.root_path)):
            self.current_active_project.set_recent_files(recent_files)

    def get_active_project_path(self):
        """Get path of the active project"""
        active_project_path = None
        if self.current_active_project:
            active_project_path = self.current_active_project.root_path
        return active_project_path

    def get_pythonpath(self, at_start=False):
        """Get project path as a list to be added to PYTHONPATH"""
        if at_start:
            current_path = self.get_option('current_project_path',
                                           default=None)
        else:
            current_path = self.get_active_project_path()
        if current_path is None:
            return []
        else:
            return [current_path]

    def get_last_working_dir(self):
        """Get the path of the last working directory"""
        return self.main.editor.get_option('last_working_dir',
                                           default=getcwd_or_home())

    def save_config(self):
        """
        Save configuration: opened projects & tree widget state.

        Also save whether dock widget is visible if a project is open.
        """
        self.set_option('recent_projects', self.recent_projects)
        self.set_option('expanded_state',
                        self.explorer.treewidget.get_expanded_state())
        self.set_option('scrollbar_position',
                        self.explorer.treewidget.get_scrollbar_position())
        if self.current_active_project and self.dockwidget:
            self.set_option('visible_if_project_open',
                            self.dockwidget.isVisible())

    def load_config(self):
        """Load configuration: opened projects & tree widget state"""
        expanded_state = self.get_option('expanded_state', None)
        # Sometimes the expanded state option may be truncated in .ini file
        # (for an unknown reason), in this case it would be converted to a
        # string by 'userconfig':
        if is_text_string(expanded_state):
            expanded_state = None
        if expanded_state is not None:
            self.explorer.treewidget.set_expanded_state(expanded_state)

    def restore_scrollbar_position(self):
        """Restoring scrollbar position after main window is visible"""
        scrollbar_pos = self.get_option('scrollbar_position', None)
        if scrollbar_pos is not None:
            self.explorer.treewidget.set_scrollbar_position(scrollbar_pos)

    def update_explorer(self):
        """Update explorer tree"""
        self.explorer.setup_project(self.get_active_project_path())

    def show_explorer(self):
        """Show the explorer"""
        if self.dockwidget is not None:
            if self.dockwidget.isHidden():
                self.dockwidget.show()
            self.dockwidget.raise_()
            self.dockwidget.update()

    def restart_consoles(self):
        """Restart consoles when closing, opening and switching projects"""
        if self.main.ipyconsole is not None:
            self.main.ipyconsole.restart()

    def is_valid_project(self, path):
        """Check if a directory is a valid Spyder project"""
        spy_project_dir = osp.join(path, '.spyproject')
        return osp.isdir(path) and osp.isdir(spy_project_dir)

    def add_to_recent(self, project):
        """
        Add an entry to recent projetcs

        We only maintain the list of the 10 most recent projects
        """
        if project not in self.recent_projects:
            self.recent_projects.insert(0, project)
        if len(self.recent_projects) > self.get_option('max_recent_projects'):
            self.recent_projects.pop(-1)

    def start_workspace_services(self):
        """Enable LSP workspace functionality."""
        self.completions_available = True
        if self.current_active_project:
            path = self.get_active_project_path()
            self.notify_project_open(path)

    def stop_workspace_services(self):
        """Disable LSP workspace functionality."""
        self.completions_available = False

    def emit_request(self, method, params, requires_response):
        """Send request/notification/response to all LSP servers."""
        params['requires_response'] = requires_response
        params['response_instance'] = self
        self.main.completions.broadcast_notification(method, params)

    @Slot(str, dict)
    def handle_response(self, method, params):
        """Method dispatcher for LSP requests."""
        if method in self.handler_registry:
            handler_name = self.handler_registry[method]
            handler = getattr(self, handler_name)
            handler(params)

    @Slot(str, str, bool)
    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    def file_moved(self, src_file, dest_file, is_dir):
        """Notify LSP server about a file that is moved."""
        # LSP specification only considers file updates
        if is_dir:
            return

        deletion_entry = {
            'file': src_file,
            'kind': FileChangeType.DELETED
        }

        addition_entry = {
            'file': dest_file,
            'kind': FileChangeType.CREATED
        }

        entries = [addition_entry, deletion_entry]
        params = {
            'params': entries
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_created(self, src_file, is_dir):
        """Notify LSP server about file creation."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.CREATED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_deleted(self, src_file, is_dir):
        """Notify LSP server about file deletion."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.DELETED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_modified(self, src_file, is_dir):
        """Notify LSP server about file modification."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.CHANGED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_FOLDERS_CHANGE,
             requires_response=False)
    def notify_project_open(self, path):
        """Notify LSP server about project path availability."""
        params = {
            'folder': path,
            'instance': self,
            'kind': 'addition'
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_FOLDERS_CHANGE,
             requires_response=False)
    def notify_project_close(self, path):
        """Notify LSP server to unregister project path."""
        params = {
            'folder': path,
            'instance': self,
            'kind': 'deletion'
        }
        return params

    @handles(LSPRequestTypes.WORKSPACE_APPLY_EDIT)
    @request(method=LSPRequestTypes.WORKSPACE_APPLY_EDIT,
             requires_response=False)
    def handle_workspace_edit(self, params):
        """Apply edits to multiple files and notify server about success."""
        edits = params['params']
        response = {
            'applied': False,
            'error': 'Not implemented',
            'language': edits['language']
        }
        return response

    # --- New API:
    # ------------------------------------------------------------------------
    def _load_project_type_class(self, path):
        """
        Load a project type class from the config project folder directly.

        Notes
        -----
        This is done directly, since using the EmptyProject would rewrite the
        value in the constructor. If the project found has not been registered
        as a valid project type, the EmptyProject type will be returned.

        Returns
        -------
        spyder.plugins.projects.api.BaseProjectType
            Loaded project type class.
        """
        fpath = osp.join(
            path, get_project_config_folder(), 'config', WORKSPACE + ".ini")

        project_type_id = EmptyProject.ID
        if osp.isfile(fpath):
            config = configparser.ConfigParser()
            config.read(fpath)
            project_type_id = config[WORKSPACE].get(
                "project_type", EmptyProject.ID)


        EmptyProject._PARENT_PLUGIN = self
        project_types = self.get_project_types()
        project_type_class = project_types.get(project_type_id, EmptyProject)
        return project_type_class

    def register_project_type(self, parent_plugin, project_type):
        """
        Register a new project type.

        Parameters
        ----------
        parent_plugin: spyder.plugins.api.plugins.SpyderPluginV2
            The parent plugin instance making the project type registration.
        project_type: spyder.plugins.projects.api.BaseProjectType
            Project type to register.
        """
        if not issubclass(project_type, BaseProjectType):
            raise SpyderAPIError("A project type must subclass "
                                 "BaseProjectType!")

        project_id = project_type.ID
        if project_id in self._project_types:
            raise SpyderAPIError("A project type id '{}' has already been "
                                 "registered!".format(project_id))

        project_type._PARENT_PLUGIN = parent_plugin
        self._project_types[project_id] = project_type

    def get_project_types(self):
        """
        Return available registered project types.

        Returns
        -------
        dict
            Project types dictionary. Keys are project type IDs and values
            are project type classes.
        """
        return self._project_types

    # TODO: To be removed after migration
    def get_plugin(self, plugin_name):
        """
        Return a plugin instance by providing the plugin's NAME.
        """
        PLUGINS = self.main._PLUGINS
        if plugin_name in PLUGINS:
            return PLUGINS[plugin_name]
Beispiel #36
0
    def __init__(self, model, *args, **kwargs):
        super(PlotWindow, self).__init__(*args, **kwargs)
        # Hide the icon in the title bar
        self.setWindowIcon(qta.icon('fa.circle', opacity=0))

        # The central widget of the sub window will be a main window so that it
        # can support having tab bars
        self._central_widget = QMainWindow()
        self.setWidget(self._central_widget)

        loadUi(os.path.join(os.path.dirname(__file__), "ui", "plot_window.ui"),
               self._central_widget)

        # The central widget of the main window widget will be the plot
        self._model = model
        self._current_item_index = None

        self._plot_widget = PlotWidget(model=self._model)
        self._plot_widget.plotItem.setMenuEnabled(False)

        self._central_widget.setCentralWidget(self._plot_widget)

        # Setup action group for interaction modes
        mode_group = QActionGroup(self.tool_bar)
        mode_group.addAction(self._central_widget.pan_mode_action)
        self._central_widget.pan_mode_action.setChecked(True)
        mode_group.addAction(self._central_widget.zoom_mode_action)

        def _toggle_mode(state):
            view_state = self.plot_widget.plotItem.getViewBox().state.copy()
            view_state.update({
                'mouseMode':
                pg.ViewBox.RectMode if state else pg.ViewBox.PanMode
            })
            self.plot_widget.plotItem.getViewBox().setState(view_state)

        # Setup plot settings options menu
        self.plot_settings_button = self.tool_bar.widgetForAction(
            self._central_widget.plot_settings_action)
        self.plot_settings_button.setPopupMode(QToolButton.InstantPopup)

        self.plot_settings_menu = QMenu(self.plot_settings_button)
        self.plot_settings_button.setMenu(self.plot_settings_menu)

        self.color_change_action = QAction("Line Color")
        self.plot_settings_menu.addAction(self.color_change_action)

        self.line_width_menu = QMenu("Line Widths")
        self.plot_settings_menu.addMenu(self.line_width_menu)

        # Setup the line width plot setting options
        for i in range(1, 4):
            act = QAction(str(i), self.line_width_menu)
            self.line_width_menu.addAction(act)
            act.triggered.connect(
                lambda *args, size=i: self._on_change_width(size))

        # Setup connections
        self._central_widget.pan_mode_action.triggered.connect(
            lambda: _toggle_mode(False))
        self._central_widget.zoom_mode_action.triggered.connect(
            lambda: _toggle_mode(True))
        self._central_widget.linear_region_action.triggered.connect(
            self.plot_widget._on_add_linear_region)
        self._central_widget.remove_region_action.triggered.connect(
            self.plot_widget._on_remove_linear_region)
        self.color_change_action.triggered.connect(self._on_change_color)
        self._central_widget.export_plot_action.triggered.connect(
            self._on_export_plot)
        self._central_widget.reset_view_action.triggered.connect(
            lambda: self._on_reset_view())
Beispiel #37
0
class Projects(SpyderPluginWidget):
    """Projects plugin."""

    CONF_SECTION = 'project_explorer'
    sig_pythonpath_changed = Signal()
    sig_project_created = Signal(object, object, object)
    sig_project_loaded = Signal(object)
    sig_project_closed = Signal(object)

    def __init__(self, parent=None):
        """Initialization."""
        SpyderPluginWidget.__init__(self, parent)

        self.explorer = ProjectExplorerWidget(
            self,
            name_filters=self.get_option('name_filters'),
            show_all=self.get_option('show_all'),
            show_hscrollbar=self.get_option('show_hscrollbar'),
            options_button=self.options_button,
            single_click_to_open=CONF.get('explorer', 'single_click_to_open'),
        )

        layout = QVBoxLayout()
        layout.addWidget(self.explorer)
        self.setLayout(layout)

        self.recent_projects = self.get_option('recent_projects', default=[])
        self.current_active_project = None
        self.latest_project = None
        self.watcher = WorkspaceWatcher(self)
        self.lsp_ready = False
        self.explorer.setup_project(self.get_active_project_path())
        self.watcher.connect_signals(self)

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _("Project")

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.explorer.treewidget

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.new_project_action = create_action(
            self, _("New Project..."), triggered=self.create_new_project)
        self.open_project_action = create_action(
            self,
            _("Open Project..."),
            triggered=lambda v: self.open_project())
        self.close_project_action = create_action(self,
                                                  _("Close Project"),
                                                  triggered=self.close_project)
        self.delete_project_action = create_action(
            self, _("Delete Project"), triggered=self.delete_project)
        self.clear_recent_projects_action =\
            create_action(self, _("Clear this list"),
                          triggered=self.clear_recent_projects)
        self.edit_project_preferences_action =\
            create_action(self, _("Project Preferences"),
                          triggered=self.edit_project_preferences)
        self.recent_project_menu = QMenu(_("Recent Projects"), self)

        if self.main is not None:
            self.main.projects_menu_actions += [
                self.new_project_action, MENU_SEPARATOR,
                self.open_project_action, self.close_project_action,
                self.delete_project_action, MENU_SEPARATOR,
                self.recent_project_menu, self._toggle_view_action
            ]

        self.setup_menu_actions()
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        ipyconsole = self.main.ipyconsole
        treewidget = self.explorer.treewidget
        lspmgr = self.main.lspmanager

        self.add_dockwidget()
        self.explorer.sig_open_file.connect(self.main.open_file)
        self.register_widget_shortcuts(treewidget)

        treewidget.sig_delete_project.connect(self.delete_project)
        treewidget.sig_edit.connect(self.main.editor.load)
        treewidget.sig_removed.connect(self.main.editor.removed)
        treewidget.sig_removed_tree.connect(self.main.editor.removed_tree)
        treewidget.sig_renamed.connect(self.main.editor.renamed)
        treewidget.sig_renamed_tree.connect(self.main.editor.renamed_tree)
        treewidget.sig_create_module.connect(self.main.editor.new)
        treewidget.sig_new_file.connect(lambda t: self.main.editor.new(text=t))
        treewidget.sig_open_interpreter.connect(
            ipyconsole.create_client_from_path)
        treewidget.redirect_stdio.connect(
            self.main.redirect_internalshell_stdio)
        treewidget.sig_run.connect(lambda fname: ipyconsole.run_script(
            fname, osp.dirname(fname), '', False, False, False, True))

        # New project connections. Order matters!
        self.sig_project_loaded.connect(
            lambda v: self.main.workingdirectory.chdir(v))
        self.sig_project_loaded.connect(lambda v: self.main.set_window_title())
        self.sig_project_loaded.connect(lspmgr.reinitialize_all_clients)
        self.sig_project_loaded.connect(
            lambda v: self.main.editor.setup_open_files())
        self.sig_project_loaded.connect(self.update_explorer)
        self.sig_project_closed[object].connect(
            lambda v: self.main.workingdirectory.chdir(self.
                                                       get_last_working_dir()))
        self.sig_project_closed.connect(lambda v: self.main.set_window_title())
        self.sig_project_closed.connect(lspmgr.reinitialize_all_clients)
        self.sig_project_closed.connect(
            lambda v: self.main.editor.setup_open_files())
        self.recent_project_menu.aboutToShow.connect(self.setup_menu_actions)

        self.main.pythonpath_changed()
        self.main.restore_scrollbar_position.connect(
            self.restore_scrollbar_position)
        self.sig_pythonpath_changed.connect(self.main.pythonpath_changed)
        self.main.editor.set_projects(self)

        # Connect to file explorer to keep single click to open files in sync
        self.main.explorer.fileexplorer.sig_option_changed.connect(
            self.set_single_click_to_open)

    def set_single_click_to_open(self, option, value):
        """Set single click to open files and directories."""
        if option == 'single_click_to_open':
            self.explorer.treewidget.set_single_click_to_open(value)

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.save_config()
        self.explorer.closing_widget()
        return True

    def switch_to_plugin(self):
        """Switch to plugin."""
        # Unmaxizime currently maximized plugin
        if (self.main.last_plugin is not None
                and self.main.last_plugin._ismaximized
                and self.main.last_plugin is not self):
            self.main.maximize_dockwidget()

        # Show plugin only if it was already visible
        if self.get_option('visible_if_project_open'):
            if not self._toggle_view_action.isChecked():
                self._toggle_view_action.setChecked(True)
            self._visibility_changed(True)

    # ------ Public API -------------------------------------------------------
    def setup_menu_actions(self):
        """Setup and update the menu actions."""
        self.recent_project_menu.clear()
        self.recent_projects_actions = []
        if self.recent_projects:
            for project in self.recent_projects:
                if self.is_valid_project(project):
                    name = project.replace(get_home_dir(), '~')
                    action = create_action(
                        self,
                        name,
                        icon=ima.icon('project'),
                        triggered=(
                            lambda _, p=project: self.open_project(path=p)))
                    self.recent_projects_actions.append(action)
                else:
                    self.recent_projects.remove(project)
            self.recent_projects_actions += [
                None, self.clear_recent_projects_action
            ]
        else:
            self.recent_projects_actions = [self.clear_recent_projects_action]
        add_actions(self.recent_project_menu, self.recent_projects_actions)
        self.update_project_actions()

    def update_project_actions(self):
        """Update actions of the Projects menu"""
        if self.recent_projects:
            self.clear_recent_projects_action.setEnabled(True)
        else:
            self.clear_recent_projects_action.setEnabled(False)

        active = bool(self.get_active_project_path())
        self.close_project_action.setEnabled(active)
        self.delete_project_action.setEnabled(active)
        self.edit_project_preferences_action.setEnabled(active)

    def edit_project_preferences(self):
        """Edit Spyder active project preferences"""
        from spyder.plugins.projects.confpage import ProjectPreferences
        if self.project_active:
            active_project = self.project_list[0]
            dlg = ProjectPreferences(self, active_project)
            #            dlg.size_change.connect(self.set_project_prefs_size)
            #            if self.projects_prefs_dialog_size is not None:
            #                dlg.resize(self.projects_prefs_dialog_size)
            dlg.show()
            #        dlg.check_all_settings()
            #        dlg.pages_widget.currentChanged.connect(self.__preference_page_changed)
            dlg.exec_()

    @Slot()
    def create_new_project(self):
        """Create new project"""
        self.switch_to_plugin()
        active_project = self.current_active_project
        dlg = ProjectDialog(self)
        dlg.sig_project_creation_requested.connect(self._create_project)
        dlg.sig_project_creation_requested.connect(self.sig_project_created)
        if dlg.exec_():
            if (active_project is None
                    and self.get_option('visible_if_project_open')):
                self.show_explorer()
            self.sig_pythonpath_changed.emit()
            self.restart_consoles()

    def _create_project(self, path):
        """Create a new project."""
        self.open_project(path=path)
        self.setup_menu_actions()
        self.add_to_recent(path)

    def open_project(self,
                     path=None,
                     restart_consoles=True,
                     save_previous_files=True):
        """Open the project located in `path`"""
        self.switch_to_plugin()
        if path is None:
            basedir = get_home_dir()
            path = getexistingdirectory(parent=self,
                                        caption=_("Open project"),
                                        basedir=basedir)
            path = encoding.to_unicode_from_fs(path)
            if not self.is_valid_project(path):
                if path:
                    QMessageBox.critical(
                        self, _('Error'),
                        _("<b>%s</b> is not a Spyder project!") % path)
                return
        else:
            path = encoding.to_unicode_from_fs(path)

        self.add_to_recent(path)

        # A project was not open before
        if self.current_active_project is None:
            if save_previous_files and self.main.editor is not None:
                self.main.editor.save_open_files()
            if self.main.editor is not None:
                self.main.editor.set_option('last_working_dir',
                                            getcwd_or_home())
            if self.get_option('visible_if_project_open'):
                self.show_explorer()
        else:
            # We are switching projects
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())

        self.current_active_project = EmptyProject(path)
        self.latest_project = EmptyProject(path)
        self.set_option('current_project_path', self.get_active_project_path())

        self.setup_menu_actions()
        self.sig_project_loaded.emit(path)
        self.sig_pythonpath_changed.emit()
        self.watcher.start(path)
        self.notify_project_open(path)

        if restart_consoles:
            self.restart_consoles()

    def close_project(self):
        """
        Close current project and return to a window without an active
        project
        """
        if self.current_active_project:
            self.switch_to_plugin()
            if self.main.editor is not None:
                self.set_project_filenames(
                    self.main.editor.get_open_filenames())
            path = self.current_active_project.root_path
            self.current_active_project = None
            self.set_option('current_project_path', None)
            self.setup_menu_actions()

            self.sig_project_closed.emit(path)
            self.sig_pythonpath_changed.emit()

            if self.dockwidget is not None:
                self.set_option('visible_if_project_open',
                                self.dockwidget.isVisible())
                self.dockwidget.close()

            self.explorer.clear()
            self.restart_consoles()
            self.watcher.stop()
            self.notify_project_close(path)

    def delete_project(self):
        """
        Delete the current project without deleting the files in the directory.
        """
        if self.current_active_project:
            self.switch_to_plugin()
            path = self.current_active_project.root_path
            buttons = QMessageBox.Yes | QMessageBox.No
            answer = QMessageBox.warning(
                self, _("Delete"),
                _("Do you really want to delete <b>{filename}</b>?<br><br>"
                  "<b>Note:</b> This action will only delete the project. "
                  "Its files are going to be preserved on disk.").format(
                      filename=osp.basename(path)), buttons)
            if answer == QMessageBox.Yes:
                try:
                    self.close_project()
                    shutil.rmtree(osp.join(path, '.spyproject'))
                except EnvironmentError as error:
                    QMessageBox.critical(
                        self, _("Project Explorer"),
                        _("<b>Unable to delete <i>{varpath}</i></b>"
                          "<br><br>The error message was:<br>{error}").format(
                              varpath=path, error=to_text_string(error)))

    def clear_recent_projects(self):
        """Clear the list of recent projects"""
        self.recent_projects = []
        self.setup_menu_actions()

    def get_active_project(self):
        """Get the active project"""
        return self.current_active_project

    def reopen_last_project(self):
        """
        Reopen the active project when Spyder was closed last time, if any
        """
        current_project_path = self.get_option('current_project_path',
                                               default=None)

        # Needs a safer test of project existence!
        if current_project_path and \
          self.is_valid_project(current_project_path):
            self.open_project(path=current_project_path,
                              restart_consoles=False,
                              save_previous_files=False)
            self.load_config()

    def get_project_filenames(self):
        """Get the list of recent filenames of a project"""
        recent_files = []
        if self.current_active_project:
            recent_files = self.current_active_project.get_recent_files()
        elif self.latest_project:
            recent_files = self.latest_project.get_recent_files()
        return recent_files

    def set_project_filenames(self, recent_files):
        """Set the list of open file names in a project"""
        if (self.current_active_project and self.is_valid_project(
                self.current_active_project.root_path)):
            self.current_active_project.set_recent_files(recent_files)

    def get_active_project_path(self):
        """Get path of the active project"""
        active_project_path = None
        if self.current_active_project:
            active_project_path = self.current_active_project.root_path
        return active_project_path

    def get_pythonpath(self, at_start=False):
        """Get project path as a list to be added to PYTHONPATH"""
        if at_start:
            current_path = self.get_option('current_project_path',
                                           default=None)
        else:
            current_path = self.get_active_project_path()
        if current_path is None:
            return []
        else:
            return [current_path]

    def get_last_working_dir(self):
        """Get the path of the last working directory"""
        return self.main.editor.get_option('last_working_dir',
                                           default=getcwd_or_home())

    def save_config(self):
        """
        Save configuration: opened projects & tree widget state.

        Also save whether dock widget is visible if a project is open.
        """
        self.set_option('recent_projects', self.recent_projects)
        self.set_option('expanded_state',
                        self.explorer.treewidget.get_expanded_state())
        self.set_option('scrollbar_position',
                        self.explorer.treewidget.get_scrollbar_position())
        if self.current_active_project and self.dockwidget:
            self.set_option('visible_if_project_open',
                            self.dockwidget.isVisible())

    def load_config(self):
        """Load configuration: opened projects & tree widget state"""
        expanded_state = self.get_option('expanded_state', None)
        # Sometimes the expanded state option may be truncated in .ini file
        # (for an unknown reason), in this case it would be converted to a
        # string by 'userconfig':
        if is_text_string(expanded_state):
            expanded_state = None
        if expanded_state is not None:
            self.explorer.treewidget.set_expanded_state(expanded_state)

    def restore_scrollbar_position(self):
        """Restoring scrollbar position after main window is visible"""
        scrollbar_pos = self.get_option('scrollbar_position', None)
        if scrollbar_pos is not None:
            self.explorer.treewidget.set_scrollbar_position(scrollbar_pos)

    def update_explorer(self):
        """Update explorer tree"""
        self.explorer.setup_project(self.get_active_project_path())

    def show_explorer(self):
        """Show the explorer"""
        if self.dockwidget is not None:
            if self.dockwidget.isHidden():
                self.dockwidget.show()
            self.dockwidget.raise_()
            self.dockwidget.update()

    def restart_consoles(self):
        """Restart consoles when closing, opening and switching projects"""
        if self.main.ipyconsole is not None:
            self.main.ipyconsole.restart()

    def is_valid_project(self, path):
        """Check if a directory is a valid Spyder project"""
        spy_project_dir = osp.join(path, '.spyproject')
        if osp.isdir(path) and osp.isdir(spy_project_dir):
            return True
        else:
            return False

    def add_to_recent(self, project):
        """
        Add an entry to recent projetcs

        We only maintain the list of the 10 most recent projects
        """
        if project not in self.recent_projects:
            self.recent_projects.insert(0, project)
            self.recent_projects = self.recent_projects[:10]

    def register_lsp_server_settings(self, settings):
        """Enable LSP workspace functions."""
        self.lsp_ready = True
        if self.current_active_project:
            path = self.get_active_project_path()
            self.notify_project_open(path)

    def stop_lsp_services(self):
        """Disable LSP workspace functions."""
        self.lsp_ready = False

    def emit_request(self, method, params, requires_response):
        """Send request/notification/response to all LSP servers."""
        params['requires_response'] = requires_response
        params['response_instance'] = self
        self.main.lspmanager.broadcast_request(method, params)

    @Slot(str, dict)
    def handle_response(self, method, params):
        """Method dispatcher for LSP requests."""
        if method in self.handler_registry:
            handler_name = self.handler_registry[method]
            handler = getattr(self, handler_name)
            handler(params)

    @Slot(str, str, bool)
    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    def file_moved(self, src_file, dest_file, is_dir):
        """Notify LSP server about a file that is moved."""
        # LSP specification only considers file updates
        if is_dir:
            return

        deletion_entry = {'file': src_file, 'kind': FileChangeType.DELETED}

        addition_entry = {'file': dest_file, 'kind': FileChangeType.CREATED}

        entries = [addition_entry, deletion_entry]
        params = {'params': entries}
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_created(self, src_file, is_dir):
        """Notify LSP server about file creation."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.CREATED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_deleted(self, src_file, is_dir):
        """Notify LSP server about file deletion."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.DELETED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_WATCHED_FILES_UPDATE,
             requires_response=False)
    @Slot(str, bool)
    def file_modified(self, src_file, is_dir):
        """Notify LSP server about file modification."""
        if is_dir:
            return

        params = {
            'params': [{
                'file': src_file,
                'kind': FileChangeType.CHANGED
            }]
        }
        return params

    @request(method=LSPRequestTypes.WORKSPACE_FOLDERS_CHANGE,
             requires_response=False)
    def notify_project_open(self, path):
        """Notify LSP server about project path availability."""
        params = {'folder': path, 'instance': self, 'kind': 'addition'}
        return params

    @request(method=LSPRequestTypes.WORKSPACE_FOLDERS_CHANGE,
             requires_response=False)
    def notify_project_close(self, path):
        """Notify LSP server to unregister project path."""
        params = {'folder': path, 'instance': self, 'kind': 'deletion'}
        return params

    @handles(LSPRequestTypes.WORKSPACE_APPLY_EDIT)
    @request(method=LSPRequestTypes.WORKSPACE_APPLY_EDIT,
             requires_response=False)
    def handle_workspace_edit(self, params):
        """Apply edits to multiple files and notify server about success."""
        edits = params['params']
        response = {
            'applied': False,
            'error': 'Not implemented',
            'language': edits['language']
        }
        return response
Beispiel #38
0
class DirView(QTreeView):
    """Base file/directory tree view."""
    sig_edit = Signal(str)
    sig_removed = Signal(str)
    sig_removed_tree = Signal(str)
    sig_renamed = Signal(str, str)
    sig_create_module = Signal(str)
    sig_run = Signal(str)
    sig_new_file = Signal(str)
    sig_open_terminal = Signal(str)
    sig_open_interpreter = Signal(str)
    redirect_stdio = Signal(bool)
    sig_add_to_project = Signal(str)

    def __init__(self, parent=None):
        super(DirView, self).__init__(parent)
        self.project_path = None
        self.name_filters = ['*.py']
        self.parent_widget = parent
        self.show_all = None
        self.menu = None
        self.common_actions = None
        self.__expanded_state = None
        self._to_be_loaded = None
        self.fsmodel = None
        self.setup_fs_model()
        self._scrollbar_positions = None

    # --- Model
    def setup_fs_model(self):
        """Setup filesystem model."""
        # On unix added Hidden to display .projectignore
        self.fsmodel = FileSystemModel(self)

    def install_model(self):
        """Install filesystem model."""
        self.setModel(self.fsmodel)

    def setup_view(self):
        """Setup view."""
        self.install_model()
        if not is_pyqt46:
            self.fsmodel.directoryLoaded.connect(
                lambda: self.resizeColumnToContents(0))
        self.setAnimated(False)
        self.setSortingEnabled(True)
        self.sortByColumn(0, Qt.AscendingOrder)
        self.fsmodel.modelReset.connect(self.reset_icon_provider)
        self.reset_icon_provider()
        # Disable the view of .spyproject.
        self.filter_directories()

    def set_name_filters(self, name_filters):
        """Set name filters."""
        self.name_filters = name_filters
        self.fsmodel.setNameFilters(name_filters)

    def set_show_all(self, state):
        """Toggle 'show all files' state."""
        if state:
            self.fsmodel.setNameFilters([])
        else:
            self.fsmodel.setNameFilters(self.name_filters)

    def get_filename(self, index):
        """Return filename associated with *index*."""
        if index:
            return osp.normpath(to_text_string(self.fsmodel.filePath(index)))

    def get_index(self, filename):
        """Return index associated with filename."""
        return self.fsmodel.index(filename)

    def get_selected_filenames(self):
        """Return selected filenames."""
        if self.selectionMode() == self.ExtendedSelection:
            return [self.get_filename(idx) for idx in self.selectedIndexes()]
        else:
            return [self.get_filename(self.currentIndex())]

    def get_dirname(self, index):
        """Return dirname associated with *index*."""
        fname = self.get_filename(index)
        if fname:
            if osp.isdir(fname):
                return fname
            else:
                return osp.dirname(fname)

    # --- Tree view widget
    def setup(self, name_filters=['*.py', '*.pyw'], show_all=False):
        """Setup tree widget."""
        self.setup_view()

        self.set_name_filters(name_filters)
        self.show_all = show_all

        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()

    def reset_icon_provider(self):
        """
        Reset file system model icon provider.

        The purpose of this is to refresh files/directories icons.
        """
        self.fsmodel.setIconProvider(IconProvider(self))

    # --- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions."""
        # Filters
        #        filters_action = create_action(
        #            self,
        #            "Edit filename filters...",
        #            None,
        #            QIcon(),  # ima.icon('filter'),
        #            triggered=self.edit_filter
        #        )
        # Show all files
        all_action = create_action(self,
                                   "Show all files",
                                   toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)

        # return [filters_action, all_action]
        return []

    @Slot()
    def edit_filter(self):
        """Edit name filters."""
        dlg = InputDialog(
            title='Edit filename filters',
            text='Name filters:',
        )

        if dlg.exec_():
            filters = dlg.text.text()
            filters = [f.strip() for f in to_text_string(filters).split(',')]
            self.parent_widget.sig_option_changed.emit('name_filters', filters)
            self.set_name_filters(filters)

    @Slot(bool)
    def toggle_all(self, checked):
        """Toggle all files mode."""
        self.parent_widget.sig_option_changed.emit('show_all', checked)
        self.show_all = checked
        self.set_show_all(checked)

    def create_file_new_actions(self, fnames):
        """Return actions for submenu 'New...'."""
        if not fnames:
            return []
        new_file_act = create_action(
            self,
            "File...",
            icon=QIcon(),  # ima.icon('filenew'),
            triggered=lambda: self.new_file(fnames[-1]))
        new_folder_act = create_action(
            self,
            "Folder...",
            icon=QIcon(),  # ima.icon('folder_new'),
            triggered=lambda: self.new_folder(fnames[-1]))
        return [new_file_act, new_folder_act]

    def create_file_import_actions(self, fnames):
        """Return actions for submenu 'Import...'."""
        return []

    def create_file_manage_actions(self, fnames):
        """Return file management actions."""
        only_files = all(osp.isfile(_fn) for _fn in fnames)
        fname = fnames[0]
        #        only_modules = all(
        #            [
        #                osp.splitext(_fn)[1] in ('.py', '.pyw', '.ipy')
        #                for _fn in fnames
        #            ]
        #        )
        #        only_notebooks = all(
        #            [osp.splitext(_fn)[1] == '.ipynb' for _fn in fnames]
        #        )
        #        only_valid = all([encoding.is_text_file(_fn) for _fn in
        #                          fnames])
        #        run_action = create_action(
        #            self,
        #            "Run",
        #            icon=QIcon(),  # ima.icon('run'),
        #            triggered=self.run,
        #        )
        #        edit_action = create_action(
        #            self,
        #            "Edit",
        #            icon=QIcon(),  # ima.icon('edit'),
        #            triggered=self.clicked,
        #        )
        move_action = create_action(
            self,
            "Move...",
            icon=QIcon(),  # "move.png",
            triggered=self.move,
        )
        delete_action = create_action(
            self,
            "Delete...",
            icon=QIcon(),  # ima.icon('editdelete'),
            triggered=self.delete)
        add_to_project_action = create_action(
            self,
            "Add to project...",
            icon=QIcon(),  # ima.icon('rename'),
            triggered=lambda x, fname=fname: self.add_to_project(fname),
        )
        rename_action = create_action(
            self,
            "Rename...",
            icon=QIcon(),  # ima.icon('rename'),
            triggered=self.rename)
        #        open_action = create_action(self, "Open", triggered=self.open)
        #        ipynb_convert_action = create_action(
        #            self,
        #            "Convert to Python script",
        #            icon=QIcon(),  # ima.icon('python'),
        #            triggered=self.convert_notebooks
        #        )

        actions = []
        #        if only_modules:
        #            actions.append(run_action)
        #        if only_valid and only_files:
        #            actions.append(edit_action)
        #        else:
        #            actions.append(open_action)
        actions += [delete_action, rename_action]
        basedir = fixpath(osp.dirname(fnames[0]))
        if all(fixpath(osp.dirname(_fn)) == basedir for _fn in fnames):
            actions.append(move_action)

        if only_files and os.path.dirname(fname) != self.project_path:
            actions += [None, add_to_project_action]

#        if only_notebooks and nbexporter is not None:
#            actions.append(ipynb_convert_action)

        return actions

    def add_to_project(self, fname):
        self.sig_add_to_project.emit(fname)

    def create_folder_manage_actions(self, fnames):
        """Return folder management actions."""
        actions = []
        # if os.name == 'nt':
        #     _title = "Open command prompt here"
        # else:
        #     _title = "Open terminal here"
        # action = create_action(
        #     self,
        #     _title,
        #     icon=QIcon(),  # ima.icon('cmdprompt'),
        #     triggered=lambda: self.open_terminal(fnames)
        # )
        # actions.append(action)
        # _title = "Open Python console here"
        # action = create_action(
        #     self,
        #     _title,
        #     icon=QIcon(),  # ima.icon('python'),
        #     triggered=lambda: self.open_interpreter(fnames)
        # )
        # actions.append(action)
        return actions

    def create_context_menu_actions(self):
        """Create context menu actions."""
        actions = []
        fnames = self.get_selected_filenames()
        new_actions = self.create_file_new_actions(fnames)
        if len(new_actions) > 1:
            # Creating a submenu only if there is more than one entry
            new_act_menu = QMenu('New', self)
            add_actions(new_act_menu, new_actions)
            actions.append(new_act_menu)
        else:
            actions += new_actions
        import_actions = self.create_file_import_actions(fnames)
        if len(import_actions) > 1:
            # Creating a submenu only if there is more than one entry
            import_act_menu = QMenu('Import', self)
            add_actions(import_act_menu, import_actions)
            actions.append(import_act_menu)
        else:
            actions += import_actions
        if actions:
            actions.append(None)
        if fnames:
            actions += self.create_file_manage_actions(fnames)
        if actions:
            actions.append(None)
        if fnames and all(osp.isdir(_fn) for _fn in fnames):
            actions += self.create_folder_manage_actions(fnames)
        if actions:
            actions.append(None)
        actions += self.common_actions
        return actions

    def update_menu(self):
        """Update context menu"""
        self.menu.clear()
        add_actions(self.menu, self.create_context_menu_actions())

    # --- Events
    def viewportEvent(self, event):
        """Reimplement Qt method"""
        # Prevent Qt from crashing or showing warnings like:
        # "QSortFilterProxyModel: index from wrong model passed to
        # mapFromSource", probably due to the fact that the file system model
        # is being built. See Issue 1250.
        #
        # This workaround was inspired by the following KDE bug:
        # https://bugs.kde.org/show_bug.cgi?id=172198
        #
        # Apparently, this is a bug from Qt itself.
        self.executeDelayedItemsLayout()

        return QTreeView.viewportEvent(self, event)

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method."""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
        elif event.key() == Qt.Key_F2:
            self.rename()
        elif event.key() == Qt.Key_Delete:
            self.delete()
        elif event.key() == Qt.Key_Backspace:
            self.go_to_parent_directory()
        else:
            QTreeView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method."""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()

    @Slot()
    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed."""
        fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isdir(fname):
                self.directory_clicked(fname)
            else:
                self.open([fname])

    def directory_clicked(self, dirname):
        """Directory was just clicked."""
        pass

    # --- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event."""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event."""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event."""
        data = QMimeData()
        data.setUrls([QUrl(fname) for fname in self.get_selected_filenames()])
        drag = QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

    # --- File/Directory actions
    @Slot()
    def open(self, fnames=None):
        """Open files with the appropriate application."""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isfile(fname) and encoding.is_text_file(fname):
                self.parent_widget.sig_open_file.emit(fname)
            else:
                self.open_outside_spyder([fname])

    def open_outside_spyder(self, fnames):
        """
        Open file outside Spyder with the appropriate application.

        If this does not work, opening unknown file in Spyder, as text file.
        """
        for path in sorted(fnames):
            path = file_uri(path)
            ok = programs.start_file(path)
            if not ok:
                self.sig_edit.emit(path)

    def open_terminal(self, fnames):
        """Open terminal."""
        for path in sorted(fnames):
            self.sig_open_terminal.emit(path)

    def open_interpreter(self, fnames):
        """Open interpreter."""
        for path in sorted(fnames):
            self.sig_open_interpreter.emit(path)

    @Slot()
    def run(self, fnames=None):
        """Run Python scripts."""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            self.sig_run.emit(fname)

    def remove_tree(self, dirname):
        """
        Remove whole directory tree.

        Reimplemented in project explorer widget.
        """
        shutil.rmtree(dirname, onerror=misc.onerror)

    def delete_file(self, fname, multiple, yes_to_all):
        """Delete file."""
        # if multiple:
        #     buttons = (
        #         QMessageBox.Yes | QMessageBox.YesAll | QMessageBox.No |
        #         QMessageBox.Cancel
        #        )
        # else:
        #     buttons = QMessageBox.Yes | QMessageBox.No
        msg_box = MessageBoxQuestion(
            title="Delete",
            text="Do you really want to delete "
            "<b>{0}</b>?".format(osp.basename(fname)),
        )

        if msg_box.exec_():
            yes_to_all = True
        else:
            return False


#            if answer == QMessageBox.No:
#                return yes_to_all
#            elif answer == QMessageBox.Cancel:
#                return False
#            elif answer == QMessageBox.YesAll:
#                yes_to_all = True
        try:
            if osp.isfile(fname):
                misc.remove_file(fname)
                self.sig_removed.emit(fname)
            else:
                self.remove_tree(fname)
                self.sig_removed_tree.emit(fname)
            return yes_to_all
        except EnvironmentError as error:
            action_str = 'delete'
            msg_box = MessageBoxError(
                text="<b>Unable to %s <i>%s</i></b>"
                "<br><br>Error message:<br>%s" %
                (action_str, fname, to_text_string(error)),
                title='Project Explorer',
            )
            msg_box.exec_()
        return False

    @Slot()
    def delete(self, fnames=None):
        """Delete files."""
        if fnames is None:
            fnames = self.get_selected_filenames()
        multiple = len(fnames) > 1
        yes_to_all = None
        for fname in fnames:
            yes_to_all = self.delete_file(fname, multiple, yes_to_all)
            if yes_to_all is not None and not yes_to_all:
                # Canceled
                break

    def convert_notebook(self, fname):
        """Convert an IPython notebook to a Python script in editor."""
        try:
            script = nbexporter().from_filename(fname)[0]
        except Exception as e:
            msg_box = MessageBoxError(
                text="It was not possible to convert this "
                "notebook. The error is:\n\n" + to_text_string(e),
                title='Conversion error',
            )
            msg_box.exec_()
            return
        self.sig_new_file.emit(script)

    @Slot()
    def convert_notebooks(self):
        """Convert IPython notebooks to Python scripts in editor."""
        fnames = self.get_selected_filenames()
        if not isinstance(fnames, (tuple, list)):
            fnames = [fnames]
        for fname in fnames:
            self.convert_notebook(fname)

    def rename_file(self, fname):
        """Rename file."""
        dlg = InputDialog(
            title='Rename',
            text='New name:',
            value=osp.basename(fname),
        )
        if dlg.exec_():
            path = dlg.text.text()
            path = osp.join(osp.dirname(fname), to_text_string(path))
            if path == fname:
                return
            if osp.exists(path):
                msg_box = MessageBoxQuestion(
                    self,
                    title="Rename",
                    text="Do you really want to rename <b>%s</b> and "
                    "overwrite the existing file <b>%s</b>?" %
                    (osp.basename(fname), osp.basename(path)),
                )
                if not msg_box.exec_():
                    return
            try:
                misc.rename_file(fname, path)
                self.sig_renamed.emit(fname, path)
                return path
            except EnvironmentError as error:
                msg_box = MessageBoxError(
                    text="<b>Unable to rename file <i>%s</i></b>"
                    "<br><br>Error message:<br>%s" %
                    (osp.basename(fname), to_text_string(error)),
                    title='Rename error',
                )
                msg_box.exec_()

    @Slot()
    def rename(self, fnames=None):
        """Rename files."""
        if fnames is None:
            fnames = self.get_selected_filenames()
        if not isinstance(fnames, (tuple, list)):
            fnames = [fnames]
        for fname in fnames:
            self.rename_file(fname)

    @Slot()
    def move(self, fnames=None):
        """Move files/directories."""
        if fnames is None:
            fnames = self.get_selected_filenames()
        orig = fixpath(osp.dirname(fnames[0]))
        while True:
            self.redirect_stdio.emit(False)
            folder = getexistingdirectory(self, "Select directory", orig)
            self.redirect_stdio.emit(True)
            if folder:
                folder = fixpath(folder)
                if folder != orig:
                    break
            else:
                return
        for fname in fnames:
            basename = osp.basename(fname)
            try:
                misc.move_file(fname, osp.join(folder, basename))
            except EnvironmentError as error:
                msg_box = MessageBoxError(
                    text="<b>Unable to move <i>%s</i></b>"
                    "<br><br>Error message:<br>%s" %
                    (basename, to_text_string(error)),
                    title='Error',
                )
                msg_box.exec_()

    def create_new_folder(self, current_path, title, subtitle, is_package):
        """Create new folder."""
        if current_path is None:
            current_path = ''
        if osp.isfile(current_path):
            current_path = osp.dirname(current_path)

        dlg = InputDialog(title=title, text=subtitle, value='')
        if dlg.exec_():
            name = dlg.text.text()
            dirname = osp.join(current_path, to_text_string(name))
            try:
                os.mkdir(dirname)
            except EnvironmentError as error:
                msg_box = MessageBoxError(
                    text="<b>Unable "
                    "to create folder <i>%s</i></b>"
                    "<br><br>Error message:<br>%s" %
                    (dirname, to_text_string(error)),
                    title=title,
                )
                msg_box.exec_()
            finally:
                if is_package:
                    fname = osp.join(dirname, '__init__.py')
                    try:
                        with open(fname, 'wb') as f:
                            f.write(to_binary_string('#'))
                        return dirname
                    except EnvironmentError as error:
                        msg_box = MessageBoxError(
                            text="<b>Unable "
                            "to create file <i>%s</i></b>"
                            "<br><br>Error message:<br>%s" %
                            (fname, to_text_string(error)),
                            title=title,
                        )
                        msg_box.exec_()

    def new_folder(self, basedir):
        """New folder."""
        title = 'New folder'
        subtitle = 'Folder name:'
        self.create_new_folder(basedir, title, subtitle, is_package=False)

    def new_package(self, basedir):
        """New package."""
        title = 'New package'
        subtitle = 'Package name:'
        self.create_new_folder(basedir, title, subtitle, is_package=True)

    def create_new_file(self, current_path, title, filters, create_func):
        """
        Create new file.

        Returns True if successful.
        """
        if current_path is None:
            current_path = ''
        if osp.isfile(current_path):
            current_path = osp.dirname(current_path)
        self.redirect_stdio.emit(False)
        fname, _selfilter = getsavefilename(self, title, current_path, filters)
        self.redirect_stdio.emit(True)
        if fname:
            try:
                create_func(fname)
                return fname
            except EnvironmentError as error:
                msg_box = MessageBoxError(
                    text="<b>Unable to create file <i>%s</i>"
                    "</b><br><br>Error message:<br>%s" %
                    (fname, to_text_string(error)),
                    title='New file error',
                )
                msg_box.exec_()

    def new_file(self, basedir):
        """New file."""
        title = "New file"
        filters = "All files" + " (*)"

        def create_func(fname):
            """File creation callback."""
            if osp.splitext(fname)[1] in ('.py', '.pyw', '.ipy'):
                create_script(fname)
            else:
                with open(fname, 'wb') as f:
                    f.write(to_binary_string(''))

        fname = self.create_new_file(basedir, title, filters, create_func)
        if fname is not None:
            self.open([fname])

    def new_module(self, basedir):
        """New module."""
        title = "New module"
        filters = "Python scripts" + " (*.py *.pyw *.ipy)"

        def create_func(fname):
            self.sig_create_module.emit(fname)

        self.create_new_file(basedir, title, filters, create_func)

    def go_to_parent_directory(self):
        pass

    # ---- Settings
    def get_scrollbar_position(self):
        """Return scrollbar positions"""
        return (self.horizontalScrollBar().value(),
                self.verticalScrollBar().value())

    def set_scrollbar_position(self, position):
        """Set scrollbar positions"""
        # Scrollbars will be restored after the expanded state
        self._scrollbar_positions = position
        if self._to_be_loaded is not None and len(self._to_be_loaded) == 0:
            self.restore_scrollbar_positions()

    def restore_scrollbar_positions(self):
        """Restore scrollbar positions once tree is loaded"""
        hor, ver = self._scrollbar_positions
        self.horizontalScrollBar().setValue(hor)
        self.verticalScrollBar().setValue(ver)

    def get_expanded_state(self):
        """Return expanded state"""
        self.save_expanded_state()
        return self.__expanded_state

    def set_expanded_state(self, state):
        """Set expanded state"""
        self.__expanded_state = state
        self.restore_expanded_state()

    def save_expanded_state(self):
        """Save all items expanded state"""
        model = self.model()
        # If model is not installed, 'model' will be None: this happens when
        # using the Project Explorer without having selected a workspace yet
        if model is not None:
            self.__expanded_state = []
            for idx in model.persistentIndexList():
                if self.isExpanded(idx):
                    self.__expanded_state.append(self.get_filename(idx))

    def restore_directory_state(self, fname):
        """Restore directory expanded state"""
        root = osp.normpath(to_text_string(fname))
        if not osp.exists(root):
            # Directory has been (re)moved outside Spyder
            return
        for basename in os.listdir(root):
            path = osp.normpath(osp.join(root, basename))
            if osp.isdir(path) and path in self.__expanded_state:
                self.__expanded_state.pop(self.__expanded_state.index(path))
                if self._to_be_loaded is None:
                    self._to_be_loaded = []
                self._to_be_loaded.append(path)
                self.setExpanded(self.get_index(path), True)
        if not self.__expanded_state and not is_pyqt46:
            self.fsmodel.directoryLoaded.disconnect(
                self.restore_directory_state)

    def follow_directories_loaded(self, fname):
        """Follow directories loaded during startup"""
        if self._to_be_loaded is None:
            return
        path = osp.normpath(to_text_string(fname))
        if path in self._to_be_loaded:
            self._to_be_loaded.remove(path)
        if (self._to_be_loaded is not None and len(self._to_be_loaded) == 0
                and not is_pyqt46):
            self.fsmodel.directoryLoaded.disconnect(
                self.follow_directories_loaded)
            if self._scrollbar_positions is not None:
                # The tree view need some time to render branches:
                QTimer.singleShot(50, self.restore_scrollbar_positions)

    def restore_expanded_state(self):
        """Restore all items expanded state"""
        if self.__expanded_state is not None:
            # In the old explorer, the expanded state was a dictionnary:
            if isinstance(self.__expanded_state, list) and not is_pyqt46:
                self.fsmodel.directoryLoaded.connect(
                    self.restore_directory_state)
                self.fsmodel.directoryLoaded.connect(
                    self.follow_directories_loaded)

    def filter_directories(self):
        """Filter the directories to show"""
        index = self.get_index('.spyproject')
        if index is not None:
            self.setRowHidden(index.row(), index.parent(), True)
Beispiel #39
0
class BasePluginWidgetMixin(object):
    """
    Implementation of the basic functionality for Spyder plugin widgets.
    """

    _ALLOWED_AREAS = Qt.AllDockWidgetAreas
    _LOCATION = Qt.LeftDockWidgetArea
    _FEATURES = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable

    def __init__(self, parent=None):
        super(BasePluginWidgetMixin, self).__init__()

        # Actions to add to the Options menu
        self._plugin_actions = None

        # Attribute to keep track if the plugin is undocked in a
        # separate window
        self._undocked_window = None

        self._ismaximized = False
        self._default_margins = None
        self._isvisible = False

        self.shortcut = None

        # Options buttons
        self.options_button = create_toolbutton(self, text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)

        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")

        # Options menu
        self._options_menu = QMenu(self)

        # We decided to create our own toggle action instead of using
        # the one that comes with dockwidget because it's not possible
        # to raise and focus the plugin with it.
        self._toggle_view_action = None

        # Default actions for Options menu
        self._dock_action = create_action(
            self,
            _("Dock"),
            icon=ima.icon('dock'),
            tip=_("Dock the pane"),
            triggered=self._close_window)

        self._undock_action = create_action(
            self,
            _("Undock"),
            icon=ima.icon('undock'),
            tip=_("Undock the pane"),
            triggered=self._create_window)

        self._close_plugin_action = create_action(
            self,
            _("Close"),
            icon=ima.icon('close_pane'),
            tip=_("Close the pane"),
            triggered=self._plugin_closed)

    def _initialize_plugin_in_mainwindow_layout(self):
        """
        If this is the first time the plugin is shown, perform actions to
        initialize plugin position in Spyder's window layout.

        Use on_first_registration to define the actions to be run
        by your plugin
        """
        if self.get_option('first_time', True):
            try:
                self.on_first_registration()
            except NotImplementedError:
                return
            self.set_option('first_time', False)

    def _update_margins(self):
        """Update plugin margins"""
        layout = self.layout()
        if self._default_margins is None:
            self._default_margins = layout.getContentsMargins()
        if CONF.get('main', 'use_custom_margin'):
            margin = CONF.get('main', 'custom_margin')
            layout.setContentsMargins(*[margin]*4)
        else:
            layout.setContentsMargins(*self._default_margins)

    def _update_plugin_title(self):
        """Update plugin title, i.e. dockwidget or window title"""
        if self.dockwidget is not None:
            win = self.dockwidget
        elif self._undocked_window is not None:
            win = self._undocked_window
        else:
            return
        win.setWindowTitle(self.get_plugin_title())

    def _create_dockwidget(self):
        """Add to parent QMainWindow as a dock widget"""
        # Creating dock widget
        dock = SpyderDockWidget(self.get_plugin_title(), self.main)

        # Set properties
        dock.setObjectName(self.__class__.__name__+"_dw")
        dock.setAllowedAreas(self._ALLOWED_AREAS)
        dock.setFeatures(self._FEATURES)
        dock.setWidget(self)
        self._update_margins()
        dock.visibilityChanged.connect(self._visibility_changed)
        dock.topLevelChanged.connect(self._on_top_level_changed)
        dock.sig_plugin_closed.connect(self._plugin_closed)
        self.dockwidget = dock

        # NOTE: Don't use the default option of CONF.get to assign a
        # None shortcut to plugins that don't have one. That will mess
        # the creation of our Keyboard Shortcuts prefs page
        try:
            context = '_'
            name = 'switch to {}'.format(self.CONF_SECTION)
            self.shortcut = CONF.get_shortcut(context, name,
                                              plugin_name=self.CONF_SECTION)
        except (configparser.NoSectionError, configparser.NoOptionError):
            pass

        if self.shortcut is not None and self.main is not None:
            sc = QShortcut(QKeySequence(self.shortcut), self.main,
                           self.switch_to_plugin)
            self.register_shortcut(sc, "_", "Switch to {}".format(
                self.CONF_SECTION))

        return (dock, self._LOCATION)

    def _switch_to_plugin(self):
        """Switch to plugin."""
        if (self.main.last_plugin is not None and
                self.main.last_plugin._ismaximized and
                self.main.last_plugin is not self):
            self.main.maximize_dockwidget()
        if not self._toggle_view_action.isChecked():
            self._toggle_view_action.setChecked(True)
        self._visibility_changed(True)

    @Slot()
    def _plugin_closed(self):
        """DockWidget was closed."""
        if self._toggle_view_action:
            self._toggle_view_action.setChecked(False)

    def _get_font(self, rich_text=False):
        """Return plugin font."""
        if rich_text:
            option = 'rich_font'
            font_size_delta = self.RICH_FONT_SIZE_DELTA
        else:
            option = 'font'
            font_size_delta = self.FONT_SIZE_DELTA

        return get_font(option=option, font_size_delta=font_size_delta)

    def set_plugin_font(self):
        """
        Set plugin font option.

        Note: All plugins in Spyder use a global font. To define a different
        size, the plugin must define a 'FONT_SIZE_DELTA' class variable.
        """
        raise Exception("Plugins font is based on the general settings, "
                        "and cannot be set directly on the plugin."
                        "This method is deprecated.")

    def _create_toggle_view_action(self):
        """Associate a toggle view action with each plugin"""
        title = self.get_plugin_title()
        if self.CONF_SECTION == 'editor':
            title = _('Editor')
        if self.shortcut is not None:
            action = create_action(self, title,
                             toggled=lambda checked: self.toggle_view(checked),
                             shortcut=QKeySequence(self.shortcut),
                             context=Qt.WidgetShortcut)
        else:
            action = create_action(self, title, toggled=lambda checked:
                                                self.toggle_view(checked))
        self._toggle_view_action = action

    @Slot()
    def _close_window(self):
        """Close QMainWindow instance that contains this plugin."""
        if self._undocked_window is not None:
            self._undocked_window.close()
            self._undocked_window = None

            # Oddly, these actions can appear disabled after the Dock
            # action is pressed
            self._undock_action.setDisabled(False)
            self._close_plugin_action.setDisabled(False)

    @Slot()
    def _create_window(self):
        """Create a QMainWindow instance containing this plugin."""
        self._undocked_window = window = PluginWindow(self)
        window.setAttribute(Qt.WA_DeleteOnClose)
        icon = self.get_plugin_icon()
        if is_text_string(icon):
            icon = self.get_icon(icon)
        window.setWindowIcon(icon)
        window.setWindowTitle(self.get_plugin_title())
        window.setCentralWidget(self)
        window.resize(self.size())
        self.refresh_plugin()

        self.dockwidget.setFloating(False)
        self.dockwidget.setVisible(False)

        window.show()

    @Slot(bool)
    def _on_top_level_changed(self, top_level):
        """Actions to perform when a plugin is undocked to be moved."""
        if top_level:
            self._undock_action.setDisabled(True)
        else:
            self._undock_action.setDisabled(False)

    def _visibility_changed(self, enable):
        """Dock widget visibility has changed."""
        if self.dockwidget is None:
            return
        if enable:
            self.dockwidget.raise_()
            widget = self.get_focus_widget()
            if widget is not None and self._undocked_window is not None:
                widget.setFocus()
        visible = self.dockwidget.isVisible() or self._ismaximized
        if self.DISABLE_ACTIONS_WHEN_HIDDEN:
            toggle_actions(self._plugin_actions, visible)
        self._isvisible = enable and visible
        if self._isvisible:
            self.refresh_plugin()

    def _refresh_actions(self):
        """Refresh Options menu."""
        self._options_menu.clear()

        # Decide what additional actions to show
        if self._undocked_window is None:
            additional_actions = [MENU_SEPARATOR,
                                  self._undock_action,
                                  self._close_plugin_action]
        else:
            additional_actions = [MENU_SEPARATOR,
                                  self._dock_action]

        # Create actions list
        self._plugin_actions = self.get_plugin_actions() + additional_actions
        add_actions(self._options_menu, self._plugin_actions)

        if sys.platform == 'darwin':
            set_menu_icons(self._options_menu, True)

    def _setup(self):
        """
        Setup Options menu, create toggle action and connect signals.
        """
        # Creat toggle view action
        self._create_toggle_view_action()

        # Create Options menu
        self._plugin_actions = self.get_plugin_actions() + [MENU_SEPARATOR,
                                                            self._undock_action]
        add_actions(self._options_menu, self._plugin_actions)
        self.options_button.setMenu(self._options_menu)
        self._options_menu.aboutToShow.connect(self._refresh_actions)

        # Show icons in Mac plugin menus
        if sys.platform == 'darwin':
            self._options_menu.aboutToHide.connect(
                lambda menu=self._options_menu:
                set_menu_icons(menu, False))

        # Update title
        self.sig_update_plugin_title.connect(self._update_plugin_title)
        self.setWindowTitle(self.get_plugin_title())

    def _register_shortcut(self, qaction_or_qshortcut, context, name,
                           add_shortcut_to_tip=False):
        """Register a shortcut associated to a QAction or QShortcut."""
        self.main.register_shortcut(
            qaction_or_qshortcut,
            context,
            name,
            add_shortcut_to_tip,
            self.CONF_SECTION)

    def _get_color_scheme(self):
        """Get the current color scheme."""
        return get_color_scheme(CONF.get('appearance', 'selected'))

    def _add_dockwidget(self):
        """Add dockwidget to the main window and set it up."""
        self.main.add_dockwidget(self)

        # This is not necessary for the Editor because it calls
        # _setup directly on init.
        if self.CONF_SECTION != 'editor':
            self._setup()

    def _tabify(self, core_plugin):
        """Tabify plugin next to a core plugin."""
        self.main.tabify_plugins(core_plugin, self)
Beispiel #40
0
class LineEditChannel(LineEditBase):
    """
    Custom line edit that uses different validators for text and url.

    More info:
    http://conda.pydata.org/docs/config.html#channel-locations-channels

    Valid entries:
    - defaults  <- Special case
    - <some-channel-name>
    - https://conda.anaconda.org/<channel>/<package>
    - https://conda.anaconda.org/t/<token>/<package>
    - http://<some.custom.url>/<channel>
    - https://<some.custom.url>/<channel>
    - file:///<some-local-directory>
    """

    VALID_RE = QRegExp('^[A-Za-z][A-Za-z0-9/_-]+$|'
                       '^https?://.*|'
                       '^file:///.*')

    sig_return_pressed = Signal()
    sig_escape_pressed = Signal()
    sig_copied = Signal()

    def __init__(self, *args, **kwargs):
        """Custom line edit that uses different validators for text and url."""
        super(LineEditChannel, self).__init__(*args, **kwargs)
        self._validator = QRegExpValidator(self.VALID_RE)
        self.menu = QMenu(parent=self)
        self.setValidator(self._validator)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def event(self, event):
        """Override Qt method."""
        if (event.type() == QEvent.MouseButtonPress
                and event.buttons() & Qt.RightButton and not self.isEnabled()):
            self.show_menu(event.pos())
            return True
        else:
            return super(LineEditChannel, self).event(event)

    def keyPressEvent(self, event):
        """Override Qt method."""
        key = event.key()

        # Display a copy menu in case the widget is disabled.
        if event.matches(QKeySequence.Paste):
            clipboard = QApplication.clipboard()
            text = clipboard.text()
            if self.VALID_RE.exactMatch(text):
                self.setText(text)
                return
        else:
            if key in [Qt.Key_Return, Qt.Key_Enter]:
                self.sig_return_pressed.emit()
            elif key in [Qt.Key_Escape]:
                self.sig_escape_pressed.emit()
        super(LineEditChannel, self).keyPressEvent(event)

    def show_menu(self, pos):
        """Show copy menu for channel item."""
        self.menu.clear()
        copy = QAction("&Copy", self.menu)
        copy.triggered.connect(self.copy_text)
        self.menu.addAction(copy)
        self.menu.setEnabled(True)
        self.menu.exec_(self.mapToGlobal(pos))

    def copy_text(self):
        """Copy channel text to clipboard."""
        clipboard = QApplication.clipboard()
        clipboard.setText(self.text())
        self.sig_copied.emit()
Beispiel #41
0
class ListItemApplication(ListWidgetItemBase):
    """Item with custom widget for the applications list."""

    ICON_SIZE = 64

    def __init__(
        self,
        name=None,
        description=None,
        command=None,
        versions=None,
        image_path=None,
        prefix=None,
        needs_license=False,
        non_conda=False,
    ):
        """Item with custom widget for the applications list."""
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.prefix = prefix
        self.name = name
        self.url = ''
        self.expired = False
        self.needs_license = needs_license
        self.description = description
        self.command = command
        self.versions = versions
        self.image_path = image_path if image_path else ANACONDA_ICON_256_PATH
        self.style_sheet = None
        self.timeout = 2000
        self.non_conda = non_conda
        self._vscode_version_value = None

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")  # or Try!
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_license = LabelApplicationLicense('')
        self.button_license = ButtonApplicationLicense('')
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(self.name)
        self.label_description = LabelApplicationDescription(self.description)
        self.button_version = ButtonApplicationVersion(
            to_text_string(self.version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.pixmap = QPixmap(self.image_path)
        self.timer = QTimer()
        self.widget = WidgetApplication()
        self.frame_spinner = FrameApplicationSpinner()
        self.spinner = NavigatorSpinner(self.widget, total_width=16)
        lay = QHBoxLayout()
        lay.addWidget(self.spinner)
        self.frame_spinner.setLayout(lay)

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.button_version.setEnabled(True)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setPixmap(self.pixmap)
        self.label_icon.setScaledContents(True)  # important on High DPI!
        self.label_icon.setMaximumWidth(self.ICON_SIZE)
        self.label_icon.setMaximumHeight(self.ICON_SIZE)
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.frame_spinner.setVisible(False)

        # Layouts
        layout_spinner = QHBoxLayout()
        layout_spinner.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout_spinner.addWidget(self.frame_spinner, 0, Qt.AlignCenter)

        layout_license = QHBoxLayout()
        layout_license.addStretch()
        layout_license.addWidget(self.label_license, 0, Qt.AlignCenter)
        layout_license.addWidget(self.button_license, 0, Qt.AlignCenter)
        layout_license.addStretch()

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.button_options, 0, Qt.AlignRight)
        layout_main.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout_main.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout_main.addLayout(layout_spinner)
        layout_main.addLayout(layout_license)
        layout_main.addWidget(self.label_description, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout_main)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget_size())
        # This might help with visual quirks on the home screen
        self.widget.setMinimumSize(self.widget_size())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.button_license.clicked.connect(self.launch_url)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _application_launched(self):
        self.button_launch.setDisabled(False)
        update_pointer()

    # --- Helpers
    # -------------------------------------------------------------------------
    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet:
            self.style_sheet = style_sheet
        else:
            self.style_sheet = load_style_sheet()

        self.menu_options.setStyleSheet(self.style_sheet)
        self.menu_versions.setStyleSheet(self.style_sheet)

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        return [
            self.button_license, self.button_install, self.button_launch,
            self.button_options
        ]

    @staticmethod
    def widget_size():
        """Return the size defined in the SASS file."""
        return QSize(SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_WIDTH,
                     SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_HEIGHT)

    def launch_url(self):
        """Launch signal for url click."""
        self.widget.sig_url_clicked.emit(self.url)

    def actions_menu_requested(self):
        """Create and display menu for the currently selected application."""
        self.menu_options.clear()
        self.menu_versions.clear()

        # Add versions menu
        versions = self.versions if self.versions else []
        version_actions = []
        for version in reversed(versions):
            action = create_action(self.widget,
                                   version,
                                   triggered=lambda value, version=version:
                                   self.install_application(version=version))

            action.setCheckable(True)
            if self.version == version and self.installed:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        install_action = create_action(
            self.widget,
            'Install application',
            triggered=lambda: self.install_application())
        install_action.setEnabled(not self.installed)

        update_action = create_action(
            self.widget,
            'Update application',
            triggered=lambda: self.update_application())

        if versions and versions[-1] == self.version:
            update_action.setDisabled(True)
        else:
            update_action.setDisabled(False)

        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            update_action.setDisabled(True)

        remove_action = create_action(
            self.widget,
            'Remove application',
            triggered=lambda: self.remove_application())
        remove_action.setEnabled(self.installed)

        actions = [
            install_action, update_action, remove_action, None,
            self.menu_versions
        ]
        add_actions(self.menu_options, actions)
        add_actions(self.menu_versions, version_actions)
        offset = QPoint(self.button_options.width(), 0)
        position = self.button_options.mapToGlobal(QPoint(0, 0))
        self.menu_versions.setEnabled(len(versions) > 1)
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        """Update status."""
        # License check
        license_label_text = ''
        license_url_text = ''
        self.url = ''
        self.expired = False
        button_label = 'Install'

        if self.needs_license:
            # TODO: Fix this method to use the api
            license_info = self.api.get_package_license(self.name)
            license_days = self.api.get_days_left(license_info)
            end_date = license_info.get('end_date', '')
            self.expired = license_days == 0
            plural = 's' if license_days != 1 else ''
            is_trial = license_info.get('type', '').lower() == 'trial'

            if self.installed and license_info:
                if is_trial and not self.expired:
                    license_label_text = ('Trial, {days} day{plural} '
                                          'remaining'.format(days=license_days,
                                                             plural=plural))
                    self.url = ''
                elif is_trial and self.expired:
                    license_label_text = 'Trial expired, '
                    license_url_text = 'contact us'
                    self.url = 'mailto:[email protected]'
                elif not is_trial and not self.expired:
                    license_label_text = 'License expires {}'.format(end_date)
                    self.url = ''
                elif not is_trial and self.expired:
                    license_url_text = 'Renew license'
                    self.url = 'mailto:[email protected]'
            elif self.installed and not bool(license_info):
                # Installed but no license found!
                license_url_text = 'No license found'
                self.url = 'mailto:[email protected]'
            else:
                if not self.expired:
                    button_label = 'Install'
                else:
                    button_label = 'Try'

        self.button_license.setText(license_url_text)
        self.button_license.setVisible(bool(self.url))
        self.label_license.setText(license_label_text)
        self.label_license.setVisible(bool(license_label_text))

        # Version and version updates
        if (self.versions and self.version != self.versions[-1]
                and self.installed):
            # The property is used with CSS to display updatable packages.
            self.button_version.setProperty('pressed', True)
            self.button_version.setToolTip('Version {0} available'.format(
                self.versions[-1]))
        else:
            self.button_version.setProperty('pressed', False)

        if not self.needs_license:
            self.button_install.setText(button_label)
            self.button_install.setVisible(not self.installed)
            self.button_launch.setVisible(self.installed)
        else:
            self.button_install.setText('Try' if self.expired else 'Install')
            self.button_launch.setVisible(not self.expired)
            self.button_install.setVisible(self.expired)

        self.button_launch.setEnabled(True)

    def update_versions(self, version=None, versions=None):
        """Update button visibility depending on update availability."""
        logger.debug(str((self.name, self.dev_tool, self.installed)))

        if self.installed and version:
            self.button_options.setVisible(True)
            self.button_version.setText(version)
            self.button_version.setVisible(True)
        elif not self.installed and versions:
            self.button_install.setEnabled(True)
            self.button_version.setText(versions[-1])
            self.button_version.setVisible(True)

        self.versions = versions
        self.version = version
        self.update_status()

    def set_loading(self, value):
        """Set loading status."""
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)
        self.button_launch.setDisabled(value)
        self.button_license.setDisabled(value)

        if value:
            self.spinner.start()
        else:
            self.spinner.stop()
            if self.version is None and self.versions is not None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)
            self.button_launch.setDisabled(self.expired)

        self.frame_spinner.setVisible(value)
        self.button_version.setVisible(not value)

    # --- Helpers using api
    # -------------------------------------------------------------------------
    def _vscode_version(self):
        """Query the vscode version for the default installation path."""
        version = None
        if self._vscode_version_value is None:
            cmd = [self.api.vscode_executable(), '--version']
            # print(cmd)
            import subprocess
            try:
                output = subprocess.check_output(cmd)
                if PY3:
                    output = output.decode()
                output = [o for o in output.split('\n') if o and '.' in o]
                # print(output)
                if output:
                    version = output[0]
            except Exception:
                pass
                # print(e)

            self._vscode_version_value = version
        else:
            version = self._vscode_version_value

        return version

    @property
    def installed(self):
        """Return the installed status of the package."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            # TODO: Vscode program location, check existence
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)
        return bool(version)

    @property
    def version(self):
        """Return the current installed version or the highest version."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)

        if not version:
            version = self.versions[-1]

        return version

    # --- Application actions
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None, install=True):
        """
        Update the application on the defined prefix environment.

        This is used for both normal install and specific version install.
        """
        if not version:
            version = self.versions[-1]

        action = C.APPLICATION_INSTALL if install else C.APPLICATION_UPDATE
        self.widget.sig_conda_action_requested.emit(
            action,
            self.name,
            version,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def remove_application(self):
        """Remove the application from the defined prefix environment."""
        self.widget.sig_conda_action_requested.emit(
            C.APPLICATION_REMOVE,
            self.name,
            None,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def update_application(self):
        """Update the application on the defined prefix environment."""
        self.install_application(version=self.versions[-1], install=False)

    def launch_application(self):
        """Launch application installed in prefix environment."""
        leave_path_alone = False
        if self.command is not None:
            if self.non_conda and self.name == GLOBAL_VSCODE_APP:
                leave_path_alone = True
                args = [self.command]
            else:
                args = self.command.split(' ')
                leave_path_alone = True

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            self.widget.sig_launch_action_requested.emit(
                self.name,
                args,
                leave_path_alone,
                self.prefix,
                C.TAB_HOME,
                self.non_conda,
            )
Beispiel #42
0
    def __init__(
        self,
        name=None,
        description=None,
        command=None,
        versions=None,
        image_path=None,
        prefix=None,
        needs_license=False,
        non_conda=False,
    ):
        """Item with custom widget for the applications list."""
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.prefix = prefix
        self.name = name
        self.url = ''
        self.expired = False
        self.needs_license = needs_license
        self.description = description
        self.command = command
        self.versions = versions
        self.image_path = image_path if image_path else ANACONDA_ICON_256_PATH
        self.style_sheet = None
        self.timeout = 2000
        self.non_conda = non_conda
        self._vscode_version_value = None

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")  # or Try!
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_license = LabelApplicationLicense('')
        self.button_license = ButtonApplicationLicense('')
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(self.name)
        self.label_description = LabelApplicationDescription(self.description)
        self.button_version = ButtonApplicationVersion(
            to_text_string(self.version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.pixmap = QPixmap(self.image_path)
        self.timer = QTimer()
        self.widget = WidgetApplication()
        self.frame_spinner = FrameApplicationSpinner()
        self.spinner = NavigatorSpinner(self.widget, total_width=16)
        lay = QHBoxLayout()
        lay.addWidget(self.spinner)
        self.frame_spinner.setLayout(lay)

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.button_version.setEnabled(True)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setPixmap(self.pixmap)
        self.label_icon.setScaledContents(True)  # important on High DPI!
        self.label_icon.setMaximumWidth(self.ICON_SIZE)
        self.label_icon.setMaximumHeight(self.ICON_SIZE)
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.frame_spinner.setVisible(False)

        # Layouts
        layout_spinner = QHBoxLayout()
        layout_spinner.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout_spinner.addWidget(self.frame_spinner, 0, Qt.AlignCenter)

        layout_license = QHBoxLayout()
        layout_license.addStretch()
        layout_license.addWidget(self.label_license, 0, Qt.AlignCenter)
        layout_license.addWidget(self.button_license, 0, Qt.AlignCenter)
        layout_license.addStretch()

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.button_options, 0, Qt.AlignRight)
        layout_main.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout_main.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout_main.addLayout(layout_spinner)
        layout_main.addLayout(layout_license)
        layout_main.addWidget(self.label_description, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout_main)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget_size())
        # This might help with visual quirks on the home screen
        self.widget.setMinimumSize(self.widget_size())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.button_license.clicked.connect(self.launch_url)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()
Beispiel #43
0
class PluginWidget(QWidget, BasePluginMixin):
    """
    Public interface for Spyder plugins.

    Warning: Don't override any methods present here!

    Signals:
      * sig_option_changed
          Example:
            plugin.sig_option_changed.emit('show_all', checked)
      * sig_show_message
      * sig_update_plugin_title
    """

    sig_option_changed = Signal(str, object)
    sig_show_message = Signal(str, int)
    sig_update_plugin_title = Signal()

    def __init__(self, main=None):
        """Bind widget to a QMainWindow instance."""
        super(PluginWidget, self).__init__(main)
        assert self.CONF_SECTION is not None

        self.dockwidget = None
        self.undocked_window = None

        # Check compatibility
        check_compatibility, message = self.check_compatibility()
        if not check_compatibility:
            self.show_compatibility_message(message)

        self.PLUGIN_PATH = os.path.dirname(inspect.getfile(self.__class__))
        self.main = main
        self.default_margins = None
        self.plugin_actions = None
        self.ismaximized = False
        self.isvisible = False

        # Options button and menu
        self.options_button = create_toolbutton(self,
                                                text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)
        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")
        self.options_menu = QMenu(self)

        # NOTE: Don't use the default option of CONF.get to assign a
        # None shortcut to plugins that don't have one. That will mess
        # the creation of our Keyboard Shortcuts prefs page
        try:
            self.shortcut = CONF.get('shortcuts',
                                     '_/switch to %s' % self.CONF_SECTION)
        except configparser.NoOptionError:
            pass

        # We decided to create our own toggle action instead of using
        # the one that comes with dockwidget because it's not possible
        # to raise and focus the plugin with it.
        self.toggle_view_action = None

    def initialize_plugin(self):
        """
        Initialize plugin: connect signals, setup actions, etc.

        It must be run at the end of __init__
        """
        self.create_toggle_view_action()

        self.plugin_actions = self.get_plugin_actions() + [
            MENU_SEPARATOR, self.undock_action
        ]
        add_actions(self.options_menu, self.plugin_actions)
        self.options_button.setMenu(self.options_menu)
        self.options_menu.aboutToShow.connect(self.refresh_actions)

        self.sig_show_message.connect(self.show_message)
        self.sig_update_plugin_title.connect(self.update_plugin_title)
        self.sig_option_changed.connect(self.set_option)
        self.setWindowTitle(self.get_plugin_title())

    def register_shortcut(self,
                          qaction_or_qshortcut,
                          context,
                          name,
                          add_sc_to_tip=False):
        """
        Register QAction or QShortcut to Spyder main application.

        if add_sc_to_tip is True, the shortcut is added to the
        action's tooltip
        """
        self.main.register_shortcut(qaction_or_qshortcut, context, name,
                                    add_sc_to_tip)

    def register_widget_shortcuts(self, widget):
        """
        Register widget shortcuts.

        Widget interface must have a method called 'get_shortcut_data'
        """
        for qshortcut, context, name in widget.get_shortcut_data():
            self.register_shortcut(qshortcut, context, name)

    def visibility_changed(self, enable):
        """
        Dock widget visibility has changed.
        """
        if self.dockwidget is None:
            return
        if enable:
            self.dockwidget.raise_()
            widget = self.get_focus_widget()
            if widget is not None and self.undocked_window is not None:
                widget.setFocus()
        visible = self.dockwidget.isVisible() or self.ismaximized
        if self.DISABLE_ACTIONS_WHEN_HIDDEN:
            toggle_actions(self.plugin_actions, visible)
        self.isvisible = enable and visible
        if self.isvisible:
            self.refresh_plugin()  # To give focus to the plugin's widget

    def set_option(self, option, value):
        """
        Set a plugin option in configuration file.

        Note: Use sig_option_changed to call it from widgets of the
              same or another plugin.
        """
        CONF.set(self.CONF_SECTION, str(option), value)

    def get_option(self, option, default=NoDefault):
        """
        Get a plugin's option from configuration file.
        """
        return CONF.get(self.CONF_SECTION, option, default)

    def starting_long_process(self, message):
        """
        Showing message in main window's status bar.

        This also changes mouse cursor to Qt.WaitCursor
        """
        self.show_message(message)
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()

    def ending_long_process(self, message=""):
        """
        Clear main window's status bar and restore mouse cursor.
        """
        QApplication.restoreOverrideCursor()
        self.show_message(message, timeout=2000)
        QApplication.processEvents()

    def get_color_scheme(self):
        """
        Get current color scheme.
        """
        return get_color_scheme(CONF.get('appearance', 'selected'))

    def show_compatibility_message(self, message):
        """
        Show compatibility message.
        """
        messageBox = QMessageBox(self)
        messageBox.setWindowModality(Qt.NonModal)
        messageBox.setAttribute(Qt.WA_DeleteOnClose)
        messageBox.setWindowTitle('Compatibility Check')
        messageBox.setText(message)
        messageBox.setStandardButtons(QMessageBox.Ok)
        messageBox.show()

    def refresh_actions(self):
        """
        Create options menu.
        """
        self.options_menu.clear()

        # Decide what additional actions to show
        if self.undocked_window is None:
            additional_actions = [
                MENU_SEPARATOR, self.undock_action, self.close_plugin_action
            ]
        else:
            additional_actions = [MENU_SEPARATOR, self.dock_action]

        # Create actions list
        self.plugin_actions = self.get_plugin_actions() + additional_actions
        add_actions(self.options_menu, self.plugin_actions)
Beispiel #44
0
    def __init__(self, parent=None):
        super(BasePluginWidgetMixin, self).__init__()

        # Actions to add to the Options menu
        self._plugin_actions = None

        # Attribute to keep track if the plugin is undocked in a
        # separate window
        self._undocked_window = None

        self._ismaximized = False
        self._default_margins = None
        self._isvisible = False

        # Options buttons
        self.options_button = create_toolbutton(self,
                                                text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)

        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")

        # Options menu
        self._options_menu = QMenu(self)

        # NOTE: Don't use the default option of CONF.get to assign a
        # None shortcut to plugins that don't have one. That will mess
        # the creation of our Keyboard Shortcuts prefs page
        try:
            self.shortcut = CONF.get('shortcuts',
                                     '_/switch to %s' % self.CONF_SECTION)
        except configparser.NoOptionError:
            pass

        # We decided to create our own toggle action instead of using
        # the one that comes with dockwidget because it's not possible
        # to raise and focus the plugin with it.
        self._toggle_view_action = None

        # Default actions for Options menu
        self._dock_action = create_action(self,
                                          _("Dock"),
                                          icon=ima.icon('dock'),
                                          tip=_("Dock the pane"),
                                          triggered=self._close_window)

        self._undock_action = create_action(self,
                                            _("Undock"),
                                            icon=ima.icon('undock'),
                                            tip=_("Undock the pane"),
                                            triggered=self._create_window)

        self._close_plugin_action = create_action(
            self,
            _("Close"),
            icon=ima.icon('close_pane'),
            tip=_("Close the pane"),
            triggered=self._plugin_closed)
Beispiel #45
0
class PlotWindow(QMdiSubWindow):
    """
    Displayed plotting subwindow available in the ``QMdiArea``.
    """
    window_removed = Signal()
    color_changed = Signal(PlotDataItem, QColor)
    width_changed = Signal(int)

    def __init__(self, model, *args, **kwargs):
        super(PlotWindow, self).__init__(*args, **kwargs)
        # Hide the icon in the title bar
        self.setWindowIcon(qta.icon('fa.circle', opacity=0))

        # The central widget of the sub window will be a main window so that it
        # can support having tab bars
        self._central_widget = QMainWindow()
        self.setWidget(self._central_widget)

        loadUi(os.path.join(os.path.dirname(__file__), "ui", "plot_window.ui"),
               self._central_widget)

        # The central widget of the main window widget will be the plot
        self._model = model
        self._current_item_index = None

        self._plot_widget = PlotWidget(model=self._model)
        self._plot_widget.plotItem.setMenuEnabled(False)

        self._central_widget.setCentralWidget(self._plot_widget)

        # Setup action group for interaction modes
        mode_group = QActionGroup(self.tool_bar)
        mode_group.addAction(self._central_widget.pan_mode_action)
        self._central_widget.pan_mode_action.setChecked(True)
        mode_group.addAction(self._central_widget.zoom_mode_action)

        def _toggle_mode(state):
            view_state = self.plot_widget.plotItem.getViewBox().state.copy()
            view_state.update({
                'mouseMode':
                pg.ViewBox.RectMode if state else pg.ViewBox.PanMode
            })
            self.plot_widget.plotItem.getViewBox().setState(view_state)

        # Setup plot settings options menu
        self.plot_settings_button = self.tool_bar.widgetForAction(
            self._central_widget.plot_settings_action)
        self.plot_settings_button.setPopupMode(QToolButton.InstantPopup)

        self.plot_settings_menu = QMenu(self.plot_settings_button)
        self.plot_settings_button.setMenu(self.plot_settings_menu)

        self.color_change_action = QAction("Line Color")
        self.plot_settings_menu.addAction(self.color_change_action)

        self.line_width_menu = QMenu("Line Widths")
        self.plot_settings_menu.addMenu(self.line_width_menu)

        # Setup the line width plot setting options
        for i in range(1, 4):
            act = QAction(str(i), self.line_width_menu)
            self.line_width_menu.addAction(act)
            act.triggered.connect(
                lambda *args, size=i: self._on_change_width(size))

        # Setup connections
        self._central_widget.pan_mode_action.triggered.connect(
            lambda: _toggle_mode(False))
        self._central_widget.zoom_mode_action.triggered.connect(
            lambda: _toggle_mode(True))
        self._central_widget.linear_region_action.triggered.connect(
            self.plot_widget._on_add_linear_region)
        self._central_widget.remove_region_action.triggered.connect(
            self.plot_widget._on_remove_linear_region)
        self.color_change_action.triggered.connect(self._on_change_color)
        self._central_widget.export_plot_action.triggered.connect(
            self._on_export_plot)
        self._central_widget.reset_view_action.triggered.connect(
            lambda: self._on_reset_view())

    @property
    def tool_bar(self):
        """
        Return the tool bar for the embedded plot widget.
        """
        return self._central_widget.tool_bar

    @property
    def current_item(self):
        """
        The currently selected plot data item.
        """
        if self._current_item_index is not None:
            return self.proxy_model.item_from_index(self._current_item_index)

    @property
    def plot_widget(self):
        """
        Return the embedded plot widget
        """
        return self._plot_widget

    @property
    def proxy_model(self):
        """
        The proxy model defined in the internal plot widget.
        """
        return self.plot_widget.proxy_model

    def closeEvent(self, event):
        """
        Called by qt when window closes, upon which
        it emits the window_removed signal.

        Parameters
        ----------
        event :
            ignored in this implementation.
        """
        self.window_removed.emit()

    def _on_current_item_changed(self, current_idx, prev_idx):
        self._current_item_index = current_idx

    def _on_reset_view(self):
        """
        Resets the visible range of the plot taking into consideration only the
        PlotDataItem objects currently attached.
        """
        self.plot_widget.autoRange(items=[
            item for item in self.plot_widget.listDataItems()
            if isinstance(item, PlotDataItem)
        ])
        self.plot_widget.sigRangeChanged.emit(*self.plot_widget.viewRange())

    def _on_change_color(self):
        """
        Listens for color changed events in plot windows, gets the currently
        selected item in the data list view, and changes the stored color
        value.
        """
        # If there is no currently selected rows, raise an error
        if self.current_item is None:
            message_box = QMessageBox()
            message_box.setText("No item selected, cannot change color.")
            message_box.setIcon(QMessageBox.Warning)
            message_box.setInformativeText(
                "There is currently no item selected. Please select an item "
                "before changing its plot color.")

            message_box.exec()
            return

        color = QColorDialog.getColor(options=QColorDialog.ShowAlphaChannel)

        if color.isValid():
            self.current_item.color = color.toRgb()
            self.color_changed.emit(self.current_item, self.current_item.color)

    def _on_change_width(self, size):
        self.plot_widget.change_width(size)
        self.width_changed.emit(size)

    def _on_export_plot(self):
        file_path, key = compat.getsavefilename(
            filters=";;".join(EXPORT_FILTERS.keys()))

        if key == '':
            return

        exporter = EXPORT_FILTERS[key](self.plot_widget.plotItem)

        # TODO: Current issue in pyqtgraph where the user cannot explicitly
        # define the output size. Fix incoming.

        # plot_size_dialog = PlotSizeDialog(self)
        # plot_size_dialog.height_line_edit.setText(
        #     str(int(exporter.params.param('height').value())))
        # plot_size_dialog.width_line_edit.setText(
        #     str(int(exporter.params.param('width').value())))
        #
        # if key != "*.svg":
        #     if plot_size_dialog.exec_():
        #         exporter.params.param('height').setValue(int(exporter.params.param('height').value()),
        #                                                  blockSignal=exporter.heightChanged)
        #         exporter.params.param('width').setValue(int(exporter.params.param('height').value()),
        #                                                  blockSignal=exporter.widthChanged)
        #     else:
        #         return

        exporter.export(file_path)
class NotebookPlugin(SpyderPluginWidget):
    """IPython Notebook plugin."""

    CONF_SECTION = 'notebook'
    CONF_DEFAULTS = [(
        CONF_SECTION,
        {
            'recent_notebooks': [],  # Items in "Open recent" menu
            'opened_notebooks': []
        })]  # Notebooks to open at start
    focus_changed = Signal()

    def __init__(self, parent, testing=False):
        """Constructor."""
        if testing:
            self.CONF_FILE = False

        SpyderPluginWidget.__init__(self, parent)
        self.testing = testing

        self.fileswitcher_dlg = None
        self.main = parent

        self.recent_notebooks = self.get_option('recent_notebooks', default=[])
        self.recent_notebook_menu = QMenu(_("Open recent"), self)

        layout = QVBoxLayout()

        new_notebook_btn = create_toolbutton(self,
                                             icon=ima.icon('options_more'),
                                             tip=_('Open a new notebook'),
                                             triggered=self.create_new_client)
        menu_btn = create_toolbutton(self,
                                     icon=ima.icon('tooloptions'),
                                     tip=_('Options'))

        self.menu_actions = self.get_plugin_actions()
        menu_btn.setMenu(self._options_menu)
        menu_btn.setPopupMode(menu_btn.InstantPopup)
        corner_widgets = {Qt.TopRightCorner: [new_notebook_btn, menu_btn]}
        dark_theme = is_dark_interface()
        self.tabwidget = NotebookTabWidget(self,
                                           menu=self._options_menu,
                                           actions=self.menu_actions,
                                           corner_widgets=corner_widgets,
                                           dark_theme=dark_theme)

        self.tabwidget.currentChanged.connect(self.refresh_plugin)

        layout.addWidget(self.tabwidget)
        self.setLayout(layout)

    # ------ SpyderPluginMixin API --------------------------------------------
    def on_first_registration(self):
        """Action to be performed on first plugin registration."""
        self.main.tabify_plugins(self.main.editor, self)

    def update_font(self):
        """Update font from Preferences."""
        # For now we're passing. We need to create an nbextension for
        # this.
        pass

    # ------ SpyderPluginWidget API -------------------------------------------
    def get_plugin_title(self):
        """Return widget title."""
        title = _('Notebook')
        return title

    def get_plugin_icon(self):
        """Return widget icon."""
        return ima.icon('ipython_console')

    def get_focus_widget(self):
        """Return the widget to give focus to."""
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client.notebookwidget

    def closing_plugin(self, cancelable=False):
        """
        Perform actions before parent main window is closed.

        This function closes all tabs. It stores the file names of all opened
        notebooks that are not temporary and all notebooks in the 'Open recent'
        menu in the config.
        """
        opened_notebooks = []
        for client_index in range(self.tabwidget.count()):
            client = self.tabwidget.widget(client_index)
            if (not self.tabwidget.is_welcome_client(client)
                    and not self.tabwidget.is_newly_created(client)):
                opened_notebooks.append(client.filename)
            client.close()

        self.set_option('recent_notebooks', self.recent_notebooks)
        self.set_option('opened_notebooks', opened_notebooks)
        return True

    def refresh_plugin(self):
        """Refresh tabwidget."""
        nb = None
        if self.tabwidget.count():
            client = self.tabwidget.currentWidget()
            nb = client.notebookwidget
            nb.setFocus()
        else:
            nb = None
        self.update_notebook_actions()

    def get_plugin_actions(self):
        """Return a list of actions related to plugin."""
        create_nb_action = create_action(self,
                                         _("New notebook"),
                                         icon=ima.icon('filenew'),
                                         triggered=self.create_new_client)
        self.save_as_action = create_action(self,
                                            _("Save as..."),
                                            icon=ima.icon('filesaveas'),
                                            triggered=self.save_as)
        open_action = create_action(self,
                                    _("Open..."),
                                    icon=ima.icon('fileopen'),
                                    triggered=self.open_notebook)
        self.open_console_action = create_action(
            self,
            _("Open console"),
            icon=ima.icon('ipython_console'),
            triggered=self.open_console)
        self.clear_recent_notebooks_action =\
            create_action(self, _("Clear this list"),
                          triggered=self.clear_recent_notebooks)
        # Plugin actions
        self.menu_actions = [
            create_nb_action, open_action, self.recent_notebook_menu,
            MENU_SEPARATOR, self.save_as_action, MENU_SEPARATOR,
            self.open_console_action
        ]
        self.setup_menu_actions()

        return self.menu_actions

    def register_plugin(self):
        """Register plugin in Spyder's main window."""
        super().register_plugin()
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.ipyconsole = self.main.ipyconsole

        # Open initial tabs
        filenames = self.get_option('opened_notebooks')
        if filenames:
            self.open_notebook(filenames)
        else:
            self.tabwidget.maybe_create_welcome_client()
            self.create_new_client()
            self.tabwidget.setCurrentIndex(0)  # bring welcome tab to top

        # Connect to switcher
        self.switcher = self.main.switcher
        self.switcher.sig_mode_selected.connect(self.handle_switcher_modes)
        self.switcher.sig_item_selected.connect(self.handle_switcher_selection)

        self.recent_notebook_menu.aboutToShow.connect(self.setup_menu_actions)

    def check_compatibility(self):
        """Check compatibility for PyQt and sWebEngine."""
        message = ''
        value = True
        if PYQT4 or PYSIDE:
            message = _("You are working with Qt4 and in order to use this "
                        "plugin you need to have Qt5.<br><br>"
                        "Please update your Qt and/or PyQt packages to "
                        "meet this requirement.")
            value = False
        return value, message

    # ------ Public API (for clients) -----------------------------------------
    def setup_menu_actions(self):
        """Setup and update the menu actions."""
        self.recent_notebook_menu.clear()
        self.recent_notebooks_actions = []
        if self.recent_notebooks:
            for notebook in self.recent_notebooks:
                name = notebook
                action = \
                    create_action(self,
                                  name,
                                  icon=ima.icon('filenew'),
                                  triggered=lambda v,
                                  path=notebook:
                                      self.create_new_client(filename=path))
                self.recent_notebooks_actions.append(action)
            self.recent_notebooks_actions += \
                [None, self.clear_recent_notebooks_action]
        else:
            self.recent_notebooks_actions = \
                [self.clear_recent_notebooks_action]
        add_actions(self.recent_notebook_menu, self.recent_notebooks_actions)
        self.update_notebook_actions()

    def update_notebook_actions(self):
        """Update actions of the recent notebooks menu."""
        if self.recent_notebooks:
            self.clear_recent_notebooks_action.setEnabled(True)
        else:
            self.clear_recent_notebooks_action.setEnabled(False)
        try:
            client = self.tabwidget.currentWidget()
        except AttributeError:  # tabwidget is not yet constructed
            client = None
        if client and not self.tabwidget.is_welcome_client(client):
            self.save_as_action.setEnabled(True)
            self.open_console_action.setEnabled(True)
        else:
            self.save_as_action.setEnabled(False)
            self.open_console_action.setEnabled(False)

    def add_to_recent(self, notebook):
        """
        Add an entry to recent notebooks.

        We only maintain the list of the 20 most recent notebooks.
        """
        if notebook not in self.recent_notebooks:
            self.recent_notebooks.insert(0, notebook)
            self.recent_notebooks = self.recent_notebooks[:20]

    def clear_recent_notebooks(self):
        """Clear the list of recent notebooks."""
        self.recent_notebooks = []
        self.setup_menu_actions()

    def create_new_client(self, filename=None):
        """Create a new notebook or load a pre-existing one."""
        # Save spyder_pythonpath before creating a client
        # because it's needed by our kernel spec.
        if not self.testing:
            self.set_option('main/spyder_pythonpath',
                            self.main.get_spyder_pythonpath())

        client = self.tabwidget.create_new_client(filename)
        if not self.tabwidget.is_newly_created(client):
            self.add_to_recent(filename)
            self.setup_menu_actions()

    def open_notebook(self, filenames=None):
        """Open a notebook from file."""
        # Save spyder_pythonpath before creating a client
        # because it's needed by our kernel spec.
        if not self.testing:
            self.set_option('main/spyder_pythonpath',
                            self.main.get_spyder_pythonpath())

        filenames = self.tabwidget.open_notebook(filenames)
        for filename in filenames:
            self.add_to_recent(filename)
        self.setup_menu_actions()

    def save_as(self):
        """Save current notebook to different file."""
        self.tabwidget.save_as()

    def open_console(self, client=None):
        """Open an IPython console for the given client or the current one."""
        if not client:
            client = self.tabwidget.currentWidget()
        if self.ipyconsole is not None:
            kernel_id = client.get_kernel_id()
            if not kernel_id:
                QMessageBox.critical(
                    self, _('Error opening console'),
                    _('There is no kernel associated to this notebook.'))
                return
            self.ipyconsole._create_client_for_kernel(kernel_id, None, None,
                                                      None)
            ipyclient = self.ipyconsole.get_current_client()
            ipyclient.allow_rename = False
            self.ipyconsole.rename_client_tab(ipyclient,
                                              client.get_short_name())

    # ------ Public API (for FileSwitcher) ------------------------------------
    def handle_switcher_modes(self, mode):
        """
        Populate switcher with opened notebooks.

        List the file names of the opened notebooks with their directories in
        the switcher. Only handle file mode, where `mode` is empty string.
        """
        if mode != '':
            return

        clients = [
            self.tabwidget.widget(i) for i in range(self.tabwidget.count())
        ]
        paths = [client.get_filename() for client in clients]
        is_unsaved = [False for client in clients]
        short_paths = shorten_paths(paths, is_unsaved)
        icon = QIcon(os.path.join(PACKAGE_PATH, 'images', 'icon.svg'))
        section = self.get_plugin_title()

        for path, short_path, client in zip(paths, short_paths, clients):
            title = osp.basename(path)
            description = osp.dirname(path)
            if len(path) > 75:
                description = short_path
            is_last_item = (client == clients[-1])
            self.switcher.add_item(title=title,
                                   description=description,
                                   icon=icon,
                                   section=section,
                                   data=client,
                                   last_item=is_last_item)

    def handle_switcher_selection(self, item, mode, search_text):
        """
        Handle user selecting item in switcher.

        If the selected item is not in the section of the switcher that
        corresponds to this plugin, then ignore it. Otherwise, switch to
        selected item in notebook plugin and hide the switcher.
        """
        if item.get_section() != self.get_plugin_title():
            return

        client = item.get_data()
        index = self.tabwidget.indexOf(client)
        self.tabwidget.setCurrentIndex(index)
        self.switch_to_plugin()
        self.switcher.hide()
Beispiel #47
0
class OneColumnTree(QTreeWidget):
    """One-column tree widget with context menu, ..."""
    def __init__(self, parent):
        QTreeWidget.__init__(self, parent)
        self.setItemsExpandable(True)
        self.setColumnCount(1)
        self.itemActivated.connect(self.activated)
        self.itemClicked.connect(self.clicked)
        # Setup context menu
        self.menu = QMenu(self)
        self.collapse_all_action = None
        self.collapse_selection_action = None
        self.expand_all_action = None
        self.expand_selection_action = None
        self.common_actions = self.setup_common_actions()

        self.__expanded_state = None

        self.itemSelectionChanged.connect(self.item_selection_changed)
        self.item_selection_changed()

    def activated(self, item):
        """Double-click event"""
        raise NotImplementedError

    def clicked(self, item):
        pass

    def set_title(self, title):
        self.setHeaderLabels([title])

    def setup_common_actions(self):
        """Setup context menu common actions"""
        self.collapse_all_action = create_action(self,
                                                 text=_('Collapse all'),
                                                 icon=ima.icon('collapse'),
                                                 triggered=self.collapseAll)
        self.expand_all_action = create_action(self,
                                               text=_('Expand all'),
                                               icon=ima.icon('expand'),
                                               triggered=self.expandAll)
        self.restore_action = create_action(
            self,
            text=_('Restore'),
            tip=_('Restore original tree layout'),
            icon=ima.icon('restore'),
            triggered=self.restore)
        self.collapse_selection_action = create_action(
            self,
            text=_('Collapse section'),
            icon=ima.icon('collapse_selection'),
            triggered=self.collapse_selection)
        self.expand_selection_action = create_action(
            self,
            text=_('Expand section'),
            icon=ima.icon('expand_selection'),
            triggered=self.expand_selection)
        return [
            self.collapse_all_action, self.expand_all_action, None,
            self.collapse_selection_action, self.expand_selection_action
        ]

    def get_menu_actions(self):
        """Returns a list of menu actions"""
        items = self.selectedItems()
        actions = self.get_actions_from_items(items)
        if actions:
            actions.append(None)
        actions += self.common_actions
        return actions

    def update_menu(self):
        self.menu.clear()
        actions = self.get_menu_actions()
        add_actions(self.menu, actions)

    def get_actions_from_items(self, items):
        # Right here: add other actions if necessary
        # (reimplement this method)
        return []

    @Slot()
    def restore(self):
        self.collapseAll()
        for item in self.get_top_level_items():
            self.expandItem(item)

    def is_item_expandable(self, item):
        """To be reimplemented in child class
        See example in project explorer widget"""
        return True

    def __expand_item(self, item):
        if self.is_item_expandable(item):
            self.expandItem(item)
            for index in range(item.childCount()):
                child = item.child(index)
                self.__expand_item(child)

    @Slot()
    def expand_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__expand_item(item)
        if items:
            self.scrollToItem(items[0])

    def __collapse_item(self, item):
        self.collapseItem(item)
        for index in range(item.childCount()):
            child = item.child(index)
            self.__collapse_item(child)

    @Slot()
    def collapse_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__collapse_item(item)
        if items:
            self.scrollToItem(items[0])

    def item_selection_changed(self):
        """Item selection has changed"""
        is_selection = len(self.selectedItems()) > 0
        self.expand_selection_action.setEnabled(is_selection)
        self.collapse_selection_action.setEnabled(is_selection)

    def get_top_level_items(self):
        """Iterate over top level items"""
        return [
            self.topLevelItem(_i) for _i in range(self.topLevelItemCount())
        ]

    def get_items(self):
        """Return items (excluding top level items)"""
        itemlist = []

        def add_to_itemlist(item):
            for index in range(item.childCount()):
                citem = item.child(index)
                itemlist.append(citem)
                add_to_itemlist(citem)

        for tlitem in self.get_top_level_items():
            add_to_itemlist(tlitem)
        return itemlist

    def get_scrollbar_position(self):
        return (self.horizontalScrollBar().value(),
                self.verticalScrollBar().value())

    def set_scrollbar_position(self, position):
        hor, ver = position
        self.horizontalScrollBar().setValue(hor)
        self.verticalScrollBar().setValue(ver)

    def get_expanded_state(self):
        self.save_expanded_state()
        return self.__expanded_state

    def set_expanded_state(self, state):
        self.__expanded_state = state
        self.restore_expanded_state()

    def save_expanded_state(self):
        """Save all items expanded state"""
        self.__expanded_state = {}

        def add_to_state(item):
            user_text = get_item_user_text(item)
            self.__expanded_state[hash(user_text)] = item.isExpanded()

        def browse_children(item):
            add_to_state(item)
            for index in range(item.childCount()):
                citem = item.child(index)
                user_text = get_item_user_text(citem)
                self.__expanded_state[hash(user_text)] = citem.isExpanded()
                browse_children(citem)

        for tlitem in self.get_top_level_items():
            browse_children(tlitem)

    def restore_expanded_state(self):
        """Restore all items expanded state"""
        if self.__expanded_state is None:
            return
        for item in self.get_items() + self.get_top_level_items():
            user_text = get_item_user_text(item)
            is_expanded = self.__expanded_state.get(hash(user_text))
            if is_expanded is not None:
                item.setExpanded(is_expanded)

    def sort_top_level_items(self, key):
        """Sorting tree wrt top level items"""
        self.save_expanded_state()
        items = sorted([
            self.takeTopLevelItem(0)
            for index in range(self.topLevelItemCount())
        ],
                       key=key)
        for index, item in enumerate(items):
            self.insertTopLevelItem(index, item)
        self.restore_expanded_state()

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())
Beispiel #48
0
class MxTreeView(QTreeView):

    def __init__(self, parent=None):
        super().__init__(parent)

        self.activated.connect(self.activated_callback)
        # self.doubleClicked.connect(self.doubleClicked_callback)

        self.mx_widget = parent.mx_widget
        self.plugin = parent.plugin
        if spyder.version_info > (5,):
            self.container = self.plugin.get_container()
        else:
            self.container = self.plugin
        self.shell = None
        self.reply = None  # To write dialog box result
        self.setAlternatingRowColors(False)

        # Context menu
        self.contextMenu = QMenu(self)

        self.action_update_properties = self.contextMenu.addAction(
            "Show Properties"
        )
        self.action_import_names = self.contextMenu.addAction(
            "Import Names"
        )
        self.action_analyze_selected = self.contextMenu.addAction(
            "Analyze Selected"
        )
        self.action_update_formulas = self.contextMenu.addAction(
            "Show Formulas"
        )
        self.action_new_model = self.contextMenu.addAction(
            "Create New Model"
        )
        self.action_new_space = self.contextMenu.addAction(
            "Create New Space"
        )
        self.action_new_cells = self.contextMenu.addAction(
            "Create New Cells"
        )
        self.action_read_model = self.contextMenu.addAction(
            "Read Model"
        )
        self.action_write_model = self.contextMenu.addAction(
            "Write Model"
        )
        self.action_delete_selected = self.contextMenu.addAction(
            "Delete Selected"
        )
        self.action_delete_model = self.contextMenu.addAction(
            "Delete Model"
        )

    def activated_callback(self, index):
        if index.isValid():
            item = self.currentIndex().internalPointer()
            if not isinstance(item, ViewItem):
                self.shell.update_mxproperty(item.itemData['fullname'])
                # self.plugin.dataview.update_object(item.itemData['fullname'])

    # def doubleClicked_callback(self, index):
    #     if index.isValid() and index.column() == TreeCol.IS_DERIVED:
    #         answer = QMessageBox.warning(self.parent(), _("Warning"),
    #                                      str(index.row()),
    #                                      QMessageBox.Yes | QMessageBox.No)


    def contextMenuEvent(self, event):
        action = self.contextMenu.exec_(self.mapToGlobal(event.pos()))

        if action == self.action_update_formulas:
            index = self.currentIndex()
            if index.isValid():
                item = index.internalPointer()
                if isinstance(item, SpaceItem):
                    pass
                else:
                    if index.parent().isValid():
                        item = index.parent().internalPointer()
                    else:
                        return

                self.shell.update_codelist(item.itemData['fullname'])

        elif action == self.action_update_properties:
            index = self.currentIndex()
            if index.isValid():
                item = self.currentIndex().internalPointer()
                if not isinstance(item, ViewItem):
                    self.shell.update_mxproperty(item.itemData['fullname'])

        elif action == self.action_import_names:
            index = self.currentIndex()
            if index.isValid():
                item = self.currentIndex().internalPointer()

                if isinstance(item, SpaceItem):
                    has_children = True
                elif isinstance(item, CellsItem) or isinstance(item, RefItem):
                    has_children = False
                else:
                    return
            else:
                return

            if has_children:
                dialog = ImportNamesDialog(self)
                dialog.exec()

                if self.reply['accepted']:
                    import_selected = self.reply['import_selected']
                    import_children = self.reply['import_children']
                    replace_existing = self.reply['replace_existing']
                    self.reply = None
                else:
                    self.reply = None
                    return
            else:
                import_selected = True
                import_children = False
                replace_existing = True

            self.shell.import_names(item.itemData['fullname'],
                                    import_selected,
                                    import_children,
                                    replace_existing
                                    )

        elif action == self.action_analyze_selected:
            index = self.currentIndex()
            if index.isValid():
                item = self.currentIndex().internalPointer()
                if isinstance(item, CellsItem):
                    self.shell.mxanalyzer.update_object(item.itemData)

        elif action == self.action_new_model:
            dialog = NewModelDialog(self)
            dialog.exec()

            if self.reply['accepted']:
                name = self.reply['name']
                define_var = self.reply['define_var']
                if define_var:
                    varname = self.reply['varname']
                else:
                    varname = ''
                self.reply = None
                self.shell.new_model(name, define_var, varname)
            else:
                self.reply = None

        elif action == self.action_new_space:
            if self.model():
                parentList = self.model().rootItem.getSpaceContainerList()
            else:
                parentList = []

            # Find current item
            index = self.currentIndex()
            if index.isValid():
                name = index.internalPointer().itemData['fullname']
                try:
                    currIndex = parentList.index(name)
                except ValueError:
                    currIndex = 0
            else:
                currIndex = 0

            if self.model():
                model = self.model().rootItem.itemData['name']
            else:
                model = ''

            dialog = NewSpaceDialog(self, parentList, currIndex)
            dialog.exec()

            if self.reply['accepted']:
                name = self.reply['name']
                parent = self.reply['parent']
                bases = self.reply['bases']
                define_var = self.reply['define_var']
                if define_var:
                    varname = self.reply['varname']
                else:
                    varname = ''
                self.reply = None
                self.shell.new_space(
                    model, parent, name, bases, define_var, varname)
            else:
                self.reply = None

        elif action == self.action_new_cells:
            if self.model():
                parentList = self.model().rootItem.getChildSpaceList()
            else:
                parentList = []

            # Find current item
            index = self.currentIndex()
            if index.isValid():
                name = index.internalPointer().itemData['namedid']
                try:
                    currIndex = parentList.index(name)
                except ValueError:
                    currIndex = 0
            else:
                currIndex = 0

            if self.model():
                model = self.model().rootItem.itemData['name']
            else:
                model = ''

            dialog = NewCellsDialog(self, parentList, currIndex)
            dialog.exec()

            if self.reply['accepted']:
                name = self.reply['name']
                parent = self.reply['parent']
                formula = self.reply['formula']
                define_var = self.reply['define_var']
                if define_var:
                    varname = self.reply['varname']
                else:
                    varname = ''
                self.reply = None
                self.shell.new_cells(
                    model, parent, name, formula, define_var, varname)
            else:
                self.reply = None

        elif action == self.action_read_model:
            dialog = ReadModelDialog(self, modelpath=self.mx_widget.last_modelpath)
            dialog.exec()

            if self.reply['accepted']:
                modelpath = self.reply['directory']
                name = self.reply['name']
                define_var = self.reply['define_var']
                if define_var:
                    varname = self.reply['varname']
                else:
                    varname = ''
                self.reply = None
                self.shell.read_model(modelpath, name, define_var, varname)
                self.mx_widget.last_modelpath = modelpath
            else:
                self.reply = None

        elif action == self.action_write_model:
            model = self.container.current_widget().model_selector.get_selected_model()
            if not model:
                QMessageBox.critical(self, "Error", "No model exits.")
                return

            dialog = WriteModelDialog(self, modelpath=self.mx_widget.last_modelpath)
            dialog.exec()

            if self.reply['accepted']:
                modelpath = self.reply['directory'] + "/" + self.reply['name']
                backup = self.reply['backup']
                zipmodel = self.reply['zipmodel']
                self.reply = None
                self.shell.write_model(model, modelpath, backup, zipmodel)
                self.mx_widget.last_modelpath = modelpath
            else:
                self.reply = None

        elif action == self.action_delete_model:
            model = self.container.current_widget().model_selector.get_selected_model()
            if model:
                answer = QMessageBox.question(
                    self, _("Delete Model"),
                    _("Do you want to delete %s?" % model),
                    QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.shell.del_model(model)
                else:
                    return
            else:
                QMessageBox.critical(self, "Error", "No model exits.")
        elif action == self.action_delete_selected:
            index = self.currentIndex()
            if index.isValid():
                item = index.internalPointer()
                if isinstance(item, ViewItem) or isinstance(item, ItemSpaceItem):
                    pass
                else:
                    if index.parent().isValid():
                        parent = index.parent().internalPointer().fullname
                    else:
                        parent = self.container.current_widget().model_selector.get_selected_model()
                    assert parent

                    answer = QMessageBox.question(
                        self, _("Delete Selected"),
                        _("Do you want to delete %s?" % item.name),
                        QMessageBox.Yes | QMessageBox.No)

                    if answer == QMessageBox.Yes:
                        self.shell.del_object(parent, item.name)

                        QMessageBox.information(
                            self, "Notice",
                            "'%s' is deleted from '%s'" % (item.name, parent))
Beispiel #49
0
    def setup_options_actions(self, parent):
        """
        Setup the actions for the Options menu. The actions are handled by the parent widget
        """
        container_widget = QWidget(self)
        layout = QHBoxLayout(container_widget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        container_widget.setLayout(layout)
        self.setCornerWidget(container_widget, Qt.TopRightCorner)

        run_button = QPushButton(container_widget)
        run_button.setObjectName(self.RUN_BUTTON_OBJECT_NAME)
        run_button.setIcon(get_icon("mdi.play", PLAY_BUTTON_GREEN_COLOR, 1.6))
        run_button.clicked.connect(parent.execute_current_async)
        layout.addWidget(run_button)

        abort_button = QPushButton(container_widget)
        abort_button.setObjectName(self.ABORT_BUTTON_OBJECT_NAME)
        abort_button.setIcon(
            get_icon("mdi.square", ABORT_BUTTON_RED_COLOR, 1.1))
        abort_button.clicked.connect(parent.abort_current)
        layout.addWidget(abort_button)

        options_button = QPushButton(container_widget)
        options_button.setObjectName(self.OPTIONS_BUTTON_OBJECT_NAME)
        options_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        options_button.setText("Options")

        options_menu = QMenu("", self)
        options_button.setMenu(options_menu)
        layout.addWidget(options_button)

        self.tabCloseRequested.connect(parent.close_tab)

        run_action = create_action(self,
                                   "Run",
                                   on_triggered=parent.execute_current_async,
                                   shortcut=("Ctrl+Enter", "Ctrl+Return"),
                                   shortcut_context=Qt.ApplicationShortcut,
                                   shortcut_visible_in_context_menu=True)

        run_all_action = create_action(self,
                                       "Run All",
                                       on_triggered=parent.execute_async,
                                       shortcut=("Ctrl+Shift+Enter",
                                                 "Ctrl+Shift+Return"),
                                       shortcut_context=Qt.ApplicationShortcut,
                                       shortcut_visible_in_context_menu=True)

        abort_action = create_action(self,
                                     "Abort",
                                     on_triggered=parent.abort_current,
                                     shortcut="Ctrl+D",
                                     shortcut_context=Qt.ApplicationShortcut,
                                     shortcut_visible_in_context_menu=True)

        # menu action to toggle the find/replace dialog
        toggle_find_replace = create_action(
            self,
            'Find/Replace...',
            on_triggered=parent.toggle_find_replace_dialog,
            shortcut='Ctrl+F',
            shortcut_visible_in_context_menu=True)

        toggle_comment_action = create_action(
            self,
            "Comment/Uncomment",
            on_triggered=parent.toggle_comment_current,
            shortcut="Ctrl+/",
            shortcut_context=Qt.ApplicationShortcut,
            shortcut_visible_in_context_menu=True)

        tabs_to_spaces_action = create_action(
            self,
            'Tabs to Spaces',
            on_triggered=parent.tabs_to_spaces_current,
            shortcut_visible_in_context_menu=True)

        spaces_to_tabs_action = create_action(
            self,
            'Spaces to Tabs',
            on_triggered=parent.spaces_to_tabs_current,
            shortcut_visible_in_context_menu=True)

        toggle_whitespace_action = create_action(
            self,
            'Toggle Whitespace Visible',
            on_triggered=parent.toggle_whitespace_visible_all,
            shortcut_visible_in_context_menu=True)

        # Store actions for adding to menu bar; None will add a separator
        editor_actions = [
            run_action, run_all_action, abort_action, None,
            toggle_find_replace, None, toggle_comment_action,
            toggle_whitespace_action, None, tabs_to_spaces_action,
            spaces_to_tabs_action
        ]

        add_actions(options_menu, editor_actions)
Beispiel #50
0
    def __init__(self, title, parent, key_defs, key=""):
        QDialog.__init__(self, parent)
        self.setWindowTitle(title)

        self.current_key = key
        self._key_defs = key_defs

        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)

        self._tab_map = {}
        self._tab_order = []

        layout = QVBoxLayout()

        self._tabs = QTabWidget()
        layout.addWidget(self._tabs)
        layout.setSizeConstraint(QLayout.SetFixedSize)  # not resizable!!!

        self._button_layout = QHBoxLayout()

        self._reset_button = QToolButton()
        self._reset_button.setIcon(resourceIcon("format_color_reset.svg"))
        self._reset_button.setToolTip("Reset all settings back to default")
        self._reset_button.clicked.connect(self.resetSettings)

        self._undo_button = QToolButton()
        self._undo_button.setIcon(resourceIcon("undo.svg"))
        self._undo_button.setToolTip("Undo")
        self._undo_button.clicked.connect(self.undoSettings)

        self._redo_button = QToolButton()
        self._redo_button.setIcon(resourceIcon("redo.svg"))
        self._redo_button.setToolTip("Redo")
        self._redo_button.clicked.connect(self.redoSettings)
        self._redo_button.setEnabled(False)

        self._copy_from_button = QToolButton()
        self._copy_from_button.setIcon(resourceIcon("download.svg"))
        self._copy_from_button.setToolTip("Copy settings from another key")
        self._copy_from_button.setPopupMode(QToolButton.InstantPopup)
        self._copy_from_button.setEnabled(False)

        self._copy_to_button = QToolButton()
        self._copy_to_button.setIcon(resourceIcon("upload.svg"))
        self._copy_to_button.setToolTip(
            "Copy current plot settings to other keys")
        self._copy_to_button.setPopupMode(QToolButton.InstantPopup)
        self._copy_to_button.clicked.connect(self.initiateCopyStyleToDialog)
        self._copy_to_button.setEnabled(True)

        tool_menu = QMenu(self._copy_from_button)
        self._popup_list = QListWidget(tool_menu)
        self._popup_list.setSortingEnabled(True)
        self._popup_list.itemClicked.connect(self.keySelected)
        action = QWidgetAction(tool_menu)
        action.setDefaultWidget(self._popup_list)
        tool_menu.addAction(action)
        self._copy_from_button.setMenu(tool_menu)

        self._apply_button = QPushButton("Apply")
        self._apply_button.setToolTip("Apply the new settings")
        self._apply_button.clicked.connect(self.applySettings)
        self._apply_button.setDefault(True)

        self._close_button = QPushButton("Close")
        self._close_button.setToolTip("Hide this dialog")
        self._close_button.clicked.connect(self.hide)

        self._button_layout.addWidget(self._reset_button)
        self._button_layout.addStretch()
        self._button_layout.addWidget(self._undo_button)
        self._button_layout.addWidget(self._redo_button)
        self._button_layout.addWidget(self._copy_from_button)
        self._button_layout.addWidget(self._copy_to_button)
        self._button_layout.addStretch()
        self._button_layout.addWidget(self._apply_button)
        self._button_layout.addWidget(self._close_button)

        layout.addStretch()
        layout.addLayout(self._button_layout)

        self.setLayout(layout)
Beispiel #51
0
    def _showHeaderMenu(self, point):
        column = self.headers.logicalIndexAt(point.x())
        if column == -1:
            return
        menu = QMenu(self)
        # Actions
        cols = self.table.selectionModel().selectedColumns()
        if len(cols) != 2 or column not in [col.column() for col in cols]:
            self.table.selectColumn(column)
            menu.aboutToHide.connect(lambda: self.table.clearSelection())

            save = QAction("Save", menu)
            save.triggered.connect(lambda: self._saveConfiguration(column))
            save_all = QAction("Save all", menu)
            save_all.triggered.connect(lambda: self._saveChanges())
            save_all.setShortcut(QKeySequence.Save)
            rename = QAction("Rename", menu)
            rename.triggered.connect(lambda: self._renameConfiguration(column))
            close = QAction("Close", menu)
            close.triggered.connect(lambda: self._closeConfiguration(column))
            close.setShortcut(QKeySequence.Close)
            close_right = QAction("Close to the right", menu)
            close_right.triggered.connect(
                lambda: self._closeConfigurationsToTheRight(column))
            close_others = QAction("Close other", menu)
            close_others.triggered.connect(
                lambda: self._closeOtherConfigurations(column))
            close_all = QAction("Close all", menu)
            close_all.triggered.connect(lambda: self._closeAllConfigurations())
            tune = QAction("Tune", menu)
            tune.triggered.connect(lambda: self._tuneConfiguration(column))

            menu.addActions([save, save_all])
            menu.addSeparator()
            menu.addActions([rename])
            menu.addSeparator()
            menu.addActions([close, close_right, close_others, close_all])
            menu.addSeparator()
            menu.addActions([tune])
        else:
            bar = QAction("Interpolate", menu)
            bar.triggered.connect(lambda: self._barConfiguration(cols))

            menu.addAction(bar)

        vheader_offset = self.table.verticalHeader().width()
        point.setX(point.x() + vheader_offset)
        menu.popup(self.mapToGlobal(point))
Beispiel #52
0
    def openMenu(self, position):
        indexes = self.ui.treeWidget.selectedIndexes()
        item = self.ui.treeWidget.itemAt(position)

        db_origin = ""
        #if item.parent():
        #   db_origin = item.parent().text(0)
        collec = str(item.text(0).encode("utf-8"))
        if len(indexes) > 0:
            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level = level + 1
            menu = QMenu()
            #print((collec, db_origin))
            if level == 0:
                pass
            else:
                #keyarray = GetKeys(collec, db_origin)
                #if "Open" in keyarray:
                if self.ui.combobox.currentText() == u"K线":
                    menu.addAction(QAction("Kline", menu, checkable=True))
                    menu.addAction(QAction("Open", menu, checkable=True))
                    menu.addAction(
                        QAction("Close", menu, checkable=True)
                    )  #open up different menu with different kind of graphs
                    menu.addAction(QAction("High", menu, checkable=True))
                    menu.addAction(QAction("Low", menu, checkable=True))
                    menu.addAction(QAction("Volume", menu, checkable=True))
                    #menu.addAction(QAction("P_change", menu, checkable=True))
                    #menu.addAction(QAction("Turnover",menu,checkable=True))
                if self.ui.combobox.currentText() == u"复权":
                    menu.addAction(QAction("Kline", menu, checkable=True))
                    menu.addAction(QAction("Open", menu, checkable=True))
                    menu.addAction(QAction("Close", menu, checkable=True))
                    menu.addAction(QAction("High", menu, checkable=True))
                    menu.addAction(QAction("Low", menu, checkable=True))
                    menu.addAction(QAction("Volume", menu, checkable=True))
                    menu.addAction(QAction("Amount", menu, checkable=True))
                if self.ui.combobox.currentText() == u"分笔数据":
                    menu.addAction(QAction("分笔", menu, checkable=True))
                #for g in keyarray:
                #menu.addAction(QAction(g, menu, checkable=True))
        menu.triggered.connect(
            lambda action: self.methodSelected(action, collec))
        menu.exec_(self.ui.treeWidget.viewport().mapToGlobal(position))
Beispiel #53
0
class EnvironmentsTab(WidgetBase):
    """Conda environments tab."""
    BLACKLIST = ['anaconda-navigator', '_license']  # Hide in package manager

    # --- Signals
    # -------------------------------------------------------------------------
    sig_ready = Signal()

    # name, prefix, sender
    sig_item_selected = Signal(object, object, object)

    # sender, func_after_dlg_accept, func_callback_on_finished
    sig_create_requested = Signal()
    sig_clone_requested = Signal()
    sig_import_requested = Signal()
    sig_remove_requested = Signal()

    # button_widget, sender_constant
    sig_channels_requested = Signal(object, object)

    # sender_constant
    sig_update_index_requested = Signal(object)
    sig_cancel_requested = Signal(object)

    # conda_packages_action_dict, pip_packages_action_dict
    sig_packages_action_requested = Signal(object, object)

    def __init__(self, parent=None):
        """Conda environments tab."""
        super(EnvironmentsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_prefix = None
        self.style_sheet = None

        # Widgets
        self.frame_header_left = FrameTabHeader()
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.text_search = LineEditSearch()
        self.list = ListWidgetEnv()
        self.menu_list = QMenu()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_clone = ButtonToolNormal(text="Clone")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.widget = CondaPackagesWidget(parent=self)

        # Widgets setup
        self.frame_list.is_expanded = True
        self.text_search.setPlaceholderText("Search Environments")
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.button_create.setObjectName("create")  # Needed for QSS selectors
        self.button_clone.setObjectName("clone")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")
        self.widget.textbox_search.set_icon_visibility(False)

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_clone)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)

        self.setLayout(layout_main)

        # Signals for buttons and boxes
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_clone.clicked.connect(self.sig_clone_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)
        self.text_search.textChanged.connect(self.filter_list)

        # Signals for list
        self.list.sig_item_selected.connect(self._item_selected)

        # Signals for packages widget
        self.widget.sig_ready.connect(self.sig_ready)
        self.widget.sig_channels_requested.connect(self.sig_channels_requested)
        self.widget.sig_update_index_requested.connect(
            self.sig_update_index_requested)
        self.widget.sig_cancel_requested.connect(self.sig_cancel_requested)
        self.widget.sig_packages_action_requested.connect(
            self.sig_packages_action_requested)

    # --- Setup methods
    # -------------------------------------------------------------------------
    def setup(self, conda_data):
        """Setup tab content and populates the list of environments."""
        self.set_widgets_enabled(False)
        conda_processed_info = conda_data.get('processed_info')
        environments = conda_processed_info.get('__environments')
        packages = conda_data.get('packages')
        self.current_prefix = conda_processed_info.get('default_prefix')
        self.set_environments(environments)
        self.set_packages(packages)

    def set_environments(self, environments):
        """Populate the list of environments."""
        self.list.clear()
        selected_item_row = 0
        for i, (env_prefix, env_name) in enumerate(environments.items()):
            item = ListItemEnv(prefix=env_prefix, name=env_name)
            item.button_options.clicked.connect(self.show_environment_menu)
            if env_prefix == self.current_prefix:
                selected_item_row = i
            self.list.addItem(item)

        self.list.setCurrentRow(selected_item_row, loading=True)
        self.filter_list()
        self.list.scrollToItem(self.list.item(selected_item_row))

    def _set_packages(self, worker, output, error):
        """Set packages callback."""
        packages, model_data = output
        self.widget.setup(packages, model_data)
        self.set_widgets_enabled(True)
        self.set_loading(prefix=self.current_prefix, value=False)

    def set_packages(self, packages):
        """Set packages widget content."""
        worker = self.api.process_packages(packages,
                                           prefix=self.current_prefix,
                                           blacklist=self.BLACKLIST)
        worker.sig_chain_finished.connect(self._set_packages)

    def show_environment_menu(self, value=None, position=None):
        """Show the environment actions menu."""
        self.menu_list.clear()
        menu_item = self.menu_list.addAction('Open Terminal')
        menu_item.triggered.connect(
            lambda: self.open_environment_in('terminal'))

        for word in ['Python', 'IPython', 'Jupyter Notebook']:
            menu_item = self.menu_list.addAction("Open with " + word)
            menu_item.triggered.connect(
                lambda x, w=word: self.open_environment_in(w.lower()))

        current_item = self.list.currentItem()
        prefix = current_item.prefix

        if isinstance(position, bool) or position is None:
            width = current_item.button_options.width()
            position = QPoint(width, 0)

        point = QPoint(0, 0)
        parent_position = current_item.button_options.mapToGlobal(point)
        self.menu_list.move(parent_position + position)

        # Disabled actions depending on the environment installed packages
        actions = self.menu_list.actions()
        actions[2].setEnabled(launch.check_prog('ipython', prefix))
        actions[3].setEnabled(launch.check_prog('notebook', prefix))

        self.menu_list.exec_()

    def open_environment_in(self, which):
        """Open selected environment in console terminal."""
        prefix = self.list.currentItem().prefix
        logger.debug("%s, %s", which, prefix)

        if which == 'terminal':
            launch.console(prefix)
        else:
            launch.py_in_console(prefix, which)

    # --- Common Helpers (# FIXME: factor out to common base widget)
    # -------------------------------------------------------------------------
    def _item_selected(self, item):
        """Callback to emit signal as user selects an item from the list."""
        self.set_loading(prefix=item.prefix)
        self.sig_item_selected.emit(item.name, item.prefix, C.TAB_ENVIRONMENT)

    def add_temporal_item(self, name):
        """Creates a temporal item on list while creation becomes effective."""
        item_names = [item.name for item in self.list.items()]
        item_names.append(name)
        index = list(sorted(item_names)).index(name) + 1
        item = ListItemEnv(name=name)
        self.list.insertItem(index, item)
        self.list.setCurrentRow(index)
        self.list.scrollToItem(item)
        item.set_loading(True)

    def expand_collapse(self):
        """Expand or collapse the list selector."""
        if self.frame_list.is_expanded:
            self.frame_list.hide()
            self.frame_list.is_expanded = False
        else:
            self.frame_list.show()
            self.frame_list.is_expanded = True

    def filter_list(self, text=None):
        """Filter items in list by name."""
        text = self.text_search.text().lower()
        for i in range(self.list.count()):
            item = self.list.item(i)
            item.setHidden(text not in item.name.lower())

            if not item.widget.isVisible():
                item.widget.repaint()

    def ordered_widgets(self, next_widget=None):
        """Return a list of the ordered widgets."""
        if next_widget is not None:
            self.widget.table_last_row.add_focus_widget(next_widget)

        ordered_widgets = [self.text_search]
        ordered_widgets += self.list.ordered_widgets()
        ordered_widgets += [
            self.button_create,
            self.button_clone,
            self.button_import,
            self.button_remove,
            self.widget.combobox_filter,
            self.widget.button_channels,
            self.widget.button_update,
            self.widget.textbox_search,
            # self.widget.table_first_row,
            self.widget.table,
            self.widget.table_last_row,
            self.widget.button_apply,
            self.widget.button_clear,
            self.widget.button_cancel,
        ]
        return ordered_widgets

    def refresh(self):
        """Refresh the enabled/disabled status of the widget and subwidgets."""
        is_root = self.current_prefix == self.api.ROOT_PREFIX
        self.button_clone.setDisabled(is_root)
        self.button_remove.setDisabled(is_root)

    def set_loading(self, prefix=None, value=True):
        """Set the item given by `prefix` to loading state."""
        for row, item in enumerate(self.list.items()):
            if item.prefix == prefix:
                item.set_loading(value)
                self.list.setCurrentRow(row)
                break

    def set_widgets_enabled(self, value):
        """Change the enabled status of widgets and subwidgets."""
        self.list.setEnabled(value)
        self.button_create.setEnabled(value)
        self.button_clone.setEnabled(value)
        self.button_import.setEnabled(value)
        self.button_remove.setEnabled(value)
        self.widget.set_widgets_enabled(value)
        if value:
            self.refresh()

    def update_status(self, action='', message='', value=None, max_value=None):
        """Update widget status and progress bar."""
        self.widget.update_status(action=action,
                                  message=message,
                                  value=value,
                                  max_value=max_value)

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet is None:
            self.style_sheet = load_style_sheet()
        else:
            self.style_sheet = style_sheet

        self.setStyleSheet(self.style_sheet)
        self.list.update_style_sheet(self.style_sheet)
        self.menu_list.setStyleSheet(self.style_sheet)
Beispiel #54
0
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin,
                      BrowseHistoryMixin):
    """
    Shell base widget
    """

    redirect_stdio = Signal(bool)
    sig_keyboard_interrupt = Signal()
    execute = Signal(str)
    append_to_history = Signal(str, str)

    def __init__(self, parent, history_filename, profile=False,
                 initial_message=None, default_foreground_color=None,
                 error_foreground_color=None, traceback_foreground_color=None,
                 prompt_foreground_color=None, background_color=None):
        """
        parent : specifies the parent widget
        """
        ConsoleBaseWidget.__init__(self, parent)
        SaveHistoryMixin.__init__(self, history_filename)
        BrowseHistoryMixin.__init__(self)

        # Prompt position: tuple (line, index)
        self.current_prompt_pos = None
        self.new_input_line = True

        # History
        assert is_text_string(history_filename)
        self.history = self.load_history()

        # Session
        self.historylog_filename = CONF.get('main', 'historylog_filename',
                                            get_conf_path('history.log'))

        # Context menu
        self.menu = None
        self.setup_context_menu()

        # Simple profiling test
        self.profile = profile

        # Buffer to increase performance of write/flush operations
        self.__buffer = []
        if initial_message:
            self.__buffer.append(initial_message)

        self.__timestamp = 0.0
        self.__flushtimer = QTimer(self)
        self.__flushtimer.setSingleShot(True)
        self.__flushtimer.timeout.connect(self.flush)

        # Give focus to widget
        self.setFocus()

        # Cursor width
        self.setCursorWidth(CONF.get('main', 'cursor/width'))

        # Adjustments to completion_widget to use it here
        self.completion_widget.currentRowChanged.disconnect()

    def toggle_wrap_mode(self, enable):
        """Enable/disable wrap mode"""
        self.set_wrap_mode('character' if enable else None)

    def set_font(self, font):
        """Set shell styles font"""
        self.setFont(font)
        self.set_pythonshell_font(font)
        cursor = self.textCursor()
        cursor.select(QTextCursor.Document)
        charformat = QTextCharFormat()
        charformat.setFontFamily(font.family())
        charformat.setFontPointSize(font.pointSize())
        cursor.mergeCharFormat(charformat)


    #------ Context menu
    def setup_context_menu(self):
        """Setup shell context menu"""
        self.menu = QMenu(self)
        self.cut_action = create_action(self, _("Cut"),
                                        shortcut=keybinding('Cut'),
                                        icon=ima.icon('editcut'),
                                        triggered=self.cut)
        self.copy_action = create_action(self, _("Copy"),
                                         shortcut=keybinding('Copy'),
                                         icon=ima.icon('editcopy'),
                                         triggered=self.copy)
        paste_action = create_action(self, _("Paste"),
                                     shortcut=keybinding('Paste'),
                                     icon=ima.icon('editpaste'),
                                     triggered=self.paste)
        save_action = create_action(self, _("Save history log..."),
                                    icon=ima.icon('filesave'),
                                    tip=_("Save current history log (i.e. all "
                                          "inputs and outputs) in a text file"),
                                    triggered=self.save_historylog)
        self.delete_action = create_action(self, _("Delete"),
                                    shortcut=keybinding('Delete'),
                                    icon=ima.icon('editdelete'),
                                    triggered=self.delete)
        selectall_action = create_action(self, _("Select All"),
                                    shortcut=keybinding('SelectAll'),
                                    icon=ima.icon('selectall'),
                                    triggered=self.selectAll)
        add_actions(self.menu, (self.cut_action, self.copy_action,
                                paste_action, self.delete_action, None,
                                selectall_action, None, save_action) )

    def contextMenuEvent(self, event):
        """Reimplement Qt method"""
        state = self.has_selected_text()
        self.copy_action.setEnabled(state)
        self.cut_action.setEnabled(state)
        self.delete_action.setEnabled(state)
        self.menu.popup(event.globalPos())
        event.accept()


    #------ Input buffer
    def get_current_line_from_cursor(self):
        return self.get_text('cursor', 'eof')

    def _select_input(self):
        """Select current line (without selecting console prompt)"""
        line, index = self.get_position('eof')
        if self.current_prompt_pos is None:
            pline, pindex = line, index
        else:
            pline, pindex = self.current_prompt_pos
        self.setSelection(pline, pindex, line, index)

    @Slot()
    def clear_terminal(self):
        """
        Clear terminal window
        Child classes reimplement this method to write prompt
        """
        self.clear()

    # The buffer being edited
    def _set_input_buffer(self, text):
        """Set input buffer"""
        if self.current_prompt_pos is not None:
            self.replace_text(self.current_prompt_pos, 'eol', text)
        else:
            self.insert(text)
        self.set_cursor_position('eof')

    def _get_input_buffer(self):
        """Return input buffer"""
        input_buffer = ''
        if self.current_prompt_pos is not None:
            input_buffer = self.get_text(self.current_prompt_pos, 'eol')
            input_buffer = input_buffer.replace(os.linesep, '\n')
        return input_buffer

    input_buffer = Property("QString", _get_input_buffer, _set_input_buffer)


    #------ Prompt
    def new_prompt(self, prompt):
        """
        Print a new prompt and save its (line, index) position
        """
        if self.get_cursor_line_column()[1] != 0:
            self.write('\n')
        self.write(prompt, prompt=True)
        # now we update our cursor giving end of prompt
        self.current_prompt_pos = self.get_position('cursor')
        self.ensureCursorVisible()
        self.new_input_line = False

    def check_selection(self):
        """
        Check if selected text is r/w,
        otherwise remove read-only parts of selection
        """
        if self.current_prompt_pos is None:
            self.set_cursor_position('eof')
        else:
            self.truncate_selection(self.current_prompt_pos)


    #------ Copy / Keyboard interrupt
    @Slot()
    def copy(self):
        """Copy text to clipboard... or keyboard interrupt"""
        if self.has_selected_text():
            ConsoleBaseWidget.copy(self)
        elif not sys.platform == 'darwin':
            self.interrupt()

    def interrupt(self):
        """Keyboard interrupt"""
        self.sig_keyboard_interrupt.emit()

    @Slot()
    def cut(self):
        """Cut text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.cut(self)

    @Slot()
    def delete(self):
        """Remove selected text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.remove_selected_text(self)

    @Slot()
    def save_historylog(self):
        """Save current history log (all text in console)"""
        title = _("Save history log")
        self.redirect_stdio.emit(False)
        filename, _selfilter = getsavefilename(self, title,
                    self.historylog_filename, "%s (*.log)" % _("History logs"))
        self.redirect_stdio.emit(True)
        if filename:
            filename = osp.normpath(filename)
            try:
                encoding.write(to_text_string(self.get_text_with_eol()),
                               filename)
                self.historylog_filename = filename
                CONF.set('main', 'historylog_filename', filename)
            except EnvironmentError:
                pass

    #------ Basic keypress event handler
    def on_enter(self, command):
        """on_enter"""
        self.execute_command(command)

    def execute_command(self, command):
        self.execute.emit(command)
        self.add_to_history(command)
        self.new_input_line = True

    def on_new_line(self):
        """On new input line"""
        self.set_cursor_position('eof')
        self.current_prompt_pos = self.get_position('cursor')
        self.new_input_line = False

    @Slot()
    def paste(self):
        """Reimplemented slot to handle multiline paste action"""
        if self.new_input_line:
            self.on_new_line()
        ConsoleBaseWidget.paste(self)

    def keyPressEvent(self, event):
        """
        Reimplement Qt Method
        Basic keypress event handler
        (reimplemented in InternalShell to add more sophisticated features)
        """
        if self.preprocess_keyevent(event):
            # Event was accepted in self.preprocess_keyevent
            return
        self.postprocess_keyevent(event)

    def preprocess_keyevent(self, event):
        """Pre-process keypress event:
        return True if event is accepted, false otherwise"""
        # Copy must be done first to be able to copy read-only text parts
        # (otherwise, right below, we would remove selection
        #  if not on current line)
        ctrl = event.modifiers() & Qt.ControlModifier
        meta = event.modifiers() & Qt.MetaModifier    # meta=ctrl in OSX
        if event.key() == Qt.Key_C and \
          ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()):
            if meta and sys.platform == 'darwin':
                self.interrupt()
            elif ctrl:
                self.copy()
            event.accept()
            return True

        if self.new_input_line and ( len(event.text()) or event.key() in \
           (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ):
            self.on_new_line()

        return False

    def postprocess_keyevent(self, event):
        """Post-process keypress event:
        in InternalShell, this is method is called when shell is ready"""
        event, text, key, ctrl, shift = restore_keyevent(event)

        # Is cursor on the last line? and after prompt?
        if len(text):
            #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ?
            if self.has_selected_text():
                self.check_selection()
            self.restrict_cursor_position(self.current_prompt_pos, 'eof')

        cursor_position = self.get_position('cursor')

        if key in (Qt.Key_Return, Qt.Key_Enter):
            if self.is_cursor_on_last_line():
                self._key_enter()
            # add and run selection
            else:
                self.insert_text(self.get_selected_text(), at_end=True)

        elif key == Qt.Key_Insert and not shift and not ctrl:
            self.setOverwriteMode(not self.overwriteMode())

        elif key == Qt.Key_Delete:
            if self.has_selected_text():
                self.check_selection()
                self.remove_selected_text()
            elif self.is_cursor_on_last_line():
                self.stdkey_clear()

        elif key == Qt.Key_Backspace:
            self._key_backspace(cursor_position)

        elif key == Qt.Key_Tab:
            self._key_tab()

        elif key == Qt.Key_Space and ctrl:
            self._key_ctrl_space()

        elif key == Qt.Key_Left:
            if self.current_prompt_pos == cursor_position:
                # Avoid moving cursor on prompt
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='left')

        elif key == Qt.Key_Right:
            if self.is_cursor_at_end():
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='right')

        elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl):
            self._key_home(shift, ctrl)

        elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl):
            self._key_end(shift, ctrl)

        elif key == Qt.Key_Up:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_prompt = self.get_coordinates(self.current_prompt_pos)[1]
            if y_cursor > y_prompt:
                self.stdkey_up(shift)
            else:
                self.browse_history(backward=True)

        elif key == Qt.Key_Down:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_end = self.get_coordinates('eol')[1]
            if y_cursor < y_end:
                self.stdkey_down(shift)
            else:
                self.browse_history(backward=False)

        elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
            #XXX: Find a way to do this programmatically instead of calling
            # widget keyhandler (this won't work if the *event* is coming from
            # the event queue - i.e. if the busy buffer is ever implemented)
            ConsoleBaseWidget.keyPressEvent(self, event)

        elif key == Qt.Key_Escape and shift:
            self.clear_line()

        elif key == Qt.Key_Escape:
            self._key_escape()

        elif key == Qt.Key_L and ctrl:
            self.clear_terminal()

        elif key == Qt.Key_V and ctrl:
            self.paste()

        elif key == Qt.Key_X and ctrl:
            self.cut()

        elif key == Qt.Key_Z and ctrl:
            self.undo()

        elif key == Qt.Key_Y and ctrl:
            self.redo()

        elif key == Qt.Key_A and ctrl:
            self.selectAll()

        elif key == Qt.Key_Question and not self.has_selected_text():
            self._key_question(text)

        elif key == Qt.Key_ParenLeft and not self.has_selected_text():
            self._key_parenleft(text)

        elif key == Qt.Key_Period and not self.has_selected_text():
            self._key_period(text)

        elif len(text) and not self.isReadOnly():
            self.hist_wholeline = False
            self.insert_text(text)
            self._key_other(text)

        else:
            # Let the parent widget handle the key press event
            ConsoleBaseWidget.keyPressEvent(self, event)


    #------ Key handlers
    def _key_enter(self):
        command = self.input_buffer
        self.insert_text('\n', at_end=True)
        self.on_enter(command)
        self.flush()
    def _key_other(self, text):
        raise NotImplementedError
    def _key_backspace(self, cursor_position):
        raise NotImplementedError
    def _key_tab(self):
        raise NotImplementedError
    def _key_ctrl_space(self):
        raise NotImplementedError
    def _key_home(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_home(shift, ctrl, self.current_prompt_pos)
    def _key_end(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_end(shift, ctrl)
    def _key_pageup(self):
        raise NotImplementedError
    def _key_pagedown(self):
        raise NotImplementedError
    def _key_escape(self):
        raise NotImplementedError
    def _key_question(self, text):
        raise NotImplementedError
    def _key_parenleft(self, text):
        raise NotImplementedError
    def _key_period(self, text):
        raise NotImplementedError


    #------ History Management
    def load_history(self):
        """Load history from a .py file in user home directory"""
        if osp.isfile(self.history_filename):
            rawhistory, _ = encoding.readlines(self.history_filename)
            rawhistory = [line.replace('\n', '') for line in rawhistory]
            if rawhistory[1] != self.INITHISTORY[1]:
                rawhistory[1] = self.INITHISTORY[1]
        else:
            rawhistory = self.INITHISTORY
        history = [line for line in rawhistory \
                   if line and not line.startswith('#')]

        # Truncating history to X entries:
        while len(history) >= CONF.get('historylog', 'max_entries'):
            del history[0]
            while rawhistory[0].startswith('#'):
                del rawhistory[0]
            del rawhistory[0]

        # Saving truncated history:
        try:
            encoding.writelines(rawhistory, self.history_filename)
        except EnvironmentError:
            pass

        return history

    #------ Simulation standards input/output
    def write_error(self, text):
        """Simulate stderr"""
        self.flush()
        self.write(text, flush=True, error=True)
        if get_debug_level():
            STDERR.write(text)

    def write(self, text, flush=False, error=False, prompt=False):
        """Simulate stdout and stderr"""
        if prompt:
            self.flush()
        if not is_string(text):
            # This test is useful to discriminate QStrings from decoded str
            text = to_text_string(text)
        self.__buffer.append(text)
        ts = time.time()
        if flush or prompt:
            self.flush(error=error, prompt=prompt)
        elif ts - self.__timestamp > 0.05:
            self.flush(error=error)
            self.__timestamp = ts
            # Timer to flush strings cached by last write() operation in series
            self.__flushtimer.start(50)

    def flush(self, error=False, prompt=False):
        """Flush buffer, write text to console"""
        # Fix for spyder-ide/spyder#2452
        if PY3:
            try:
                text = "".join(self.__buffer)
            except TypeError:
                text = b"".join(self.__buffer)
                try:
                    text = text.decode( locale.getdefaultlocale()[1] )
                except:
                    pass
        else:
            text = "".join(self.__buffer)

        self.__buffer = []
        self.insert_text(text, at_end=True, error=error, prompt=prompt)
        QCoreApplication.processEvents()
        self.repaint()
        # Clear input buffer:
        self.new_input_line = True


    #------ Text Insertion
    def insert_text(self, text, at_end=False, error=False, prompt=False):
        """
        Insert text at the current cursor position
        or at the end of the command line
        """
        if at_end:
            # Insert text at the end of the command line
            self.append_text_to_shell(text, error, prompt)
        else:
            # Insert text at current cursor position
            ConsoleBaseWidget.insert_text(self, text)


    #------ Re-implemented Qt Methods
    def focusNextPrevChild(self, next):
        """
        Reimplemented to stop Tab moving to the next window
        """
        if next:
            return False
        return ConsoleBaseWidget.focusNextPrevChild(self, next)


    #------ Drag and drop
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """Drag and Drop - Drop event"""
        if (event.mimeData().hasFormat("text/plain")):
            text = to_text_string(event.mimeData().text())
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text, at_end=True)
            self.setFocus()
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def drop_pathlist(self, pathlist):
        """Drop path list"""
        raise NotImplementedError
Beispiel #55
0
    def right_click(self, position=None):
        _duplicate_row = -1
        _plot_sofq = -1
        _remove_row = -1
        _new_row = -1
        _copy = -1
        _paste = -1
        _cut = -1
        _refresh_table = -1
        _clear_table = -1
        # _import = -1
        # _export = -1
        _check_all = -1
        _uncheck_all = -1
        _undo = -1
        _redo = -1
        _plot_sofq_diff_first_run_row = -1
        _plot_sofq_diff_average_row = -1
        _plot_cryostat = -1
        _plot_furnace = -1
        _invert_selection = -1

        menu = QMenu(self.parent)

        if self.parent.table_selection_buffer == {}:
            paste_status = False
        else:
            paste_status = True

        if (self.parent.postprocessing_ui.table.rowCount() > 0):
            _undo = menu.addAction("Undo")
            _undo.setEnabled(self.parent.undo_button_enabled)
            _redo = menu.addAction("Redo")
            _redo.setEnabled(self.parent.redo_button_enabled)
            menu.addSeparator()
            _copy = menu.addAction("Copy")
            _paste = menu.addAction("Paste")
            self._paste_menu = _paste
            _paste.setEnabled(paste_status)
            _cut = menu.addAction("Clear")
            menu.addSeparator()
            _check_all = menu.addAction("Check All")
            _uncheck_all = menu.addAction("Unchecked All")
            menu.addSeparator()
            _invert_selection = menu.addAction("Inverse Selection")
            menu.addSeparator()

        _new_row = menu.addAction("Insert Blank Row")

        if (self.parent.postprocessing_ui.table.rowCount() > 0):
            _duplicate_row = menu.addAction("Duplicate Row")
            _remove_row = menu.addAction("Remove Row(s)")

            menu.addSeparator()
            _plot_menu = menu.addMenu('Plot')
            _plot_sofq = _plot_menu.addAction("S(Q) ...")
            _plot_sofq_diff_first_run_row = _plot_menu.addAction(
                "S(Q) Diff (1st run)...")
            _plot_sofq_diff_average_row = _plot_menu.addAction(
                "S(Q) Diff (Avg.)...")

            _temp_menu = _plot_menu.addMenu("Temperature")
            _plot_cryostat = _temp_menu.addAction("Cyrostat...")
            _plot_furnace = _temp_menu.addAction("Furnace...")

            menu.addSeparator()
            _refresh_table = menu.addAction("Refresh/Reset Table")
            _clear_table = menu.addAction("Clear Table")

        action = menu.exec_(QCursor.pos())
        self.current_row = self.current_row()

        if action == _undo:
            self.parent.action_undo_clicked()
        elif action == _redo:
            self.parent.action_redo_clicked()
        elif action == _copy:
            self._copy()
        elif action == _paste:
            self._paste()
        elif action == _cut:
            self._cut()
        elif action == _duplicate_row:
            self._duplicate_row()
        elif action == _plot_sofq:
            self._plot_sofq()
        elif action == _plot_sofq_diff_first_run_row:
            self._plot_sofq_diff_first_run_row()
        elif action == _plot_sofq_diff_average_row:
            self._plot_sofq_diff_average_row()
        elif action == _plot_cryostat:
            self._plot_temperature(samp_env_choice='cryostat')
        elif action == _plot_furnace:
            self._plot_temperature(samp_env_choice='furnace')
        elif action == _invert_selection:
            self._inverse_selection()
        elif action == _new_row:
            self._new_row()
        elif action == _remove_row:
            self._remove_selected_rows()
        elif action == _refresh_table:
            self._refresh_table()
        elif action == _clear_table:
            self._clear_table()
        elif action == _check_all:
            self.check_all()
        elif action == _uncheck_all:
            self.uncheck_all()
Beispiel #56
0
    def __init__(self, parent=None):
        """Conda environments tab."""
        super(EnvironmentsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_prefix = None
        self.style_sheet = None

        # Widgets
        self.frame_header_left = FrameTabHeader()
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.text_search = LineEditSearch()
        self.list = ListWidgetEnv()
        self.menu_list = QMenu()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_clone = ButtonToolNormal(text="Clone")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.widget = CondaPackagesWidget(parent=self)

        # Widgets setup
        self.frame_list.is_expanded = True
        self.text_search.setPlaceholderText("Search Environments")
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.button_create.setObjectName("create")  # Needed for QSS selectors
        self.button_clone.setObjectName("clone")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")
        self.widget.textbox_search.set_icon_visibility(False)

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_clone)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)

        self.setLayout(layout_main)

        # Signals for buttons and boxes
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_clone.clicked.connect(self.sig_clone_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)
        self.text_search.textChanged.connect(self.filter_list)

        # Signals for list
        self.list.sig_item_selected.connect(self._item_selected)

        # Signals for packages widget
        self.widget.sig_ready.connect(self.sig_ready)
        self.widget.sig_channels_requested.connect(self.sig_channels_requested)
        self.widget.sig_update_index_requested.connect(
            self.sig_update_index_requested)
        self.widget.sig_cancel_requested.connect(self.sig_cancel_requested)
        self.widget.sig_packages_action_requested.connect(
            self.sig_packages_action_requested)
Beispiel #57
0
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin):
    """
    Shell base widget
    """
    
    redirect_stdio = Signal(bool)
    sig_keyboard_interrupt = Signal()
    execute = Signal(str)
    append_to_history = Signal(str, str)
    
    def __init__(self, parent, history_filename, profile=False):
        """
        parent : specifies the parent widget
        """
        ConsoleBaseWidget.__init__(self, parent)
        SaveHistoryMixin.__init__(self)
                
        # Prompt position: tuple (line, index)
        self.current_prompt_pos = None
        self.new_input_line = True
        
        # History
        self.histidx = None
        self.hist_wholeline = False
        assert is_text_string(history_filename)
        self.history_filename = history_filename
        self.history = self.load_history()
        
        # Session
        self.historylog_filename = CONF.get('main', 'historylog_filename',
                                            get_conf_path('history.log'))
        
        # Context menu
        self.menu = None
        self.setup_context_menu()

        # Simple profiling test
        self.profile = profile
        
        # Buffer to increase performance of write/flush operations
        self.__buffer = []
        self.__timestamp = 0.0
        self.__flushtimer = QTimer(self)
        self.__flushtimer.setSingleShot(True)
        self.__flushtimer.timeout.connect(self.flush)

        # Give focus to widget
        self.setFocus()

        # Cursor width
        self.setCursorWidth( CONF.get('main', 'cursor/width') )

    def toggle_wrap_mode(self, enable):
        """Enable/disable wrap mode"""
        self.set_wrap_mode('character' if enable else None)

    def set_font(self, font):
        """Set shell styles font"""
        self.setFont(font)
        self.set_pythonshell_font(font)
        cursor = self.textCursor()
        cursor.select(QTextCursor.Document)
        charformat = QTextCharFormat()
        charformat.setFontFamily(font.family())
        charformat.setFontPointSize(font.pointSize())
        cursor.mergeCharFormat(charformat)


    #------ Context menu
    def setup_context_menu(self):
        """Setup shell context menu"""
        self.menu = QMenu(self)
        self.cut_action = create_action(self, _("Cut"),
                                        shortcut=keybinding('Cut'),
                                        icon=ima.icon('editcut'),
                                        triggered=self.cut)
        self.copy_action = create_action(self, _("Copy"),
                                         shortcut=keybinding('Copy'),
                                         icon=ima.icon('editcopy'),
                                         triggered=self.copy)
        paste_action = create_action(self, _("Paste"),
                                     shortcut=keybinding('Paste'),
                                     icon=ima.icon('editpaste'),
                                     triggered=self.paste)
        save_action = create_action(self, _("Save history log..."),
                                    icon=ima.icon('filesave'),
                                    tip=_("Save current history log (i.e. all "
                                          "inputs and outputs) in a text file"),
                                    triggered=self.save_historylog)
        self.delete_action = create_action(self, _("Delete"),
                                    shortcut=keybinding('Delete'),
                                    icon=ima.icon('editdelete'),
                                    triggered=self.delete)
        selectall_action = create_action(self, _("Select All"),
                                    shortcut=keybinding('SelectAll'),
                                    icon=ima.icon('selectall'),
                                    triggered=self.selectAll)
        add_actions(self.menu, (self.cut_action, self.copy_action,
                                paste_action, self.delete_action, None,
                                selectall_action, None, save_action) )
          
    def contextMenuEvent(self, event):
        """Reimplement Qt method"""
        state = self.has_selected_text()
        self.copy_action.setEnabled(state)
        self.cut_action.setEnabled(state)
        self.delete_action.setEnabled(state)
        self.menu.popup(event.globalPos())
        event.accept()        
        
        
    #------ Input buffer
    def get_current_line_from_cursor(self):
        return self.get_text('cursor', 'eof')
    
    def _select_input(self):
        """Select current line (without selecting console prompt)"""
        line, index = self.get_position('eof')
        if self.current_prompt_pos is None:
            pline, pindex = line, index
        else:
            pline, pindex = self.current_prompt_pos
        self.setSelection(pline, pindex, line, index)

    @Slot()
    def clear_line(self):
        """Clear current line (without clearing console prompt)"""
        if self.current_prompt_pos is not None:
            self.remove_text(self.current_prompt_pos, 'eof')

    @Slot()
    def clear_terminal(self):
        """
        Clear terminal window
        Child classes reimplement this method to write prompt
        """
        self.clear()

    # The buffer being edited
    def _set_input_buffer(self, text):
        """Set input buffer"""
        if self.current_prompt_pos is not None:
            self.replace_text(self.current_prompt_pos, 'eol', text)
        else:
            self.insert(text)
        self.set_cursor_position('eof')

    def _get_input_buffer(self):
        """Return input buffer"""
        input_buffer = ''
        if self.current_prompt_pos is not None:
            input_buffer = self.get_text(self.current_prompt_pos, 'eol')
            input_buffer = input_buffer.replace(os.linesep, '\n')
        return input_buffer

    input_buffer = Property("QString", _get_input_buffer, _set_input_buffer)
        
        
    #------ Prompt
    def new_prompt(self, prompt):
        """
        Print a new prompt and save its (line, index) position
        """
        if self.get_cursor_line_column()[1] != 0:
            self.write('\n')
        self.write(prompt, prompt=True)
        # now we update our cursor giving end of prompt
        self.current_prompt_pos = self.get_position('cursor')
        self.ensureCursorVisible()
        self.new_input_line = False
        
    def check_selection(self):
        """
        Check if selected text is r/w,
        otherwise remove read-only parts of selection
        """
        if self.current_prompt_pos is None:
            self.set_cursor_position('eof')
        else:
            self.truncate_selection(self.current_prompt_pos)
        
        
    #------ Copy / Keyboard interrupt
    @Slot()
    def copy(self):
        """Copy text to clipboard... or keyboard interrupt"""
        if self.has_selected_text():
            ConsoleBaseWidget.copy(self)
        elif not sys.platform == 'darwin':
            self.interrupt()

    def interrupt(self):
        """Keyboard interrupt"""
        self.sig_keyboard_interrupt.emit()

    @Slot()
    def cut(self):
        """Cut text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.cut(self)

    @Slot()
    def delete(self):
        """Remove selected text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.remove_selected_text(self)

    @Slot()
    def save_historylog(self):
        """Save current history log (all text in console)"""
        title = _("Save history log")
        self.redirect_stdio.emit(False)
        filename, _selfilter = getsavefilename(self, title,
                    self.historylog_filename, "%s (*.log)" % _("History logs"))
        self.redirect_stdio.emit(True)
        if filename:
            filename = osp.normpath(filename)
            try:
                encoding.write(to_text_string(self.get_text_with_eol()),
                               filename)
                self.historylog_filename = filename
                CONF.set('main', 'historylog_filename', filename)
            except EnvironmentError as error:
                QMessageBox.critical(self, title,
                                     _("<b>Unable to save file '%s'</b>"
                                       "<br><br>Error message:<br>%s"
                                       ) % (osp.basename(filename),
                                            to_text_string(error)))
        
        
    #------ Basic keypress event handler
    def on_enter(self, command):
        """on_enter"""
        self.execute_command(command)
        
    def execute_command(self, command):
        self.execute.emit(command)
        self.add_to_history(command)
        self.new_input_line = True
        
    def on_new_line(self):
        """On new input line"""
        self.set_cursor_position('eof')
        self.current_prompt_pos = self.get_position('cursor')
        self.new_input_line = False

    @Slot()
    def paste(self):
        """Reimplemented slot to handle multiline paste action"""
        if self.new_input_line:
            self.on_new_line()
        ConsoleBaseWidget.paste(self)
        
    def keyPressEvent(self, event):
        """
        Reimplement Qt Method
        Basic keypress event handler
        (reimplemented in InternalShell to add more sophisticated features)
        """
        if self.preprocess_keyevent(event):
            # Event was accepted in self.preprocess_keyevent
            return
        self.postprocess_keyevent(event)
        
    def preprocess_keyevent(self, event):
        """Pre-process keypress event:
        return True if event is accepted, false otherwise"""
        # Copy must be done first to be able to copy read-only text parts
        # (otherwise, right below, we would remove selection
        #  if not on current line)
        ctrl = event.modifiers() & Qt.ControlModifier
        meta = event.modifiers() & Qt.MetaModifier    # meta=ctrl in OSX
        if event.key() == Qt.Key_C and \
          ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()):
            if meta and sys.platform == 'darwin':
                self.interrupt()
            elif ctrl:
                self.copy()
            event.accept()
            return True
        
        if self.new_input_line and ( len(event.text()) or event.key() in \
           (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ):
            self.on_new_line()
        
        return False
        
    def postprocess_keyevent(self, event):
        """Post-process keypress event:
        in InternalShell, this is method is called when shell is ready"""
        event, text, key, ctrl, shift = restore_keyevent(event)
        
        # Is cursor on the last line? and after prompt?
        if len(text):
            #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ?
            if self.has_selected_text():
                self.check_selection()
            self.restrict_cursor_position(self.current_prompt_pos, 'eof')
            
        cursor_position = self.get_position('cursor')

        if key in (Qt.Key_Return, Qt.Key_Enter):
            if self.is_cursor_on_last_line():
                self._key_enter()
            # add and run selection
            else:
                self.insert_text(self.get_selected_text(), at_end=True)
            
        elif key == Qt.Key_Insert and not shift and not ctrl:
            self.setOverwriteMode(not self.overwriteMode())
            
        elif key == Qt.Key_Delete:
            if self.has_selected_text():
                self.check_selection()
                self.remove_selected_text()
            elif self.is_cursor_on_last_line():
                self.stdkey_clear()
            
        elif key == Qt.Key_Backspace:
            self._key_backspace(cursor_position)
            
        elif key == Qt.Key_Tab:
            self._key_tab()
            
        elif key == Qt.Key_Space and ctrl:
            self._key_ctrl_space()

        elif key == Qt.Key_Left:
            if self.current_prompt_pos == cursor_position:
                # Avoid moving cursor on prompt
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='left')
                
        elif key == Qt.Key_Right:
            if self.is_cursor_at_end():
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='right')

        elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl):
            self._key_home(shift, ctrl)

        elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl):
            self._key_end(shift, ctrl)

        elif key == Qt.Key_Up:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_prompt = self.get_coordinates(self.current_prompt_pos)[1]
            if y_cursor > y_prompt:
                self.stdkey_up(shift)
            else:
                self.browse_history(backward=True)
                
        elif key == Qt.Key_Down:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_end = self.get_coordinates('eol')[1]
            if y_cursor < y_end:
                self.stdkey_down(shift)
            else:
                self.browse_history(backward=False)
            
        elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
            #XXX: Find a way to do this programmatically instead of calling
            # widget keyhandler (this won't work if the *event* is coming from
            # the event queue - i.e. if the busy buffer is ever implemented)
            ConsoleBaseWidget.keyPressEvent(self, event)

        elif key == Qt.Key_Escape and shift:
            self.clear_line()

        elif key == Qt.Key_Escape:
            self._key_escape()
                
        elif key == Qt.Key_L and ctrl:
            self.clear_terminal()
            
        elif key == Qt.Key_V and ctrl:
            self.paste()
            
        elif key == Qt.Key_X and ctrl:
            self.cut()
            
        elif key == Qt.Key_Z and ctrl:
            self.undo()
            
        elif key == Qt.Key_Y and ctrl:
            self.redo()
            
        elif key == Qt.Key_A and ctrl:
            self.selectAll()
                
        elif key == Qt.Key_Question and not self.has_selected_text():
            self._key_question(text)
            
        elif key == Qt.Key_ParenLeft and not self.has_selected_text():
            self._key_parenleft(text)
            
        elif key == Qt.Key_Period and not self.has_selected_text():
            self._key_period(text)

        elif len(text) and not self.isReadOnly():
            self.hist_wholeline = False
            self.insert_text(text)
            self._key_other(text)
                
        else:
            # Let the parent widget handle the key press event
            ConsoleBaseWidget.keyPressEvent(self, event)
            
                
    #------ Key handlers
    def _key_enter(self):
        command = self.input_buffer
        self.insert_text('\n', at_end=True)
        self.on_enter(command)
        self.flush()
    def _key_other(self, text):
        raise NotImplementedError
    def _key_backspace(self, cursor_position):
        raise NotImplementedError
    def _key_tab(self):
        raise NotImplementedError
    def _key_ctrl_space(self):
        raise NotImplementedError
    def _key_home(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_home(shift, ctrl, self.current_prompt_pos)
    def _key_end(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_end(shift, ctrl)
    def _key_pageup(self):
        raise NotImplementedError
    def _key_pagedown(self):
        raise NotImplementedError
    def _key_escape(self):
        raise NotImplementedError
    def _key_question(self, text):
        raise NotImplementedError
    def _key_parenleft(self, text):
        raise NotImplementedError
    def _key_period(self, text):
        raise NotImplementedError

        
    #------ History Management
    def load_history(self):
        """Load history from a .py file in user home directory"""
        if osp.isfile(self.history_filename):
            rawhistory, _ = encoding.readlines(self.history_filename)
            rawhistory = [line.replace('\n', '') for line in rawhistory]
            if rawhistory[1] != self.INITHISTORY[1]:
                rawhistory[1] = self.INITHISTORY[1]
        else:
            rawhistory = self.INITHISTORY
        history = [line for line in rawhistory \
                   if line and not line.startswith('#')]

        # Truncating history to X entries:
        while len(history) >= CONF.get('historylog', 'max_entries'):
            del history[0]
            while rawhistory[0].startswith('#'):
                del rawhistory[0]
            del rawhistory[0]
        # Saving truncated history:
        encoding.writelines(rawhistory, self.history_filename)
        return history
        
    def browse_history(self, backward):
        """Browse history"""
        if self.is_cursor_before('eol') and self.hist_wholeline:
            self.hist_wholeline = False
        tocursor = self.get_current_line_to_cursor()
        text, self.histidx = self.__find_in_history(tocursor,
                                                    self.histidx, backward)
        if text is not None:
            if self.hist_wholeline:
                self.clear_line()
                self.insert_text(text)
            else:
                cursor_position = self.get_position('cursor')
                # Removing text from cursor to the end of the line
                self.remove_text('cursor', 'eol')
                # Inserting history text
                self.insert_text(text)
                self.set_cursor_position(cursor_position)

    def __find_in_history(self, tocursor, start_idx, backward):
        """Find text 'tocursor' in history, from index 'start_idx'"""
        if start_idx is None:
            start_idx = len(self.history)
        # Finding text in history
        step = -1 if backward else 1
        idx = start_idx
        if len(tocursor) == 0 or self.hist_wholeline:
            idx += step
            if idx >= len(self.history) or len(self.history) == 0:
                return "", len(self.history)
            elif idx < 0:
                idx = 0
            self.hist_wholeline = True
            return self.history[idx], idx
        else:
            for index in range(len(self.history)):
                idx = (start_idx+step*(index+1)) % len(self.history)
                entry = self.history[idx]
                if entry.startswith(tocursor):
                    return entry[len(tocursor):], idx
            else:
                return None, start_idx
    
    
    #------ Simulation standards input/output
    def write_error(self, text):
        """Simulate stderr"""
        self.flush()
        self.write(text, flush=True, error=True)
        if DEBUG:
            STDERR.write(text)

    def write(self, text, flush=False, error=False, prompt=False):
        """Simulate stdout and stderr"""
        if prompt:
            self.flush()
        if not is_string(text):
            # This test is useful to discriminate QStrings from decoded str
            text = to_text_string(text)
        self.__buffer.append(text)
        ts = time.time()
        if flush or prompt:
            self.flush(error=error, prompt=prompt)
        elif ts - self.__timestamp > 0.05:
            self.flush(error=error)
            self.__timestamp = ts
            # Timer to flush strings cached by last write() operation in series
            self.__flushtimer.start(50)

    def flush(self, error=False, prompt=False):
        """Flush buffer, write text to console"""
        # Fix for Issue 2452 
        if PY3:
            try:
                text = "".join(self.__buffer)
            except TypeError:
                text = b"".join(self.__buffer)
                try:
                    text = text.decode( locale.getdefaultlocale()[1] )
                except:
                    pass
        else:
            text = "".join(self.__buffer)

        self.__buffer = []
        self.insert_text(text, at_end=True, error=error, prompt=prompt)
        QCoreApplication.processEvents()
        self.repaint()
        # Clear input buffer:
        self.new_input_line = True


    #------ Text Insertion
    def insert_text(self, text, at_end=False, error=False, prompt=False):
        """
        Insert text at the current cursor position
        or at the end of the command line
        """
        if at_end:
            # Insert text at the end of the command line
            self.append_text_to_shell(text, error, prompt)
        else:
            # Insert text at current cursor position
            ConsoleBaseWidget.insert_text(self, text)

            
    #------ Re-implemented Qt Methods
    def focusNextPrevChild(self, next):
        """
        Reimplemented to stop Tab moving to the next window
        """
        if next:
            return False
        return ConsoleBaseWidget.focusNextPrevChild(self, next)

    
    #------ Drag and drop
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """Drag and Drop - Drop event"""
        if (event.mimeData().hasFormat("text/plain")):
            text = to_text_string(event.mimeData().text())
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text, at_end=True)
            self.setFocus()
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()
            
    def drop_pathlist(self, pathlist):
        """Drop path list"""
        raise NotImplementedError
Beispiel #58
0
    def setup(self,
              check_all=None,
              exclude_private=None,
              exclude_uppercase=None,
              exclude_capitalized=None,
              exclude_unsupported=None,
              excluded_names=None,
              truncate=None,
              minmax=None,
              remote_editing=None,
              autorefresh=None):
        """Setup the namespace browser"""
        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.truncate = truncate
        self.minmax = minmax
        self.remote_editing = remote_editing
        self.autorefresh = autorefresh

        if self.editor is not None:
            self.editor.setup_menu(truncate, minmax)
            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)
            if self.auto_refresh_button is not None:
                self.auto_refresh_button.setChecked(autorefresh)
            self.refresh_table()
            return

        self.editor = RemoteCollectionsEditorTableView(
            self,
            None,
            truncate=truncate,
            minmax=minmax,
            remote_editing=remote_editing,
            get_value_func=self.get_value,
            set_value_func=self.set_value,
            new_value_func=self.set_value,
            remove_values_func=self.remove_values,
            copy_value_func=self.copy_value,
            is_list_func=self.is_list,
            get_len_func=self.get_len,
            is_array_func=self.is_array,
            is_image_func=self.is_image,
            is_dict_func=self.is_dict,
            is_data_frame_func=self.is_data_frame,
            is_series_func=self.is_series,
            get_array_shape_func=self.get_array_shape,
            get_array_ndim_func=self.get_array_ndim,
            oedit_func=self.oedit,
            plot_func=self.plot,
            imshow_func=self.imshow,
            show_image_func=self.show_image)
        self.editor.sig_option_changed.connect(self.sig_option_changed.emit)
        self.editor.sig_files_dropped.connect(self.import_data)

        # Setup layout
        layout = QVBoxLayout()
        blayout = QHBoxLayout()
        toolbar = self.setup_toolbar(exclude_private, exclude_uppercase,
                                     exclude_capitalized, exclude_unsupported,
                                     autorefresh)
        for widget in toolbar:
            blayout.addWidget(widget)

        # Options menu
        options_button = create_toolbutton(self,
                                           text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        editor = self.editor
        actions = [
            self.exclude_private_action, self.exclude_uppercase_action,
            self.exclude_capitalized_action, self.exclude_unsupported_action,
            None, editor.truncate_action
        ]
        if is_module_installed('numpy'):
            actions.append(editor.minmax_action)
        add_actions(menu, actions)
        options_button.setMenu(menu)

        blayout.addStretch()
        blayout.addWidget(options_button)
        layout.addLayout(blayout)
        layout.addWidget(self.editor)
        self.setLayout(layout)
        layout.setContentsMargins(0, 0, 0, 0)

        self.sig_option_changed.connect(self.option_changed)
Beispiel #59
0
 def more_options_device_view(self, button):
     if 'Disconnect' in button.text():
         menu = QMenu("Menu", self)
         menu.addAction("Pair / Ping", self.ping_paired_device)
         menu.addAction("Attempt TCPIP on device", self.tcpip_paired_device)
         menu.addAction("Forget device", self.forget_paired_device)
     else:
         menu = QMenu("Menu", self)
         menu.addAction("Attempt TCPIP on device", self.tcpip_paired_device)
         menu.addAction("Attempt reconnection", self.ping_paired_device)
         menu.addAction("Refresh", self.refresh_devices)
     _, identifier = self.current_device_identifier()
     if platform.System.system() == "Linux" and identifier.count('.') >= 3:
         menu.addAction("Add Desktop Shortcut to this device",
                        self.create_desktop_shortcut_linux_os)
     menu.exec_(
         self.devices_view.mapToGlobal(
             QPoint(
                 self.devices_view.visualItemRect(button).x() + 22,
                 self.devices_view.visualItemRect(button).y() + 22)))
Beispiel #60
0
class BaseTabs(QTabWidget):
    """TabWidget with context menu and corner widgets"""
    sig_close_tab = Signal(int)

    def __init__(self,
                 parent,
                 actions=None,
                 menu=None,
                 corner_widgets=None,
                 menu_use_tooltips=False):
        QTabWidget.__init__(self, parent)
        self.setUsesScrollButtons(True)

        # To style tabs on Mac
        if sys.platform == 'darwin':
            self.setObjectName('plugin-tab')

        self.corner_widgets = {}
        self.menu_use_tooltips = menu_use_tooltips

        if menu is None:
            self.menu = QMenu(self)
            if actions:
                add_actions(self.menu, actions)
        else:
            self.menu = menu

        # Corner widgets
        if corner_widgets is None:
            corner_widgets = {}
        corner_widgets.setdefault(Qt.TopLeftCorner, [])
        corner_widgets.setdefault(Qt.TopRightCorner, [])
        self.browse_button = create_toolbutton(self,
                                               icon=ima.icon('browse_tab'),
                                               tip=_("Browse tabs"))
        self.browse_tabs_menu = QMenu(self)
        self.browse_button.setMenu(self.browse_tabs_menu)
        self.browse_button.setPopupMode(self.browse_button.InstantPopup)
        self.browse_tabs_menu.aboutToShow.connect(self.update_browse_tabs_menu)
        corner_widgets[Qt.TopLeftCorner] += [self.browse_button]

        self.set_corner_widgets(corner_widgets)

    def update_browse_tabs_menu(self):
        """Update browse tabs menu"""
        self.browse_tabs_menu.clear()
        names = []
        dirnames = []
        for index in range(self.count()):
            if self.menu_use_tooltips:
                text = to_text_string(self.tabToolTip(index))
            else:
                text = to_text_string(self.tabText(index))
            names.append(text)
            if osp.isfile(text):
                # Testing if tab names are filenames
                dirnames.append(osp.dirname(text))
        offset = None

        # If tab names are all filenames, removing common path:
        if len(names) == len(dirnames):
            common = get_common_path(dirnames)
            if common is None:
                offset = None
            else:
                offset = len(common) + 1
                if offset <= 3:
                    # Common path is not a path but a drive letter...
                    offset = None

        for index, text in enumerate(names):
            tab_action = create_action(
                self,
                text[offset:],
                icon=self.tabIcon(index),
                toggled=lambda state, index=index: self.setCurrentIndex(index),
                tip=self.tabToolTip(index))
            tab_action.setChecked(index == self.currentIndex())
            self.browse_tabs_menu.addAction(tab_action)

    def set_corner_widgets(self, corner_widgets):
        """
        Set tabs corner widgets
        corner_widgets: dictionary of (corner, widgets)
        corner: Qt.TopLeftCorner or Qt.TopRightCorner
        widgets: list of widgets (may contains integers to add spacings)
        """
        assert isinstance(corner_widgets, dict)
        assert all(key in (Qt.TopLeftCorner, Qt.TopRightCorner)
                   for key in corner_widgets)
        self.corner_widgets.update(corner_widgets)
        for corner, widgets in list(self.corner_widgets.items()):
            cwidget = QWidget()
            cwidget.hide()
            prev_widget = self.cornerWidget(corner)
            if prev_widget:
                prev_widget.close()
            self.setCornerWidget(cwidget, corner)
            clayout = QHBoxLayout()
            clayout.setContentsMargins(0, 0, 0, 0)
            for widget in widgets:
                if isinstance(widget, int):
                    clayout.addSpacing(widget)
                else:
                    clayout.addWidget(widget)
            cwidget.setLayout(clayout)
            cwidget.show()

    def add_corner_widgets(self, widgets, corner=Qt.TopRightCorner):
        self.set_corner_widgets(
            {corner: self.corner_widgets.get(corner, []) + widgets})

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.setCurrentIndex(self.tabBar().tabAt(event.pos()))
        if self.menu:
            self.menu.popup(event.globalPos())

    def mousePressEvent(self, event):
        """Override Qt method"""
        if event.button() == Qt.MidButton:
            index = self.tabBar().tabAt(event.pos())
            if index >= 0:
                self.sig_close_tab.emit(index)
                event.accept()
                return
        QTabWidget.mousePressEvent(self, event)

    def keyPressEvent(self, event):
        """Override Qt method"""
        ctrl = event.modifiers() & Qt.ControlModifier
        key = event.key()
        handled = False
        if ctrl and self.count() > 0:
            index = self.currentIndex()
            if key == Qt.Key_PageUp:
                if index > 0:
                    self.setCurrentIndex(index - 1)
                else:
                    self.setCurrentIndex(self.count() - 1)
                handled = True
            elif key == Qt.Key_PageDown:
                if index < self.count() - 1:
                    self.setCurrentIndex(index + 1)
                else:
                    self.setCurrentIndex(0)
                handled = True
        if not handled:
            QTabWidget.keyPressEvent(self, event)

    def set_close_function(self, func):
        """Setting Tabs close function
        None -> tabs are not closable"""
        state = func is not None
        if state:
            self.sig_close_tab.connect(func)
        try:
            # Assuming Qt >= 4.5
            QTabWidget.setTabsClosable(self, state)
            self.tabCloseRequested.connect(func)
        except AttributeError:
            # Workaround for Qt < 4.5
            close_button = create_toolbutton(self,
                                             triggered=func,
                                             icon=ima.icon('fileclose'),
                                             tip=_("Close current tab"))
            self.setCornerWidget(close_button if state else None)