--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/QScintilla/EditorButtonsWidget.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,363 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a widget containing various buttons for accessing +editor actions. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolButton, QFrame, QMenu, \ + QSizePolicy, QScrollArea + +import UI.PixmapCache +import Preferences + +from . import MarkupProviders + + +class EditorButtonsWidget(QWidget): + """ + Class implementing a widget containing various buttons for accessing + editor actions. + """ + def __init__(self, editor, parent=None): + """ + Constructor + + @param editor reference to the editor + @type Editor + @param parent reference to the parent widget + @type QWidget + """ + super(EditorButtonsWidget, self).__init__(parent) + + margin = 2 + spacing = 3 + + self.__buttonsWidget = QWidget(self) + + self.__layout = QVBoxLayout(self.__buttonsWidget) + self.__layout.setContentsMargins(0, 0, 0, 0) + self.__layout.setSpacing(spacing) + + self.__provider = None + + self.__editor = editor + self.__editor.languageChanged.connect(self.__updateButtonStates) + self.__editor.editorSaved.connect(self.__updateButtonStates) + self.__editor.editorRenamed.connect(self.__updateButtonStates) + self.__editor.selectionChanged.connect(self.__editorSelectionChanged) + self.__editor.settingsRead.connect(self.__editorSettingsRead) + + self.__createButtons() + + self.__layout.addStretch() + + self.__outerLayout = QVBoxLayout(self) + self.__outerLayout.setContentsMargins(margin, margin, margin, margin) + self.__outerLayout.setSpacing(spacing) + self.__outerLayout.setAlignment(Qt.AlignHCenter) + + self.__upButton = QToolButton(self) + self.__upButton.setArrowType(Qt.UpArrow) + self.__upButton.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) + self.__upButton.setAutoRepeat(True) + + self.__scroller = QScrollArea(self) + self.__scroller.setWidget(self.__buttonsWidget) + self.__scroller.setSizePolicy( + QSizePolicy.Minimum, QSizePolicy.Expanding) + self.__scroller.setFrameShape(QFrame.NoFrame) + self.__scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.__scroller.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.__scroller.setWidgetResizable(False) + + self.__downButton = QToolButton(self) + self.__downButton.setArrowType(Qt.DownArrow) + self.__downButton.setSizePolicy( + QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) + self.__downButton.setAutoRepeat(True) + + self.__outerLayout.addWidget(self.__upButton) + self.__outerLayout.addWidget(self.__scroller) + self.__outerLayout.addWidget(self.__downButton) + + self.__upButton.clicked.connect(self.__slideUp) + self.__downButton.clicked.connect(self.__slideDown) + + self.setMaximumWidth( + self.__buttons["bold"].sizeHint().width() + 2 * margin) + + self.__updateButtonStates() + + ####################################################################### + ## Methods below implement some event handlers + ####################################################################### + + def show(self): + """ + Public slot to show the widget. + """ + super(EditorButtonsWidget, self).show() + self.__enableScrollerButtons() + + def resizeEvent(self, evt): + """ + Protected method to handle resize events. + + @param evt reference to the resize event (QResizeEvent) + """ + self.__enableScrollerButtons() + super(EditorButtonsWidget, self).resizeEvent(evt) + + ####################################################################### + ## Methods below implement scroller related functions + ####################################################################### + + def __enableScrollerButtons(self): + """ + Private method to set the enabled state of the scroll buttons. + """ + scrollBar = self.__scroller.verticalScrollBar() + self.__upButton.setEnabled(scrollBar.value() > 0) + self.__downButton.setEnabled(scrollBar.value() < scrollBar.maximum()) + + def __slideUp(self): + """ + Private slot to move the widget upwards, i.e. show contents to the + bottom. + """ + self.__slide(True) + + def __slideDown(self): + """ + Private slot to move the widget downwards, i.e. show contents to + the top. + """ + self.__slide(False) + + def __slide(self, up): + """ + Private method to move the sliding widget. + + @param up flag indicating to move upwards (boolean) + """ + scrollBar = self.__scroller.verticalScrollBar() + stepSize = scrollBar.singleStep() + if up: + stepSize = -stepSize + newValue = scrollBar.value() + stepSize + if newValue < 0: + newValue = 0 + elif newValue > scrollBar.maximum(): + newValue = scrollBar.maximum() + scrollBar.setValue(newValue) + self.__enableScrollerButtons() + + ####################################################################### + ## Methods below implement the format button functions + ####################################################################### + + def __createButtons(self): + """ + Private slot to create the various tool buttons. + """ + self.__buttons = {} + self.__separators = [] + self.__headerMenu = QMenu() + + self.__addButton("bold", "formatTextBold.png", + self.tr("Bold")) + self.__addButton("italic", "formatTextItalic.png", + self.tr("Italic")) + self.__addButton("strikethrough", "formatTextStrikethrough.png", + self.tr("Strike Through")) + self.__addSeparator() + self.__addButton("header1", "formatTextHeader1.png", + self.tr("Header 1")) + self.__addButton("header2", "formatTextHeader2.png", + self.tr("Header 2")) + self.__addButton("header3", "formatTextHeader3.png", + self.tr("Header 3")) + button = self.__addButton("header", "formatTextHeader.png", + self.tr("Header")) + button.setPopupMode(QToolButton.InstantPopup) + button.setMenu(self.__headerMenu) + self.__addSeparator() + self.__addButton("code", "formatTextInlineCode.png", + self.tr("Inline Code")) + self.__addButton("codeBlock", "formatTextCodeBlock.png", + self.tr("Code Block")) + self.__addButton("quote", "formatTextQuote.png", + self.tr("Quote")) + self.__addSeparator() + self.__addButton("hyperlink", "formatTextHyperlink.png", + self.tr("Add Hyperlink")) + self.__addButton("line", "formatTextHorizontalLine.png", + self.tr("Add Horizontal Line")) + self.__addButton("image", "formatTextImage.png", + self.tr("Add Image")) + self.__addSeparator() + self.__addButton("bulletedList", "formatTextBulletedList.png", + self.tr("Add Bulleted List")) + self.__addButton("numberedList", "formatTextNumberedList.png", + self.tr("Add Numbered List")) + + self.__headerMenu.triggered.connect(self.__headerMenuTriggered) + + def __addButton(self, formatName, iconName, toolTip): + """ + Private method to add a format button. + + @param formatName unique name of the format + @type str + @param iconName name of the icon for the button + @type str + @param toolTip text for the tool tip + @type str + @return generated button + @rtype QToolButton + """ + button = QToolButton(self.__buttonsWidget) + button.setIcon(UI.PixmapCache.getIcon(iconName)) + button.setToolTip(toolTip) + button.clicked.connect(lambda: self.__formatClicked(formatName)) + self.__layout.addWidget(button) + self.__buttons[formatName] = button + + return button + + def __addSeparator(self): + """ + Private method to add a separator line. + """ + line = QFrame(self.__buttonsWidget) + line.setLineWidth(2) + if isinstance(self.__layout, QVBoxLayout): + line.setFrameShape(QFrame.HLine) + else: + line.setFrameShape(QFrame.VLine) + line.setFrameShadow(QFrame.Sunken) + + self.__layout.addWidget(line) + self.__separators.append(line) + + @pyqtSlot() + def __updateButtonStates(self): + """ + Private slot to change the button states. + """ + provider = MarkupProviders.getMarkupProvider(self.__editor) + if self.__provider is None or \ + provider.kind() != self.__provider.kind(): + self.__provider = provider + + self.__buttons["bold"].setEnabled(self.__provider.hasBold()) + self.__buttons["italic"].setEnabled(self.__provider.hasItalic()) + self.__buttons["strikethrough"].setEnabled( + self.__provider.hasStrikethrough()) + + headerLevels = self.__provider.headerLevels() + self.__buttons["header1"].setEnabled(headerLevels >= 1) + self.__buttons["header2"].setEnabled(headerLevels >= 2) + self.__buttons["header3"].setEnabled(headerLevels >= 3) + self.__buttons["header"].setEnabled(headerLevels > 3) + self.__headerMenu.clear() + for level in range(1, headerLevels + 1): + act = self.__headerMenu.addAction( + self.tr("Level {0}").format(level)) + act.setData("header{0}".format(level)) + + self.__buttons["code"].setEnabled(self.__provider.hasCode()) + self.__buttons["codeBlock"].setEnabled( + self.__provider.hasCodeBlock()) + + self.__buttons["bulletedList"].setEnabled( + self.__provider.hasBulletedList()) + self.__buttons["numberedList"].setEnabled( + self.__provider.hasNumberedList()) + + self.__editorSelectionChanged() + + if Preferences.getEditor("HideFormatButtons"): + self.setVisible(self.__provider.kind() != "none") + + def __formatClicked(self, formatName): + """ + Private slot to handle a format button being clicked. + + @param formatName format type of the button + @type str + """ + if formatName == "bold": + self.__provider.bold(self.__editor) + elif formatName == "italic": + self.__provider.italic(self.__editor) + elif formatName == "strikethrough": + self.__provider.strikethrough(self.__editor) + elif formatName.startswith("header"): + try: + level = int(formatName[-1]) + self.__provider.header(self.__editor, level) + except ValueError: + pass + elif formatName == "code": + self.__provider.code(self.__editor) + elif formatName == "codeBlock": + self.__provider.codeBlock(self.__editor) + elif formatName == "quote": + self.__provider.quote(self.__editor) + elif formatName == "hyperlink": + self.__provider.hyperlink(self.__editor) + elif formatName == "line": + self.__provider.line(self.__editor) + elif formatName == "image": + self.__provider.image(self.__editor) + elif formatName == "bulletedList": + self.__provider.bulletedList(self.__editor) + elif formatName == "numberedList": + self.__provider.numberedList(self.__editor) + + def __headerMenuTriggered(self, act): + """ + Private method handling the selection of a header menu entry. + + @param act action of the headers menu that was triggered + @type QAction + """ + formatName = act.data() + self.__formatClicked(formatName) + + def __editorSelectionChanged(self): + """ + Private slot to handle a change of the editor's selection. + """ + hasSelection = self.__editor.hasSelectedText() + if self.__provider: + self.__buttons["quote"].setEnabled( + self.__provider.hasQuote() and ( + self.__provider.kind() == "html" or hasSelection + ) + ) + self.__buttons["hyperlink"].setEnabled( + self.__provider.hasHyperlink() and not hasSelection) + self.__buttons["line"].setEnabled( + self.__provider.hasLine() and not hasSelection) + self.__buttons["image"].setEnabled( + self.__provider.hasImage() and not hasSelection) + + def __editorSettingsRead(self): + """ + Private slot to handle a change of the editor related settings. + """ + if Preferences.getEditor("HideFormatButtons"): + if self.__provider is not None: + self.setVisible(self.__provider.kind() != "none") + else: + self.setVisible(True)