|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a manager object for color themes. |
|
8 """ |
|
9 |
|
10 import json |
|
11 import os |
|
12 import re |
|
13 |
|
14 from PyQt6.QtCore import QObject |
|
15 |
|
16 from EricWidgets import EricMessageBox, EricFileDialog |
|
17 |
|
18 import Globals |
|
19 import Preferences |
|
20 |
|
21 from eric7config import getConfig |
|
22 |
|
23 |
|
24 class ThemeManager(QObject): |
|
25 """ |
|
26 Class implementing a manager object for color themes. |
|
27 """ |
|
28 ColorKeyPatternList = [ |
|
29 "Diff/.*Color", |
|
30 "Editor/Colour/", |
|
31 "IRC/.*Colou?r", |
|
32 "Project/Colour", |
|
33 "Python/.*Color", |
|
34 "Scintilla/.*color", |
|
35 "Scintilla/.*paper", |
|
36 "Tasks/.*Color", |
|
37 "WebBrowser/.*Colou?r", |
|
38 ] |
|
39 ColorKeyList = [ |
|
40 "Debugger/BgColorChanged", |
|
41 "Debugger/BgColorNew", |
|
42 "UI/IconBarColor", |
|
43 "UI/LogStdErrColour", |
|
44 "UI/NotificationCriticalBackground", |
|
45 "UI/NotificationCriticalForeground", |
|
46 "UI/NotificationWarningBackground", |
|
47 "UI/NotificationWarningForeground", |
|
48 ] |
|
49 |
|
50 def __init__(self: "ThemeManager", parent: QObject = None): |
|
51 """ |
|
52 Constructor |
|
53 |
|
54 @param parent reference to the parent object (defaults to None) |
|
55 @type QObject (optional) |
|
56 """ |
|
57 super().__init__(parent) |
|
58 |
|
59 def importTheme(self: "ThemeManager") -> bool: |
|
60 """ |
|
61 Public method to import a theme file and set the colors. |
|
62 |
|
63 @return flag indicating a successful import |
|
64 @rtype bool |
|
65 """ |
|
66 filename = EricFileDialog.getOpenFileName( |
|
67 None, |
|
68 self.tr("Import Theme"), |
|
69 getConfig("ericThemesDir"), |
|
70 self.tr("eric Theme Files (*.ethj);;All Files (*)") |
|
71 ) |
|
72 if filename: |
|
73 try: |
|
74 with open(filename, "r") as f: |
|
75 jsonString = f.read() |
|
76 themeDict = json.loads(jsonString) |
|
77 except (TypeError, OSError) as err: |
|
78 EricMessageBox.critical( |
|
79 None, |
|
80 self.tr("Import Theme"), |
|
81 self.tr( |
|
82 "<p>The theme file <b>{0}</b> could not" |
|
83 " be read.</p><p>Reason: {1}</p>" |
|
84 ).format(filename, str(err)) |
|
85 ) |
|
86 return False |
|
87 |
|
88 # step 1: process stylesheet data |
|
89 stylesheetDict = themeDict["stylesheet"] |
|
90 if stylesheetDict["name"]: |
|
91 stylesheetsDir = os.path.join( |
|
92 Globals.getConfigDir(), "stylesheets") |
|
93 if not os.path.exists(stylesheetsDir): |
|
94 os.makedirs(stylesheetsDir) |
|
95 stylesheetFile = os.path.join( |
|
96 stylesheetsDir, stylesheetDict["name"]) |
|
97 ok = EricMessageBox.yesNo( |
|
98 None, |
|
99 self.tr("Import Theme"), |
|
100 self.tr( |
|
101 "The stylesheet file {0} exists already." |
|
102 " Shall it be overwritten?" |
|
103 ).format(stylesheetDict["name"]) |
|
104 ) if os.path.exists(stylesheetFile) else True |
|
105 if ok: |
|
106 try: |
|
107 with open(stylesheetFile, "w") as f: |
|
108 f.write(stylesheetDict["contents"]) |
|
109 except OSError as err: |
|
110 EricMessageBox.critical( |
|
111 None, |
|
112 self.tr("Import Theme"), |
|
113 self.tr( |
|
114 "<p>The stylesheet file <b>{0}</b> could" |
|
115 " not be written.</p><p>Reason: {1}</p>" |
|
116 ).format(stylesheetFile, str(err)) |
|
117 ) |
|
118 stylesheetFile = "" |
|
119 Preferences.setUI("StyleSheet", stylesheetFile) |
|
120 |
|
121 # step 2: transfer the color entries |
|
122 settings = Preferences.getSettings() |
|
123 colorsDict = themeDict["colors"] |
|
124 for key, value in colorsDict.items(): |
|
125 settings.setValue(key, value) |
|
126 |
|
127 Preferences.syncPreferences() |
|
128 return True |
|
129 |
|
130 return False |
|
131 |
|
132 def exportTheme(self: "ThemeManager"): |
|
133 """ |
|
134 Public method to export the current colors to a theme file. |
|
135 """ |
|
136 filename, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
|
137 None, |
|
138 self.tr("Export Theme"), |
|
139 os.path.expanduser("~"), |
|
140 self.tr("eric Theme Files (*.ethj)"), |
|
141 "", |
|
142 EricFileDialog.DontConfirmOverwrite |
|
143 ) |
|
144 if filename: |
|
145 ext = os.path.splitext(filename)[1] |
|
146 if not ext: |
|
147 filename = "{0}{1}".format( |
|
148 filename, |
|
149 selectedFilter.rsplit(None, 1)[-1][2:-1]) |
|
150 |
|
151 ok = ( |
|
152 EricMessageBox.yesNo( |
|
153 None, |
|
154 self.tr("Export Theme"), |
|
155 self.tr( |
|
156 """<p>The theme file <b>{0}</b> exists""" |
|
157 """ already. Overwrite it?</p>""").format(filename)) |
|
158 if os.path.exists(filename) else |
|
159 True |
|
160 ) |
|
161 |
|
162 if ok: |
|
163 # step 1: generate a dictionary with all color settings |
|
164 settings = Preferences.getSettings() |
|
165 colorKeyFilterRe = re.compile("|".join( |
|
166 ThemeManager.ColorKeyPatternList + |
|
167 ThemeManager.ColorKeyList |
|
168 )) |
|
169 |
|
170 keys = [k for k in settings.allKeys() |
|
171 if colorKeyFilterRe.match(k)] |
|
172 colorsDict = {} |
|
173 for key in keys: |
|
174 colorsDict[key] = settings.value(key) |
|
175 |
|
176 # step 2: read the contents of the current stylesheet |
|
177 stylesheetDict = { |
|
178 "contents": "", |
|
179 "name": "" |
|
180 } |
|
181 stylesheet = Preferences.getUI("StyleSheet") |
|
182 if stylesheet and os.path.exists(stylesheet): |
|
183 try: |
|
184 with open(stylesheet, "r") as f: |
|
185 stylesheetDict["contents"] = f.read() |
|
186 stylesheetDict["name"] = os.path.basename(stylesheet) |
|
187 except OSError as err: |
|
188 EricMessageBox.critical( |
|
189 None, |
|
190 self.tr("Export Theme"), |
|
191 self.tr( |
|
192 "<p>The stylesheet file <b>{0}</b> could not" |
|
193 " be read.</p><p>Reason: {1}</p>" |
|
194 ).format(stylesheet, str(err)) |
|
195 ) |
|
196 |
|
197 themeDict = { |
|
198 "colors": colorsDict, |
|
199 "stylesheet": stylesheetDict, |
|
200 } |
|
201 |
|
202 try: |
|
203 jsonString = json.dumps(themeDict, indent=2) |
|
204 with open(filename, "w") as f: |
|
205 f.write(jsonString) |
|
206 except (TypeError, OSError) as err: |
|
207 EricMessageBox.critical( |
|
208 None, |
|
209 self.tr("Export Theme"), |
|
210 self.tr( |
|
211 "<p>The theme file <b>{0}</b> could not" |
|
212 " be written.</p><p>Reason: {1}</p>" |
|
213 ).format(filename, str(err)) |
|
214 ) |