|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2005 - 2022 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 pathlib |
|
13 import re |
|
14 |
|
15 from PyQt6.QtCore import QFile, QIODevice, Qt, QCoreApplication |
|
16 from PyQt6.QtWidgets import ( |
|
17 QTreeWidget, QDialog, QApplication, QMenu, QTreeWidgetItem |
|
18 ) |
|
19 |
|
20 from EricWidgets.EricApplication import ericApp |
|
21 from EricWidgets import EricMessageBox, EricFileDialog |
|
22 |
|
23 import Preferences |
|
24 |
|
25 import UI.PixmapCache |
|
26 import Utilities |
|
27 |
|
28 from .TemplatesFile import TemplatesFile |
|
29 |
|
30 |
|
31 class TemplateGroup(QTreeWidgetItem): |
|
32 """ |
|
33 Class implementing a template group. |
|
34 """ |
|
35 def __init__(self, parent, name, language="All"): |
|
36 """ |
|
37 Constructor |
|
38 |
|
39 @param parent parent widget of the template group (QWidget) |
|
40 @param name name of the group (string) |
|
41 @param language programming language for the group (string) |
|
42 """ |
|
43 self.name = name |
|
44 self.language = language |
|
45 self.entries = {} |
|
46 |
|
47 super().__init__(parent, [name]) |
|
48 |
|
49 if Preferences.getTemplates("ShowTooltip"): |
|
50 self.setToolTip(0, language) |
|
51 |
|
52 def setName(self, name): |
|
53 """ |
|
54 Public method to update the name of the group. |
|
55 |
|
56 @param name name of the group (string) |
|
57 """ |
|
58 self.name = name |
|
59 self.setText(0, name) |
|
60 |
|
61 def getName(self): |
|
62 """ |
|
63 Public method to get the name of the group. |
|
64 |
|
65 @return name of the group (string) |
|
66 """ |
|
67 return self.name |
|
68 |
|
69 def setLanguage(self, language): |
|
70 """ |
|
71 Public method to update the name of the group. |
|
72 |
|
73 @param language programming language for the group (string) |
|
74 """ |
|
75 self.language = language |
|
76 if Preferences.getTemplates("ShowTooltip"): |
|
77 self.setToolTip(0, language) |
|
78 |
|
79 def getLanguage(self): |
|
80 """ |
|
81 Public method to get the name of the group. |
|
82 |
|
83 @return language of the group (string) |
|
84 """ |
|
85 return self.language |
|
86 |
|
87 def addEntry(self, name, description, template, quiet=False): |
|
88 """ |
|
89 Public method to add a template entry to this group. |
|
90 |
|
91 @param name name of the entry (string) |
|
92 @param description description of the entry to add (string) |
|
93 @param template template text of the entry (string) |
|
94 @param quiet flag indicating quiet operation (boolean) |
|
95 """ |
|
96 if name in self.entries: |
|
97 if not quiet: |
|
98 EricMessageBox.critical( |
|
99 None, |
|
100 QCoreApplication.translate("TemplateGroup", |
|
101 "Add Template"), |
|
102 QCoreApplication.translate( |
|
103 "TemplateGroup", |
|
104 """<p>The group <b>{0}</b> already contains a""" |
|
105 """ template named <b>{1}</b>.</p>""") |
|
106 .format(self.name, name)) |
|
107 return |
|
108 |
|
109 self.entries[name] = TemplateEntry(self, name, description, template) |
|
110 |
|
111 if ( |
|
112 Preferences.getTemplates("AutoOpenGroups") and |
|
113 not self.isExpanded() |
|
114 ): |
|
115 self.setExpanded(True) |
|
116 |
|
117 def removeEntry(self, name): |
|
118 """ |
|
119 Public method to remove a template entry from this group. |
|
120 |
|
121 @param name name of the entry to be removed (string) |
|
122 """ |
|
123 if name in self.entries: |
|
124 index = self.indexOfChild(self.entries[name]) |
|
125 self.takeChild(index) |
|
126 del self.entries[name] |
|
127 |
|
128 if ( |
|
129 len(self.entries) == 0 and |
|
130 Preferences.getTemplates("AutoOpenGroups") and |
|
131 self.isExpanded() |
|
132 ): |
|
133 self.setExpanded(False) |
|
134 |
|
135 def removeAllEntries(self): |
|
136 """ |
|
137 Public method to remove all template entries of this group. |
|
138 """ |
|
139 for name in list(self.entries.keys())[:]: |
|
140 self.removeEntry(name) |
|
141 |
|
142 def hasEntry(self, name): |
|
143 """ |
|
144 Public method to check, if the group has an entry with the given name. |
|
145 |
|
146 @param name name of the entry to check for (string) |
|
147 @return flag indicating existence (boolean) |
|
148 """ |
|
149 return name in self.entries |
|
150 |
|
151 def getEntry(self, name): |
|
152 """ |
|
153 Public method to get an entry. |
|
154 |
|
155 @param name name of the entry to retrieve (string) |
|
156 @return reference to the entry (TemplateEntry) |
|
157 """ |
|
158 try: |
|
159 return self.entries[name] |
|
160 except KeyError: |
|
161 return None |
|
162 |
|
163 def getEntryNames(self, beginning): |
|
164 """ |
|
165 Public method to get the names of all entries, who's name starts with |
|
166 the given string. |
|
167 |
|
168 @param beginning string denoting the beginning of the template name |
|
169 (string) |
|
170 @return list of entry names found (list of strings) |
|
171 """ |
|
172 names = [] |
|
173 for name in self.entries: |
|
174 if name.startswith(beginning): |
|
175 names.append(name) |
|
176 |
|
177 return names |
|
178 |
|
179 def getAllEntries(self): |
|
180 """ |
|
181 Public method to retrieve all entries. |
|
182 |
|
183 @return list of all entries (list of TemplateEntry) |
|
184 """ |
|
185 return list(self.entries.values()) |
|
186 |
|
187 |
|
188 class TemplateEntry(QTreeWidgetItem): |
|
189 """ |
|
190 Class immplementing a template entry. |
|
191 """ |
|
192 def __init__(self, parent, name, description, templateText): |
|
193 """ |
|
194 Constructor |
|
195 |
|
196 @param parent parent widget of the template entry (QWidget) |
|
197 @param name name of the entry (string) |
|
198 @param description descriptive text for the template (string) |
|
199 @param templateText text of the template entry (string) |
|
200 """ |
|
201 self.name = name |
|
202 self.description = description |
|
203 self.template = templateText |
|
204 self.__extractVariables() |
|
205 |
|
206 super().__init__(parent, [self.__displayText()]) |
|
207 if Preferences.getTemplates("ShowTooltip"): |
|
208 self.setToolTip(0, self.template) |
|
209 |
|
210 def __displayText(self): |
|
211 """ |
|
212 Private method to generate the display text. |
|
213 |
|
214 @return display text (string) |
|
215 """ |
|
216 txt = ( |
|
217 "{0} - {1}".format(self.name, self.description) |
|
218 if self.description else |
|
219 self.name |
|
220 ) |
|
221 return txt |
|
222 |
|
223 def setName(self, name): |
|
224 """ |
|
225 Public method to update the name of the entry. |
|
226 |
|
227 @param name name of the entry (string) |
|
228 """ |
|
229 self.name = name |
|
230 self.setText(0, self.__displayText()) |
|
231 |
|
232 def getName(self): |
|
233 """ |
|
234 Public method to get the name of the entry. |
|
235 |
|
236 @return name of the entry (string) |
|
237 """ |
|
238 return self.name |
|
239 |
|
240 def setDescription(self, description): |
|
241 """ |
|
242 Public method to update the description of the entry. |
|
243 |
|
244 @param description description of the entry (string) |
|
245 """ |
|
246 self.description = description |
|
247 self.setText(0, self.__displayText()) |
|
248 |
|
249 def getDescription(self): |
|
250 """ |
|
251 Public method to get the description of the entry. |
|
252 |
|
253 @return description of the entry (string) |
|
254 """ |
|
255 return self.description |
|
256 |
|
257 def getGroupName(self): |
|
258 """ |
|
259 Public method to get the name of the group this entry belongs to. |
|
260 |
|
261 @return name of the group containing this entry (string) |
|
262 """ |
|
263 return self.parent().getName() |
|
264 |
|
265 def setTemplateText(self, templateText): |
|
266 """ |
|
267 Public method to update the template text. |
|
268 |
|
269 @param templateText text of the template entry (string) |
|
270 """ |
|
271 self.template = templateText |
|
272 self.__extractVariables() |
|
273 if Preferences.getTemplates("ShowTooltip"): |
|
274 self.setToolTip(0, self.template) |
|
275 |
|
276 def getTemplateText(self): |
|
277 """ |
|
278 Public method to get the template text. |
|
279 |
|
280 @return the template text (string) |
|
281 """ |
|
282 return self.template |
|
283 |
|
284 def getExpandedText(self, varDict, indent): |
|
285 """ |
|
286 Public method to get the template text with all variables expanded. |
|
287 |
|
288 @param varDict dictionary containing the texts of each variable |
|
289 with the variable name as key. |
|
290 @param indent indentation of the line receiving he expanded |
|
291 template text (string) |
|
292 @return a tuple of the expanded template text (string), the |
|
293 number of lines (integer) and the length of the last line (integer) |
|
294 """ |
|
295 txt = self.template |
|
296 for var, val in list(varDict.items()): |
|
297 txt = ( |
|
298 self.__expandFormattedVariable(var, val, txt) |
|
299 if var in self.formatedVariables else |
|
300 txt.replace(var, val) |
|
301 ) |
|
302 sepchar = Preferences.getTemplates("SeparatorChar") |
|
303 txt = txt.replace("{0}{1}".format(sepchar, sepchar), sepchar) |
|
304 prefix = "{0}{1}".format(os.linesep, indent) |
|
305 trailingEol = txt.endswith(os.linesep) |
|
306 lines = txt.splitlines() |
|
307 lineCount = len(lines) |
|
308 lineLen = len(lines[-1]) |
|
309 txt = prefix.join(lines).lstrip() |
|
310 if trailingEol: |
|
311 txt = "{0}{1}".format(txt, os.linesep) |
|
312 lineCount += 1 |
|
313 lineLen = 0 |
|
314 return txt, lineCount, lineLen |
|
315 |
|
316 def __expandFormattedVariable(self, var, val, txt): |
|
317 """ |
|
318 Private method to expand a template variable with special formatting. |
|
319 |
|
320 @param var template variable name (string) |
|
321 @param val value of the template variable (string) |
|
322 @param txt template text (string) |
|
323 @return expanded and formatted variable (string) |
|
324 """ |
|
325 t = "" |
|
326 for line in txt.splitlines(): |
|
327 ind = line.find(var) |
|
328 if ind >= 0: |
|
329 variableFormat = var[1:-1].split(':', 1)[1] |
|
330 if variableFormat == 'rl': |
|
331 prefix = line[:ind] |
|
332 postfix = line[ind + len(var):] |
|
333 for v in val.splitlines(): |
|
334 t = "{0}{1}{2}{3}{4}".format( |
|
335 t, os.linesep, prefix, v, postfix) |
|
336 elif variableFormat == 'ml': |
|
337 indent = line.replace(line.lstrip(), "") |
|
338 prefix = line[:ind] |
|
339 postfix = line[ind + len(var):] |
|
340 for count, v in enumerate(val.splitlines()): |
|
341 t = ( |
|
342 "{0}{1}{2}{3}".format(t, os.linesep, indent, v) |
|
343 if count else |
|
344 "{0}{1}{2}{3}".format(t, os.linesep, prefix, v) |
|
345 ) |
|
346 t = "{0}{1}".format(t, postfix) |
|
347 else: |
|
348 t = "{0}{1}{2}".format(t, os.linesep, line) |
|
349 else: |
|
350 t = "{0}{1}{2}".format(t, os.linesep, line) |
|
351 return "".join(t.splitlines(1)[1:]) |
|
352 |
|
353 def getVariables(self): |
|
354 """ |
|
355 Public method to get the list of variables. |
|
356 |
|
357 @return list of variables (list of strings) |
|
358 """ |
|
359 return self.variables |
|
360 |
|
361 def __extractVariables(self): |
|
362 """ |
|
363 Private method to retrieve the list of variables. |
|
364 """ |
|
365 sepchar = Preferences.getTemplates("SeparatorChar") |
|
366 variablesPattern = re.compile( |
|
367 r"""\{0}[a-zA-Z][a-zA-Z0-9_]*(?::(?:ml|rl))?\{1}""".format( |
|
368 sepchar, sepchar) |
|
369 ) |
|
370 variables = variablesPattern.findall(self.template) |
|
371 self.variables = [] |
|
372 self.formatedVariables = [] |
|
373 for var in variables: |
|
374 if var not in self.variables: |
|
375 self.variables.append(var) |
|
376 if var.find(':') >= 0 and var not in self.formatedVariables: |
|
377 self.formatedVariables.append(var) |
|
378 |
|
379 |
|
380 class TemplateViewer(QTreeWidget): |
|
381 """ |
|
382 Class implementing the template viewer. |
|
383 """ |
|
384 def __init__(self, parent, viewmanager): |
|
385 """ |
|
386 Constructor |
|
387 |
|
388 @param parent the parent (QWidget) |
|
389 @param viewmanager reference to the viewmanager object |
|
390 """ |
|
391 super().__init__(parent) |
|
392 |
|
393 self.viewmanager = viewmanager |
|
394 self.groups = {} |
|
395 |
|
396 self.setHeaderLabels(["Template"]) |
|
397 self.header().hide() |
|
398 self.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) |
|
399 self.setRootIsDecorated(True) |
|
400 self.setAlternatingRowColors(True) |
|
401 |
|
402 self.__menu = QMenu(self) |
|
403 self.applyAct = self.__menu.addAction( |
|
404 self.tr("Apply"), self.__templateItemActivated) |
|
405 self.__menu.addSeparator() |
|
406 self.__menu.addAction(self.tr("Add entry..."), self.__addEntry) |
|
407 self.__menu.addAction(self.tr("Add group..."), self.__addGroup) |
|
408 self.__menu.addAction(self.tr("Edit..."), self.__edit) |
|
409 self.__menu.addAction(self.tr("Remove"), self.__remove) |
|
410 self.__menu.addSeparator() |
|
411 self.saveAct = self.__menu.addAction(self.tr("Save"), self.save) |
|
412 self.__menu.addAction(self.tr("Import..."), self.__import) |
|
413 self.__menu.addAction(self.tr("Export..."), self.__export) |
|
414 self.__menu.addAction(self.tr("Reload"), self.__reload) |
|
415 self.__menu.addSeparator() |
|
416 self.__menu.addAction( |
|
417 self.tr("Help about Templates..."), self.__showHelp) |
|
418 self.__menu.addSeparator() |
|
419 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
|
420 |
|
421 self.__backMenu = QMenu(self) |
|
422 self.__backMenu.addAction(self.tr("Add group..."), self.__addGroup) |
|
423 self.__backMenu.addSeparator() |
|
424 self.bmSaveAct = self.__backMenu.addAction(self.tr("Save"), self.save) |
|
425 self.__backMenu.addAction(self.tr("Import..."), self.__import) |
|
426 self.bmExportAct = self.__backMenu.addAction( |
|
427 self.tr("Export..."), self.__export) |
|
428 self.__backMenu.addAction(self.tr("Reload"), self.__reload) |
|
429 self.__backMenu.addSeparator() |
|
430 self.__backMenu.addAction( |
|
431 self.tr("Help about Templates..."), self.__showHelp) |
|
432 self.__backMenu.addSeparator() |
|
433 self.__backMenu.addAction( |
|
434 self.tr("Configure..."), self.__configure) |
|
435 |
|
436 self.__activating = False |
|
437 self.__dirty = False |
|
438 |
|
439 self.__templatesFile = TemplatesFile(self) |
|
440 |
|
441 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
|
442 self.customContextMenuRequested.connect(self.__showContextMenu) |
|
443 self.itemActivated.connect(self.__templateItemActivated) |
|
444 |
|
445 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
|
446 |
|
447 def __resort(self): |
|
448 """ |
|
449 Private method to resort the tree. |
|
450 """ |
|
451 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) |
|
452 |
|
453 def __templateItemActivated(self, itm=None, col=0): |
|
454 """ |
|
455 Private slot to handle the activation of an item. |
|
456 |
|
457 @param itm reference to the activated item (QTreeWidgetItem) |
|
458 @param col column the item was activated in (integer) |
|
459 """ |
|
460 if not self.__activating: |
|
461 self.__activating = True |
|
462 itm = self.currentItem() |
|
463 if isinstance(itm, TemplateEntry): |
|
464 self.applyTemplate(itm) |
|
465 self.__activating = False |
|
466 |
|
467 def __showContextMenu(self, coord): |
|
468 """ |
|
469 Private slot to show the context menu of the list. |
|
470 |
|
471 @param coord the position of the mouse pointer (QPoint) |
|
472 """ |
|
473 itm = self.itemAt(coord) |
|
474 coord = self.mapToGlobal(coord) |
|
475 if itm is None: |
|
476 self.bmSaveAct.setEnabled(self.__dirty) |
|
477 self.bmExportAct.setEnabled(self.topLevelItemCount() != 0) |
|
478 self.__backMenu.popup(coord) |
|
479 else: |
|
480 self.applyAct.setEnabled( |
|
481 self.viewmanager.activeWindow() is not None and |
|
482 isinstance(itm, TemplateEntry)) |
|
483 self.saveAct.setEnabled(self.__dirty) |
|
484 self.__menu.popup(coord) |
|
485 |
|
486 def __addEntry(self): |
|
487 """ |
|
488 Private slot to handle the Add Entry context menu action. |
|
489 """ |
|
490 itm = self.currentItem() |
|
491 groupName = ( |
|
492 itm.getName() |
|
493 if isinstance(itm, TemplateGroup) else |
|
494 itm.getGroupName() |
|
495 ) |
|
496 |
|
497 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
498 dlg = TemplatePropertiesDialog(self) |
|
499 dlg.setSelectedGroup(groupName) |
|
500 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
501 name, description, groupName, template = dlg.getData() |
|
502 self.addEntry(groupName, name, description, template) |
|
503 self.__dirty = True |
|
504 |
|
505 def __addGroup(self): |
|
506 """ |
|
507 Private slot to handle the Add Group context menu action. |
|
508 """ |
|
509 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
510 dlg = TemplatePropertiesDialog(self, True) |
|
511 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
512 name, language = dlg.getData() |
|
513 self.addGroup(name, language) |
|
514 self.__dirty = True |
|
515 |
|
516 def __edit(self): |
|
517 """ |
|
518 Private slot to handle the Edit context menu action. |
|
519 """ |
|
520 itm = self.currentItem() |
|
521 editGroup = not isinstance(itm, TemplateEntry) |
|
522 |
|
523 from .TemplatePropertiesDialog import TemplatePropertiesDialog |
|
524 dlg = TemplatePropertiesDialog(self, editGroup, itm) |
|
525 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
526 if editGroup: |
|
527 name, language = dlg.getData() |
|
528 self.changeGroup(itm.getName(), name, language) |
|
529 else: |
|
530 name, description, groupName, template = dlg.getData() |
|
531 self.changeEntry(itm, name, groupName, description, template) |
|
532 self.__dirty = True |
|
533 |
|
534 def __remove(self): |
|
535 """ |
|
536 Private slot to handle the Remove context menu action. |
|
537 """ |
|
538 itm = self.currentItem() |
|
539 res = EricMessageBox.yesNo( |
|
540 self, |
|
541 self.tr("Remove Template"), |
|
542 self.tr("""<p>Do you really want to remove <b>{0}</b>?</p>""") |
|
543 .format(itm.getName())) |
|
544 if not res: |
|
545 return |
|
546 |
|
547 if isinstance(itm, TemplateGroup): |
|
548 self.removeGroup(itm) |
|
549 else: |
|
550 self.removeEntry(itm) |
|
551 self.__dirty = True |
|
552 |
|
553 def save(self): |
|
554 """ |
|
555 Public slot to save the templates. |
|
556 """ |
|
557 if self.__dirty: |
|
558 ok = self.writeTemplates() |
|
559 if ok: |
|
560 self.__dirty = False |
|
561 |
|
562 def __import(self): |
|
563 """ |
|
564 Private slot to handle the Import context menu action. |
|
565 """ |
|
566 fn = EricFileDialog.getOpenFileName( |
|
567 self, |
|
568 self.tr("Import Templates"), |
|
569 "", |
|
570 self.tr("Templates Files (*.ecj);;" |
|
571 "XML Templates Files (*.e4c);;" |
|
572 "All Files (*)")) |
|
573 |
|
574 if fn: |
|
575 self.readTemplates(fn) |
|
576 self.__dirty = True |
|
577 |
|
578 def __export(self): |
|
579 """ |
|
580 Private slot to handle the Export context menu action. |
|
581 """ |
|
582 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
|
583 self, |
|
584 self.tr("Export Templates"), |
|
585 "", |
|
586 self.tr("Templates Files (*.ecj);;" |
|
587 "All Files (*)"), |
|
588 "", |
|
589 EricFileDialog.DontConfirmOverwrite) |
|
590 |
|
591 if fn: |
|
592 fpath = pathlib.Path(fn) |
|
593 if not fpath.suffix: |
|
594 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
595 if ex: |
|
596 fpath = fpath.with_suffix(ex) |
|
597 if fpath.exists(): |
|
598 ok = EricMessageBox.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(fpath)) |
|
603 else: |
|
604 ok = True |
|
605 |
|
606 if ok: |
|
607 self.writeTemplates(str(fpath)) |
|
608 |
|
609 def __reload(self): |
|
610 """ |
|
611 Private slot to reload the templates. |
|
612 """ |
|
613 if self.__dirty: |
|
614 res = EricMessageBox.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=EricMessageBox.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 EricMessageBox.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 = ericApp().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 EricMessageBox.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(), "eric7templates.ecj") |
|
981 |
|
982 return self.__templatesFile.writeFile(filename) |
|
983 |
|
984 def readTemplates(self, filename=None): |
|
985 """ |
|
986 Public method to read in the templates file (.e4c). |
|
987 |
|
988 @param filename name of a templates file to read |
|
989 @type str |
|
990 """ |
|
991 if filename is None: |
|
992 # new JSON based file first |
|
993 filename = os.path.join( |
|
994 Utilities.getConfigDir(), "eric7templates.ecj") |
|
995 if not os.path.exists(filename): |
|
996 # old XML based file second |
|
997 filename = os.path.join( |
|
998 Utilities.getConfigDir(), "eric7templates.e4c") |
|
999 if not os.path.exists(filename): |
|
1000 return |
|
1001 |
|
1002 if filename.endswith(".ecj"): |
|
1003 self.__templatesFile.readFile(filename) |
|
1004 else: |
|
1005 f = QFile(filename) |
|
1006 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
1007 from EricXML.TemplatesReader import TemplatesReader |
|
1008 reader = TemplatesReader(f, viewer=self) |
|
1009 reader.readXML() |
|
1010 f.close() |
|
1011 else: |
|
1012 EricMessageBox.critical( |
|
1013 self, |
|
1014 self.tr("Read Templates"), |
|
1015 self.tr( |
|
1016 "<p>The templates file <b>{0}</b> could not be read." |
|
1017 "</p>") |
|
1018 .format(filename)) |
|
1019 |
|
1020 def __configure(self): |
|
1021 """ |
|
1022 Private method to open the configuration dialog. |
|
1023 """ |
|
1024 ericApp().getObject("UserInterface").showPreferences("templatesPage") |
|
1025 |
|
1026 def hasTemplate(self, entryName, groupName=None): |
|
1027 """ |
|
1028 Public method to check, if an entry of the given name exists. |
|
1029 |
|
1030 @param entryName name of the entry to check for (string) |
|
1031 @param groupName name of the group to check for the entry (string). |
|
1032 None or empty means to check all groups. |
|
1033 @return flag indicating the existence (boolean) |
|
1034 """ |
|
1035 if groupName: |
|
1036 if self.hasGroup(groupName): |
|
1037 groups = [self.groups[groupName]] |
|
1038 else: |
|
1039 groups = [] |
|
1040 else: |
|
1041 groups = list(self.groups.values()) |
|
1042 |
|
1043 return any(group.hasEntry(entryName) for group in groups) |
|
1044 |
|
1045 def getTemplateNames(self, start, groupName=None): |
|
1046 """ |
|
1047 Public method to get the names of templates starting with the |
|
1048 given string. |
|
1049 |
|
1050 @param start start string of the name (string) |
|
1051 @param groupName name of the group to get the entry from (string). |
|
1052 None or empty means to look in all groups. |
|
1053 @return sorted list of matching template names (list of strings) |
|
1054 """ |
|
1055 names = [] |
|
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 for group in groups: |
|
1064 names.extend(group.getEntryNames(start)) |
|
1065 return sorted(names) |