|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a widget containing various buttons for accessing |
|
8 editor actions. |
|
9 """ |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 |
|
13 from PyQt5.QtCore import pyqtSlot, Qt |
|
14 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolButton, QFrame, QMenu, \ |
|
15 QSizePolicy, QScrollArea |
|
16 |
|
17 import UI.PixmapCache |
|
18 import Preferences |
|
19 |
|
20 from . import MarkupProviders |
|
21 |
|
22 |
|
23 class EditorButtonsWidget(QWidget): |
|
24 """ |
|
25 Class implementing a widget containing various buttons for accessing |
|
26 editor actions. |
|
27 """ |
|
28 def __init__(self, editor, parent=None): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param editor reference to the editor |
|
33 @type Editor |
|
34 @param parent reference to the parent widget |
|
35 @type QWidget |
|
36 """ |
|
37 super(EditorButtonsWidget, self).__init__(parent) |
|
38 |
|
39 margin = 2 |
|
40 spacing = 3 |
|
41 |
|
42 self.__buttonsWidget = QWidget(self) |
|
43 |
|
44 self.__layout = QVBoxLayout(self.__buttonsWidget) |
|
45 self.__layout.setContentsMargins(0, 0, 0, 0) |
|
46 self.__layout.setSpacing(spacing) |
|
47 |
|
48 self.__provider = None |
|
49 |
|
50 self.__editor = editor |
|
51 self.__editor.languageChanged.connect(self.__updateButtonStates) |
|
52 self.__editor.editorSaved.connect(self.__updateButtonStates) |
|
53 self.__editor.editorRenamed.connect(self.__updateButtonStates) |
|
54 self.__editor.selectionChanged.connect(self.__editorSelectionChanged) |
|
55 self.__editor.settingsRead.connect(self.__editorSettingsRead) |
|
56 |
|
57 self.__createButtons() |
|
58 |
|
59 self.__layout.addStretch() |
|
60 |
|
61 self.__outerLayout = QVBoxLayout(self) |
|
62 self.__outerLayout.setContentsMargins(margin, margin, margin, margin) |
|
63 self.__outerLayout.setSpacing(spacing) |
|
64 self.__outerLayout.setAlignment(Qt.AlignHCenter) |
|
65 |
|
66 self.__upButton = QToolButton(self) |
|
67 self.__upButton.setArrowType(Qt.UpArrow) |
|
68 self.__upButton.setSizePolicy( |
|
69 QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) |
|
70 self.__upButton.setAutoRepeat(True) |
|
71 |
|
72 self.__scroller = QScrollArea(self) |
|
73 self.__scroller.setWidget(self.__buttonsWidget) |
|
74 self.__scroller.setSizePolicy( |
|
75 QSizePolicy.Minimum, QSizePolicy.Expanding) |
|
76 self.__scroller.setFrameShape(QFrame.NoFrame) |
|
77 self.__scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) |
|
78 self.__scroller.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) |
|
79 self.__scroller.setWidgetResizable(False) |
|
80 |
|
81 self.__downButton = QToolButton(self) |
|
82 self.__downButton.setArrowType(Qt.DownArrow) |
|
83 self.__downButton.setSizePolicy( |
|
84 QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) |
|
85 self.__downButton.setAutoRepeat(True) |
|
86 |
|
87 self.__outerLayout.addWidget(self.__upButton) |
|
88 self.__outerLayout.addWidget(self.__scroller) |
|
89 self.__outerLayout.addWidget(self.__downButton) |
|
90 |
|
91 self.__upButton.clicked.connect(self.__slideUp) |
|
92 self.__downButton.clicked.connect(self.__slideDown) |
|
93 |
|
94 self.setMaximumWidth( |
|
95 self.__buttons["bold"].sizeHint().width() + 2 * margin) |
|
96 |
|
97 self.__updateButtonStates() |
|
98 |
|
99 ####################################################################### |
|
100 ## Methods below implement some event handlers |
|
101 ####################################################################### |
|
102 |
|
103 def show(self): |
|
104 """ |
|
105 Public slot to show the widget. |
|
106 """ |
|
107 super(EditorButtonsWidget, self).show() |
|
108 self.__enableScrollerButtons() |
|
109 |
|
110 def resizeEvent(self, evt): |
|
111 """ |
|
112 Protected method to handle resize events. |
|
113 |
|
114 @param evt reference to the resize event (QResizeEvent) |
|
115 """ |
|
116 self.__enableScrollerButtons() |
|
117 super(EditorButtonsWidget, self).resizeEvent(evt) |
|
118 |
|
119 ####################################################################### |
|
120 ## Methods below implement scroller related functions |
|
121 ####################################################################### |
|
122 |
|
123 def __enableScrollerButtons(self): |
|
124 """ |
|
125 Private method to set the enabled state of the scroll buttons. |
|
126 """ |
|
127 scrollBar = self.__scroller.verticalScrollBar() |
|
128 self.__upButton.setEnabled(scrollBar.value() > 0) |
|
129 self.__downButton.setEnabled(scrollBar.value() < scrollBar.maximum()) |
|
130 |
|
131 def __slideUp(self): |
|
132 """ |
|
133 Private slot to move the widget upwards, i.e. show contents to the |
|
134 bottom. |
|
135 """ |
|
136 self.__slide(True) |
|
137 |
|
138 def __slideDown(self): |
|
139 """ |
|
140 Private slot to move the widget downwards, i.e. show contents to |
|
141 the top. |
|
142 """ |
|
143 self.__slide(False) |
|
144 |
|
145 def __slide(self, up): |
|
146 """ |
|
147 Private method to move the sliding widget. |
|
148 |
|
149 @param up flag indicating to move upwards (boolean) |
|
150 """ |
|
151 scrollBar = self.__scroller.verticalScrollBar() |
|
152 stepSize = scrollBar.singleStep() |
|
153 if up: |
|
154 stepSize = -stepSize |
|
155 newValue = scrollBar.value() + stepSize |
|
156 if newValue < 0: |
|
157 newValue = 0 |
|
158 elif newValue > scrollBar.maximum(): |
|
159 newValue = scrollBar.maximum() |
|
160 scrollBar.setValue(newValue) |
|
161 self.__enableScrollerButtons() |
|
162 |
|
163 ####################################################################### |
|
164 ## Methods below implement the format button functions |
|
165 ####################################################################### |
|
166 |
|
167 def __createButtons(self): |
|
168 """ |
|
169 Private slot to create the various tool buttons. |
|
170 """ |
|
171 self.__buttons = {} |
|
172 self.__separators = [] |
|
173 self.__headerMenu = QMenu() |
|
174 |
|
175 self.__addButton("bold", "formatTextBold.png", |
|
176 self.tr("Bold")) |
|
177 self.__addButton("italic", "formatTextItalic.png", |
|
178 self.tr("Italic")) |
|
179 self.__addButton("strikethrough", "formatTextStrikethrough.png", |
|
180 self.tr("Strike Through")) |
|
181 self.__addSeparator() |
|
182 self.__addButton("header1", "formatTextHeader1.png", |
|
183 self.tr("Header 1")) |
|
184 self.__addButton("header2", "formatTextHeader2.png", |
|
185 self.tr("Header 2")) |
|
186 self.__addButton("header3", "formatTextHeader3.png", |
|
187 self.tr("Header 3")) |
|
188 button = self.__addButton("header", "formatTextHeader.png", |
|
189 self.tr("Header")) |
|
190 button.setPopupMode(QToolButton.InstantPopup) |
|
191 button.setMenu(self.__headerMenu) |
|
192 self.__addSeparator() |
|
193 self.__addButton("code", "formatTextInlineCode.png", |
|
194 self.tr("Inline Code")) |
|
195 self.__addButton("codeBlock", "formatTextCodeBlock.png", |
|
196 self.tr("Code Block")) |
|
197 self.__addButton("quote", "formatTextQuote.png", |
|
198 self.tr("Quote")) |
|
199 self.__addSeparator() |
|
200 self.__addButton("hyperlink", "formatTextHyperlink.png", |
|
201 self.tr("Add Hyperlink")) |
|
202 self.__addButton("line", "formatTextHorizontalLine.png", |
|
203 self.tr("Add Horizontal Line")) |
|
204 self.__addButton("image", "formatTextImage.png", |
|
205 self.tr("Add Image")) |
|
206 self.__addSeparator() |
|
207 self.__addButton("bulletedList", "formatTextBulletedList.png", |
|
208 self.tr("Add Bulleted List")) |
|
209 self.__addButton("numberedList", "formatTextNumberedList.png", |
|
210 self.tr("Add Numbered List")) |
|
211 |
|
212 self.__headerMenu.triggered.connect(self.__headerMenuTriggered) |
|
213 |
|
214 def __addButton(self, formatName, iconName, toolTip): |
|
215 """ |
|
216 Private method to add a format button. |
|
217 |
|
218 @param formatName unique name of the format |
|
219 @type str |
|
220 @param iconName name of the icon for the button |
|
221 @type str |
|
222 @param toolTip text for the tool tip |
|
223 @type str |
|
224 @return generated button |
|
225 @rtype QToolButton |
|
226 """ |
|
227 button = QToolButton(self.__buttonsWidget) |
|
228 button.setIcon(UI.PixmapCache.getIcon(iconName)) |
|
229 button.setToolTip(toolTip) |
|
230 button.clicked.connect(lambda: self.__formatClicked(formatName)) |
|
231 self.__layout.addWidget(button) |
|
232 self.__buttons[formatName] = button |
|
233 |
|
234 return button |
|
235 |
|
236 def __addSeparator(self): |
|
237 """ |
|
238 Private method to add a separator line. |
|
239 """ |
|
240 line = QFrame(self.__buttonsWidget) |
|
241 line.setLineWidth(2) |
|
242 if isinstance(self.__layout, QVBoxLayout): |
|
243 line.setFrameShape(QFrame.HLine) |
|
244 else: |
|
245 line.setFrameShape(QFrame.VLine) |
|
246 line.setFrameShadow(QFrame.Sunken) |
|
247 |
|
248 self.__layout.addWidget(line) |
|
249 self.__separators.append(line) |
|
250 |
|
251 @pyqtSlot() |
|
252 def __updateButtonStates(self): |
|
253 """ |
|
254 Private slot to change the button states. |
|
255 """ |
|
256 provider = MarkupProviders.getMarkupProvider(self.__editor) |
|
257 if self.__provider is None or \ |
|
258 provider.kind() != self.__provider.kind(): |
|
259 self.__provider = provider |
|
260 |
|
261 self.__buttons["bold"].setEnabled(self.__provider.hasBold()) |
|
262 self.__buttons["italic"].setEnabled(self.__provider.hasItalic()) |
|
263 self.__buttons["strikethrough"].setEnabled( |
|
264 self.__provider.hasStrikethrough()) |
|
265 |
|
266 headerLevels = self.__provider.headerLevels() |
|
267 self.__buttons["header1"].setEnabled(headerLevels >= 1) |
|
268 self.__buttons["header2"].setEnabled(headerLevels >= 2) |
|
269 self.__buttons["header3"].setEnabled(headerLevels >= 3) |
|
270 self.__buttons["header"].setEnabled(headerLevels > 3) |
|
271 self.__headerMenu.clear() |
|
272 for level in range(1, headerLevels + 1): |
|
273 act = self.__headerMenu.addAction( |
|
274 self.tr("Level {0}").format(level)) |
|
275 act.setData("header{0}".format(level)) |
|
276 |
|
277 self.__buttons["code"].setEnabled(self.__provider.hasCode()) |
|
278 self.__buttons["codeBlock"].setEnabled( |
|
279 self.__provider.hasCodeBlock()) |
|
280 |
|
281 self.__buttons["bulletedList"].setEnabled( |
|
282 self.__provider.hasBulletedList()) |
|
283 self.__buttons["numberedList"].setEnabled( |
|
284 self.__provider.hasNumberedList()) |
|
285 |
|
286 self.__editorSelectionChanged() |
|
287 |
|
288 if Preferences.getEditor("HideFormatButtons"): |
|
289 self.setVisible(self.__provider.kind() != "none") |
|
290 |
|
291 def __formatClicked(self, formatName): |
|
292 """ |
|
293 Private slot to handle a format button being clicked. |
|
294 |
|
295 @param formatName format type of the button |
|
296 @type str |
|
297 """ |
|
298 if formatName == "bold": |
|
299 self.__provider.bold(self.__editor) |
|
300 elif formatName == "italic": |
|
301 self.__provider.italic(self.__editor) |
|
302 elif formatName == "strikethrough": |
|
303 self.__provider.strikethrough(self.__editor) |
|
304 elif formatName.startswith("header"): |
|
305 try: |
|
306 level = int(formatName[-1]) |
|
307 self.__provider.header(self.__editor, level) |
|
308 except ValueError: |
|
309 pass |
|
310 elif formatName == "code": |
|
311 self.__provider.code(self.__editor) |
|
312 elif formatName == "codeBlock": |
|
313 self.__provider.codeBlock(self.__editor) |
|
314 elif formatName == "quote": |
|
315 self.__provider.quote(self.__editor) |
|
316 elif formatName == "hyperlink": |
|
317 self.__provider.hyperlink(self.__editor) |
|
318 elif formatName == "line": |
|
319 self.__provider.line(self.__editor) |
|
320 elif formatName == "image": |
|
321 self.__provider.image(self.__editor) |
|
322 elif formatName == "bulletedList": |
|
323 self.__provider.bulletedList(self.__editor) |
|
324 elif formatName == "numberedList": |
|
325 self.__provider.numberedList(self.__editor) |
|
326 |
|
327 def __headerMenuTriggered(self, act): |
|
328 """ |
|
329 Private method handling the selection of a header menu entry. |
|
330 |
|
331 @param act action of the headers menu that was triggered |
|
332 @type QAction |
|
333 """ |
|
334 formatName = act.data() |
|
335 self.__formatClicked(formatName) |
|
336 |
|
337 def __editorSelectionChanged(self): |
|
338 """ |
|
339 Private slot to handle a change of the editor's selection. |
|
340 """ |
|
341 hasSelection = self.__editor.hasSelectedText() |
|
342 if self.__provider: |
|
343 self.__buttons["quote"].setEnabled( |
|
344 self.__provider.hasQuote() and ( |
|
345 self.__provider.kind() == "html" or hasSelection |
|
346 ) |
|
347 ) |
|
348 self.__buttons["hyperlink"].setEnabled( |
|
349 self.__provider.hasHyperlink() and not hasSelection) |
|
350 self.__buttons["line"].setEnabled( |
|
351 self.__provider.hasLine() and not hasSelection) |
|
352 self.__buttons["image"].setEnabled( |
|
353 self.__provider.hasImage() and not hasSelection) |
|
354 |
|
355 def __editorSettingsRead(self): |
|
356 """ |
|
357 Private slot to handle a change of the editor related settings. |
|
358 """ |
|
359 if Preferences.getEditor("HideFormatButtons"): |
|
360 if self.__provider is not None: |
|
361 self.setVisible(self.__provider.kind() != "none") |
|
362 else: |
|
363 self.setVisible(True) |