示例#1
0
    def __init__(self, parent):
        super(DockTitleBar, self).__init__(parent)

        icon_size = QApplication.style().standardIcon(
            QStyle.SP_TitleBarNormalButton).actualSize(QSize(100, 100))
        button_size = icon_size + QSize(8, 8)

        left_spacer = QWidget(self)
        left_spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        if is_dark_interface():
            left_spacer.setStyleSheet("background-color: #32414B")

        drag_button = DragButton(self, button_size)

        right_spacer = QWidget(self)
        right_spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        if is_dark_interface():
            right_spacer.setStyleSheet("background-color: #32414B")

        close_button = CloseButton(self, button_size)
        close_button.clicked.connect(parent.sig_plugin_closed.emit)

        hlayout = QHBoxLayout(self)
        hlayout.setSpacing(0)
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.addWidget(left_spacer)
        hlayout.addWidget(drag_button)
        hlayout.addWidget(right_spacer)
        hlayout.addWidget(close_button)

        # To signal that dock widgets can be dragged from here
        self.setCursor(Qt.SizeAllCursor)
示例#2
0
def get_icon_by_extension(fname, scale_factor):
    """Return the icon depending on the file extension"""
    application_icons = {}
    application_icons.update(BIN_FILES)
    application_icons.update(DOCUMENT_FILES)
    if osp.isdir(fname):
        return icon('DirOpenIcon', scale_factor)
    else:
        basename = osp.basename(fname)
        __, extension = osp.splitext(basename.lower())
        mime_type, __ = mime.guess_type(basename)
        icon_by_extension = icon('FileIcon', scale_factor)

        if extension in OFFICE_FILES:
            icon_by_extension = icon(OFFICE_FILES[extension], scale_factor)

        if extension in LANGUAGE_ICONS:
            icon_by_extension = icon(LANGUAGE_ICONS[extension], scale_factor)
        else:
            if extension == '.ipynb':
                if is_dark_interface():
                    icon_by_extension = QIcon(
                        get_image_path('notebook_dark.svg'))
                else:
                    icon_by_extension = QIcon(
                        get_image_path('notebook_light.svg'))
            elif extension == '.tex':
                if is_dark_interface():
                    icon_by_extension = QIcon(
                        get_image_path('file_type_tex.svg'))
                else:
                    icon_by_extension = QIcon(
                        get_image_path('file_type_light_tex.svg'))
            elif mime_type is not None:
                try:
                    # Fix for issue 5080. Even though
                    # mimetypes.guess_type documentation states that
                    # the return value will be None or a tuple of
                    # the form type/subtype, in the Windows registry,
                    # .sql has a mimetype of text\plain
                    # instead of text/plain therefore mimetypes is
                    # returning it incorrectly.
                    file_type, bin_name = mime_type.split('/')
                except ValueError:
                    file_type = 'text'
                if file_type == 'text':
                    icon_by_extension = icon('TextFileIcon', scale_factor)
                elif file_type == 'audio':
                    icon_by_extension = icon('AudioFileIcon', scale_factor)
                elif file_type == 'video':
                    icon_by_extension = icon('VideoFileIcon', scale_factor)
                elif file_type == 'image':
                    icon_by_extension = icon('ImageFileIcon', scale_factor)
                elif file_type == 'application':
                    if bin_name in application_icons:
                        icon_by_extension = icon(application_icons[bin_name],
                                                 scale_factor)
    return icon_by_extension
示例#3
0
def get_icon(name, default=None, resample=False, adjust_for_interface=False):
    """Return image inside a QIcon object.

    default: default image name or icon
    resample: if True, manually resample icon pixmaps for usual sizes
    (16, 24, 32, 48, 96, 128, 256). This is recommended for QMainWindow icons
    created from SVG images on non-Windows platforms due to a Qt bug.
    See spyder-ide/spyder#1314.
    """

    if adjust_for_interface:
        name = (name + '_dark.svg' if is_dark_interface() else name +
                '_light.svg')

    icon_path = get_image_path(name, default=None)
    if icon_path is not None:
        icon = QIcon(icon_path)
    elif isinstance(default, QIcon):
        icon = default
    elif default is None:
        try:
            icon = get_std_icon(name[:-4])
        except AttributeError:
            icon = QIcon(get_image_path(name, default))
    else:
        icon = QIcon(get_image_path(name, default))
    if resample:
        icon0 = QIcon()
        for size in (16, 24, 32, 48, 96, 128, 256, 512):
            icon0.addPixmap(icon.pixmap(size, size))
        return icon0
    else:
        return icon
示例#4
0
    def __init__(self, widget):
        super().__init__()
        self.widget = widget

        # Setting interface theme
        if is_dark_interface():
            self.setStyleSheet(qdarkstyle.load_stylesheet())
示例#5
0
    def register_plugin(self):
        """Register plugin in Spyder's main window."""
        super(UnitTestPlugin, self).register_plugin()

        # Get information from Spyder proper into plugin
        self.update_pythonpath()
        self.update_default_wdir()
        self.unittestwidget.use_dark_interface(is_dark_interface())

        # Connect to relevant signals
        self.main.sig_pythonpath_changed.connect(self.update_pythonpath)
        self.main.workingdirectory.set_explorer_cwd.connect(
            self.update_default_wdir)
        self.main.projects.sig_project_created.connect(
            self.handle_project_change)
        self.main.projects.sig_project_loaded.connect(
            self.handle_project_change)
        self.main.projects.sig_project_closed.connect(
            self.handle_project_change)
        self.unittestwidget.sig_newconfig.connect(self.save_config)
        self.unittestwidget.sig_edit_goto.connect(self.goto_in_editor)

        # Create action and add it to Spyder's menu
        unittesting_act = create_action(
            self,
            _("Run unit tests"),
            icon=ima.icon('profiler'),
            shortcut="Shift+Alt+F11",
            triggered=self.maybe_configure_and_start)
        self.main.run_menu_actions += [unittesting_act]
        self.main.editor.pythonfile_dependent_actions += [unittesting_act]

        # Save all files before running tests
        self.unittestwidget.pre_test_hook = self.main.editor.save_all
示例#6
0
    def __init__(self, plugin):
        QMainWindow.__init__(self)
        self.plugin = plugin

        # Setting interface theme
        if is_dark_interface():
            self.setStyleSheet(qdarkstyle.load_stylesheet_from_environment())
示例#7
0
    def __init__(self, parent):
        super(KiteIntegrationInfo, self).__init__(parent)
        # Images
        images_layout = QHBoxLayout()
        if is_dark_interface():
            icon_filename = 'spyder_kite.svg'
        else:
            icon_filename = 'spyder_kite_dark.svg'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        screen = QApplication.primaryScreen()
        device_image_ratio = screen.devicePixelRatio()
        if device_image_ratio > 1:
            image.setDevicePixelRatio(device_image_ratio)
        else:
            image_height = image.height() * 0.5
            image_width = image.width() * 0.5
            image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                                 Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()

        # Label
        integration_label = QLabel(
            _("Now Spyder can use <a href=\"{kite_url}\">Kite</a> to "
              "provide better and more accurate code completions in its "
              "editor <br>for the most important packages in the Python "
              "scientific ecosystem, such as Numpy, <br>Matplotlib and "
              "Pandas.<br><br>Would you like to install it or learn more "
              "about it?<br><br><i>Note:</i> Kite is free to use "
              "but is not an open source program.").format(
                  kite_url=KITE_SPYDER_URL))
        integration_label.setOpenExternalLinks(True)

        # Buttons
        buttons_layout = QHBoxLayout()
        learn_more_button = QPushButton(_('Learn more'))
        learn_more_button.setAutoDefault(False)
        install_button = QPushButton(_('Install Kite'))
        install_button.setAutoDefault(False)
        dismiss_button = QPushButton(_('Dismiss'))
        dismiss_button.setAutoDefault(False)
        buttons_layout.addStretch()
        buttons_layout.addWidget(install_button)
        buttons_layout.addWidget(learn_more_button)
        buttons_layout.addWidget(dismiss_button)

        general_layout = QVBoxLayout()
        general_layout.addLayout(images_layout)
        general_layout.addWidget(integration_label)
        general_layout.addLayout(buttons_layout)
        self.setLayout(general_layout)

        learn_more_button.clicked.connect(self.sig_learn_more_button_clicked)
        install_button.clicked.connect(self.sig_install_button_clicked)
        dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)
示例#8
0
        def page(self, title, contents):
            """Format an HTML page."""
            rich_text_font = get_font(option="rich_font").family()
            plain_text_font = get_font(option="font").family()

            if is_dark_interface():
                css_path = "static/css/dark_pydoc.css"
            else:
                css_path = "static/css/light_pydoc.css"

            css_link = ('<link rel="stylesheet" type="text/css" href="%s">' %
                        css_path)

            code_style = ('<style>code {font-family: "%s"}</style>' %
                          plain_text_font)

            html_page = '''\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Pydoc: %s</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
%s%s</head><body style="clear:both;font-family:'%s'">
%s<div style="clear:both;padding-top:.7em;">%s</div>
</body></html>''' % (title, css_link, code_style, rich_text_font,
                     html_navbar(), contents)

            return html_page
示例#9
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

        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)
示例#10
0
文件: client.py 项目: impact27/spyder
    def get_toolbar_buttons(self):
        """Return toolbar buttons list."""
        buttons = []

        # Code to add the stop button
        if self.stop_button is None:
            self.stop_button = create_toolbutton(
                                   self,
                                   text=_("Stop"),
                                   icon=self.stop_icon,
                                   tip=_("Stop the current command"))
            self.disable_stop_button()
            # set click event handler
            self.stop_button.clicked.connect(self.stop_button_click_handler)
            if is_dark_interface():
                self.stop_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.stop_button is not None:
            buttons.append(self.stop_button)

        # Reset namespace button
        if self.reset_button is None:
            self.reset_button = create_toolbutton(
                                    self,
                                    text=_("Remove"),
                                    icon=ima.icon('editdelete'),
                                    tip=_("Remove all variables"),
                                    triggered=self.reset_namespace)
            if is_dark_interface():
                self.reset_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.reset_button is not None:
            buttons.append(self.reset_button)

        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(self,
                        text=_('Options'), icon=ima.icon('tooloptions'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)

        return buttons
示例#11
0
    def get_toolbar_buttons(self):
        """Return toolbar buttons list."""
        buttons = []

        # Code to add the stop button
        if self.stop_button is None:
            self.stop_button = create_toolbutton(
                self,
                text=_("Stop"),
                icon=self.stop_icon,
                tip=_("Stop the current command"))
            self.disable_stop_button()
            # set click event handler
            self.stop_button.clicked.connect(self.stop_button_click_handler)
            if is_dark_interface():
                self.stop_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.stop_button is not None:
            buttons.append(self.stop_button)

        # Reset namespace button
        if self.reset_button is None:
            self.reset_button = create_toolbutton(
                self,
                text=_("Remove"),
                icon=ima.icon('editdelete'),
                tip=_("Remove all variables"),
                triggered=self.reset_namespace)
            if is_dark_interface():
                self.reset_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.reset_button is not None:
            buttons.append(self.reset_button)

        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(
                    self, text=_('Options'), icon=ima.icon('tooloptions'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)

        return buttons
示例#12
0
    def set_layout(self):
        """Set layout for default widgets."""
        # Icon
        if self.show_icon:
            self._icon = self.get_icon()
            self._pixmap = None
            self._icon_size = QSize(16, 16)  # Should this be adjustable?
            self.label_icon = QLabel()
            self.set_icon()

        # Label
        if self.show_label:
            self.label_value = QLabel()
            self.set_value('')

            # See spyder-ide/spyder#9044.
            self.text_font = QFont(QFont().defaultFamily(),
                                   weight=QFont.Normal)
            self.label_value.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            self.label_value.setFont(self.text_font)

        # Custom widget
        if self.CUSTOM_WIDGET_CLASS:
            if not issubclass(self.CUSTOM_WIDGET_CLASS, QWidget):
                raise SpyderAPIError(
                    'Any custom status widget must subclass QWidget!')
            self.custom_widget = self.CUSTOM_WIDGET_CLASS(self._parent)

        # Spinner
        if self.show_spinner:
            self.spinner = create_waitspinner(size=14, parent=self)
            self.spinner.hide()

        # Layout setup
        layout = QHBoxLayout(self)
        layout.setSpacing(0)  # Reduce space between icon and label
        if self.show_icon:
            layout.addWidget(self.label_icon)
        if self.show_label:
            layout.addWidget(self.label_value)
        if self.custom_widget:
            layout.addWidget(self.custom_widget)
        if self.show_spinner:
            layout.addWidget(self.spinner)

        if is_dark_interface():
            layout.addSpacing(0)
        else:
            layout.addSpacing(10)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setAlignment(Qt.AlignVCenter)

        # Setup
        self.update_tooltip()
示例#13
0
    def add_image_path(self, path):
        """Add path to the image path list."""
        if not osp.isdir(path):
            return

        for dirpath, __, _filenames in os.walk(path):
            if is_dark_interface() and osp.basename(dirpath) == 'light':
                continue
            elif not is_dark_interface() and osp.basename(dirpath) == 'dark':
                continue
            for filename in _filenames:
                if filename.startswith('.'):
                    continue
                name, __ = osp.splitext(osp.basename(filename))
                complete_path = osp.join(dirpath, filename)
                if name in self.IMG_PATH:
                    warnings.warn(
                        f'The icon located in {complete_path} is overriding '
                        f'the existing {name}')
                self.IMG_PATH[name] = complete_path
示例#14
0
    def _complete_options(self):
        """Find available completion options."""
        text = to_text_string(self.currentText())
        opts = glob.glob(text + "*")
        opts = sorted([opt for opt in opts if osp.isdir(opt)])

        completer = QCompleter(opts, self)
        if is_dark_interface():
            dark_qss = qdarkstyle.load_stylesheet_from_environment()
            completer.popup().setStyleSheet(dark_qss)
        self.setCompleter(completer)

        return opts
示例#15
0
def get_icon_by_extension(fname, scale_factor):
    """Return the icon depending on the file extension"""
    application_icons = {}
    application_icons.update(BIN_FILES)
    application_icons.update(DOCUMENT_FILES)
    if osp.isdir(fname):
        return icon('DirOpenIcon', scale_factor)
    else:
        basename = osp.basename(fname)
        __, extension = osp.splitext(basename.lower())
        mime_type, __ = mime.guess_type(basename)
        icon_by_extension = icon('FileIcon', scale_factor)

        if extension in OFFICE_FILES:
            icon_by_extension = icon(OFFICE_FILES[extension], scale_factor)

        if extension in LANGUAGE_ICONS:
            icon_by_extension = icon(LANGUAGE_ICONS[extension], scale_factor)
        else:
            if extension == '.ipynb':
                if is_dark_interface():
                    icon_by_extension = QIcon(
                        get_image_path('notebook_dark.svg'))
                else:
                    icon_by_extension = QIcon(
                        get_image_path('notebook_light.svg'))
            elif mime_type is not None:
                try:
                    # Fix for issue 5080. Even though
                    # mimetypes.guess_type documentation states that
                    # the return value will be None or a tuple of
                    # the form type/subtype, in the Windows registry,
                    # .sql has a mimetype of text\plain
                    # instead of text/plain therefore mimetypes is
                    # returning it incorrectly.
                    file_type, bin_name = mime_type.split('/')
                except ValueError:
                    file_type = 'text'
                if file_type == 'text':
                    icon_by_extension = icon('TextFileIcon', scale_factor)
                elif file_type == 'audio':
                    icon_by_extension = icon('AudioFileIcon', scale_factor)
                elif file_type == 'video':
                    icon_by_extension = icon('VideoFileIcon', scale_factor)
                elif file_type == 'image':
                    icon_by_extension = icon('ImageFileIcon', scale_factor)
                elif file_type == 'application':
                    if bin_name in application_icons:
                        icon_by_extension = icon(
                            application_icons[bin_name], scale_factor)
    return icon_by_extension
示例#16
0
    def __init__(self, parent):
        super().__init__(parent)
        newcb = self.create_checkbox

        introspection_group = QGroupBox(_("Basic features"))
        goto_definition_box = newcb(
            _("Enable Go to definition"),
            'jedi_definition',
            tip=_("If enabled, left-clicking on an object name while \n"
                  "pressing the {} key will go to that object's definition\n"
                  "(if resolved).").format(self.CTRL))
        follow_imports_box = newcb(_("Follow imports when going to a "
                                     "definition"),
                                   'jedi_definition/follow_imports')
        show_signature_box = newcb(_("Show calltips"), 'jedi_signature_help')
        enable_hover_hints_box = newcb(
            _("Enable hover hints"),
            'enable_hover_hints',
            tip=_("If enabled, hovering the mouse pointer over an object\n"
                  "name will display that object's signature and/or\n"
                  "docstring (if present)."))
        introspection_layout = QVBoxLayout()
        introspection_layout.addWidget(goto_definition_box)
        introspection_layout.addWidget(follow_imports_box)
        introspection_layout.addWidget(show_signature_box)
        introspection_layout.addWidget(enable_hover_hints_box)
        introspection_group.setLayout(introspection_layout)

        goto_definition_box.toggled.connect(follow_imports_box.setEnabled)

        # Advanced group
        advanced_group = QGroupBox(_("Advanced"))
        modules_textedit = self.create_textedit(
            _("Preload the following modules to make completion faster "
              "and more accurate:"),
            'preload_modules'
        )
        if is_dark_interface():
            modules_textedit.textbox.setStyleSheet(
                "border: 1px solid #32414B;"
            )

        advanced_layout = QVBoxLayout()
        advanced_layout.addWidget(modules_textedit)
        advanced_group.setLayout(advanced_layout)

        layout = QVBoxLayout()
        layout.addWidget(introspection_group)
        layout.addWidget(advanced_group)
        layout.addStretch(1)
        self.setLayout(layout)
示例#17
0
 def show_fig_outline_in_viewer(self, state):
     """Draw a frame around the figure viewer if state is True."""
     if state is True:
         if is_dark_interface():
             self.figviewer.figcanvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" %
                 qdarkstyle.palette.DarkPalette.COLOR_BACKGROUND_NORMAL)
         else:
             self.figviewer.figcanvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" %
                 self.figviewer.figcanvas.palette().shadow().color().name())
     else:
         self.figviewer.figcanvas.setStyleSheet(
             "FigureCanvas{border: 0px;}")
示例#18
0
    def __init__(self, parent, button_size):
        super(QToolButton, self).__init__(parent)
        self.parent = parent

        # Style
        self.setMaximumSize(button_size)
        self.setAutoRaise(True)
        self.setIcon(ima.icon('drag-horizontal'))
        if is_dark_interface():
            self.setStyleSheet("QToolButton {"
                               "border-radius: 0px;"
                               "border: 0px;"
                               "background-color: #32414B;}")
        else:
            self.setStyleSheet("QToolButton {border: 0px;}")
示例#19
0
 def _calculate_figure_canvas_width(self):
     """
     Calculate the width the thumbnails need to have to fit the scrollarea.
     """
     extra_padding = 10 if sys.platform == 'darwin' else 0
     figure_canvas_width = (
         self.scrollarea.width() - 2 * self.lineWidth() -
         self.scrollarea.viewportMargins().left() -
         self.scrollarea.viewportMargins().right() - extra_padding -
         self.scrollarea.verticalScrollBar().sizeHint().width())
     if is_dark_interface():
         # This is required to take into account some hard-coded padding
         # and margin in qdarkstyle.
         figure_canvas_width = figure_canvas_width - 6
     return figure_canvas_width
示例#20
0
def get_kite_icon(scale_factor=0.85):
    """Return the Kite logo taking into account the theme of the interface."""
    icon_path = 'kite_light.svg'
    if is_dark_interface():
        icon_path = 'kite_dark.svg'
    pixmap = QPixmap(get_image_path(icon_path))
    if scale_factor is not None:
        pixmap_height = 16 * scale_factor
        pixmap_width = 16 * scale_factor
        pixmap = pixmap.scaled(pixmap_width,
                               pixmap_height,
                               aspectRatioMode=Qt.KeepAspectRatio,
                               transformMode=Qt.SmoothTransformation)
    kite_icon = QIcon(pixmap)
    return kite_icon
示例#21
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
示例#22
0
文件: mixins.py 项目: impact27/spyder
 def _update_stylesheet(self, widget):
     """Update the background stylesheet to make it lighter."""
     if is_dark_interface():
         css = qdarkstyle.load_stylesheet_from_environment()
         widget.setStyleSheet(css)
         palette = widget.palette()
         background = palette.color(palette.Window).lighter(150).name()
         border = palette.color(palette.Window).lighter(200).name()
         name = widget.__class__.__name__
         widget.setObjectName(name)
         extra_css = '''
             {0}#{0} {{
                 background-color:{1};
                 border: 1px solid {2};
             }}'''.format(name, background, border)
         widget.setStyleSheet(css + extra_css)
示例#23
0
文件: mixins.py 项目: rong002/spyder
 def _update_stylesheet(self, widget):
     """Update the background stylesheet to make it lighter."""
     if is_dark_interface():
         css = qdarkstyle.load_stylesheet_from_environment()
         widget.setStyleSheet(css)
         palette = widget.palette()
         background = palette.color(palette.Window).lighter(150).name()
         border = palette.color(palette.Window).lighter(200).name()
         name = widget.__class__.__name__
         widget.setObjectName(name)
         extra_css = '''
             {0}#{0} {{
                 background-color:{1};
                 border: 1px solid {2};
             }}'''.format(name, background, border)
         widget.setStyleSheet(css + extra_css)
示例#24
0
    def __init__(self, parent, button_size):
        super(QToolButton, self).__init__(parent)
        self.parent = parent

        # Style
        self.setMaximumSize(button_size)
        self.setAutoRaise(True)
        self.setIcon(ima.icon('drag-horizontal'))
        if is_dark_interface():
            self.setStyleSheet(("QToolButton {{"
                                "border-radius: 0px;"
                                "border: 0px;"
                                "background-color: {color};}}").format(
                                    color=QStylePalette.COLOR_BACKGROUND_3))
        else:
            self.setStyleSheet("QToolButton {border: 0px;}")
示例#25
0
 def _calculate_figure_canvas_width(self, thumbnail):
     """
     Calculate the witdh the thumbnail's figure canvas need to have for the
     thumbnail to fit the scrollarea.
     """
     extra_padding = 10 if sys.platform == 'darwin' else 0
     figure_canvas_width = (self.scrollarea.width() - 2 * self.lineWidth() -
                            self.scrollarea.viewportMargins().left() -
                            self.scrollarea.viewportMargins().right() -
                            thumbnail.savefig_btn.width() -
                            thumbnail.layout().spacing() - extra_padding)
     if is_dark_interface():
         # This is required to take into account some hard-coded padding
         # and margin in qdarkstyle.
         figure_canvas_width = figure_canvas_width - 6
     return figure_canvas_width
示例#26
0
 def highlight_canvas(self, highlight):
     """
     Set a colored frame around the FigureCanvas if highlight is True.
     """
     colorname = self.canvas.palette().highlight().color().name()
     if highlight:
         # Highlighted figure is not clear in dark mode with blue color.
         # See spyder-ide/spyder#10255.
         if is_dark_interface():
             self.canvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" % "#148CD2")
         else:
             self.canvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" % colorname)
     else:
         self.canvas.setStyleSheet("FigureCanvas{}")
示例#27
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
示例#28
0
        def page(self, title, contents):
            """Format an HTML page."""
            if is_dark_interface():
                css_path = "static/css/dark_pydoc.css"
            else:
                css_path = "static/css/light_pydoc.css"

            css_link = ('<link rel="stylesheet" type="text/css" href="%s">' %
                        css_path)
            html_page = '''\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Pydoc: %s</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
%s</head><body>%s<div style="clear:both;padding-top:.7em;">%s</div>
</body></html>''' % (title, css_link, html_navbar(), contents)

            return html_page
示例#29
0
 def highlight_canvas(self, highlight):
     """
     Set a colored frame around the FigureCanvas if highlight is True.
     """
     if highlight:
         # Highlighted figure is not clear in dark mode with blue color.
         # See spyder-ide/spyder#10255.
         if is_dark_interface():
             self.canvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" %
                 qdarkstyle.palette.DarkPalette.COLOR_SELECTION_LIGHT)
         else:
             self.canvas.setStyleSheet(
                 "FigureCanvas{border: 2px solid %s;}" %
                 self.canvas.palette().highlight().color().name())
     else:
         self.canvas.setStyleSheet("FigureCanvas{}")
示例#30
0
    def __init__(self, parent, button_size):
        super(QToolButton, self).__init__(parent)

        # Style
        self.setMaximumSize(button_size)
        self.setAutoRaise(True)
        self.setCursor(Qt.ArrowCursor)
        if is_dark_interface():
            self.setStyleSheet("QToolButton {"
                               "border-radius: 0px;"
                               "border: 0px;"
                               "image: url(:/qss_icons/rc/close.png);"
                               "background-color: #32414B;}"
                               "QToolButton:hover {"
                               "image: url(:/qss_icons/rc/close-hover.png);}")
        else:
            self.setIcon(QApplication.style().standardIcon(
                QStyle.SP_DockWidgetCloseButton))
示例#31
0
文件: status.py 项目: ubuntu11/spyder
    def __init__(self, parent=None, spinner=False):
        """Status bar widget base."""
        super().__init__(parent)

        # Variables
        self.value = None
        self._parent = parent

        # Widget
        self._icon = self.get_icon()
        self._pixmap = None
        self._icon_size = QSize(16, 16)  # Should this be adjustable?
        self.label_icon = QLabel()
        self.label_value = QLabel()
        self.spinner = None
        if spinner:
            self.spinner = create_waitspinner(size=14, parent=self)

        # Layout setup
        layout = QHBoxLayout(self)
        layout.setSpacing(0)  # Reduce space between icon and label
        layout.addWidget(self.label_icon)
        layout.addWidget(self.label_value)
        if spinner:
            layout.addWidget(self.spinner)
            self.spinner.hide()
        if is_dark_interface():
            layout.addSpacing(0)
        else:
            layout.addSpacing(10)
        layout.setContentsMargins(0, 0, 0, 0)

        # Widget setup
        self.set_icon()

        # See spyder-ide/spyder#9044.
        self.text_font = QFont(QFont().defaultFamily(), weight=QFont.Normal)
        self.label_value.setAlignment(Qt.AlignRight)
        self.label_value.setFont(self.text_font)

        # Setup
        self.set_value('')
        self.update_tooltip()
示例#32
0
    def _setup_menu(self,
                    show_callable_attributes=False,
                    show_special_attributes=False):
        """Sets up the main menu."""
        self.tools_layout = QHBoxLayout()

        callable_attributes = create_toolbutton(
            self,
            text=_("Show callable attributes"),
            icon=ima.icon("class"),
            toggled=self._toggle_show_callable_attributes_action)
        callable_attributes.setCheckable(True)
        callable_attributes.setChecked(show_callable_attributes)
        self.tools_layout.addWidget(callable_attributes)

        special_attributes = create_toolbutton(
            self,
            text=_("Show __special__ attributes"),
            icon=ima.icon("private2"),
            toggled=self._toggle_show_special_attributes_action)
        special_attributes.setCheckable(True)
        special_attributes.setChecked(show_special_attributes)
        self.tools_layout.addWidget(special_attributes)

        self.tools_layout.addStretch()

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

        self.show_cols_submenu = QMenu(self)
        self.options_button.setMenu(self.show_cols_submenu)
        # 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.tools_layout.addWidget(self.options_button)
示例#33
0
def create_waitspinner(size=32, n=11, parent=None):
    """
    Create a wait spinner with the specified size built with n circling dots.
    """
    dot_padding = 1

    # To calculate the size of the dots, we need to solve the following
    # system of two equations in two variables.
    # (1) middle_circumference = pi * (size - dot_size)
    # (2) middle_circumference = n * (dot_size + dot_padding)
    dot_size = (pi * size - n * dot_padding) / (n + pi)
    inner_radius = (size - 2 * dot_size) / 2

    spinner = QWaitingSpinner(parent, centerOnParent=False)
    spinner.setTrailSizeDecreasing(True)
    spinner.setNumberOfLines(n)
    spinner.setLineLength(dot_size)
    spinner.setLineWidth(dot_size)
    spinner.setInnerRadius(inner_radius)
    spinner.setColor(Qt.white if is_dark_interface() else Qt.black)

    return spinner
示例#34
0
    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.server_manager = ServerManager(dark_theme)
        self.tabwidget = NotebookTabWidget(
            self, self.server_manager, 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)
示例#35
0
    def __init__(self, parent=None, inline=True, offset=0, force_float=False):
        QDialog.__init__(self, parent=parent)
        self._parent = parent
        self._text = None
        self._valid = None
        self._offset = offset

        # TODO: add this as an option in the General Preferences?
        self._force_float = force_float

        self._help_inline = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Type an array in Matlab    : <code>[1 2;3 4]</code><br>
           or Spyder simplified syntax : <code>1 2;3 4</code>
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two spaces or two tabs to generate a ';'.
           """)

        self._help_table = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Enter an array in the table. <br>
           Use Tab to move between cells.
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two tabs at the end of a row to move to the next row.
           """)

        # Widgets
        self._button_warning = QToolButton()
        self._button_help = HelperToolButton()
        self._button_help.setIcon(ima.icon('MessageBoxInformation'))

        if is_dark_interface():
            style = """
                QToolButton {
                  border: 1px solid grey;
                  padding:0px;
                  border-radius: 2px;
                  background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                      stop: 0 #32414B, stop: 1 #16252B);
                }
                """
        else:
            style = """
                QToolButton {
                  border: 1px solid grey;
                  padding:0px;
                  border-radius: 2px;
                  background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                      stop: 0 #f6f7fa, stop: 1 #dadbde);
                }
                """
        self._button_help.setStyleSheet(style)

        if inline:
            self._button_help.setToolTip(self._help_inline)
            self._text = NumpyArrayInline(self)
            self._widget = self._text
        else:
            self._button_help.setToolTip(self._help_table)
            self._table = NumpyArrayTable(self)
            self._widget = self._table

        style = """
            QDialog {
              margin:0px;
              border: 1px solid grey;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self._button_warning.setStyleSheet(style)

        # widget setup
        self.setWindowFlags(Qt.Window | Qt.Dialog | Qt.FramelessWindowHint)
        self.setModal(True)
        self.setWindowOpacity(0.90)
        self._widget.setMinimumWidth(200)

        # layout
        self._layout = QHBoxLayout()
        self._layout.addWidget(self._widget)
        self._layout.addWidget(self._button_warning, 1, Qt.AlignTop)
        self._layout.addWidget(self._button_help, 1, Qt.AlignTop)
        self.setLayout(self._layout)

        self._widget.setFocus()
示例#36
0
文件: plugin.py 项目: burrbull/spyder
# Third party imports
from qtpy.QtCore import Qt, Slot
from qtpy.QtWidgets import QGroupBox, QInputDialog, QLabel, QVBoxLayout

# Local imports
from spyder.config.base import _
from spyder.config.gui import is_dark_interface
from spyder.api.plugins import SpyderPluginWidget
from spyder.api.preferences import PluginConfigPage
from spyder.utils import icon_manager as ima
from spyder.utils.programs import is_module_installed
from spyder.utils.qthelpers import create_action, MENU_SEPARATOR
from .widgets.pylintgui import PylintWidget


if is_dark_interface():
    MAIN_TEXT_COLOR = 'white'
    MAIN_PREVRATE_COLOR = 'white'
else:
    MAIN_TEXT_COLOR = '#444444'
    MAIN_PREVRATE_COLOR = '#666666'


class PylintConfigPage(PluginConfigPage):
    def setup_page(self):
        settings_group = QGroupBox(_("Settings"))
        save_box = self.create_checkbox(_("Save file before analyzing it"),
                                        'save_before', default=True)
        
        hist_group = QGroupBox(_("History"))
        hist_label1 = QLabel(_("The following option will be applied at next "
示例#37
0
 def update_qt_style_combobox(self):
     """Enable/disable the Qt style combobox."""
     if is_dark_interface():
         self.style_combobox.setEnabled(False)
     else:
         self.style_combobox.setEnabled(True)
示例#38
0
    def setup_page(self):
        newcb = self.create_checkbox

        # --- Introspection ---
        # Basic features group
        basic_features_group = QGroupBox(_("Basic features"))
        completion_box = newcb(_("Enable code completion"), 'code_completion')
        goto_definition_box = newcb(
            _("Enable Go to definition"),
            'jedi_definition',
            tip=_("If this option is enabled, left-clicking on\n"
                  "an object name while pressing the {} key will go to\n"
                  "that object's definition (if resolved).".format(self.CTRL)))
        follow_imports_box = newcb(_("Follow imports when going to a "
                                     "definition"),
                                   'jedi_definition/follow_imports')
        show_signature_box = newcb(_("Show calltips"), 'jedi_signature_help')

        basic_features_layout = QVBoxLayout()
        basic_features_layout.addWidget(completion_box)
        basic_features_layout.addWidget(goto_definition_box)
        basic_features_layout.addWidget(follow_imports_box)
        basic_features_layout.addWidget(show_signature_box)
        basic_features_group.setLayout(basic_features_layout)

        # Advanced group
        advanced_group = QGroupBox(_("Advanced"))
        modules_textedit = self.create_textedit(
            _("Preload the following modules to make completion faster "
              "and more accurate:"),
            'preload_modules'
        )
        if is_dark_interface():
            modules_textedit.textbox.setStyleSheet(
                "border: 1px solid #32414B;"
            )

        advanced_layout = QVBoxLayout()
        advanced_layout.addWidget(modules_textedit)
        advanced_group.setLayout(advanced_layout)

        # --- Linting ---
        # Linting options
        linting_label = QLabel(_("Spyder can optionally highlight syntax "
                                 "errors and possible problems with your "
                                 "code in the editor."))
        linting_label.setOpenExternalLinks(True)
        linting_label.setWordWrap(True)
        linting_check = self.create_checkbox(
            _("Enable basic linting"),
            'pyflakes')

        linting_complexity_box = self.create_checkbox(
            _("Enable complexity linting with "
              "the Mccabe package"), 'mccabe')

        # Linting layout
        linting_layout = QVBoxLayout()
        linting_layout.addWidget(linting_label)
        linting_layout.addWidget(linting_check)
        linting_layout.addWidget(linting_complexity_box)
        linting_widget = QWidget()
        linting_widget.setLayout(linting_layout)

        # --- Code style tab ---
        # Code style label
        pep_url = (
            '<a href="https://www.python.org/dev/peps/pep-0008">PEP 8</a>')
        code_style_codes_url = _(
            "<a href='http://pycodestyle.pycqa.org/en/stable"
            "/intro.html#error-codes'>pycodestyle error codes</a>")
        code_style_label = QLabel(
            _("Spyder can use pycodestyle to analyze your code for "
              "conformance to the {} convention. You can also "
              "manually show or hide specific warnings by their "
              "{}.").format(pep_url, code_style_codes_url))
        code_style_label.setOpenExternalLinks(True)
        code_style_label.setWordWrap(True)

        # Code style checkbox
        code_style_check = self.create_checkbox(
            _("Enable code style linting"),
            'pycodestyle')

        # Code style options
        self.code_style_filenames_match = self.create_lineedit(
            _("Only check filenames matching these patterns:"),
            'pycodestyle/filename', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Check Python files: *.py"))
        self.code_style_exclude = self.create_lineedit(
            _("Exclude files or directories matching these patterns:"),
            'pycodestyle/exclude', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Exclude all test files: (?!test_).*\\.py"))
        code_style_select = self.create_lineedit(
            _("Show the following errors or warnings:").format(
                code_style_codes_url),
            'pycodestyle/select', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Example codes: E113, W391"))
        code_style_ignore = self.create_lineedit(
            _("Ignore the following errors or warnings:"),
            'pycodestyle/ignore', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Example codes: E201, E303"))
        code_style_max_line_length = self.create_spinbox(
            _("Maximum allowed line length:"), None,
            'pycodestyle/max_line_length', min_=10, max_=500, step=1,
            tip=_("Default is 79"))

        # Code style layout
        code_style_g_layout = QGridLayout()
        code_style_g_layout.addWidget(
            self.code_style_filenames_match.label, 1, 0)
        code_style_g_layout.addWidget(
            self.code_style_filenames_match.textbox, 1, 1)
        code_style_g_layout.addWidget(self.code_style_exclude.label, 2, 0)
        code_style_g_layout.addWidget(self.code_style_exclude.textbox, 2, 1)
        code_style_g_layout.addWidget(code_style_select.label, 3, 0)
        code_style_g_layout.addWidget(code_style_select.textbox, 3, 1)
        code_style_g_layout.addWidget(code_style_ignore.label, 4, 0)
        code_style_g_layout.addWidget(code_style_ignore.textbox, 4, 1)
        code_style_g_layout.addWidget(code_style_max_line_length.plabel, 5, 0)
        code_style_g_layout.addWidget(
            code_style_max_line_length.spinbox, 5, 1)

        # Set Code style options enabled/disabled
        code_style_g_widget = QWidget()
        code_style_g_widget.setLayout(code_style_g_layout)
        code_style_g_widget.setEnabled(self.get_option('pycodestyle'))
        code_style_check.toggled.connect(code_style_g_widget.setEnabled)

        # Code style layout
        code_style_layout = QVBoxLayout()
        code_style_layout.addWidget(code_style_label)
        code_style_layout.addWidget(code_style_check)
        code_style_layout.addWidget(code_style_g_widget)

        code_style_widget = QWidget()
        code_style_widget.setLayout(code_style_layout)

        # --- Docstring tab ---
        # Docstring style label
        numpy_url = (
            "<a href='https://numpydoc.readthedocs.io/en/"
            "latest/format.html'>Numpy</a>")
        pep257_url = (
            "<a href='https://www.python.org/dev/peps/pep-0257/'>PEP 257</a>")
        docstring_style_codes = _(
            "<a href='http://www.pydocstyle.org/en/stable"
            "/error_codes.html'>page</a>")
        docstring_style_label = QLabel(
            _("Here you can decide if you want to perform style analysis on "
              "your docstrings according to the {} or {} conventions. You can "
              "also decide if you want to show or ignore specific errors, "
              "according to the codes found on this {}.").format(
                  numpy_url, pep257_url, docstring_style_codes))
        docstring_style_label.setOpenExternalLinks(True)
        docstring_style_label.setWordWrap(True)

        # Docstring style checkbox
        docstring_style_check = self.create_checkbox(
            _("Enable docstring style linting"),
            'pydocstyle')

        # Docstring style options
        docstring_style_convention = self.create_combobox(
            _("Choose the convention used to lint docstrings: "),
            (("Numpy", 'numpy'),
             ("PEP 257", 'pep257'),
             ("Custom", 'custom')),
            'pydocstyle/convention')
        self.docstring_style_select = self.create_lineedit(
            _("Show the following errors:"),
            'pydocstyle/select', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Example codes: D413, D414"))
        self.docstring_style_ignore = self.create_lineedit(
            _("Ignore the following errors:"),
            'pydocstyle/ignore', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Example codes: D107, D402"))
        self.docstring_style_match = self.create_lineedit(
            _("Only check filenames matching these patterns:"),
            'pydocstyle/match', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Skip test files: (?!test_).*\\.py"))
        self.docstring_style_match_dir = self.create_lineedit(
            _("Only check in directories matching these patterns:"),
            'pydocstyle/match_dir', alignment=Qt.Horizontal, word_wrap=False,
            placeholder=_("Skip dot directories: [^\\.].*"))

        # Custom option handling
        docstring_style_convention.combobox.currentTextChanged.connect(
                self.setup_docstring_style_convention)
        current_convention = docstring_style_convention.combobox.currentText()
        self.setup_docstring_style_convention(current_convention)

        # Docstring style layout
        docstring_style_g_layout = QGridLayout()
        docstring_style_g_layout.addWidget(
            docstring_style_convention.label, 1, 0)
        docstring_style_g_layout.addWidget(
            docstring_style_convention.combobox, 1, 1)
        docstring_style_g_layout.addWidget(
            self.docstring_style_select.label, 2, 0)
        docstring_style_g_layout.addWidget(
            self.docstring_style_select.textbox, 2, 1)
        docstring_style_g_layout.addWidget(
            self.docstring_style_ignore.label, 3, 0)
        docstring_style_g_layout.addWidget(
            self.docstring_style_ignore.textbox, 3, 1)
        docstring_style_g_layout.addWidget(
            self.docstring_style_match.label, 4, 0)
        docstring_style_g_layout.addWidget(
            self.docstring_style_match.textbox, 4, 1)
        docstring_style_g_layout.addWidget(
            self.docstring_style_match_dir.label, 5, 0)
        docstring_style_g_layout.addWidget(
            self.docstring_style_match_dir.textbox, 5, 1)

        # Set Docstring style options enabled/disabled
        docstring_style_g_widget = QWidget()
        docstring_style_g_widget.setLayout(docstring_style_g_layout)
        docstring_style_g_widget.setEnabled(self.get_option('pydocstyle'))
        docstring_style_check.toggled.connect(
            docstring_style_g_widget.setEnabled)

        # Docstring style layout
        docstring_style_layout = QVBoxLayout()
        docstring_style_layout.addWidget(docstring_style_label)
        docstring_style_layout.addWidget(docstring_style_check)
        docstring_style_layout.addWidget(docstring_style_g_widget)

        docstring_style_widget = QWidget()
        docstring_style_widget.setLayout(docstring_style_layout)

        # --- Advanced tab ---
        # Advanced label
        advanced_label = QLabel(
            _("Please don't modify these values unless "
              "you know what you're doing!"))
        advanced_label.setWordWrap(True)
        advanced_label.setAlignment(Qt.AlignJustify)

        # Advanced options
        advanced_command_launch = self.create_lineedit(
            _("Command to launch the Python language server: "),
            'advanced/command_launch', alignment=Qt.Horizontal,
            word_wrap=False)
        advanced_host = self.create_lineedit(
            _("IP Address and port to bind the server to: "),
            'advanced/host', alignment=Qt.Horizontal,
            word_wrap=False)
        advanced_port = self.create_spinbox(
            ":", "", 'advanced/port', min_=1, max_=65535, step=1)

        # Advanced layout
        advanced_g_layout = QGridLayout()
        advanced_g_layout.addWidget(advanced_command_launch.label, 1, 0)
        advanced_g_layout.addWidget(advanced_command_launch.textbox, 1, 1)
        advanced_g_layout.addWidget(advanced_host.label, 2, 0)

        advanced_host_port_g_layout = QGridLayout()
        advanced_host_port_g_layout.addWidget(advanced_host.textbox, 1, 0)
        advanced_host_port_g_layout.addWidget(advanced_port.plabel, 1, 1)
        advanced_host_port_g_layout.addWidget(advanced_port.spinbox, 1, 2)
        advanced_g_layout.addLayout(advanced_host_port_g_layout, 2, 1)

        advanced_widget = QWidget()
        advanced_layout = QVBoxLayout()
        advanced_layout.addWidget(advanced_label)
        advanced_layout.addLayout(advanced_g_layout)
        advanced_widget.setLayout(advanced_layout)

        # --- Other servers tab ---
        # Section label
        servers_label = QLabel(
            _("Spyder uses the <a href=\"{lsp_url}\">Language Server "
              "Protocol</a> to provide code completion and linting "
              "for its Editor. Here, you can setup and configure LSP servers "
              "for languages other than Python, so Spyder can provide such "
              "features for those languages as well."
              ).format(lsp_url=LSP_URL))
        servers_label.setOpenExternalLinks(True)
        servers_label.setWordWrap(True)
        servers_label.setAlignment(Qt.AlignJustify)

        # Servers table
        table_group = QGroupBox(_('Available servers:'))
        self.table = LSPServerTable(self, text_color=ima.MAIN_FG_COLOR)
        table_layout = QVBoxLayout()
        table_layout.addWidget(self.table)
        table_group.setLayout(table_layout)

        # Buttons
        self.reset_btn = QPushButton(_("Reset to default values"))
        self.new_btn = QPushButton(_("Set up a new server"))
        self.delete_btn = QPushButton(_("Delete currently selected server"))
        self.delete_btn.setEnabled(False)

        # Slots connected to buttons
        self.new_btn.clicked.connect(self.create_new_server)
        self.reset_btn.clicked.connect(self.reset_to_default)
        self.delete_btn.clicked.connect(self.delete_server)

        # Buttons layout
        btns = [self.new_btn, self.delete_btn, self.reset_btn]
        buttons_layout = QGridLayout()
        for i, btn in enumerate(btns):
            buttons_layout.addWidget(btn, i, 1)
        buttons_layout.setColumnStretch(0, 1)
        buttons_layout.setColumnStretch(1, 2)
        buttons_layout.setColumnStretch(2, 1)

        # Combined layout
        servers_widget = QWidget()
        servers_layout = QVBoxLayout()
        servers_layout.addSpacing(-10)
        servers_layout.addWidget(servers_label)
        servers_layout.addWidget(table_group)
        servers_layout.addSpacing(10)
        servers_layout.addLayout(buttons_layout)
        servers_widget.setLayout(servers_layout)

        # --- Tabs organization ---
        tabs = QTabWidget()
        tabs.addTab(self.create_tab(basic_features_group, advanced_group),
                    _('Introspection'))
        tabs.addTab(self.create_tab(linting_widget), _('Linting'))
        tabs.addTab(self.create_tab(code_style_widget), _('Code style'))
        tabs.addTab(self.create_tab(docstring_style_widget),
                    _('Docstring style'))
        tabs.addTab(self.create_tab(advanced_widget),
                    _('Advanced'))
        tabs.addTab(self.create_tab(servers_widget), _('Other languages'))

        vlayout = QVBoxLayout()
        vlayout.addWidget(tabs)
        self.setLayout(vlayout)