|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2005 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a template viewer and associated classes. |
|
8 """ |
|
9 |
|
10 import datetime |
|
11 import os |
|
12 import re |
|
13 |
|
14 from PyQt5.QtCore import QFile, QFileInfo, QIODevice, Qt, QCoreApplication |
|
15 from PyQt5.QtWidgets import ( |
|
16 QTreeWidget, QDialog, QApplication, QMenu, QTreeWidgetItem |
|
17 ) |
|
18 |
|
19 from E5Gui.E5Application import e5App |
|
20 from E5Gui import E5MessageBox, E5FileDialog |
|
21 |
|
22 import Preferences |
|
23 |
|
24 import UI.PixmapCache |
|
25 import Utilities |
|
26 |
|
27 from .TemplatesFile import TemplatesFile |
|
28 |
|
29 |
|
30 class TemplateGroup(QTreeWidgetItem): |
|
31 """ |
|
32 Class implementing a template group. |
|
33 """ |
|
34 def __init__(self, parent, name, language="All"): |
|
35 """ |
|
36 Constructor |
|
37 |
|
38 @param parent parent widget of the template group (QWidget) |
|
39 @param name name of the group (string) |
|
40 @param language programming language for the group (string) |
|
41 """ |
|
42 self.name = name |
|
43 self.language = language |
|
44 self.entries = {} |
|
45 |
|
46 super().__init__(parent, [name]) |
|
47 |
|
48 if Preferences.getTemplates("ShowTooltip"): |
|
49 self.setToolTip(0, language) |
|
50 |
|
51 def setName(self, name): |
|
52 """ |
|
53 Public method to update the name of the group. |
|
54 |
|
55 @param name name of the group (string) |
|
56 """ |
|
57 self.name = name |
|
58 self.setText(0, name) |
|
59 |
|
60 def getName(self): |
|
61 """ |
|
62 Public method to get the name of the group. |
|
63 |
|
64 @return name of the group (string) |
|
65 """ |
|
66 return self.name |
|
67 |
|
68 def setLanguage(self, language): |
|
69 """ |
|
70 Public method to update the name of the group. |
|
71 |
|
72 @param language programming language for the group (string) |
|
73 """ |
|
74 self.language = language |
|
75 if Preferences.getTemplates("ShowTooltip"): |
|
76 self.setToolTip(0, language) |
|
77 |
|
78 def getLanguage(self): |
|
79 """ |
|
80 Public method to get the name of the group. |
|
81 |
|
82 @return language of the group (string) |
|
83 """ |
|
84 return self.language |
|
85 |
|
86 def addEntry(self, name, description, template, quiet=False): |
|
87 """ |
|
88 Public method to add a template entry to this group. |
|
89 |
|
90 @param name name of the entry (string) |
|
91 @param description description of the entry to add (string) |
|
92 @param template template text of the entry (string) |
|
93 @param quiet flag indicating quiet operation (boolean) |
|
94 """ |
|
95 if name in self.entries: |
|
96 if not quiet: |
|
97 E5MessageBox.critical( |
|
98 None, |
|
99 QCoreApplication.translate("TemplateGroup", |
|
100 "Add Template"), |
|
101 QCoreApplication.translate( |
|
102 "TemplateGroup", |
|
103 """<p>The group <b>{0}</b> already contains a""" |
|
104 """ template named <b>{1}</b>.</p>""") |
|
105 .format(self.name, name)) |
|
106 return |
|
107 |
|
108 self.entries[name] = TemplateEntry(self, name, description, template) |
|
109 |
|
110 if ( |
|
111 Preferences.getTemplates("AutoOpenGroups") and |
|
112 not self.isExpanded() |
|
113 ): |
|
114 self.setExpanded(True) |
|
115 |
|
116 def removeEntry(self, name): |
|
117 """ |
|
118 Public method to remove a template entry from this group. |
|
119 |
|
120 @param name name of the entry to be removed (string) |
|
121 """ |
|
122 if name in self.entries: |
|
123 index = self.indexOfChild(self.entries[name]) |
|
124 self.takeChild(index) |
|
125 del self.entries[name] |
|
126 |
|
127 if ( |
|
128 len(self.entries) == 0 and |
|
129 Preferences.getTemplates("AutoOpenGroups") and |
|
130 self.isExpanded() |
|
131 ): |
|
132 self.setExpanded(False) |
|
133 |
|
134 def removeAllEntries(self): |
|
135 """ |
|
136 Public method to remove all template entries of this group. |
|
137 """ |
|
138 for name in list(self.entries.keys())[:]: |
|
139 self.removeEntry(name) |
|
140 |
|
141 def hasEntry(self, name): |
|
142 """ |
|
143 Public method to check, if the group has an entry with the given name. |
|
144 |
|
145 @param name name of the entry to check for (string) |
|
146 @return flag indicating existence (boolean) |
|
147 """ |
|
148 return name in self.entries |
|
149 |
|
150 def getEntry(self, name): |
|
151 """ |
|
152 Public method to get an entry. |
|
153 |
|
154 @param name name of the entry to retrieve (string) |
|
155 @return reference to the entry (TemplateEntry) |
|
156 """ |
|
157 try: |
|
158 return self.entries[name] |
|
159 except KeyError: |
|
160 return None |
|
161 |
|
162 def getEntryNames(self, beginning): |
|
163 """ |
|
164 Public method to get the names of all entries, who's name starts with |
|
165 the given string. |
|
166 |
|
167 @param beginning string denoting the beginning of the template name |
|
168 (string) |
|
169 @return list of entry names found (list of strings) |
|
170 """ |
|
171 names = [] |
|
172 for name in self.entries: |
|
173 if name.startswith(beginning): |
|
174 names.append(name) |
|
175 |
|
176 return names |
|
177 |
|
178 def getAllEntries(self): |
|
179 """ |
|
180 Public method to retrieve all entries. |
|
181 |
|
182 @return list of all entries (list of TemplateEntry) |
|
183 """ |
|
184 return list(self.entries.values()) |
|
185 |
|
186 |
|
187 class TemplateEntry(QTreeWidgetItem): |
|
188 """ |
|
189 Class immplementing a template entry. |
|
190 """ |
|
191 def __init__(self, parent, name, description, templateText): |
|
192 """ |
|
193 Constructor |
|
194 |
|
195 @param parent parent widget of the template entry (QWidget) |
|
196 @param name name of the entry (string) |
|
197 @param description descriptive text for the template (string) |
|
198 @param templateText text of the template entry (string) |
|
199 """ |
|
200 self.name = name |
|
201 self.description = description |
|
202 self.template = templateText |
|
203 self.__extractVariables() |
|
204 |
|
205 super().__init__(parent, [self.__displayText()]) |
|
206 if Preferences.getTemplates("ShowTooltip"): |
|
207 self.setToolTip(0, self.template) |
|
208 |
|
209 def __displayText(self): |
|
210 """ |
|
211 Private method to generate the display text. |
|
212 |
|
213 @return display text (string) |
|
214 """ |
|
215 txt = ( |
|
216 "{0} - {1}".format(self.name, self.description) |
|
217 if self.description else |
|
218 self.name |
|
219 ) |
|
220 return txt |
|
221 |
|
222 def setName(self, name): |
|
223 """ |
|
224 Public method to update the name of the entry. |
|
225 |
|
226 @param name name of the entry (string) |
|
227 """ |
|
228 self.name = name |
|
229 self.setText(0, self.__displayText()) |
|
230 |
|
231 def getName(self): |
|
232 """ |
|
233 Public method to get the name of the entry. |
|
234 |
|
235 @return name of the entry (string) |
|
236 """ |
|
237 return self.name |
|
238 |
|
239 def setDescription(self, description): |
|
240 """ |
|
241 Public method to update the description of the entry. |
|
242 |
|
243 @param description description of the entry (string) |
|
244 """ |
|
245 self.description = description |
|
246 self.setText(0, self.__displayText()) |
|
247 |
|
248 def getDescription(self): |
|
249 """ |
|
250 Public method to get the description of the entry. |
|
251 |
|
252 @return description of the entry (string) |
|
253 """ |
|
254 return self.description |
|
255 |
|
256 def getGroupName(self): |
|
257 """ |
|
258 Public method to get the name of the group this entry belongs to. |
|
259 |
|
260 @return name of the group containing this entry (string) |
|
261 """ |
|
262 return self.parent().getName() |
|
263 |
|
264 def setTemplateText(self, templateText): |
|
265 """ |
|
266 Public method to update the template text. |
|
267 |
|
268 @param templateText text of the template entry (string) |
|
269 """ |
|
270 self.template = templateText |
|
271 self.__extractVariables() |
|
272 if Preferences.getTemplates("ShowTooltip"): |
|
273 self.setToolTip(0, self.template) |
|
274 |
|
275 def getTemplateText(self): |
|
276 """ |
|
277 Public method to get the template text. |
|
278 |
|
279 @return the template text (string) |
|
280 """ |
|
281 return self.template |
|
282 |
|
283 def getExpandedText(self, varDict, indent): |
|
284 """ |
|
285 Public method to get the template text with all variables expanded. |
|
286 |
|
287 @param varDict dictionary containing the texts of each variable |
|
288 with the variable name as key. |
|
289 @param indent indentation of the line receiving he expanded |
|
290 template text (string) |
|
291 @return a tuple of the expanded template text (string), the |
|
292 number of lines (integer) and the length of the last line (integer) |
|
293 """ |
|
294 txt = self.template |
|
295 for var, val in list(varDict.items()): |
|
296 txt = ( |
|
297 self.__expandFormattedVariable(var, val, txt) |
|
298 if var in self.formatedVariables else |
|
299 txt.replace(var, val) |
|
300 ) |
|
301 sepchar = Preferences.getTemplates("SeparatorChar") |
|
302 txt = txt.replace("{0}{1}".format(sepchar, sepchar), sepchar) |
|
303 prefix = "{0}{1}".format(os.linesep, indent) |
|
304 trailingEol = txt.endswith(os.linesep) |
|
305 lines = txt.splitlines() |
|
306 lineCount = len(lines) |
|
307 lineLen = len(lines[-1]) |
|
308 txt = prefix.join(lines).lstrip() |
|
309 if trailingEol: |
|
310 txt = "{0}{1}".format(txt, os.linesep) |
|
311 lineCount += 1 |
|
312 lineLen = 0 |
|
313 return txt, lineCount, lineLen |
|
314 |
|
315 def __expandFormattedVariable(self, var, val, txt): |
|
316 """ |
|
317 Private method to expand a template variable with special formatting. |
|
318 |
|
319 @param var template variable name (string) |
|
320 @param val value of the template variable (string) |
|
321 @param txt template text (string) |
|
322 @return expanded and formatted variable (string) |
|
323 """ |
|
324 t = "" |
|
325 for line in txt.splitlines(): |
|
326 ind = line.find(var) |
|
327 if ind >= 0: |
|
328 variableFormat = var[1:-1].split(':', 1)[1] |
|
329 if variableFormat == 'rl': |
|
330 prefix = line[:ind] |
|
331 postfix = line[ind + len(var):] |
|
332 for v in val.splitlines(): |
|
333 t = "{0}{1}{2}{3}{4}".format( |
|
334 t, os.linesep, prefix, v, postfix) |
|
335 elif variableFormat == 'ml': |
|
336 indent = line.replace(line.lstrip(), "") |
|
337 prefix = line[:ind] |
|
338 postfix = line[ind + len(var):] |
|
339 for count, v in enumerate(val.splitlines()): |
|
340 t = ( |
|
341 "{0}{1}{2}{3}".format(t, os.linesep, indent, v) |
|
342 if count else |
|
343 "{0}{1}{2}{3}".format(t, os.linesep, prefix, v) |
|
344 ) |
|
345 t = "{0}{1}".format(t, postfix) |
|
346 else: |
|
347 t = "{0}{1}{2}".format(t, os.linesep, line) |
|
348 else: |
|
349 t = "{0}{1}{2}".format(t, os.linesep, line) |
|
350 return "".join(t.splitlines(1)[1:]) |
|
351 |
|
352 def getVariables(self): |
|
353 """ |
|
354 Public method to get the list of variables. |
|
355 |
|
356 @return list of variables (list of strings) |
|
357 """ |
|
358 return self.variables |
|
359 |
|
360 def __extractVariables(self): |
|
361 """ |
|
362 Private method to retrieve the list of variables. |
|
363 """ |
|
364 sepchar = Preferences.getTemplates("SeparatorChar") |
|
365 variablesPattern = re.compile( |
|
366 r"""\{0}[a-zA-Z][a-zA-Z0-9_]*(?::(?:ml|rl))?\{1}""".format( |
|
367 sepchar, sepchar) |
|
368 ) |
|
369 variables = variablesPattern.findall(self.template) |
|
370 self.variables = [] |
|
371 self.formatedVariables = [] |
|
372 for var in variables: |
|
373 if var not in self.variables: |
|
374 self.variables.append(var) |
|
375 if var.find(':') >= 0 and var not in self.formatedVariables: |
|
376 self.formatedVariables.append(var) |
|
377 |
|
378 |
|
379 class TemplateViewer(QTreeWidget): |
|
380 """ |
|
381 Class implementing the template viewer. |
|
382 """ |
|
383 def __init__(self, parent, viewmanager): |
|
384 """ |
|
385 Constructor |
|
386 |
|
387 @param parent the parent (QWidget) |
|
388 @param viewmanager reference to the viewmanager object |
|
389 """ |
|
390 super().__init__(parent) |
|
391 |
|
392 self.viewmanager = viewmanager |
|
393 self.groups = {} |
|
394 |
|
395 self.setHeaderLabels(["Template"]) |
|
396 self.header().hide() |
|
397 self.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) |
|
398 self.setRootIsDecorated(True) |
|
399 self.setAlternatingRowColors(True) |
|
400 |
|
401 self.__menu = QMenu(self) |
|
402 self.applyAct = self.__menu.addAction( |
|
403 self.tr("Apply"), self.__templateItemActivated) |
|
404 self.__menu.addSeparator() |
|
405 self.__menu.addAction(self.tr("Add entry..."), self.__addEntry) |
|
406 self.__menu.addAction(self.tr("Add group..."), self.__addGroup) |
|
407 self.__menu.addAction(self.tr("Edit..."), self.__edit) |
|
408 self.__menu.addAction(self.tr("Remove"), self.__remove) |
|
409 self.__menu.addSeparator() |
|
410 self.saveAct = self.__menu.addAction(self.tr("Save"), self.save) |
|
411 self.__menu.addAction(self.tr("Import..."), self.__import) |
|
412 self.__menu.addAction(self.tr("Export..."), self.__export) |
|
413 self.__menu.addAction(self.tr("Reload"), self.__reload) |
|
414 self.__menu.addSeparator() |
|
415 self.__menu.addAction( |
|
416 self.tr("Help about Templates..."), self.__showHelp) |
|
417 self.__menu.addSeparator() |
|
418 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
|
419 |
|
420 self.__backMenu = QMenu(self) |
|
421 self.__backMenu.addAction(self.tr("Add group..."), self.__addGroup) |
|
422 self.__backMenu.addSeparator() |
|
423 self.bmSaveAct = self.__backMenu.addAction(self.tr("Save"), self.save) |
|
424 self.__backMenu.addAction(self.tr("Import..."), self.__import) |
|
425 self.bmExportAct = self.__backMenu.addAction( |
|
426 self.tr("Export..."), self.__export) |
|
427 self.__backMenu.addAction(self.tr("Reload"), self.__reload) |
|
428 self.__backMenu.addSeparator() |
|
429 self.__backMenu.addAction( |
|
430 self.tr("Help about Templates..."), self.__showHelp) |
|
431 self.__backMenu.addSeparator() |
|
432 self.__backMenu.addAction( |
|
433 self.tr("Configure..."), self.__configure) |
|
434 |
|
435 self.__activating = False |
|
436 self.__dirty = False |
|
437 |
|
438 self.__templatesFile = TemplatesFile(self) |
|
439 |
|
440 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
|
441 self.customContextMenuRequested.connect(self.__showContextMenu) |
|
442 self.itemActivated.connect(self.__templateItemActivated) |
|
443 |
|
444 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
|
445 |
|
446 def __resort(self): |
|
447 """ |
|
448 Private method to resort the tree. |
|
449 """ |
|
450 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) |
|
451 |
|
452 def __templateItemActivated(self, itm=None, col=0): |
|
453 """ |
|
454 Private slot to handle the activation of an item. |
|
455 |
|
456 @param itm reference to the activated item (QTreeWidgetItem) |
|
457 @param col column the item was activated in (integer) |
|
458 """ |
|
459 if not self.__activating: |
|
460 self.__activating = True |
|
461 itm = self.currentItem() |
|
462 if isinstance(itm, TemplateEntry): |
|
463 self.applyTemplate(itm) |
|
464 self.__activating = False |
|
465 |
|
466 def __showContextMenu(self, coord): |
|
467 """ |
|
468 Private slot to show the context menu of the list. |
|
469 |
|
470 @param coord the position of the mouse pointer (QPoint) |
|
471 """ |
|
472 itm = self.itemAt(coord) |
|
473 coord = self.mapToGlobal(coord) |
|
474 if itm is None: |
|
475 self.bmSaveAct.setEnabled(self.__dirty) |
|
476 self.bmExportAct.setEnabled(self.topLevelItemCount() != 0) |
|
477 self.__backMenu.popup(coord) |
|
478 else: |
|
479 self.applyAct.setEnabled( |
|
480 self.viewmanager.activeWindow() is not None and |
|
481 isinstance(itm, TemplateEntry)) |
|
482 self.saveAct.setEnabled(self.__dirty) |
|
483 self.__menu.popup(coord) |
|
484 |
|
485 def __addEntry(self): |
|
486 """ |
|
487 Private slot to handle the Add Entry context menu action. |
|
488 """ |
|
489 itm = self.currentItem() |
|
490 groupName = ( |
|
491 itm.getName() |
|
492 if isinstance(itm, TemplateGroup) else |
|
493 itm.getGroupName() |
|
494 ) |
|
495 |
|
496 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
497 dlg = TemplatePropertiesDialog(self) |
|
498 dlg.setSelectedGroup(groupName) |
|
499 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
500 name, description, groupName, template = dlg.getData() |
|
501 self.addEntry(groupName, name, description, template) |
|
502 self.__dirty = True |
|
503 |
|
504 def __addGroup(self): |
|
505 """ |
|
506 Private slot to handle the Add Group context menu action. |
|
507 """ |
|
508 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
509 dlg = TemplatePropertiesDialog(self, True) |
|
510 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
511 name, language = dlg.getData() |
|
512 self.addGroup(name, language) |
|
513 self.__dirty = True |
|
514 |
|
515 def __edit(self): |
|
516 """ |
|
517 Private slot to handle the Edit context menu action. |
|
518 """ |
|
519 itm = self.currentItem() |
|
520 editGroup = not isinstance(itm, TemplateEntry) |
|
521 |
|
522 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
523 dlg = TemplatePropertiesDialog(self, editGroup, itm) |
|
524 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
525 if editGroup: |
|
526 name, language = dlg.getData() |
|
527 self.changeGroup(itm.getName(), name, language) |
|
528 else: |
|
529 name, description, groupName, template = dlg.getData() |
|
530 self.changeEntry(itm, name, groupName, description, template) |
|
531 self.__dirty = True |
|
532 |
|
533 def __remove(self): |
|
534 """ |
|
535 Private slot to handle the Remove context menu action. |
|
536 """ |
|
537 itm = self.currentItem() |
|
538 res = E5MessageBox.yesNo( |
|
539 self, |
|
540 self.tr("Remove Template"), |
|
541 self.tr("""<p>Do you really want to remove <b>{0}</b>?</p>""") |
|
542 .format(itm.getName())) |
|
543 if not res: |
|
544 return |
|
545 |
|
546 if isinstance(itm, TemplateGroup): |
|
547 self.removeGroup(itm) |
|
548 else: |
|
549 self.removeEntry(itm) |
|
550 self.__dirty = True |
|
551 |
|
552 def save(self): |
|
553 """ |
|
554 Public slot to save the templates. |
|
555 """ |
|
556 if self.__dirty: |
|
557 ok = self.writeTemplates() |
|
558 if ok: |
|
559 self.__dirty = False |
|
560 |
|
561 def __import(self): |
|
562 """ |
|
563 Private slot to handle the Import context menu action. |
|
564 """ |
|
565 fn = E5FileDialog.getOpenFileName( |
|
566 self, |
|
567 self.tr("Import Templates"), |
|
568 "", |
|
569 self.tr("Templates Files (*.ecj);;" |
|
570 "XML Templates Files (*.e4c);;" |
|
571 "All Files (*)")) |
|
572 |
|
573 if fn: |
|
574 self.readTemplates(fn) |
|
575 self.__dirty = True |
|
576 |
|
577 def __export(self): |
|
578 """ |
|
579 Private slot to handle the Export context menu action. |
|
580 """ |
|
581 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
582 self, |
|
583 self.tr("Export Templates"), |
|
584 "", |
|
585 self.tr("Templates Files (*.ecj);;" |
|
586 "XML Templates Files (*.e4c);;" |
|
587 "All Files (*)"), |
|
588 "", |
|
589 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
590 |
|
591 if fn: |
|
592 ext = QFileInfo(fn).suffix() |
|
593 if not ext: |
|
594 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
595 if ex: |
|
596 fn += ex |
|
597 if os.path.exists(fn): |
|
598 ok = E5MessageBox.yesNo( |
|
599 self, |
|
600 self.tr("Export Templates"), |
|
601 self.tr("""<p>The templates file <b>{0}</b> exists""" |
|
602 """ already. Overwrite it?</p>""").format(fn)) |
|
603 else: |
|
604 ok = True |
|
605 |
|
606 if ok: |
|
607 self.writeTemplates(fn) |
|
608 |
|
609 def __reload(self): |
|
610 """ |
|
611 Private slot to reload the templates. |
|
612 """ |
|
613 if self.__dirty: |
|
614 res = E5MessageBox.yesNo( |
|
615 self, |
|
616 self.tr("Reload Templates"), |
|
617 self.tr( |
|
618 """The templates contain unsaved changes. Shall these""" |
|
619 """ changes be discarded?"""), |
|
620 icon=E5MessageBox.Warning) |
|
621 if not res: |
|
622 return |
|
623 |
|
624 self.clear() |
|
625 self.groups = {} |
|
626 |
|
627 self.readTemplates() |
|
628 |
|
629 def __showHelp(self): |
|
630 """ |
|
631 Private method to show some help. |
|
632 """ |
|
633 E5MessageBox.information( |
|
634 self, |
|
635 self.tr("Template Help"), |
|
636 self.tr( |
|
637 """<p><b>Template groups</b> are a means of grouping""" |
|
638 """ individual templates. Groups have an attribute that""" |
|
639 """ specifies, which programming language they apply for.""" |
|
640 """ In order to add template entries, at least one group""" |
|
641 """ has to be defined.</p>""" |
|
642 """<p><b>Template entries</b> are the actual templates.""" |
|
643 """ They are grouped by the template groups. Help about""" |
|
644 """ how to define them is available in the template edit""" |
|
645 """ dialog.</p>""")) |
|
646 |
|
647 def __getPredefinedVars(self): |
|
648 """ |
|
649 Private method to return predefined variables. |
|
650 |
|
651 @return dictionary of predefined variables and their values |
|
652 """ |
|
653 project = e5App().getObject("Project") |
|
654 editor = self.viewmanager.activeWindow() |
|
655 now = datetime.datetime.now() |
|
656 sepchar = Preferences.getTemplates("SeparatorChar") |
|
657 keyfmt = sepchar + "{0}" + sepchar |
|
658 varValues = { |
|
659 keyfmt.format('date'): now.date().isoformat(), |
|
660 keyfmt.format('year'): str(now.date().year), |
|
661 keyfmt.format('time'): now.time().strftime("%H:%M:%S"), |
|
662 } |
|
663 |
|
664 if project.name: |
|
665 varValues[keyfmt.format('project_name')] = project.name |
|
666 |
|
667 if project.ppath: |
|
668 varValues[keyfmt.format('project_path')] = project.ppath |
|
669 |
|
670 path_name = editor.getFileName() |
|
671 if path_name: |
|
672 dir_name, file_name = os.path.split(path_name) |
|
673 base_name, ext = os.path.splitext(file_name) |
|
674 if ext: |
|
675 ext = ext[1:] |
|
676 path_name_rel = project.getRelativePath(path_name) |
|
677 dir_name_rel = project.getRelativePath(dir_name) |
|
678 varValues.update({ |
|
679 keyfmt.format('path_name'): path_name, |
|
680 keyfmt.format('path_name_rel'): path_name_rel, |
|
681 keyfmt.format('dir_name'): dir_name, |
|
682 keyfmt.format('dir_name_rel'): dir_name_rel, |
|
683 keyfmt.format('file_name'): file_name, |
|
684 keyfmt.format('base_name'): base_name, |
|
685 keyfmt.format('ext'): ext |
|
686 }) |
|
687 |
|
688 varValues[keyfmt.format('clipboard:ml')] = ( |
|
689 QApplication.clipboard().text() |
|
690 ) |
|
691 varValues[keyfmt.format('clipboard')] = QApplication.clipboard().text() |
|
692 |
|
693 if editor.hasSelectedText(): |
|
694 varValues[keyfmt.format('cur_select:ml')] = editor.selectedText() |
|
695 varValues[keyfmt.format('cur_select')] = editor.selectedText() |
|
696 else: |
|
697 varValues[keyfmt.format('cur_select:ml')] = os.linesep |
|
698 varValues[keyfmt.format('cur_select')] = "" |
|
699 |
|
700 varValues[keyfmt.format('insertion')] = "i_n_s_e_r_t_i_o_n" |
|
701 |
|
702 varValues[keyfmt.format('select_start')] = "s_e_l_e_c_t_s_t_a_r_t" |
|
703 varValues[keyfmt.format('select_end')] = "s_e_l_e_c_t_e_n_d" |
|
704 |
|
705 return varValues |
|
706 |
|
707 def applyTemplate(self, itm): |
|
708 """ |
|
709 Public method to apply the template. |
|
710 |
|
711 @param itm reference to the template item to apply (TemplateEntry) |
|
712 """ |
|
713 editor = self.viewmanager.activeWindow() |
|
714 if editor is None: |
|
715 return |
|
716 |
|
717 ok = False |
|
718 variables = itm.getVariables() |
|
719 varValues = self.__getPredefinedVars() |
|
720 |
|
721 # Remove predefined variables from list so user doesn't have to fill |
|
722 # these values out in the dialog. |
|
723 for v in list(varValues.keys()): |
|
724 if v in variables: |
|
725 variables.remove(v) |
|
726 |
|
727 if variables: |
|
728 if Preferences.getTemplates("SingleDialog"): |
|
729 from .TemplateMultipleVariablesDialog import ( |
|
730 TemplateMultipleVariablesDialog |
|
731 ) |
|
732 dlg = TemplateMultipleVariablesDialog(variables, self) |
|
733 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
734 varValues.update(dlg.getVariables()) |
|
735 ok = True |
|
736 else: |
|
737 from .TemplateSingleVariableDialog import ( |
|
738 TemplateSingleVariableDialog |
|
739 ) |
|
740 for var in variables: |
|
741 dlg = TemplateSingleVariableDialog(var, self) |
|
742 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
743 varValues[var] = dlg.getVariable() |
|
744 else: |
|
745 return |
|
746 del dlg |
|
747 ok = True |
|
748 else: |
|
749 ok = True |
|
750 |
|
751 if ok: |
|
752 line = editor.text( |
|
753 editor.getCursorPosition()[0]).replace(os.linesep, "") |
|
754 indent = line.replace(line.lstrip(), "") |
|
755 txt, lines, count = itm.getExpandedText(varValues, indent) |
|
756 # It should be done in this way to allow undo |
|
757 editor.beginUndoAction() |
|
758 if editor.hasSelectedText(): |
|
759 line, index = editor.getSelection()[0:2] |
|
760 editor.removeSelectedText() |
|
761 else: |
|
762 line, index = editor.getCursorPosition() |
|
763 |
|
764 if lines == 1: |
|
765 count += index |
|
766 else: |
|
767 if len(indent) > 0: |
|
768 count += len(indent) |
|
769 |
|
770 if "i_n_s_e_r_t_i_o_n" in txt and "s_e_l_e_c_t" in txt: |
|
771 txt = ( |
|
772 "'Insertion and selection can not be in" |
|
773 " template together'" |
|
774 ) |
|
775 |
|
776 if "i_n_s_e_r_t_i_o_n" in txt: |
|
777 lines = 1 |
|
778 for aline in txt.splitlines(): |
|
779 count = aline.find("i_n_s_e_r_t_i_o_n") |
|
780 if count >= 0: |
|
781 txt = txt.replace("i_n_s_e_r_t_i_o_n", "") |
|
782 if lines == 1: |
|
783 count += index |
|
784 else: |
|
785 if len(indent) > 0: |
|
786 count += len(indent) |
|
787 break |
|
788 else: |
|
789 lines += 1 |
|
790 |
|
791 setselect = False |
|
792 if "s_e_l_e_c_t_s_t_a_r_t" in txt and "s_e_l_e_c_t_e_n_d" in txt: |
|
793 setselect = True |
|
794 linea = 1 |
|
795 for aline in txt.splitlines(): |
|
796 posa = aline.find("s_e_l_e_c_t_s_t_a_r_t") |
|
797 if posa >= 0: |
|
798 txt = txt.replace("s_e_l_e_c_t_s_t_a_r_t", "") |
|
799 break |
|
800 else: |
|
801 linea += 1 |
|
802 lineb = 1 |
|
803 for aline in txt.splitlines(): |
|
804 posb = aline.find("s_e_l_e_c_t_e_n_d") |
|
805 if posb >= 0: |
|
806 txt = txt.replace("s_e_l_e_c_t_e_n_d", "") |
|
807 break |
|
808 else: |
|
809 lineb += 1 |
|
810 |
|
811 editor.insert(txt) |
|
812 |
|
813 if setselect: |
|
814 editor.setSelection(line + linea - 1, posa, |
|
815 line + lineb - 1, posb) |
|
816 else: |
|
817 editor.setCursorPosition(line + lines - 1, count) |
|
818 |
|
819 editor.endUndoAction() |
|
820 editor.setFocus() |
|
821 |
|
822 def applyNamedTemplate(self, templateName, groupName=None): |
|
823 """ |
|
824 Public method to apply a template given a template name. |
|
825 |
|
826 @param templateName name of the template item to apply (string) |
|
827 @param groupName name of the group to get the entry from (string). |
|
828 None or empty means to apply the first template found with the |
|
829 given name. |
|
830 """ |
|
831 if groupName: |
|
832 if self.hasGroup(groupName): |
|
833 groups = [self.groups[groupName]] |
|
834 else: |
|
835 return |
|
836 else: |
|
837 groups = list(self.groups.values()) |
|
838 for group in groups: |
|
839 template = group.getEntry(templateName) |
|
840 if template is not None: |
|
841 self.applyTemplate(template) |
|
842 break |
|
843 |
|
844 def addEntry(self, groupName, name, description, template, quiet=False): |
|
845 """ |
|
846 Public method to add a template entry. |
|
847 |
|
848 @param groupName name of the group to add to (string) |
|
849 @param name name of the entry to add (string) |
|
850 @param description description of the entry to add (string) |
|
851 @param template template text of the entry (string) |
|
852 @param quiet flag indicating quiet operation (boolean) |
|
853 """ |
|
854 self.groups[groupName].addEntry( |
|
855 name, description, template, quiet=quiet) |
|
856 self.__resort() |
|
857 |
|
858 def hasGroup(self, name): |
|
859 """ |
|
860 Public method to check, if a group with the given name exists. |
|
861 |
|
862 @param name name of the group to be checked for (string) |
|
863 @return flag indicating an existing group (boolean) |
|
864 """ |
|
865 return name in self.groups |
|
866 |
|
867 def addGroup(self, name, language="All"): |
|
868 """ |
|
869 Public method to add a group. |
|
870 |
|
871 @param name name of the group to be added (string) |
|
872 @param language programming language for the group (string) |
|
873 """ |
|
874 if name not in self.groups: |
|
875 self.groups[name] = TemplateGroup(self, name, language) |
|
876 self.__resort() |
|
877 |
|
878 def changeGroup(self, oldname, newname, language="All"): |
|
879 """ |
|
880 Public method to rename a group. |
|
881 |
|
882 @param oldname old name of the group (string) |
|
883 @param newname new name of the group (string) |
|
884 @param language programming language for the group (string) |
|
885 """ |
|
886 if oldname != newname: |
|
887 if newname in self.groups: |
|
888 E5MessageBox.warning( |
|
889 self, |
|
890 self.tr("Edit Template Group"), |
|
891 self.tr("""<p>A template group with the name""" |
|
892 """ <b>{0}</b> already exists.</p>""") |
|
893 .format(newname)) |
|
894 return |
|
895 |
|
896 self.groups[newname] = self.groups[oldname] |
|
897 del self.groups[oldname] |
|
898 self.groups[newname].setName(newname) |
|
899 |
|
900 self.groups[newname].setLanguage(language) |
|
901 self.__resort() |
|
902 |
|
903 def getAllGroups(self): |
|
904 """ |
|
905 Public method to get all groups. |
|
906 |
|
907 @return list of all groups (list of TemplateGroup) |
|
908 """ |
|
909 return list(self.groups.values()) |
|
910 |
|
911 def getGroupNames(self): |
|
912 """ |
|
913 Public method to get all group names. |
|
914 |
|
915 @return list of all group names (list of strings) |
|
916 """ |
|
917 groups = sorted(list(self.groups.keys())[:]) |
|
918 return groups |
|
919 |
|
920 def removeGroup(self, itm): |
|
921 """ |
|
922 Public method to remove a group. |
|
923 |
|
924 @param itm template group to be removed (TemplateGroup) |
|
925 """ |
|
926 name = itm.getName() |
|
927 itm.removeAllEntries() |
|
928 index = self.indexOfTopLevelItem(itm) |
|
929 self.takeTopLevelItem(index) |
|
930 del self.groups[name] |
|
931 |
|
932 def removeEntry(self, itm): |
|
933 """ |
|
934 Public method to remove a template entry. |
|
935 |
|
936 @param itm template entry to be removed (TemplateEntry) |
|
937 """ |
|
938 groupName = itm.getGroupName() |
|
939 self.groups[groupName].removeEntry(itm.getName()) |
|
940 |
|
941 def changeEntry(self, itm, name, groupName, description, template): |
|
942 """ |
|
943 Public method to change a template entry. |
|
944 |
|
945 @param itm template entry to be changed (TemplateEntry) |
|
946 @param name new name for the entry (string) |
|
947 @param groupName name of the group the entry should belong to |
|
948 (string) |
|
949 @param description description of the entry (string) |
|
950 @param template template text of the entry (string) |
|
951 """ |
|
952 if itm.getGroupName() != groupName: |
|
953 # move entry to another group |
|
954 self.groups[itm.getGroupName()].removeEntry(itm.getName()) |
|
955 self.groups[groupName].addEntry(name, description, template) |
|
956 return |
|
957 |
|
958 if itm.getName() != name: |
|
959 # entry was renamed |
|
960 self.groups[groupName].removeEntry(itm.getName()) |
|
961 self.groups[groupName].addEntry(name, description, template) |
|
962 return |
|
963 |
|
964 tmpl = self.groups[groupName].getEntry(name) |
|
965 tmpl.setDescription(description) |
|
966 tmpl.setTemplateText(template) |
|
967 self.__resort() |
|
968 |
|
969 def writeTemplates(self, filename=None): |
|
970 """ |
|
971 Public method to write the templates data to a JSON file (.ecj). |
|
972 |
|
973 @param filename name of a templates file to write |
|
974 @type str |
|
975 @return flag indicating success |
|
976 @rtype bool |
|
977 """ |
|
978 if filename is None: |
|
979 filename = os.path.join( |
|
980 Utilities.getConfigDir(), "eric6templates.ecj") |
|
981 if filename.endswith(".ecj"): |
|
982 # new JSON based file |
|
983 res = self.__templatesFile.writeFile(filename) |
|
984 else: |
|
985 # old XML based file |
|
986 f = QFile(filename) |
|
987 ok = f.open(QIODevice.OpenModeFlag.WriteOnly) |
|
988 if not ok: |
|
989 E5MessageBox.critical( |
|
990 self, |
|
991 self.tr("Save Templates"), |
|
992 self.tr( |
|
993 "<p>The templates file <b>{0}</b> could not be" |
|
994 " written.</p>") |
|
995 .format(filename)) |
|
996 res = False |
|
997 else: |
|
998 from E5XML.TemplatesWriter import TemplatesWriter |
|
999 TemplatesWriter(f, self).writeXML() |
|
1000 f.close() |
|
1001 res = True |
|
1002 |
|
1003 return res |
|
1004 |
|
1005 def readTemplates(self, filename=None): |
|
1006 """ |
|
1007 Public method to read in the templates file (.e4c). |
|
1008 |
|
1009 @param filename name of a templates file to read |
|
1010 @type str |
|
1011 """ |
|
1012 if filename is None: |
|
1013 # new JSON based file first |
|
1014 filename = os.path.join( |
|
1015 Utilities.getConfigDir(), "eric6templates.ecj") |
|
1016 if not os.path.exists(filename): |
|
1017 # old XML based file second |
|
1018 filename = os.path.join( |
|
1019 Utilities.getConfigDir(), "eric6templates.e4c") |
|
1020 if not os.path.exists(filename): |
|
1021 return |
|
1022 |
|
1023 if filename.endswith(".ecj"): |
|
1024 self.__templatesFile.readFile(filename) |
|
1025 else: |
|
1026 f = QFile(filename) |
|
1027 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
1028 from E5XML.TemplatesReader import TemplatesReader |
|
1029 reader = TemplatesReader(f, viewer=self) |
|
1030 reader.readXML() |
|
1031 f.close() |
|
1032 else: |
|
1033 E5MessageBox.critical( |
|
1034 self, |
|
1035 self.tr("Read Templates"), |
|
1036 self.tr( |
|
1037 "<p>The templates file <b>{0}</b> could not be read." |
|
1038 "</p>") |
|
1039 .format(filename)) |
|
1040 |
|
1041 def __configure(self): |
|
1042 """ |
|
1043 Private method to open the configuration dialog. |
|
1044 """ |
|
1045 e5App().getObject("UserInterface").showPreferences("templatesPage") |
|
1046 |
|
1047 def hasTemplate(self, entryName, groupName=None): |
|
1048 """ |
|
1049 Public method to check, if an entry of the given name exists. |
|
1050 |
|
1051 @param entryName name of the entry to check for (string) |
|
1052 @param groupName name of the group to check for the entry (string). |
|
1053 None or empty means to check all groups. |
|
1054 @return flag indicating the existence (boolean) |
|
1055 """ |
|
1056 if groupName: |
|
1057 if self.hasGroup(groupName): |
|
1058 groups = [self.groups[groupName]] |
|
1059 else: |
|
1060 groups = [] |
|
1061 else: |
|
1062 groups = list(self.groups.values()) |
|
1063 |
|
1064 return any(group.hasEntry(entryName) for group in groups) |
|
1065 |
|
1066 def getTemplateNames(self, start, groupName=None): |
|
1067 """ |
|
1068 Public method to get the names of templates starting with the |
|
1069 given string. |
|
1070 |
|
1071 @param start start string of the name (string) |
|
1072 @param groupName name of the group to get the entry from (string). |
|
1073 None or empty means to look in all groups. |
|
1074 @return sorted list of matching template names (list of strings) |
|
1075 """ |
|
1076 names = [] |
|
1077 if groupName: |
|
1078 if self.hasGroup(groupName): |
|
1079 groups = [self.groups[groupName]] |
|
1080 else: |
|
1081 groups = [] |
|
1082 else: |
|
1083 groups = list(self.groups.values()) |
|
1084 for group in groups: |
|
1085 names.extend(group.getEntryNames(start)) |
|
1086 return sorted(names) |