|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2006 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Editor Highlighting Styles configuration page. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt5.QtCore import pyqtSlot, Qt, QFileInfo, QFile, QIODevice |
|
13 from PyQt5.QtGui import QPalette, QFont, QColor |
|
14 from PyQt5.QtWidgets import ( |
|
15 QColorDialog, QFontDialog, QInputDialog, QMenu, QTreeWidgetItem, QDialog |
|
16 ) |
|
17 |
|
18 from .ConfigurationPageBase import ConfigurationPageBase |
|
19 from .Ui_EditorHighlightingStylesPage import Ui_EditorHighlightingStylesPage |
|
20 from ..SubstyleDefinitionDialog import SubstyleDefinitionDialog |
|
21 |
|
22 from E5Gui import E5MessageBox, E5FileDialog |
|
23 |
|
24 import UI.PixmapCache |
|
25 |
|
26 try: |
|
27 MonospacedFontsOption = QFontDialog.FontDialogOption.MonospacedFonts |
|
28 except AttributeError: |
|
29 MonospacedFontsOption = QFontDialog.FontDialogOptions(0x10) |
|
30 NoFontsOption = QFontDialog.FontDialogOptions(0) |
|
31 |
|
32 |
|
33 class EditorHighlightingStylesPage(ConfigurationPageBase, |
|
34 Ui_EditorHighlightingStylesPage): |
|
35 """ |
|
36 Class implementing the Editor Highlighting Styles configuration page. |
|
37 """ |
|
38 FAMILYONLY = 0 |
|
39 SIZEONLY = 1 |
|
40 FAMILYANDSIZE = 2 |
|
41 FONT = 99 |
|
42 |
|
43 StyleRole = Qt.ItemDataRole.UserRole + 1 |
|
44 SubstyleRole = Qt.ItemDataRole.UserRole + 2 |
|
45 |
|
46 def __init__(self, lexers): |
|
47 """ |
|
48 Constructor |
|
49 |
|
50 @param lexers reference to the lexers dictionary |
|
51 """ |
|
52 super().__init__() |
|
53 self.setupUi(self) |
|
54 self.setObjectName("EditorHighlightingStylesPage") |
|
55 |
|
56 self.defaultSubstylesButton.setIcon(UI.PixmapCache.getIcon("editUndo")) |
|
57 self.addSubstyleButton.setIcon(UI.PixmapCache.getIcon("plus")) |
|
58 self.deleteSubstyleButton.setIcon(UI.PixmapCache.getIcon("minus")) |
|
59 self.editSubstyleButton.setIcon(UI.PixmapCache.getIcon("edit")) |
|
60 self.copySubstyleButton.setIcon(UI.PixmapCache.getIcon("editCopy")) |
|
61 |
|
62 self.__fontButtonMenu = QMenu() |
|
63 act = self.__fontButtonMenu.addAction(self.tr("Font")) |
|
64 act.setData(self.FONT) |
|
65 self.__fontButtonMenu.addSeparator() |
|
66 act = self.__fontButtonMenu.addAction( |
|
67 self.tr("Family and Size only")) |
|
68 act.setData(self.FAMILYANDSIZE) |
|
69 act = self.__fontButtonMenu.addAction(self.tr("Family only")) |
|
70 act.setData(self.FAMILYONLY) |
|
71 act = self.__fontButtonMenu.addAction(self.tr("Size only")) |
|
72 act.setData(self.SIZEONLY) |
|
73 self.__fontButtonMenu.triggered.connect(self.__fontButtonMenuTriggered) |
|
74 self.fontButton.setMenu(self.__fontButtonMenu) |
|
75 |
|
76 self.__allFontsButtonMenu = QMenu() |
|
77 act = self.__allFontsButtonMenu.addAction(self.tr("Font")) |
|
78 act.setData(self.FONT) |
|
79 self.__allFontsButtonMenu.addSeparator() |
|
80 act = self.__allFontsButtonMenu.addAction( |
|
81 self.tr("Family and Size only")) |
|
82 act.setData(self.FAMILYANDSIZE) |
|
83 act = self.__allFontsButtonMenu.addAction(self.tr("Family only")) |
|
84 act.setData(self.FAMILYONLY) |
|
85 act = self.__allFontsButtonMenu.addAction(self.tr("Size only")) |
|
86 act.setData(self.SIZEONLY) |
|
87 self.__allFontsButtonMenu.triggered.connect( |
|
88 self.__allFontsButtonMenuTriggered) |
|
89 self.allFontsButton.setMenu(self.__allFontsButtonMenu) |
|
90 |
|
91 self.lexer = None |
|
92 self.lexers = lexers |
|
93 |
|
94 # set initial values |
|
95 import QScintilla.Lexers |
|
96 languages = sorted([''] + list(self.lexers.keys())) |
|
97 for language in languages: |
|
98 self.lexerLanguageComboBox.addItem( |
|
99 QScintilla.Lexers.getLanguageIcon(language, False), |
|
100 language) |
|
101 self.on_lexerLanguageComboBox_activated(0) |
|
102 |
|
103 def save(self): |
|
104 """ |
|
105 Public slot to save the Editor Highlighting Styles configuration. |
|
106 """ |
|
107 for lexer in list(self.lexers.values()): |
|
108 lexer.writeSettings() |
|
109 |
|
110 @pyqtSlot(int) |
|
111 def on_lexerLanguageComboBox_activated(self, index): |
|
112 """ |
|
113 Private slot to fill the style combo of the source page. |
|
114 |
|
115 @param index index of the selected entry |
|
116 @type int |
|
117 """ |
|
118 language = self.lexerLanguageComboBox.itemText(index) |
|
119 |
|
120 self.styleElementList.clear() |
|
121 self.styleGroup.setEnabled(False) |
|
122 self.lexer = None |
|
123 |
|
124 if not language: |
|
125 return |
|
126 |
|
127 try: |
|
128 self.lexer = self.lexers[language] |
|
129 except KeyError: |
|
130 return |
|
131 |
|
132 self.styleGroup.setEnabled(True) |
|
133 for description, styleNo, subStyleNo in self.lexer.getStyles(): |
|
134 if subStyleNo >= 0: |
|
135 parent = self.styleElementList.findItems( |
|
136 self.lexer.description(styleNo), |
|
137 Qt.MatchFlag.MatchExactly)[0] |
|
138 parent.setExpanded(True) |
|
139 else: |
|
140 parent = self.styleElementList |
|
141 itm = QTreeWidgetItem(parent, [description]) |
|
142 itm.setData(0, self.StyleRole, styleNo) |
|
143 itm.setData(0, self.SubstyleRole, subStyleNo) |
|
144 self.__styleAllItems() |
|
145 self.styleElementList.setCurrentItem( |
|
146 self.styleElementList.topLevelItem(0)) |
|
147 |
|
148 def __stylesForItem(self, itm): |
|
149 """ |
|
150 Private method to get the style and sub-style number of the given item. |
|
151 |
|
152 @param itm reference to the item to extract the styles from |
|
153 @type QTreeWidgetItem |
|
154 @return tuple containing the style and sub-style numbers |
|
155 @rtype tuple of (int, int) |
|
156 """ |
|
157 style = itm.data(0, self.StyleRole) |
|
158 substyle = itm.data(0, self.SubstyleRole) |
|
159 |
|
160 return (style, substyle) |
|
161 |
|
162 def __currentStyles(self): |
|
163 """ |
|
164 Private method to get the styles of the current item. |
|
165 |
|
166 @return tuple containing the style and sub-style numbers |
|
167 @rtype tuple of (int, int) |
|
168 """ |
|
169 itm = self.styleElementList.currentItem() |
|
170 # return default style, if no current item |
|
171 styles = (0, -1) if itm is None else self.__stylesForItem(itm) |
|
172 |
|
173 return styles |
|
174 |
|
175 def __styleOneItem(self, item, style, substyle): |
|
176 """ |
|
177 Private method to style one item of the style element list. |
|
178 |
|
179 @param item reference to the item to be styled |
|
180 @type QTreeWidgetItem |
|
181 @param style base style number |
|
182 @type int |
|
183 @param substyle sub-style number |
|
184 @type int |
|
185 """ |
|
186 colour = self.lexer.color(style, substyle) |
|
187 paper = self.lexer.paper(style, substyle) |
|
188 font = self.lexer.font(style, substyle) |
|
189 eolfill = self.lexer.eolFill(style, substyle) |
|
190 |
|
191 item.setFont(0, font) |
|
192 item.setBackground(0, paper) |
|
193 item.setForeground(0, colour) |
|
194 if eolfill: |
|
195 item.setCheckState(0, Qt.CheckState.Checked) |
|
196 else: |
|
197 item.setCheckState(0, Qt.CheckState.Unchecked) |
|
198 |
|
199 def __styleAllItems(self): |
|
200 """ |
|
201 Private method to style all items of the style element list. |
|
202 """ |
|
203 itm = self.styleElementList.topLevelItem(0) |
|
204 while itm is not None: |
|
205 style, substyle = self.__stylesForItem(itm) |
|
206 self.__styleOneItem(itm, style, substyle) |
|
207 itm = self.styleElementList.itemBelow(itm) |
|
208 |
|
209 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
210 def on_styleElementList_currentItemChanged(self, current, previous): |
|
211 """ |
|
212 Private method to handle a change of the current row. |
|
213 |
|
214 @param current reference to the current item |
|
215 @type QTreeWidgetItem |
|
216 @param previous reference to the previous item |
|
217 @type QTreeWidgetItem |
|
218 """ |
|
219 if current is None: |
|
220 return |
|
221 |
|
222 style, substyle = self.__stylesForItem(current) |
|
223 colour = self.lexer.color(style, substyle) |
|
224 paper = self.lexer.paper(style, substyle) |
|
225 eolfill = self.lexer.eolFill(style, substyle) |
|
226 font = self.lexer.font(style, substyle) |
|
227 |
|
228 self.sampleText.setFont(font) |
|
229 pl = self.sampleText.palette() |
|
230 pl.setColor(QPalette.ColorRole.Text, colour) |
|
231 pl.setColor(QPalette.ColorRole.Base, paper) |
|
232 self.sampleText.setPalette(pl) |
|
233 self.sampleText.repaint() |
|
234 self.eolfillCheckBox.setChecked(eolfill) |
|
235 |
|
236 selectedOne = len(self.styleElementList.selectedItems()) == 1 |
|
237 self.defaultSubstylesButton.setEnabled( |
|
238 selectedOne and substyle < 0 and self.lexer.isBaseStyle(style)) |
|
239 self.addSubstyleButton.setEnabled( |
|
240 selectedOne and substyle < 0 and self.lexer.isBaseStyle(style)) |
|
241 self.deleteSubstyleButton.setEnabled(selectedOne and substyle >= 0) |
|
242 self.editSubstyleButton.setEnabled(selectedOne and substyle >= 0) |
|
243 self.copySubstyleButton.setEnabled(selectedOne and substyle >= 0) |
|
244 |
|
245 @pyqtSlot() |
|
246 def on_foregroundButton_clicked(self): |
|
247 """ |
|
248 Private method used to select the foreground colour of the selected |
|
249 style and lexer. |
|
250 """ |
|
251 style, substyle = self.__currentStyles() |
|
252 colour = QColorDialog.getColor(self.lexer.color(style, substyle)) |
|
253 if colour.isValid(): |
|
254 pl = self.sampleText.palette() |
|
255 pl.setColor(QPalette.ColorRole.Text, colour) |
|
256 self.sampleText.setPalette(pl) |
|
257 self.sampleText.repaint() |
|
258 for selItem in self.styleElementList.selectedItems(): |
|
259 style, substyle = self.__stylesForItem(selItem) |
|
260 self.lexer.setColor(colour, style, substyle) |
|
261 selItem.setForeground(0, colour) |
|
262 |
|
263 @pyqtSlot() |
|
264 def on_backgroundButton_clicked(self): |
|
265 """ |
|
266 Private method used to select the background colour of the selected |
|
267 style and lexer. |
|
268 """ |
|
269 style, substyle = self.__currentStyles() |
|
270 colour = QColorDialog.getColor(self.lexer.paper(style, substyle)) |
|
271 if colour.isValid(): |
|
272 pl = self.sampleText.palette() |
|
273 pl.setColor(QPalette.ColorRole.Base, colour) |
|
274 self.sampleText.setPalette(pl) |
|
275 self.sampleText.repaint() |
|
276 for selItem in self.styleElementList.selectedItems(): |
|
277 style, substyle = self.__stylesForItem(selItem) |
|
278 self.lexer.setPaper(colour, style, substyle) |
|
279 selItem.setBackground(0, colour) |
|
280 |
|
281 @pyqtSlot() |
|
282 def on_allBackgroundColoursButton_clicked(self): |
|
283 """ |
|
284 Private method used to select the background colour of all styles of a |
|
285 selected lexer. |
|
286 """ |
|
287 style, substyle = self.__currentStyles() |
|
288 colour = QColorDialog.getColor(self.lexer.paper(style, substyle)) |
|
289 if colour.isValid(): |
|
290 pl = self.sampleText.palette() |
|
291 pl.setColor(QPalette.ColorRole.Base, colour) |
|
292 self.sampleText.setPalette(pl) |
|
293 self.sampleText.repaint() |
|
294 |
|
295 itm = self.styleElementList.topLevelItem(0) |
|
296 while itm is not None: |
|
297 style, substyle = self.__stylesForItem(itm) |
|
298 self.lexer.setPaper(colour, style, substyle) |
|
299 itm = self.styleElementList.itemBelow(itm) |
|
300 self.__styleAllItems() |
|
301 |
|
302 def __changeFont(self, doAll, familyOnly, sizeOnly): |
|
303 """ |
|
304 Private slot to change the highlighter font. |
|
305 |
|
306 @param doAll flag indicating to change the font for all styles |
|
307 (boolean) |
|
308 @param familyOnly flag indicating to set the font family only (boolean) |
|
309 @param sizeOnly flag indicating to set the font size only (boolean |
|
310 """ |
|
311 def setFont(font, style, substyle, familyOnly, sizeOnly): |
|
312 """ |
|
313 Local function to set the font. |
|
314 |
|
315 @param font font to be set |
|
316 @type QFont |
|
317 @param style style number |
|
318 @type int |
|
319 @param substyle sub-style number |
|
320 @type int |
|
321 @param familyOnly flag indicating to set the font family only |
|
322 @type bool |
|
323 @param sizeOnly flag indicating to set the font size only |
|
324 @type bool |
|
325 """ |
|
326 if familyOnly or sizeOnly: |
|
327 newFont = QFont(self.lexer.font(style)) |
|
328 if familyOnly: |
|
329 newFont.setFamily(font.family()) |
|
330 if sizeOnly: |
|
331 newFont.setPointSize(font.pointSize()) |
|
332 self.lexer.setFont(newFont, style, substyle) |
|
333 else: |
|
334 self.lexer.setFont(font, style, substyle) |
|
335 |
|
336 def setSampleFont(font, familyOnly, sizeOnly): |
|
337 """ |
|
338 Local function to set the font of the sample text. |
|
339 |
|
340 @param font font to be set (QFont) |
|
341 @param familyOnly flag indicating to set the font family only |
|
342 (boolean) |
|
343 @param sizeOnly flag indicating to set the font size only (boolean |
|
344 """ |
|
345 if familyOnly or sizeOnly: |
|
346 style, substyle = self.__currentStyles() |
|
347 newFont = QFont(self.lexer.font(style, substyle)) |
|
348 if familyOnly: |
|
349 newFont.setFamily(font.family()) |
|
350 if sizeOnly: |
|
351 newFont.setPointSize(font.pointSize()) |
|
352 self.sampleText.setFont(newFont) |
|
353 else: |
|
354 self.sampleText.setFont(font) |
|
355 |
|
356 style, substyle = self.__currentStyles() |
|
357 options = ( |
|
358 MonospacedFontsOption |
|
359 if self.monospacedButton.isChecked() else |
|
360 NoFontsOption |
|
361 ) |
|
362 font, ok = QFontDialog.getFont(self.lexer.font(style, substyle), self, |
|
363 "", options) |
|
364 if ok: |
|
365 setSampleFont(font, familyOnly, sizeOnly) |
|
366 if doAll: |
|
367 itm = self.styleElementList.topLevelItem(0) |
|
368 while itm is not None: |
|
369 style, substyle = self.__stylesForItem(itm) |
|
370 setFont(font, style, substyle, familyOnly, sizeOnly) |
|
371 itm = self.styleElementList.itemBelow(itm) |
|
372 self.__styleAllItems() |
|
373 else: |
|
374 for selItem in self.styleElementList.selectedItems(): |
|
375 style, substyle = self.__stylesForItem(selItem) |
|
376 setFont(font, style, substyle, familyOnly, sizeOnly) |
|
377 itmFont = self.lexer.font(style, substyle) |
|
378 selItem.setFont(0, itmFont) |
|
379 |
|
380 def __fontButtonMenuTriggered(self, act): |
|
381 """ |
|
382 Private slot used to select the font of the selected style and lexer. |
|
383 |
|
384 @param act reference to the triggering action (QAction) |
|
385 """ |
|
386 if act is None: |
|
387 return |
|
388 |
|
389 familyOnly = act.data() in [self.FAMILYANDSIZE, self.FAMILYONLY] |
|
390 sizeOnly = act.data() in [self.FAMILYANDSIZE, self.SIZEONLY] |
|
391 self.__changeFont(False, familyOnly, sizeOnly) |
|
392 |
|
393 def __allFontsButtonMenuTriggered(self, act): |
|
394 """ |
|
395 Private slot used to change the font of all styles of a selected lexer. |
|
396 |
|
397 @param act reference to the triggering action (QAction) |
|
398 """ |
|
399 if act is None: |
|
400 return |
|
401 |
|
402 familyOnly = act.data() in [self.FAMILYANDSIZE, self.FAMILYONLY] |
|
403 sizeOnly = act.data() in [self.FAMILYANDSIZE, self.SIZEONLY] |
|
404 self.__changeFont(True, familyOnly, sizeOnly) |
|
405 |
|
406 @pyqtSlot(bool) |
|
407 def on_eolfillCheckBox_clicked(self, on): |
|
408 """ |
|
409 Private method used to set the eolfill for the selected style and |
|
410 lexer. |
|
411 |
|
412 @param on flag indicating enabled or disabled state (boolean) |
|
413 """ |
|
414 style, substyle = self.__currentStyles() |
|
415 checkState = Qt.CheckState.Checked if on else Qt.CheckState.Unchecked |
|
416 for selItem in self.styleElementList.selectedItems(): |
|
417 style, substyle = self.__stylesForItem(selItem) |
|
418 self.lexer.setEolFill(on, style, substyle) |
|
419 selItem.setCheckState(0, checkState) |
|
420 |
|
421 @pyqtSlot() |
|
422 def on_allEolFillButton_clicked(self): |
|
423 """ |
|
424 Private method used to set the eolfill for all styles of a selected |
|
425 lexer. |
|
426 """ |
|
427 on = self.tr("Enabled") |
|
428 off = self.tr("Disabled") |
|
429 selection, ok = QInputDialog.getItem( |
|
430 self, |
|
431 self.tr("Fill to end of line"), |
|
432 self.tr("Select fill to end of line for all styles"), |
|
433 [on, off], |
|
434 0, False) |
|
435 if ok: |
|
436 enabled = selection == on |
|
437 self.eolfillCheckBox.setChecked(enabled) |
|
438 |
|
439 itm = self.styleElementList.topLevelItem(0) |
|
440 while itm is not None: |
|
441 style, substyle = self.__stylesForItem(itm) |
|
442 self.lexer.setEolFill(enabled, style, substyle) |
|
443 itm = self.styleElementList.itemBelow(itm) |
|
444 self.__styleAllItems() |
|
445 |
|
446 @pyqtSlot() |
|
447 def on_defaultButton_clicked(self): |
|
448 """ |
|
449 Private method to set the current style to its default values. |
|
450 """ |
|
451 for selItem in self.styleElementList.selectedItems(): |
|
452 style, substyle = self.__stylesForItem(selItem) |
|
453 self.__setToDefault(style, substyle) |
|
454 self.on_styleElementList_currentItemChanged( |
|
455 self.styleElementList.currentItem(), None) |
|
456 self.__styleAllItems() |
|
457 |
|
458 @pyqtSlot() |
|
459 def on_allDefaultButton_clicked(self): |
|
460 """ |
|
461 Private method to set all styles to their default values. |
|
462 """ |
|
463 itm = self.styleElementList.topLevelItem(0) |
|
464 while itm is not None: |
|
465 style, substyle = self.__stylesForItem(itm) |
|
466 self.__setToDefault(style, substyle) |
|
467 itm = self.styleElementList.itemBelow(itm) |
|
468 self.on_styleElementList_currentItemChanged( |
|
469 self.styleElementList.currentItem(), None) |
|
470 self.__styleAllItems() |
|
471 |
|
472 def __setToDefault(self, style, substyle): |
|
473 """ |
|
474 Private method to set a specific style to its default values. |
|
475 |
|
476 @param style style number |
|
477 @type int |
|
478 @param substyle sub-style number |
|
479 @type int |
|
480 """ |
|
481 self.lexer.setColor(self.lexer.defaultColor(style, substyle), |
|
482 style, substyle) |
|
483 self.lexer.setPaper(self.lexer.defaultPaper(style, substyle), |
|
484 style, substyle) |
|
485 self.lexer.setFont(self.lexer.defaultFont(style, substyle), |
|
486 style, substyle) |
|
487 self.lexer.setEolFill(self.lexer.defaultEolFill(style, substyle), |
|
488 style, substyle) |
|
489 |
|
490 ####################################################################### |
|
491 ## Importing and exporting of styles |
|
492 ####################################################################### |
|
493 |
|
494 @pyqtSlot() |
|
495 def on_importButton_clicked(self): |
|
496 """ |
|
497 Private slot to import styles to be selected. |
|
498 """ |
|
499 self.__importStyles(importAll=False) |
|
500 |
|
501 @pyqtSlot() |
|
502 def on_exportButton_clicked(self): |
|
503 """ |
|
504 Private slot to export styles to be selected. |
|
505 """ |
|
506 self.__exportStyles(exportAll=False) |
|
507 |
|
508 @pyqtSlot() |
|
509 def on_importAllButton_clicked(self): |
|
510 """ |
|
511 Private slot to import the styles of all lexers. |
|
512 """ |
|
513 self.__importStyles(importAll=True) |
|
514 |
|
515 @pyqtSlot() |
|
516 def on_exportAllButton_clicked(self): |
|
517 """ |
|
518 Private slot to export the styles of all lexers. |
|
519 """ |
|
520 self.__exportStyles(exportAll=True) |
|
521 |
|
522 def __exportStyles(self, exportAll=False): |
|
523 """ |
|
524 Private method to export the styles of selectable lexers. |
|
525 |
|
526 @param exportAll flag indicating to export all styles without asking |
|
527 (defaults to False) |
|
528 @type bool (optional) |
|
529 """ |
|
530 from eric6config import getConfig |
|
531 stylesDir = getConfig("ericStylesDir") |
|
532 |
|
533 lexerNames = list(self.lexers.keys()) |
|
534 if not exportAll: |
|
535 if self.lexer: |
|
536 preselect = [self.lexer.language()] |
|
537 else: |
|
538 preselect = [] |
|
539 from .EditorHighlightingStylesSelectionDialog import ( |
|
540 EditorHighlightingStylesSelectionDialog) |
|
541 dlg = EditorHighlightingStylesSelectionDialog( |
|
542 lexerNames, forImport=False, preselect=preselect) |
|
543 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
544 lexerNames = dlg.getLexerNames() |
|
545 else: |
|
546 # Cancelled by user |
|
547 return |
|
548 |
|
549 lexers = [self.lexers[name] for name in lexerNames] |
|
550 |
|
551 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
552 self, |
|
553 self.tr("Export Highlighting Styles"), |
|
554 stylesDir, |
|
555 self.tr("Highlighting Styles File (*.ehj);;" |
|
556 "XML Highlighting Styles File (*.e6h)"), |
|
557 "", |
|
558 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
559 |
|
560 if not fn: |
|
561 return |
|
562 |
|
563 ext = QFileInfo(fn).suffix() |
|
564 if not ext: |
|
565 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
566 if ex: |
|
567 fn += ex |
|
568 |
|
569 ok = ( |
|
570 E5MessageBox.yesNo( |
|
571 self, |
|
572 self.tr("Export Highlighting Styles"), |
|
573 self.tr("""<p>The highlighting styles file <b>{0}</b> exists""" |
|
574 """ already. Overwrite it?</p>""").format(fn)) |
|
575 if os.path.exists(fn) else |
|
576 True |
|
577 ) |
|
578 |
|
579 if ok: |
|
580 if fn.endswith(".ehj"): |
|
581 from Preferences.HighlightingStylesFile import ( |
|
582 HighlightingStylesFile |
|
583 ) |
|
584 highlightingStylesFile = HighlightingStylesFile() |
|
585 highlightingStylesFile.writeFile(fn, lexers) |
|
586 else: |
|
587 f = QFile(fn) |
|
588 if f.open(QIODevice.OpenModeFlag.WriteOnly): |
|
589 from E5XML.HighlightingStylesWriter import ( |
|
590 HighlightingStylesWriter |
|
591 ) |
|
592 HighlightingStylesWriter(f, lexers).writeXML() |
|
593 f.close() |
|
594 else: |
|
595 E5MessageBox.critical( |
|
596 self, |
|
597 self.tr("Export Highlighting Styles"), |
|
598 self.tr("<p>The highlighting styles file <b>{0}</b>" |
|
599 " could not be written.</p><p>Reason: {1}</p>") |
|
600 .format(fn, f.errorString()) |
|
601 ) |
|
602 |
|
603 def __importStyles(self, importAll=False): |
|
604 """ |
|
605 Private method to import the styles of lexers to be selected. |
|
606 |
|
607 @param importAll flag indicating to import all styles without asking |
|
608 (defaults to False) |
|
609 @type bool (optional) |
|
610 """ |
|
611 from eric6config import getConfig |
|
612 stylesDir = getConfig("ericStylesDir") |
|
613 |
|
614 fn = E5FileDialog.getOpenFileName( |
|
615 self, |
|
616 self.tr("Import Highlighting Styles"), |
|
617 stylesDir, |
|
618 self.tr("Highlighting Styles File (*.ehj);;" |
|
619 "XML Highlighting Styles File (*.e6h *.e4h)")) |
|
620 |
|
621 if not fn: |
|
622 return |
|
623 |
|
624 if fn.endswith(".ehj"): |
|
625 # new JSON based file |
|
626 from Preferences.HighlightingStylesFile import ( |
|
627 HighlightingStylesFile |
|
628 ) |
|
629 highlightingStylesFile = HighlightingStylesFile() |
|
630 styles = highlightingStylesFile.readFile(fn) |
|
631 if not styles: |
|
632 return |
|
633 else: |
|
634 # old XML based file |
|
635 f = QFile(fn) |
|
636 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
637 from E5XML.HighlightingStylesReader import ( |
|
638 HighlightingStylesReader |
|
639 ) |
|
640 reader = HighlightingStylesReader(f, self.lexers) |
|
641 styles = reader.readXML() |
|
642 f.close() |
|
643 if not styles: |
|
644 return |
|
645 else: |
|
646 E5MessageBox.critical( |
|
647 self, |
|
648 self.tr("Import Highlighting Styles"), |
|
649 self.tr( |
|
650 "<p>The highlighting styles file <b>{0}</b> could not" |
|
651 " be read.</p><p>Reason: {1}</p>" |
|
652 ).format(fn, f.errorString()) |
|
653 ) |
|
654 return |
|
655 |
|
656 self.__applyStyles(styles, importAll=importAll) |
|
657 self.on_lexerLanguageComboBox_activated( |
|
658 self.lexerLanguageComboBox.currentIndex()) |
|
659 |
|
660 def __applyStyles(self, stylesList, importAll=False): |
|
661 """ |
|
662 Private method to apply the imported styles to this dialog. |
|
663 |
|
664 @param stylesList list of imported lexer styles |
|
665 @type list of dict |
|
666 @param importAll flag indicating to import all styles without asking |
|
667 (defaults to False) |
|
668 @type bool (optional) |
|
669 """ |
|
670 lexerNames = [d["name"] |
|
671 for d in stylesList |
|
672 if d["name"] in self.lexers] |
|
673 |
|
674 if not importAll: |
|
675 from .EditorHighlightingStylesSelectionDialog import ( |
|
676 EditorHighlightingStylesSelectionDialog) |
|
677 dlg = EditorHighlightingStylesSelectionDialog( |
|
678 lexerNames, forImport=True) |
|
679 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
680 lexerNames = dlg.getLexerNames() |
|
681 else: |
|
682 # Cancelled by user |
|
683 return |
|
684 |
|
685 for lexerDict in stylesList: |
|
686 if lexerDict["name"] in lexerNames: |
|
687 lexer = self.lexers[lexerDict["name"]] |
|
688 for styleDict in lexerDict["styles"]: |
|
689 style = styleDict["style"] |
|
690 substyle = styleDict["substyle"] |
|
691 lexer.setColor(QColor(styleDict["color"]), style, substyle) |
|
692 lexer.setPaper(QColor(styleDict["paper"]), style, substyle) |
|
693 font = QFont() |
|
694 font.fromString(styleDict["font"]) |
|
695 lexer.setFont(font, style, substyle) |
|
696 lexer.setEolFill(styleDict["eolfill"], style, substyle) |
|
697 if substyle >= 0: |
|
698 # description and words can only be set for sub-styles |
|
699 lexer.setDescription(styleDict["description"], |
|
700 style, substyle) |
|
701 lexer.setWords(styleDict["words"], style, substyle) |
|
702 |
|
703 ####################################################################### |
|
704 ## Methods to save and restore the state |
|
705 ####################################################################### |
|
706 |
|
707 def saveState(self): |
|
708 """ |
|
709 Public method to save the current state of the widget. |
|
710 |
|
711 @return list containing the index of the selected lexer language |
|
712 and a tuple containing the index of the parent selected lexer |
|
713 entry and the index of the selected entry |
|
714 @rtype list of int and tuple of (int, int) |
|
715 """ |
|
716 itm = self.styleElementList.currentItem() |
|
717 if itm: |
|
718 parent = itm.parent() |
|
719 if parent is None: |
|
720 currentData = ( |
|
721 None, self.styleElementList.indexOfTopLevelItem(itm)) |
|
722 else: |
|
723 currentData = ( |
|
724 self.styleElementList.indexOfTopLevelItem(parent), |
|
725 parent.indexOfChild(itm) |
|
726 ) |
|
727 |
|
728 savedState = [ |
|
729 self.lexerLanguageComboBox.currentIndex(), |
|
730 currentData, |
|
731 ] |
|
732 else: |
|
733 savedState = [] |
|
734 return savedState |
|
735 |
|
736 def setState(self, state): |
|
737 """ |
|
738 Public method to set the state of the widget. |
|
739 |
|
740 @param state state data generated by saveState |
|
741 """ |
|
742 if state: |
|
743 self.lexerLanguageComboBox.setCurrentIndex(state[0]) |
|
744 self.on_lexerLanguageComboBox_activated( |
|
745 self.lexerLanguageComboBox.currentIndex()) |
|
746 |
|
747 parentIndex, index = state[1] |
|
748 if parentIndex is None: |
|
749 itm = self.styleElementList.topLevelItem(index) |
|
750 else: |
|
751 parent = self.styleElementList.topLevelItem(parentIndex) |
|
752 itm = parent.child(index) |
|
753 self.styleElementList.setCurrentItem(itm) |
|
754 |
|
755 ####################################################################### |
|
756 ## Methods to add, delete and edit sub-styles and their definitions |
|
757 ####################################################################### |
|
758 |
|
759 @pyqtSlot() |
|
760 def on_addSubstyleButton_clicked(self): |
|
761 """ |
|
762 Private slot to add a new sub-style. |
|
763 """ |
|
764 style, substyle = self.__currentStyles() |
|
765 dlg = SubstyleDefinitionDialog( |
|
766 self.lexer, style, substyle, parent=self) |
|
767 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
768 description, words = dlg.getData() |
|
769 substyle = self.lexer.addSubstyle(style) |
|
770 self.lexer.setDescription(description, style, substyle) |
|
771 self.lexer.setWords(words, style, substyle) |
|
772 |
|
773 parent = self.styleElementList.findItems( |
|
774 self.lexer.description(style), Qt.MatchFlag.MatchExactly)[0] |
|
775 parent.setExpanded(True) |
|
776 itm = QTreeWidgetItem(parent, [description]) |
|
777 itm.setData(0, self.StyleRole, style) |
|
778 itm.setData(0, self.SubstyleRole, substyle) |
|
779 self.__styleOneItem(itm, style, substyle) |
|
780 |
|
781 @pyqtSlot() |
|
782 def on_deleteSubstyleButton_clicked(self): |
|
783 """ |
|
784 Private slot to delete the selected sub-style. |
|
785 """ |
|
786 style, substyle = self.__currentStyles() |
|
787 ok = E5MessageBox.yesNo( |
|
788 self, |
|
789 self.tr("Delete Sub-Style"), |
|
790 self.tr("""<p>Shall the sub-style <b>{0}</b> really be""" |
|
791 """ deleted?</p>""").format( |
|
792 self.lexer.description(style, substyle)) |
|
793 ) |
|
794 if ok: |
|
795 self.lexer.delSubstyle(style, substyle) |
|
796 |
|
797 itm = self.styleElementList.currentItem() |
|
798 parent = itm.parent() |
|
799 index = parent.indexOfChild(itm) |
|
800 parent.takeChild(index) |
|
801 del itm |
|
802 |
|
803 @pyqtSlot() |
|
804 def on_editSubstyleButton_clicked(self): |
|
805 """ |
|
806 Private slot to edit the selected sub-style entry. |
|
807 """ |
|
808 style, substyle = self.__currentStyles() |
|
809 dlg = SubstyleDefinitionDialog( |
|
810 self.lexer, style, substyle, parent=self) |
|
811 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
812 description, words = dlg.getData() |
|
813 self.lexer.setDescription(description, style, substyle) |
|
814 self.lexer.setWords(words, style, substyle) |
|
815 |
|
816 itm = self.styleElementList.currentItem() |
|
817 itm.setText(0, description) |
|
818 |
|
819 @pyqtSlot() |
|
820 def on_copySubstyleButton_clicked(self): |
|
821 """ |
|
822 Private slot to copy the selected sub-style. |
|
823 """ |
|
824 style, substyle = self.__currentStyles() |
|
825 newSubstyle = self.lexer.addSubstyle(style) |
|
826 |
|
827 description = self.tr("{0} - Copy").format( |
|
828 self.lexer.description(style, substyle)) |
|
829 self.lexer.setDescription(description, style, newSubstyle) |
|
830 self.lexer.setWords(self.lexer.words(style, substyle), |
|
831 style, newSubstyle) |
|
832 self.lexer.setColor(self.lexer.color(style, substyle), |
|
833 style, newSubstyle) |
|
834 self.lexer.setPaper(self.lexer.paper(style, substyle), |
|
835 style, newSubstyle) |
|
836 self.lexer.setFont(self.lexer.font(style, substyle), |
|
837 style, newSubstyle) |
|
838 self.lexer.setEolFill(self.lexer.eolFill(style, substyle), |
|
839 style, newSubstyle) |
|
840 |
|
841 parent = self.styleElementList.findItems( |
|
842 self.lexer.description(style), Qt.MatchFlag.MatchExactly)[0] |
|
843 parent.setExpanded(True) |
|
844 itm = QTreeWidgetItem(parent, [description]) |
|
845 itm.setData(0, self.StyleRole, style) |
|
846 itm.setData(0, self.SubstyleRole, newSubstyle) |
|
847 self.__styleOneItem(itm, style, newSubstyle) |
|
848 |
|
849 @pyqtSlot() |
|
850 def on_defaultSubstylesButton_clicked(self): |
|
851 """ |
|
852 Private slot to reset all substyles to default values. |
|
853 """ |
|
854 style, substyle = self.__currentStyles() |
|
855 ok = E5MessageBox.yesNo( |
|
856 self, |
|
857 self.tr("Reset Sub-Styles to Default"), |
|
858 self.tr("<p>Do you really want to reset all defined sub-styles of" |
|
859 " <b>{0}</b> to the default values?</p>""") |
|
860 .format(self.lexer.description(style, substyle)) |
|
861 ) |
|
862 if ok: |
|
863 # 1. reset sub-styles |
|
864 self.lexer.loadDefaultSubStyles(style) |
|
865 |
|
866 # 2. delete all existing sub-style items |
|
867 parent = self.styleElementList.currentItem() |
|
868 while parent.childCount() > 0: |
|
869 itm = parent.takeChild(0) # __IGNORE_WARNING__ |
|
870 del itm |
|
871 |
|
872 # 3. create the new list of sub-style items |
|
873 for description, _, substyle in self.lexer.getSubStyles(style): |
|
874 itm = QTreeWidgetItem(parent, [description]) |
|
875 itm.setData(0, self.StyleRole, style) |
|
876 itm.setData(0, self.SubstyleRole, substyle) |
|
877 self.__styleOneItem(itm, style, substyle) |
|
878 |
|
879 |
|
880 def create(dlg): |
|
881 """ |
|
882 Module function to create the configuration page. |
|
883 |
|
884 @param dlg reference to the configuration dialog |
|
885 @return reference to the instantiated page (ConfigurationPageBase) |
|
886 """ |
|
887 page = EditorHighlightingStylesPage(dlg.getLexers()) |
|
888 return page |