eric6/Tasks/TaskViewer.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2005 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a task viewer and associated classes.
8
9 Tasks can be defined manually or automatically. Automatically
10 generated tasks are derived from a comment with a special
11 introductory text. This text is configurable.
12 """
13
14 from __future__ import unicode_literals
15
16 import os
17 import fnmatch
18 import threading
19
20 from PyQt5.QtCore import pyqtSignal, Qt, QThread
21 from PyQt5.QtWidgets import QHeaderView, QLineEdit, QTreeWidget, QDialog, \
22 QInputDialog, QApplication, QMenu, QAbstractItemView, QTreeWidgetItem
23
24 from E5Gui.E5Application import e5App
25 from E5Gui import E5MessageBox
26 from E5Gui.E5ProgressDialog import E5ProgressDialog
27
28 from .Task import Task
29
30 import UI.PixmapCache
31
32 import Preferences
33 import Utilities
34
35 from Utilities.AutoSaver import AutoSaver
36
37
38 class TaskViewer(QTreeWidget):
39 """
40 Class implementing the task viewer.
41
42 @signal displayFile(str, int) emitted to go to a file task
43 """
44 displayFile = pyqtSignal(str, int)
45
46 def __init__(self, parent, project):
47 """
48 Constructor
49
50 @param parent the parent (QWidget)
51 @param project reference to the project object
52 """
53 super(TaskViewer, self).__init__(parent)
54
55 self.setSortingEnabled(True)
56 self.setExpandsOnDoubleClick(False)
57
58 self.__headerItem = QTreeWidgetItem(
59 ["", "", self.tr("Summary"), self.tr("Filename"),
60 self.tr("Line"), ""])
61 self.__headerItem.setIcon(
62 0, UI.PixmapCache.getIcon("taskCompleted.png"))
63 self.__headerItem.setIcon(
64 1, UI.PixmapCache.getIcon("taskPriority.png"))
65 self.setHeaderItem(self.__headerItem)
66
67 self.header().setSortIndicator(2, Qt.AscendingOrder)
68 self.__resizeColumns()
69
70 self.tasks = []
71 self.copyTask = None
72 self.projectOpen = False
73 self.project = project
74 self.projectTasksScanFilter = ""
75
76 from .TaskFilter import TaskFilter
77 self.taskFilter = TaskFilter()
78 self.taskFilter.setActive(False)
79
80 self.__projectTasksSaveTimer = AutoSaver(self, self.saveProjectTasks)
81 self.__projectTaskExtractionThread = ProjectTaskExtractionThread()
82 self.__projectTaskExtractionThread.taskFound.connect(self.addFileTask)
83
84 self.__projectTasksMenu = QMenu(
85 self.tr("P&roject Tasks"), self)
86 self.__projectTasksMenu.addAction(
87 self.tr("&Regenerate project tasks"),
88 self.regenerateProjectTasks)
89 self.__projectTasksMenu.addSeparator()
90 self.__projectTasksMenu.addAction(
91 self.tr("&Configure scan options"),
92 self.__configureProjectTasksScanOptions)
93
94 self.__menu = QMenu(self)
95 self.__menu.addAction(self.tr("&New Task..."), self.__newTask)
96 self.subtaskItem = self.__menu.addAction(
97 self.tr("New &Sub-Task..."), self.__newSubTask)
98 self.__menu.addSeparator()
99 self.projectTasksMenuItem = self.__menu.addMenu(
100 self.__projectTasksMenu)
101 self.__menu.addSeparator()
102 self.gotoItem = self.__menu.addAction(
103 self.tr("&Go To"), self.__goToTask)
104 self.__menu.addSeparator()
105 self.copyItem = self.__menu.addAction(
106 self.tr("&Copy"), self.__copyTask)
107 self.pasteItem = self.__menu.addAction(
108 self.tr("&Paste"), self.__pasteTask)
109 self.pasteMainItem = self.__menu.addAction(
110 self.tr("Paste as &Main Task"), self.__pasteMainTask)
111 self.deleteItem = self.__menu.addAction(
112 self.tr("&Delete"), self.__deleteTask)
113 self.__menu.addSeparator()
114 self.markCompletedItem = self.__menu.addAction(
115 self.tr("&Mark Completed"), self.__markCompleted)
116 self.__menu.addAction(
117 self.tr("Delete Completed &Tasks"), self.__deleteCompleted)
118 self.__menu.addSeparator()
119 self.__menu.addAction(
120 self.tr("P&roperties..."), self.__editTaskProperties)
121 self.__menu.addSeparator()
122 self.__menuFilteredAct = self.__menu.addAction(
123 self.tr("&Filtered display"))
124 self.__menuFilteredAct.setCheckable(True)
125 self.__menuFilteredAct.setChecked(False)
126 self.__menuFilteredAct.triggered[bool].connect(self.__activateFilter)
127 self.__menu.addAction(
128 self.tr("Filter c&onfiguration..."), self.__configureFilter)
129 self.__menu.addSeparator()
130 self.__menu.addAction(
131 self.tr("Resi&ze columns"), self.__resizeColumns)
132 self.__menu.addSeparator()
133 self.__menu.addAction(self.tr("Configure..."), self.__configure)
134
135 self.__backMenu = QMenu(self)
136 self.__backMenu.addAction(self.tr("&New Task..."), self.__newTask)
137 self.__backMenu.addSeparator()
138 self.backProjectTasksMenuItem = self.__backMenu.addMenu(
139 self.__projectTasksMenu)
140 self.__backMenu.addSeparator()
141 self.backPasteItem = self.__backMenu.addAction(
142 self.tr("&Paste"), self.__pasteTask)
143 self.backPasteMainItem = self.__backMenu.addAction(
144 self.tr("Paste as &Main Task"), self.__pasteMainTask)
145 self.__backMenu.addSeparator()
146 self.backDeleteCompletedItem = self.__backMenu.addAction(
147 self.tr("Delete Completed &Tasks"), self.__deleteCompleted)
148 self.__backMenu.addSeparator()
149 self.__backMenuFilteredAct = self.__backMenu.addAction(
150 self.tr("&Filtered display"))
151 self.__backMenuFilteredAct.setCheckable(True)
152 self.__backMenuFilteredAct.setChecked(False)
153 self.__backMenuFilteredAct.triggered[bool].connect(
154 self.__activateFilter)
155 self.__backMenu.addAction(
156 self.tr("Filter c&onfiguration..."), self.__configureFilter)
157 self.__backMenu.addSeparator()
158 self.__backMenu.addAction(
159 self.tr("Resi&ze columns"), self.__resizeColumns)
160 self.__backMenu.addSeparator()
161 self.__backMenu.addAction(
162 self.tr("Configure..."), self.__configure)
163
164 self.__activating = False
165
166 self.setContextMenuPolicy(Qt.CustomContextMenu)
167 self.customContextMenuRequested.connect(self.__showContextMenu)
168 self.itemActivated.connect(self.__taskItemActivated)
169
170 self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
171
172 self.__generateTopLevelItems()
173
174 def __generateTopLevelItems(self):
175 """
176 Private method to generate the 'Extracted Tasks' item.
177 """
178 self.__extractedItem = QTreeWidgetItem(self,
179 [self.tr("Extracted Tasks")])
180 self.__manualItem = QTreeWidgetItem(self,
181 [self.tr("Manual Tasks")])
182 for itm in [self.__extractedItem, self.__manualItem]:
183 itm.setFirstColumnSpanned(True)
184 itm.setExpanded(True)
185 itm.setHidden(True)
186 font = itm.font(0)
187 font.setUnderline(True)
188 itm.setFont(0, font)
189
190 def __checkTopLevelItems(self):
191 """
192 Private slot to check the 'Extracted Tasks' item for children.
193 """
194 for itm in [self.__extractedItem, self.__manualItem]:
195 visibleCount = itm.childCount()
196 for index in range(itm.childCount()):
197 if itm.child(index).isHidden():
198 visibleCount -= 1
199 itm.setHidden(visibleCount == 0)
200
201 def __resort(self):
202 """
203 Private method to resort the tree.
204 """
205 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder())
206
207 def __resizeColumns(self):
208 """
209 Private method to resize the list columns.
210 """
211 self.header().resizeSections(QHeaderView.ResizeToContents)
212 self.header().setStretchLastSection(True)
213
214 def findParentTask(self, parentUid):
215 """
216 Public method to find a parent task by its ID.
217
218 @param parentUid uid of the parent task (string)
219 @return reference to the task (Task)
220 """
221 if not parentUid:
222 return None
223
224 parentTask = None
225 for task in self.tasks:
226 if task.getUuid() == parentUid:
227 parentTask = task
228 break
229
230 return parentTask
231
232 def __refreshDisplay(self):
233 """
234 Private method to refresh the display.
235 """
236 for task in self.tasks:
237 task.setHidden(not self.taskFilter.showTask(task))
238
239 self.__checkTopLevelItems()
240 self.__resort()
241 self.__resizeColumns()
242
243 def __taskItemActivated(self, itm, col):
244 """
245 Private slot to handle the activation of an item.
246
247 @param itm reference to the activated item (QTreeWidgetItem)
248 @param col column the item was activated in (integer)
249 """
250 if not self.__activating and \
251 itm is not self.__extractedItem and \
252 itm is not self.__manualItem:
253 self.__activating = True
254 fn = itm.getFilename()
255 if fn:
256 if os.path.exists(fn):
257 self.displayFile.emit(fn, itm.getLineno())
258 else:
259 if itm.isProjectTask():
260 self.__deleteTask(itm)
261 else:
262 self.__editTaskProperties()
263 self.__activating = False
264
265 def __showContextMenu(self, coord):
266 """
267 Private slot to show the context menu of the list.
268
269 @param coord the position of the mouse pointer (QPoint)
270 """
271 itm = self.itemAt(coord)
272 coord = self.mapToGlobal(coord)
273 if itm is None or \
274 itm is self.__extractedItem or \
275 itm is self.__manualItem:
276 self.backProjectTasksMenuItem.setEnabled(self.projectOpen)
277 if self.copyTask:
278 self.backPasteItem.setEnabled(True)
279 self.backPasteMainItem.setEnabled(True)
280 else:
281 self.backPasteItem.setEnabled(False)
282 self.backPasteMainItem.setEnabled(False)
283 self.backDeleteCompletedItem.setEnabled(
284 bool(self.tasks))
285 self.__backMenu.popup(coord)
286 else:
287 self.projectTasksMenuItem.setEnabled(self.projectOpen)
288 if itm.getFilename():
289 self.gotoItem.setEnabled(True)
290 self.deleteItem.setEnabled(True)
291 self.markCompletedItem.setEnabled(False)
292 self.copyItem.setEnabled(False)
293 self.subtaskItem.setEnabled(False)
294 else:
295 self.gotoItem.setEnabled(False)
296 self.deleteItem.setEnabled(True)
297 self.markCompletedItem.setEnabled(True)
298 self.copyItem.setEnabled(True)
299 self.subtaskItem.setEnabled(True)
300 if self.copyTask:
301 self.pasteItem.setEnabled(True)
302 self.pasteMainItem.setEnabled(True)
303 else:
304 self.pasteItem.setEnabled(False)
305 self.pasteMainItem.setEnabled(False)
306
307 self.__menu.popup(coord)
308
309 def setProjectOpen(self, o=False):
310 """
311 Public slot to set the project status.
312
313 @param o flag indicating the project status
314 """
315 self.projectOpen = o
316
317 def addTask(self, summary, priority=1, filename="", lineno=0,
318 completed=False, _time=0, isProjectTask=False,
319 taskType=Task.TypeTodo, description="", uid="",
320 parentTask=None):
321 """
322 Public slot to add a task.
323
324 @param summary summary text of the task (string)
325 @param priority priority of the task (0=high, 1=normal, 2=low)
326 @param filename filename containing the task (string)
327 @param lineno line number containing the task (integer)
328 @param completed flag indicating completion status (boolean)
329 @param _time creation time of the task (float, if 0 use current time)
330 @param isProjectTask flag indicating a task related to the current
331 project (boolean)
332 @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo,
333 Task.TypeWarning, Task.TypeNote)
334 @param description explanatory text of the task (string)
335 @param uid unique id of the task (string)
336 @param parentTask reference to the parent task item (Task)
337 @return reference to the task item (Task)
338 """
339 if parentTask:
340 parentUid = parentTask.getUuid()
341 else:
342 parentUid = ""
343 task = Task(summary, priority, filename, lineno, completed,
344 _time, isProjectTask, taskType,
345 self.project, description, uid, parentUid)
346 self.tasks.append(task)
347 if parentTask:
348 parentTask.addChild(task)
349 parentTask.setExpanded(True)
350 elif filename:
351 self.__extractedItem.addChild(task)
352 else:
353 self.__manualItem.addChild(task)
354 task.setHidden(not self.taskFilter.showTask(task))
355
356 self.__checkTopLevelItems()
357 self.__resort()
358 self.__resizeColumns()
359
360 if isProjectTask:
361 self.__projectTasksSaveTimer.changeOccurred()
362
363 return task
364
365 def addFileTask(self, summary, filename, lineno, taskType=Task.TypeTodo,
366 description=""):
367 """
368 Public slot to add a file related task.
369
370 @param summary summary text of the task (string)
371 @param filename filename containing the task (string)
372 @param lineno line number containing the task (integer)
373 @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo,
374 Task.TypeWarning, Task.TypeNote)
375 @param description explanatory text of the task (string)
376 """
377 self.addTask(summary, filename=filename, lineno=lineno,
378 isProjectTask=(
379 self.project and
380 self.project.isProjectSource(filename)),
381 taskType=taskType, description=description)
382
383 def getProjectTasks(self):
384 """
385 Public method to retrieve all project related tasks.
386
387 @return copy of tasks (list of Task)
388 """
389 tasks = [task for task in self.tasks if task.isProjectTask()]
390 return tasks[:]
391
392 def getGlobalTasks(self):
393 """
394 Public method to retrieve all non project related tasks.
395
396 @return copy of tasks (list of Task)
397 """
398 tasks = [task for task in self.tasks if not task.isProjectTask()]
399 return tasks[:]
400
401 def clearTasks(self):
402 """
403 Public slot to clear all tasks from display.
404 """
405 self.tasks = []
406 self.clear()
407 self.__generateTopLevelItems()
408
409 def clearProjectTasks(self, fileOnly=False):
410 """
411 Public slot to clear project related tasks.
412
413 @keyparam fileOnly flag indicating to clear only file related
414 project tasks (boolean)
415 """
416 for task in reversed(self.tasks[:]):
417 if (fileOnly and task.isProjectFileTask()) or \
418 (not fileOnly and task.isProjectTask()):
419 if self.copyTask == task:
420 self.copyTask = None
421 parent = task.parent()
422 parent.removeChild(task)
423 self.tasks.remove(task)
424 del task
425
426 self.__checkTopLevelItems()
427 self.__resort()
428 self.__resizeColumns()
429
430 def clearFileTasks(self, filename, conditionally=False):
431 """
432 Public slot to clear all tasks related to a file.
433
434 @param filename name of the file (string)
435 @param conditionally flag indicating to clear the tasks of the file
436 checking some conditions (boolean)
437 """
438 if conditionally:
439 if self.project and self.project.isProjectSource(filename):
440 # project related tasks will not be cleared
441 return
442 if not Preferences.getTasks("ClearOnFileClose"):
443 return
444 for task in self.tasks[:]:
445 if task.getFilename() == filename:
446 if self.copyTask == task:
447 self.copyTask = None
448 self.__extractedItem.removeChild(task)
449 self.tasks.remove(task)
450 if task.isProjectTask:
451 self.__projectTasksSaveTimer.changeOccurred()
452 del task
453
454 self.__checkTopLevelItems()
455 self.__resort()
456 self.__resizeColumns()
457
458 def __editTaskProperties(self):
459 """
460 Private slot to handle the "Properties" context menu entry.
461 """
462 from .TaskPropertiesDialog import TaskPropertiesDialog
463 task = self.currentItem()
464 dlg = TaskPropertiesDialog(task, self, self.projectOpen)
465 ro = task.getFilename() != ""
466 if ro:
467 dlg.setReadOnly()
468 if dlg.exec_() == QDialog.Accepted and not ro:
469 summary, priority, completed, isProjectTask, description = \
470 dlg.getData()
471 task.setSummary(summary)
472 task.setPriority(priority)
473 task.setCompleted(completed)
474 task.setProjectTask(isProjectTask)
475 task.setDescription(description)
476 self.__projectTasksSaveTimer.changeOccurred()
477
478 def __newTask(self):
479 """
480 Private slot to handle the "New Task" context menu entry.
481 """
482 from .TaskPropertiesDialog import TaskPropertiesDialog
483 dlg = TaskPropertiesDialog(None, self, self.projectOpen)
484 if dlg.exec_() == QDialog.Accepted:
485 summary, priority, completed, isProjectTask, description = \
486 dlg.getData()
487 self.addTask(summary, priority, completed=completed,
488 isProjectTask=isProjectTask, description=description)
489
490 def __newSubTask(self):
491 """
492 Private slot to handle the "New Sub-Task" context menu entry.
493 """
494 parentTask = self.currentItem()
495 projectTask = parentTask.isProjectTask()
496
497 from .TaskPropertiesDialog import TaskPropertiesDialog
498 dlg = TaskPropertiesDialog(None, self, self.projectOpen)
499 dlg.setSubTaskMode(projectTask)
500 if dlg.exec_() == QDialog.Accepted:
501 summary, priority, completed, isProjectTask, description = \
502 dlg.getData()
503 self.addTask(summary, priority, completed=completed,
504 isProjectTask=isProjectTask, description=description,
505 parentTask=parentTask)
506
507 def __markCompleted(self):
508 """
509 Private slot to handle the "Mark Completed" context menu entry.
510 """
511 task = self.currentItem()
512 task.setCompleted(True)
513
514 def __deleteCompleted(self):
515 """
516 Private slot to handle the "Delete Completed Tasks" context menu entry.
517 """
518 for task in reversed(self.tasks[:]):
519 if task.isCompleted():
520 if self.copyTask == task:
521 self.copyTask = None
522 parent = task.parent()
523 parent.removeChild(task)
524 self.tasks.remove(task)
525 if task.isProjectTask:
526 self.__projectTasksSaveTimer.changeOccurred()
527 del task
528
529 self.__checkTopLevelItems()
530 self.__resort()
531 self.__resizeColumns()
532
533 ci = self.currentItem()
534 if ci:
535 ind = self.indexFromItem(ci, self.currentColumn())
536 self.scrollTo(ind, QAbstractItemView.PositionAtCenter)
537
538 def __copyTask(self):
539 """
540 Private slot to handle the "Copy" context menu entry.
541 """
542 task = self.currentItem()
543 self.copyTask = task
544
545 def __pasteTask(self):
546 """
547 Private slot to handle the "Paste" context menu entry.
548 """
549 if self.copyTask:
550 parent = self.copyTask.parent()
551 if not isinstance(parent, Task):
552 parent = None
553
554 self.addTask(self.copyTask.summary,
555 priority=self.copyTask.priority,
556 completed=self.copyTask.completed,
557 description=self.copyTask.description,
558 isProjectTask=self.copyTask._isProjectTask,
559 parentTask=parent)
560
561 def __pasteMainTask(self):
562 """
563 Private slot to handle the "Paste as Main Task" context menu entry.
564 """
565 if self.copyTask:
566 self.addTask(self.copyTask.summary,
567 priority=self.copyTask.priority,
568 completed=self.copyTask.completed,
569 description=self.copyTask.description,
570 isProjectTask=self.copyTask._isProjectTask)
571
572 def __deleteSubTasks(self, task):
573 """
574 Private method to delete all sub-tasks.
575
576 @param task task to delete sub-tasks of (Task)
577 """
578 for subtask in task.takeChildren():
579 if self.copyTask == subtask:
580 self.copyTask = None
581 if subtask.childCount() > 0:
582 self.__deleteSubTasks(subtask)
583 self.tasks.remove(subtask)
584
585 def __deleteTask(self, task=None):
586 """
587 Private slot to delete a task.
588
589 @param task task to be deleted
590 @type Task
591 """
592 if task is None:
593 # called via "Delete Task" context menu entry
594 task = self.currentItem()
595
596 if self.copyTask is task:
597 self.copyTask = None
598 if task.childCount() > 0:
599 self.__deleteSubTasks(task)
600 parent = task.parent()
601 parent.removeChild(task)
602 self.tasks.remove(task)
603 if task.isProjectTask:
604 self.__projectTasksSaveTimer.changeOccurred()
605 del task
606
607 self.__checkTopLevelItems()
608 self.__resort()
609 self.__resizeColumns()
610
611 ci = self.currentItem()
612 if ci:
613 ind = self.indexFromItem(ci, self.currentColumn())
614 self.scrollTo(ind, QAbstractItemView.PositionAtCenter)
615
616 def __goToTask(self):
617 """
618 Private slot to handle the "Go To" context menu entry.
619 """
620 task = self.currentItem()
621 self.displayFile.emit(task.getFilename(), task.getLineno())
622
623 def handlePreferencesChanged(self):
624 """
625 Public slot to react to changes of the preferences.
626 """
627 for task in self.tasks:
628 task.colorizeTask()
629
630 def __activateFilter(self, on):
631 """
632 Private slot to handle the "Filtered display" context menu entry.
633
634 @param on flag indicating the filter state (boolean)
635 """
636 if on and not self.taskFilter.hasActiveFilter():
637 res = E5MessageBox.yesNo(
638 self,
639 self.tr("Activate task filter"),
640 self.tr(
641 """The task filter doesn't have any active filters."""
642 """ Do you want to configure the filter settings?"""),
643 yesDefault=True)
644 if not res:
645 on = False
646 else:
647 self.__configureFilter()
648 on = self.taskFilter.hasActiveFilter()
649
650 self.taskFilter.setActive(on)
651 self.__menuFilteredAct.setChecked(on)
652 self.__backMenuFilteredAct.setChecked(on)
653 self.__refreshDisplay()
654
655 def __configureFilter(self):
656 """
657 Private slot to handle the "Configure filter" context menu entry.
658 """
659 from .TaskFilterConfigDialog import TaskFilterConfigDialog
660 dlg = TaskFilterConfigDialog(self.taskFilter)
661 if dlg.exec_() == QDialog.Accepted:
662 dlg.configureTaskFilter(self.taskFilter)
663 self.__refreshDisplay()
664
665 def __configureProjectTasksScanOptions(self):
666 """
667 Private slot to configure scan options for project tasks.
668 """
669 scanFilter, ok = QInputDialog.getText(
670 self,
671 self.tr("Scan Filter Patterns"),
672 self.tr("Enter filename patterns of files"
673 " to be excluded separated by a comma:"),
674 QLineEdit.Normal,
675 self.projectTasksScanFilter)
676 if ok:
677 self.projectTasksScanFilter = scanFilter
678
679 def regenerateProjectTasks(self, quiet=False):
680 """
681 Public slot to regenerate project related tasks.
682
683 @param quiet flag indicating quiet operation
684 @type bool
685 """
686 markers = {
687 Task.TypeWarning:
688 Preferences.getTasks("TasksWarningMarkers").split(),
689 Task.TypeNote: Preferences.getTasks("TasksNoteMarkers").split(),
690 Task.TypeTodo: Preferences.getTasks("TasksTodoMarkers").split(),
691 Task.TypeFixme: Preferences.getTasks("TasksFixmeMarkers").split(),
692 }
693 files = self.project.pdata["SOURCES"]
694
695 # apply file filter
696 filterList = [f.strip() for f in self.projectTasksScanFilter.split(",")
697 if f.strip()]
698 if filterList:
699 for scanFilter in filterList:
700 files = [f for f in files
701 if not fnmatch.fnmatch(f, scanFilter)]
702
703 # remove all project tasks
704 self.clearProjectTasks(fileOnly=True)
705
706 # now process them
707 if quiet:
708 ppath = self.project.getProjectPath()
709 self.__projectTaskExtractionThread.scan(
710 markers, [os.path.join(ppath, f) for f in files])
711 else:
712 progress = E5ProgressDialog(
713 self.tr("Extracting project tasks..."),
714 self.tr("Abort"), 0, len(files), self.tr("%v/%m Files"))
715 progress.setMinimumDuration(0)
716 progress.setWindowTitle(self.tr("Tasks"))
717 count = 0
718
719 ppath = self.project.getProjectPath()
720 for file in files:
721 progress.setLabelText(
722 self.tr("Extracting project tasks...\n{0}").format(file))
723 progress.setValue(count)
724 QApplication.processEvents()
725 if progress.wasCanceled():
726 break
727
728 fn = os.path.join(ppath, file)
729 # read the file and split it into textlines
730 try:
731 text, encoding = Utilities.readEncodedFile(fn)
732 lines = text.splitlines()
733 except (UnicodeError, IOError):
734 count += 1
735 progress.setValue(count)
736 continue
737
738 # now search tasks and record them
739 lineIndex = 0
740 for line in lines:
741 lineIndex += 1
742 shouldBreak = False
743
744 if line.endswith("__NO-TASK__"):
745 # ignore potential task marker
746 continue
747
748 for taskType, taskMarkers in markers.items():
749 for taskMarker in taskMarkers:
750 index = line.find(taskMarker)
751 if index > -1:
752 task = line[index:]
753 self.addFileTask(task, fn, lineIndex, taskType)
754 shouldBreak = True
755 break
756 if shouldBreak:
757 break
758
759 count += 1
760
761 progress.setValue(len(files))
762
763 def __configure(self):
764 """
765 Private method to open the configuration dialog.
766 """
767 e5App().getObject("UserInterface").showPreferences("tasksPage")
768
769 def saveProjectTasks(self):
770 """
771 Public method to write the project tasks.
772 """
773 if self.projectOpen and Preferences.getProject("TasksProjectAutoSave"):
774 self.project.writeTasks()
775
776 def stopProjectTaskExtraction(self):
777 """
778 Public method to stop the project task extraction thread.
779 """
780 self.__projectTaskExtractionThread.requestInterrupt()
781 self.__projectTaskExtractionThread.wait()
782
783
784 class ProjectTaskExtractionThread(QThread):
785 """
786 Class implementing a thread to extract tasks related to a project.
787
788 @signal taskFound(str, str, int, int) emitted with the task description,
789 the file name, the line number and task type to signal the presence of
790 a task
791 """
792 taskFound = pyqtSignal(str, str, int, int)
793
794 def __init__(self, parent=None):
795 """
796 Constructor
797
798 @param parent reference to the parent object (QObject)
799 """
800 super(ProjectTaskExtractionThread, self).__init__()
801
802 self.__lock = threading.Lock()
803 self.__interrupt = False
804
805 def requestInterrupt(self):
806 """
807 Public method to request iterruption of the thread.
808 """
809 if self.isRunning():
810 self.__interrupt = True
811
812 def scan(self, markers, files):
813 """
814 Public method to scan the given list of files for tasks.
815
816 @param markers dictionary of defined task markers
817 @type dict of lists of str
818 @param files list of file names to be scanned
819 @type list of str
820 """
821 with self.__lock:
822 self.__interrupt = False
823 self.__files = files[:]
824 self.__markers = {}
825 for markerType in markers:
826 self.__markers[markerType] = markers[markerType][:]
827
828 if not self.isRunning():
829 self.start(QThread.LowPriority)
830
831 def run(self):
832 """
833 Public thread method to scan the given files.
834 """
835 with self.__lock:
836 files = self.__files[:]
837 markers = {}
838 for markerType in self.__markers:
839 markers[markerType] = self.__markers[markerType][:]
840
841 for fn in files:
842 if self.__interrupt:
843 break
844
845 # read the file and split it into textlines
846 try:
847 text, encoding = Utilities.readEncodedFile(fn)
848 lines = text.splitlines()
849 except (UnicodeError, IOError):
850 continue
851
852 # now search tasks and record them
853 lineIndex = 0
854 for line in lines:
855 if self.__interrupt:
856 break
857
858 lineIndex += 1
859 found = False
860
861 if line.endswith("__NO-TASK__"):
862 # ignore potential task marker
863 continue
864
865 for taskType, taskMarkers in markers.items():
866 for taskMarker in taskMarkers:
867 index = line.find(taskMarker)
868 if index > -1:
869 task = line[index:]
870 with self.__lock:
871 self.taskFound.emit(task, fn, lineIndex,
872 taskType)
873 found = True
874 break
875 if found:
876 break

eric ide

mercurial