41 |
41 |
42 class ColorStringPlugin(QObject): |
42 class ColorStringPlugin(QObject): |
43 """ |
43 """ |
44 Class implementing the 'Color String' tool plug-in. |
44 Class implementing the 'Color String' tool plug-in. |
45 """ |
45 """ |
|
46 |
46 def __init__(self, ui): |
47 def __init__(self, ui): |
47 """ |
48 """ |
48 Constructor |
49 Constructor |
49 |
50 |
50 @param ui reference to the user interface object |
51 @param ui reference to the user interface object |
51 @type UserInterface |
52 @type UserInterface |
52 """ |
53 """ |
53 super().__init__(ui) |
54 super().__init__(ui) |
54 self.__ui = ui |
55 self.__ui = ui |
55 |
56 |
56 self.__translator = None |
57 self.__translator = None |
57 self.__loadTranslator() |
58 self.__loadTranslator() |
58 |
59 |
59 self.__initMenu() |
60 self.__initMenu() |
60 |
61 |
61 self.__editors = {} |
62 self.__editors = {} |
62 self.__mainActions = [] |
63 self.__mainActions = [] |
63 |
64 |
64 def activate(self): |
65 def activate(self): |
65 """ |
66 """ |
66 Public method to activate this plugin. |
67 Public method to activate this plugin. |
67 |
68 |
68 @return tuple of None and activation status |
69 @return tuple of None and activation status |
69 @rtype tuple of (None, bool) |
70 @rtype tuple of (None, bool) |
70 """ |
71 """ |
71 global error |
72 global error |
72 error = "" # clear previous error |
73 error = "" # clear previous error |
73 |
74 |
74 self.__ui.showMenu.connect(self.__populateMenu) |
75 self.__ui.showMenu.connect(self.__populateMenu) |
75 |
76 |
76 menu = self.__ui.getMenu("plugin_tools") |
77 menu = self.__ui.getMenu("plugin_tools") |
77 if menu is not None: |
78 if menu is not None: |
78 if not menu.isEmpty(): |
79 if not menu.isEmpty(): |
79 act = menu.addSeparator() |
80 act = menu.addSeparator() |
80 self.__mainActions.append(act) |
81 self.__mainActions.append(act) |
81 act = menu.addMenu(self.__menu) |
82 act = menu.addMenu(self.__menu) |
82 self.__mainActions.append(act) |
83 self.__mainActions.append(act) |
83 |
84 |
84 ericApp().getObject("ViewManager").editorOpenedEd.connect( |
85 ericApp().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened) |
85 self.__editorOpened) |
86 ericApp().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed) |
86 ericApp().getObject("ViewManager").editorClosedEd.connect( |
87 |
87 self.__editorClosed) |
|
88 |
|
89 for editor in ericApp().getObject("ViewManager").getOpenEditors(): |
88 for editor in ericApp().getObject("ViewManager").getOpenEditors(): |
90 self.__editorOpened(editor) |
89 self.__editorOpened(editor) |
91 |
90 |
92 return None, True |
91 return None, True |
93 |
92 |
94 def deactivate(self): |
93 def deactivate(self): |
95 """ |
94 """ |
96 Public method to deactivate this plugin. |
95 Public method to deactivate this plugin. |
97 """ |
96 """ |
98 self.__ui.showMenu.disconnect(self.__populateMenu) |
97 self.__ui.showMenu.disconnect(self.__populateMenu) |
99 |
98 |
100 menu = self.__ui.getMenu("plugin_tools") |
99 menu = self.__ui.getMenu("plugin_tools") |
101 if menu is not None: |
100 if menu is not None: |
102 for act in self.__mainActions: |
101 for act in self.__mainActions: |
103 menu.removeAction(act) |
102 menu.removeAction(act) |
104 self.__mainActions = [] |
103 self.__mainActions = [] |
105 |
104 |
106 ericApp().getObject("ViewManager").editorOpenedEd.disconnect( |
105 ericApp().getObject("ViewManager").editorOpenedEd.disconnect( |
107 self.__editorOpened) |
106 self.__editorOpened |
|
107 ) |
108 ericApp().getObject("ViewManager").editorClosedEd.disconnect( |
108 ericApp().getObject("ViewManager").editorClosedEd.disconnect( |
109 self.__editorClosed) |
109 self.__editorClosed |
110 |
110 ) |
|
111 |
111 for editor, acts in self.__editors.items(): |
112 for editor, acts in self.__editors.items(): |
112 editor.showMenu.disconnect(self.__editorShowMenu) |
113 editor.showMenu.disconnect(self.__editorShowMenu) |
113 menu = editor.getMenu("Tools") |
114 menu = editor.getMenu("Tools") |
114 if menu is not None: |
115 if menu is not None: |
115 for act in acts: |
116 for act in acts: |
116 menu.removeAction(act) |
117 menu.removeAction(act) |
117 self.__editors = {} |
118 self.__editors = {} |
118 |
119 |
119 def __loadTranslator(self): |
120 def __loadTranslator(self): |
120 """ |
121 """ |
121 Private method to load the translation file. |
122 Private method to load the translation file. |
122 """ |
123 """ |
123 if self.__ui is not None: |
124 if self.__ui is not None: |
124 loc = self.__ui.getLocale() |
125 loc = self.__ui.getLocale() |
125 if loc and loc != "C": |
126 if loc and loc != "C": |
126 locale_dir = os.path.join( |
127 locale_dir = os.path.join( |
127 os.path.dirname(__file__), "ColorString", "i18n") |
128 os.path.dirname(__file__), "ColorString", "i18n" |
|
129 ) |
128 translation = "colorstring_{0}".format(loc) |
130 translation = "colorstring_{0}".format(loc) |
129 translator = QTranslator(None) |
131 translator = QTranslator(None) |
130 loaded = translator.load(translation, locale_dir) |
132 loaded = translator.load(translation, locale_dir) |
131 if loaded: |
133 if loaded: |
132 self.__translator = translator |
134 self.__translator = translator |
133 ericApp().installTranslator(self.__translator) |
135 ericApp().installTranslator(self.__translator) |
134 else: |
136 else: |
135 print("Warning: translation file '{0}' could not be" |
137 print( |
136 " loaded.".format(translation)) |
138 "Warning: translation file '{0}' could not be" |
|
139 " loaded.".format(translation) |
|
140 ) |
137 print("Using default.") |
141 print("Using default.") |
138 |
142 |
139 def __initMenu(self): |
143 def __initMenu(self): |
140 """ |
144 """ |
141 Private method to initialize the menu. |
145 Private method to initialize the menu. |
142 """ |
146 """ |
143 self.__menu = QMenu(self.tr("Color String")) |
147 self.__menu = QMenu(self.tr("Color String")) |
144 self.__menu.addAction(self.tr("Hex Color"), self.__selectHexColor) |
148 self.__menu.addAction(self.tr("Hex Color"), self.__selectHexColor) |
145 self.__menu.addAction(self.tr("Color Name"), self.__selectColorName) |
149 self.__menu.addAction(self.tr("Color Name"), self.__selectColorName) |
146 self.__menu.addAction(self.tr("RGBA Color"), self.__selectRgbaColor) |
150 self.__menu.addAction(self.tr("RGBA Color"), self.__selectRgbaColor) |
147 self.__menu.setEnabled(False) |
151 self.__menu.setEnabled(False) |
148 |
152 |
149 def __populateMenu(self, name, menu): |
153 def __populateMenu(self, name, menu): |
150 """ |
154 """ |
151 Private slot to populate the tools menu with our entry. |
155 Private slot to populate the tools menu with our entry. |
152 |
156 |
153 @param name name of the menu |
157 @param name name of the menu |
154 @type str |
158 @type str |
155 @param menu reference to the menu to be populated |
159 @param menu reference to the menu to be populated |
156 @type QMenu |
160 @type QMenu |
157 """ |
161 """ |
158 if name not in ["Tools", "PluginTools"]: |
162 if name not in ["Tools", "PluginTools"]: |
159 return |
163 return |
160 |
164 |
161 editor = ericApp().getObject("ViewManager").activeWindow() |
165 editor = ericApp().getObject("ViewManager").activeWindow() |
162 |
166 |
163 if name == "Tools": |
167 if name == "Tools": |
164 if not menu.isEmpty(): |
168 if not menu.isEmpty(): |
165 menu.addSeparator() |
169 menu.addSeparator() |
166 act = menu.addMenu(self.__menu) |
170 act = menu.addMenu(self.__menu) |
167 act.setEnabled(editor is not None) |
171 act.setEnabled(editor is not None) |
168 elif name == "PluginTools" and self.__mainActions: |
172 elif name == "PluginTools" and self.__mainActions: |
169 self.__mainActions[-1].setEnabled(editor is not None) |
173 self.__mainActions[-1].setEnabled(editor is not None) |
170 |
174 |
171 def __editorOpened(self, editor): |
175 def __editorOpened(self, editor): |
172 """ |
176 """ |
173 Private slot called, when a new editor was opened. |
177 Private slot called, when a new editor was opened. |
174 |
178 |
175 @param editor reference to the new editor |
179 @param editor reference to the new editor |
176 @type Editor |
180 @type Editor |
177 """ |
181 """ |
178 menu = editor.getMenu("Tools") |
182 menu = editor.getMenu("Tools") |
179 if menu is not None: |
183 if menu is not None: |
183 self.__editors[editor].append(act) |
187 self.__editors[editor].append(act) |
184 act = menu.addMenu(self.__menu) |
188 act = menu.addMenu(self.__menu) |
185 self.__menu.setEnabled(True) |
189 self.__menu.setEnabled(True) |
186 self.__editors[editor].append(act) |
190 self.__editors[editor].append(act) |
187 editor.showMenu.connect(self.__editorShowMenu) |
191 editor.showMenu.connect(self.__editorShowMenu) |
188 |
192 |
189 def __editorClosed(self, editor): |
193 def __editorClosed(self, editor): |
190 """ |
194 """ |
191 Private slot called, when an editor was closed. |
195 Private slot called, when an editor was closed. |
192 |
196 |
193 @param editor reference to the editor |
197 @param editor reference to the editor |
194 @type Editor |
198 @type Editor |
195 """ |
199 """ |
196 with contextlib.suppress(KeyError): |
200 with contextlib.suppress(KeyError): |
197 del self.__editors[editor] |
201 del self.__editors[editor] |
198 if not self.__editors: |
202 if not self.__editors: |
199 self.__menu.setEnabled(False) |
203 self.__menu.setEnabled(False) |
200 |
204 |
201 def __editorShowMenu(self, menuName, menu, editor): |
205 def __editorShowMenu(self, menuName, menu, editor): |
202 """ |
206 """ |
203 Private slot called, when the the editor context menu or a submenu is |
207 Private slot called, when the the editor context menu or a submenu is |
204 about to be shown. |
208 about to be shown. |
205 |
209 |
206 @param menuName name of the menu to be shown |
210 @param menuName name of the menu to be shown |
207 @type str |
211 @type str |
208 @param menu reference to the menu |
212 @param menu reference to the menu |
209 @type QMenu |
213 @type QMenu |
210 @param editor reference to the editor |
214 @param editor reference to the editor |
211 @type Editor |
215 @type Editor |
212 """ |
216 """ |
213 if ( |
217 if menuName == "Tools" and self.__menu.menuAction() not in menu.actions(): |
214 menuName == "Tools" and |
|
215 self.__menu.menuAction() not in menu.actions() |
|
216 ): |
|
217 # Re-add our menu |
218 # Re-add our menu |
218 self.__editors[editor] = [] |
219 self.__editors[editor] = [] |
219 if not menu.isEmpty(): |
220 if not menu.isEmpty(): |
220 act = menu.addSeparator() |
221 act = menu.addSeparator() |
221 self.__editors[editor].append(act) |
222 self.__editors[editor].append(act) |
222 act = menu.addMenu(self.__menu) |
223 act = menu.addMenu(self.__menu) |
223 self.__editors[editor].append(act) |
224 self.__editors[editor].append(act) |
224 |
225 |
225 def __isHexString(self, text): |
226 def __isHexString(self, text): |
226 """ |
227 """ |
227 Private method to check, if a given text is a hex string. |
228 Private method to check, if a given text is a hex string. |
228 |
229 |
229 @param text text to check |
230 @param text text to check |
230 @type str |
231 @type str |
231 @return flag indicating a hex string |
232 @return flag indicating a hex string |
232 @rtype bool |
233 @rtype bool |
233 """ |
234 """ |
234 return all(c in "0123456789abcdefABCDEF" for c in text) |
235 return all(c in "0123456789abcdefABCDEF" for c in text) |
235 |
236 |
236 def __isValidColor(self, name): |
237 def __isValidColor(self, name): |
237 """ |
238 """ |
238 Private method to check for a valid color name. |
239 Private method to check for a valid color name. |
239 |
240 |
240 @param name color name to check |
241 @param name color name to check |
241 @type str |
242 @type str |
242 @return flag indicating a valid color name |
243 @return flag indicating a valid color name |
243 @rtype bool |
244 @rtype bool |
244 """ |
245 """ |
245 if self.__isHexString(name) and len(name) in (3, 6, 8, 9, 12): |
246 if self.__isHexString(name) and len(name) in (3, 6, 8, 9, 12): |
246 return True |
247 return True |
247 return QColor.isValidColor(name) |
248 return QColor.isValidColor(name) |
248 |
249 |
249 def __selectHexColor(self): |
250 def __selectHexColor(self): |
250 """ |
251 """ |
251 Private slot implementing the hex color string selection. |
252 Private slot implementing the hex color string selection. |
252 """ |
253 """ |
253 editor = ericApp().getObject("ViewManager").activeWindow() |
254 editor = ericApp().getObject("ViewManager").activeWindow() |
254 if editor is None: |
255 if editor is None: |
255 return |
256 return |
256 |
257 |
257 if editor.hasSelectedText(): |
258 if editor.hasSelectedText(): |
258 currColor = editor.selectedText() |
259 currColor = editor.selectedText() |
259 if not self.__isValidColor(currColor): |
260 if not self.__isValidColor(currColor): |
260 EricMessageBox.critical( |
261 EricMessageBox.critical( |
261 self.__ui, |
262 self.__ui, |
262 self.tr("Color String"), |
263 self.tr("Color String"), |
263 self.tr( |
264 self.tr( |
264 """<p>The selected string <b>{0}</b> is not a""" |
265 """<p>The selected string <b>{0}</b> is not a""" |
265 """ valid color string. Aborting!</p>""") |
266 """ valid color string. Aborting!</p>""" |
266 .format(currColor)) |
267 ).format(currColor), |
|
268 ) |
267 return |
269 return |
268 |
270 |
269 if currColor.startswith("#"): |
271 if currColor.startswith("#"): |
270 withHash = True |
272 withHash = True |
271 elif self.__isHexString(currColor): |
273 elif self.__isHexString(currColor): |
272 withHash = False |
274 withHash = False |
273 currColor = "#" + currColor |
275 currColor = "#" + currColor |
296 else: |
301 else: |
297 line, index = editor.getCursorPosition() |
302 line, index = editor.getCursorPosition() |
298 editor.insert(colorStr) |
303 editor.insert(colorStr) |
299 editor.setCursorPosition(line, index + len(colorStr)) |
304 editor.setCursorPosition(line, index + len(colorStr)) |
300 editor.endUndoAction() |
305 editor.endUndoAction() |
301 |
306 |
302 def __selectColorName(self): |
307 def __selectColorName(self): |
303 """ |
308 """ |
304 Private slot implementing the named color string selection. |
309 Private slot implementing the named color string selection. |
305 """ |
310 """ |
306 editor = ericApp().getObject("ViewManager").activeWindow() |
311 editor = ericApp().getObject("ViewManager").activeWindow() |
307 if editor is None: |
312 if editor is None: |
308 return |
313 return |
309 |
314 |
310 if editor.hasSelectedText(): |
315 if editor.hasSelectedText(): |
311 currColor = editor.selectedText() |
316 currColor = editor.selectedText() |
312 if currColor not in QColor.colorNames(): |
317 if currColor not in QColor.colorNames(): |
313 EricMessageBox.critical( |
318 EricMessageBox.critical( |
314 self.__ui, |
319 self.__ui, |
315 self.tr("Color String"), |
320 self.tr("Color String"), |
316 self.tr( |
321 self.tr( |
317 """<p>The selected string <b>{0}</b> is not a""" |
322 """<p>The selected string <b>{0}</b> is not a""" |
318 """ valid color name. Aborting!</p>""") |
323 """ valid color name. Aborting!</p>""" |
319 .format(currColor)) |
324 ).format(currColor), |
|
325 ) |
320 return |
326 return |
321 else: |
327 else: |
322 currColor = "" |
328 currColor = "" |
323 |
329 |
324 from ColorString.ColorSelectionDialog import ColorSelectionDialog |
330 from ColorString.ColorSelectionDialog import ColorSelectionDialog |
|
331 |
325 dlg = ColorSelectionDialog(currColor, self.__ui) |
332 dlg = ColorSelectionDialog(currColor, self.__ui) |
326 if dlg.exec() == QDialog.DialogCode.Accepted: |
333 if dlg.exec() == QDialog.DialogCode.Accepted: |
327 colorStr = dlg.getColor() |
334 colorStr = dlg.getColor() |
328 editor.beginUndoAction() |
335 editor.beginUndoAction() |
329 if editor.hasSelectedText(): |
336 if editor.hasSelectedText(): |
331 else: |
338 else: |
332 line, index = editor.getCursorPosition() |
339 line, index = editor.getCursorPosition() |
333 editor.insert(colorStr) |
340 editor.insert(colorStr) |
334 editor.setCursorPosition(line, index + len(colorStr)) |
341 editor.setCursorPosition(line, index + len(colorStr)) |
335 editor.endUndoAction() |
342 editor.endUndoAction() |
336 |
343 |
337 def __selectRgbaColor(self): |
344 def __selectRgbaColor(self): |
338 """ |
345 """ |
339 Private slot implementing the RGBA color string selection. |
346 Private slot implementing the RGBA color string selection. |
340 """ |
347 """ |
341 editor = ericApp().getObject("ViewManager").activeWindow() |
348 editor = ericApp().getObject("ViewManager").activeWindow() |
342 if editor is None: |
349 if editor is None: |
343 return |
350 return |
344 |
351 |
345 if editor.hasSelectedText(): |
352 if editor.hasSelectedText(): |
346 currColor = editor.selectedText() |
353 currColor = editor.selectedText() |
347 valid, rgba = self.__isValidRgbaColor(currColor) |
354 valid, rgba = self.__isValidRgbaColor(currColor) |
348 if not valid: |
355 if not valid: |
349 EricMessageBox.critical( |
356 EricMessageBox.critical( |
350 self.__ui, |
357 self.__ui, |
351 self.tr("Color String"), |
358 self.tr("Color String"), |
352 self.tr( |
359 self.tr( |
353 """<p>The selected string <b>{0}</b> is not a""" |
360 """<p>The selected string <b>{0}</b> is not a""" |
354 """ valid RGBA color string. Aborting!</p>""") |
361 """ valid RGBA color string. Aborting!</p>""" |
355 .format(currColor)) |
362 ).format(currColor), |
|
363 ) |
356 return |
364 return |
357 initColor = QColor(*rgba) |
365 initColor = QColor(*rgba) |
358 else: |
366 else: |
359 initColor = QColor() |
367 initColor = QColor() |
360 |
368 |
361 color = QColorDialog.getColor( |
369 color = QColorDialog.getColor( |
362 initColor, self.__ui, self.tr("Color String"), |
370 initColor, |
363 QColorDialog.ColorDialogOption.ShowAlphaChannel) |
371 self.__ui, |
|
372 self.tr("Color String"), |
|
373 QColorDialog.ColorDialogOption.ShowAlphaChannel, |
|
374 ) |
364 if color.isValid(): |
375 if color.isValid(): |
365 rgba = color.getRgb() |
376 rgba = color.getRgb() |
366 if rgba[-1] == 255: |
377 if rgba[-1] == 255: |
367 colorStr = "{0}, {1}, {2}".format(*rgba[:-1]) |
378 colorStr = "{0}, {1}, {2}".format(*rgba[:-1]) |
368 else: |
379 else: |
373 else: |
384 else: |
374 line, index = editor.getCursorPosition() |
385 line, index = editor.getCursorPosition() |
375 editor.insert(colorStr) |
386 editor.insert(colorStr) |
376 editor.setCursorPosition(line, index + len(colorStr)) |
387 editor.setCursorPosition(line, index + len(colorStr)) |
377 editor.endUndoAction() |
388 editor.endUndoAction() |
378 |
389 |
379 def __isValidRgbaColor(self, color): |
390 def __isValidRgbaColor(self, color): |
380 """ |
391 """ |
381 Private method to check for a valid RGBA color. |
392 Private method to check for a valid RGBA color. |
382 |
393 |
383 @param color color string to check |
394 @param color color string to check |
384 @type str |
395 @type str |
385 @return flag indicating a valid RGBA color (boolean) and a list with |
396 @return flag indicating a valid RGBA color (boolean) and a list with |
386 the RGBA components of the color (three or four integers) |
397 the RGBA components of the color (three or four integers) |
387 @rtype tuple of (bool, [int, int, int]) or (bool, [int, int, int, int]) |
398 @rtype tuple of (bool, [int, int, int]) or (bool, [int, int, int, int]) |
388 """ |
399 """ |
389 rgba = [] |
400 rgba = [] |
390 |
401 |
391 parts = color.split(",") |
402 parts = color.split(",") |
392 if len(parts) not in [3, 4]: |
403 if len(parts) not in [3, 4]: |
393 return False, [] |
404 return False, [] |
394 |
405 |
395 for part in parts: |
406 for part in parts: |
396 try: |
407 try: |
397 c = int(float(part)) if "." in part else int(part) |
408 c = int(float(part)) if "." in part else int(part) |
398 except ValueError: |
409 except ValueError: |
399 return False, [] |
410 return False, [] |
400 |
411 |
401 if c < 0 or c > 255: |
412 if c < 0 or c > 255: |
402 return False, [] |
413 return False, [] |
403 |
414 |
404 rgba.append(c) |
415 rgba.append(c) |
405 |
416 |
406 return True, rgba |
417 return True, rgba |
|
418 |
407 |
419 |
408 # |
420 # |
409 # eflag: noqa = M801 |
421 # eflag: noqa = M801 |