32 |
39 |
33 import Preferences |
40 import Preferences |
34 import Utilities |
41 import Utilities |
35 |
42 |
36 from Utilities.AutoSaver import AutoSaver |
43 from Utilities.AutoSaver import AutoSaver |
37 |
44 |
38 |
45 |
39 class TaskViewer(QTreeWidget): |
46 class TaskViewer(QTreeWidget): |
40 """ |
47 """ |
41 Class implementing the task viewer. |
48 Class implementing the task viewer. |
42 |
49 |
43 @signal displayFile(str, int) emitted to go to a file task |
50 @signal displayFile(str, int) emitted to go to a file task |
44 """ |
51 """ |
|
52 |
45 displayFile = pyqtSignal(str, int) |
53 displayFile = pyqtSignal(str, int) |
46 |
54 |
47 def __init__(self, parent, project): |
55 def __init__(self, parent, project): |
48 """ |
56 """ |
49 Constructor |
57 Constructor |
50 |
58 |
51 @param parent the parent (QWidget) |
59 @param parent the parent (QWidget) |
52 @param project reference to the project object |
60 @param project reference to the project object |
53 """ |
61 """ |
54 super().__init__(parent) |
62 super().__init__(parent) |
55 |
63 |
56 self.setSortingEnabled(True) |
64 self.setSortingEnabled(True) |
57 self.setExpandsOnDoubleClick(False) |
65 self.setExpandsOnDoubleClick(False) |
58 |
66 |
59 self.__headerItem = QTreeWidgetItem( |
67 self.__headerItem = QTreeWidgetItem( |
60 ["", "", self.tr("Summary"), self.tr("Filename"), |
68 ["", "", self.tr("Summary"), self.tr("Filename"), self.tr("Line"), ""] |
61 self.tr("Line"), ""]) |
69 ) |
62 self.__headerItem.setIcon( |
70 self.__headerItem.setIcon(0, UI.PixmapCache.getIcon("taskCompleted")) |
63 0, UI.PixmapCache.getIcon("taskCompleted")) |
71 self.__headerItem.setIcon(1, UI.PixmapCache.getIcon("taskPriority")) |
64 self.__headerItem.setIcon( |
|
65 1, UI.PixmapCache.getIcon("taskPriority")) |
|
66 self.setHeaderItem(self.__headerItem) |
72 self.setHeaderItem(self.__headerItem) |
67 |
73 |
68 self.header().setSortIndicator(2, Qt.SortOrder.AscendingOrder) |
74 self.header().setSortIndicator(2, Qt.SortOrder.AscendingOrder) |
69 self.__resizeColumns() |
75 self.__resizeColumns() |
70 |
76 |
71 self.tasks = [] |
77 self.tasks = [] |
72 self.copyTask = None |
78 self.copyTask = None |
73 self.projectOpen = False |
79 self.projectOpen = False |
74 self.project = project |
80 self.project = project |
75 self.__projectTasksScanFilter = "" |
81 self.__projectTasksScanFilter = "" |
76 |
82 |
77 from .TaskFilter import TaskFilter |
83 from .TaskFilter import TaskFilter |
|
84 |
78 self.taskFilter = TaskFilter() |
85 self.taskFilter = TaskFilter() |
79 self.taskFilter.setActive(False) |
86 self.taskFilter.setActive(False) |
80 |
87 |
81 self.__projectTasksSaveTimer = AutoSaver(self, self.saveProjectTasks) |
88 self.__projectTasksSaveTimer = AutoSaver(self, self.saveProjectTasks) |
82 self.__projectTaskExtractionThread = ProjectTaskExtractionThread() |
89 self.__projectTaskExtractionThread = ProjectTaskExtractionThread() |
83 self.__projectTaskExtractionThread.taskFound.connect(self.addFileTask) |
90 self.__projectTaskExtractionThread.taskFound.connect(self.addFileTask) |
84 |
91 |
85 self.__projectTasksMenu = QMenu( |
92 self.__projectTasksMenu = QMenu(self.tr("P&roject Tasks"), self) |
86 self.tr("P&roject Tasks"), self) |
|
87 self.__projectTasksMenu.addAction( |
93 self.__projectTasksMenu.addAction( |
88 self.tr("&Regenerate project tasks"), |
94 self.tr("&Regenerate project tasks"), self.regenerateProjectTasks |
89 self.regenerateProjectTasks) |
95 ) |
90 self.__projectTasksMenu.addSeparator() |
96 self.__projectTasksMenu.addSeparator() |
91 self.__projectTasksMenu.addAction( |
97 self.__projectTasksMenu.addAction( |
92 self.tr("&Configure scan options"), |
98 self.tr("&Configure scan options"), self.__configureProjectTasksScanOptions |
93 self.__configureProjectTasksScanOptions) |
99 ) |
94 |
100 |
95 self.__menu = QMenu(self) |
101 self.__menu = QMenu(self) |
96 self.__menu.addAction(self.tr("&New Task..."), self.__newTask) |
102 self.__menu.addAction(self.tr("&New Task..."), self.__newTask) |
97 self.subtaskItem = self.__menu.addAction( |
103 self.subtaskItem = self.__menu.addAction( |
98 self.tr("New &Sub-Task..."), self.__newSubTask) |
104 self.tr("New &Sub-Task..."), self.__newSubTask |
|
105 ) |
99 self.__menu.addSeparator() |
106 self.__menu.addSeparator() |
100 self.projectTasksMenuItem = self.__menu.addMenu( |
107 self.projectTasksMenuItem = self.__menu.addMenu(self.__projectTasksMenu) |
101 self.__projectTasksMenu) |
|
102 self.__menu.addSeparator() |
108 self.__menu.addSeparator() |
103 self.gotoItem = self.__menu.addAction( |
109 self.gotoItem = self.__menu.addAction(self.tr("&Go To"), self.__goToTask) |
104 self.tr("&Go To"), self.__goToTask) |
|
105 self.__menu.addSeparator() |
110 self.__menu.addSeparator() |
106 self.copyItem = self.__menu.addAction( |
111 self.copyItem = self.__menu.addAction(self.tr("&Copy"), self.__copyTask) |
107 self.tr("&Copy"), self.__copyTask) |
112 self.pasteItem = self.__menu.addAction(self.tr("&Paste"), self.__pasteTask) |
108 self.pasteItem = self.__menu.addAction( |
|
109 self.tr("&Paste"), self.__pasteTask) |
|
110 self.pasteMainItem = self.__menu.addAction( |
113 self.pasteMainItem = self.__menu.addAction( |
111 self.tr("Paste as &Main Task"), self.__pasteMainTask) |
114 self.tr("Paste as &Main Task"), self.__pasteMainTask |
112 self.deleteItem = self.__menu.addAction( |
115 ) |
113 self.tr("&Delete"), self.__deleteTask) |
116 self.deleteItem = self.__menu.addAction(self.tr("&Delete"), self.__deleteTask) |
114 self.__menu.addSeparator() |
117 self.__menu.addSeparator() |
115 self.markCompletedItem = self.__menu.addAction( |
118 self.markCompletedItem = self.__menu.addAction( |
116 self.tr("&Mark Completed"), self.__markCompleted) |
119 self.tr("&Mark Completed"), self.__markCompleted |
|
120 ) |
117 self.__menu.addAction( |
121 self.__menu.addAction( |
118 self.tr("Delete Completed &Tasks"), self.__deleteCompleted) |
122 self.tr("Delete Completed &Tasks"), self.__deleteCompleted |
|
123 ) |
119 self.__menu.addSeparator() |
124 self.__menu.addSeparator() |
120 self.__menu.addAction( |
125 self.__menu.addAction(self.tr("P&roperties..."), self.__editTaskProperties) |
121 self.tr("P&roperties..."), self.__editTaskProperties) |
|
122 self.__menu.addSeparator() |
126 self.__menu.addSeparator() |
123 self.__menuFilteredAct = self.__menu.addAction( |
127 self.__menuFilteredAct = self.__menu.addAction(self.tr("&Filtered display")) |
124 self.tr("&Filtered display")) |
|
125 self.__menuFilteredAct.setCheckable(True) |
128 self.__menuFilteredAct.setCheckable(True) |
126 self.__menuFilteredAct.setChecked(False) |
129 self.__menuFilteredAct.setChecked(False) |
127 self.__menuFilteredAct.triggered[bool].connect(self.__activateFilter) |
130 self.__menuFilteredAct.triggered[bool].connect(self.__activateFilter) |
128 self.__menu.addAction( |
131 self.__menu.addAction( |
129 self.tr("Filter c&onfiguration..."), self.__configureFilter) |
132 self.tr("Filter c&onfiguration..."), self.__configureFilter |
|
133 ) |
130 self.__menu.addSeparator() |
134 self.__menu.addSeparator() |
131 self.__menu.addAction( |
135 self.__menu.addAction(self.tr("Resi&ze columns"), self.__resizeColumns) |
132 self.tr("Resi&ze columns"), self.__resizeColumns) |
|
133 self.__menu.addSeparator() |
136 self.__menu.addSeparator() |
134 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
137 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
135 |
138 |
136 self.__backMenu = QMenu(self) |
139 self.__backMenu = QMenu(self) |
137 self.__backMenu.addAction(self.tr("&New Task..."), self.__newTask) |
140 self.__backMenu.addAction(self.tr("&New Task..."), self.__newTask) |
138 self.__backMenu.addSeparator() |
141 self.__backMenu.addSeparator() |
139 self.backProjectTasksMenuItem = self.__backMenu.addMenu( |
142 self.backProjectTasksMenuItem = self.__backMenu.addMenu(self.__projectTasksMenu) |
140 self.__projectTasksMenu) |
|
141 self.__backMenu.addSeparator() |
143 self.__backMenu.addSeparator() |
142 self.backPasteItem = self.__backMenu.addAction( |
144 self.backPasteItem = self.__backMenu.addAction( |
143 self.tr("&Paste"), self.__pasteTask) |
145 self.tr("&Paste"), self.__pasteTask |
|
146 ) |
144 self.backPasteMainItem = self.__backMenu.addAction( |
147 self.backPasteMainItem = self.__backMenu.addAction( |
145 self.tr("Paste as &Main Task"), self.__pasteMainTask) |
148 self.tr("Paste as &Main Task"), self.__pasteMainTask |
|
149 ) |
146 self.__backMenu.addSeparator() |
150 self.__backMenu.addSeparator() |
147 self.backDeleteCompletedItem = self.__backMenu.addAction( |
151 self.backDeleteCompletedItem = self.__backMenu.addAction( |
148 self.tr("Delete Completed &Tasks"), self.__deleteCompleted) |
152 self.tr("Delete Completed &Tasks"), self.__deleteCompleted |
|
153 ) |
149 self.__backMenu.addSeparator() |
154 self.__backMenu.addSeparator() |
150 self.__backMenuFilteredAct = self.__backMenu.addAction( |
155 self.__backMenuFilteredAct = self.__backMenu.addAction( |
151 self.tr("&Filtered display")) |
156 self.tr("&Filtered display") |
|
157 ) |
152 self.__backMenuFilteredAct.setCheckable(True) |
158 self.__backMenuFilteredAct.setCheckable(True) |
153 self.__backMenuFilteredAct.setChecked(False) |
159 self.__backMenuFilteredAct.setChecked(False) |
154 self.__backMenuFilteredAct.triggered[bool].connect( |
160 self.__backMenuFilteredAct.triggered[bool].connect(self.__activateFilter) |
155 self.__activateFilter) |
|
156 self.__backMenu.addAction( |
161 self.__backMenu.addAction( |
157 self.tr("Filter c&onfiguration..."), self.__configureFilter) |
162 self.tr("Filter c&onfiguration..."), self.__configureFilter |
|
163 ) |
158 self.__backMenu.addSeparator() |
164 self.__backMenu.addSeparator() |
159 self.__backMenu.addAction( |
165 self.__backMenu.addAction(self.tr("Resi&ze columns"), self.__resizeColumns) |
160 self.tr("Resi&ze columns"), self.__resizeColumns) |
|
161 self.__backMenu.addSeparator() |
166 self.__backMenu.addSeparator() |
162 self.__backMenu.addAction( |
167 self.__backMenu.addAction(self.tr("Configure..."), self.__configure) |
163 self.tr("Configure..."), self.__configure) |
168 |
164 |
|
165 self.__activating = False |
169 self.__activating = False |
166 |
170 |
167 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
171 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
168 self.customContextMenuRequested.connect(self.__showContextMenu) |
172 self.customContextMenuRequested.connect(self.__showContextMenu) |
169 self.itemActivated.connect(self.__taskItemActivated) |
173 self.itemActivated.connect(self.__taskItemActivated) |
170 |
174 |
171 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
175 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
172 |
176 |
173 self.__generateTopLevelItems() |
177 self.__generateTopLevelItems() |
174 |
178 |
175 def __generateTopLevelItems(self): |
179 def __generateTopLevelItems(self): |
176 """ |
180 """ |
177 Private method to generate the 'Extracted Tasks' item. |
181 Private method to generate the 'Extracted Tasks' item. |
178 """ |
182 """ |
179 self.__extractedItem = QTreeWidgetItem(self, |
183 self.__extractedItem = QTreeWidgetItem(self, [self.tr("Extracted Tasks")]) |
180 [self.tr("Extracted Tasks")]) |
184 self.__manualItem = QTreeWidgetItem(self, [self.tr("Manual Tasks")]) |
181 self.__manualItem = QTreeWidgetItem(self, |
|
182 [self.tr("Manual Tasks")]) |
|
183 for itm in [self.__extractedItem, self.__manualItem]: |
185 for itm in [self.__extractedItem, self.__manualItem]: |
184 itm.setFirstColumnSpanned(True) |
186 itm.setFirstColumnSpanned(True) |
185 itm.setExpanded(True) |
187 itm.setExpanded(True) |
186 itm.setHidden(True) |
188 itm.setHidden(True) |
187 font = itm.font(0) |
189 font = itm.font(0) |
188 font.setUnderline(True) |
190 font.setUnderline(True) |
189 itm.setFont(0, font) |
191 itm.setFont(0, font) |
190 |
192 |
191 def __checkTopLevelItems(self): |
193 def __checkTopLevelItems(self): |
192 """ |
194 """ |
193 Private slot to check the 'Extracted Tasks' item for children. |
195 Private slot to check the 'Extracted Tasks' item for children. |
194 """ |
196 """ |
195 for itm in [self.__extractedItem, self.__manualItem]: |
197 for itm in [self.__extractedItem, self.__manualItem]: |
196 visibleCount = itm.childCount() |
198 visibleCount = itm.childCount() |
197 for index in range(itm.childCount()): |
199 for index in range(itm.childCount()): |
198 if itm.child(index).isHidden(): |
200 if itm.child(index).isHidden(): |
199 visibleCount -= 1 |
201 visibleCount -= 1 |
200 itm.setHidden(visibleCount == 0) |
202 itm.setHidden(visibleCount == 0) |
201 |
203 |
202 def __resort(self): |
204 def __resort(self): |
203 """ |
205 """ |
204 Private method to resort the tree. |
206 Private method to resort the tree. |
205 """ |
207 """ |
206 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) |
208 self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) |
207 |
209 |
208 def __resizeColumns(self): |
210 def __resizeColumns(self): |
209 """ |
211 """ |
210 Private method to resize the list columns. |
212 Private method to resize the list columns. |
211 """ |
213 """ |
212 self.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
214 self.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
213 self.header().setStretchLastSection(True) |
215 self.header().setStretchLastSection(True) |
214 |
216 |
215 def findParentTask(self, parentUid): |
217 def findParentTask(self, parentUid): |
216 """ |
218 """ |
217 Public method to find a parent task by its ID. |
219 Public method to find a parent task by its ID. |
218 |
220 |
219 @param parentUid uid of the parent task (string) |
221 @param parentUid uid of the parent task (string) |
220 @return reference to the task (Task) |
222 @return reference to the task (Task) |
221 """ |
223 """ |
222 if not parentUid: |
224 if not parentUid: |
223 return None |
225 return None |
224 |
226 |
225 parentTask = None |
227 parentTask = None |
226 for task in self.tasks: |
228 for task in self.tasks: |
227 if task.getUuid() == parentUid: |
229 if task.getUuid() == parentUid: |
228 parentTask = task |
230 parentTask = task |
229 break |
231 break |
230 |
232 |
231 return parentTask |
233 return parentTask |
232 |
234 |
233 def containsTask(self, taskToTest): |
235 def containsTask(self, taskToTest): |
234 """ |
236 """ |
235 Public method to test, if a task is already in the tasks list. |
237 Public method to test, if a task is already in the tasks list. |
236 |
238 |
237 @param taskToTest task to look for |
239 @param taskToTest task to look for |
238 @type Task |
240 @type Task |
239 @return flag indicating the existence of the task |
241 @return flag indicating the existence of the task |
240 @rtype bool |
242 @rtype bool |
241 """ |
243 """ |
242 if taskToTest is None: |
244 if taskToTest is None: |
243 # play it safe |
245 # play it safe |
244 return False |
246 return False |
245 |
247 |
246 return any( |
248 return any( |
247 (task.summary == taskToTest.summary) and |
249 (task.summary == taskToTest.summary) |
248 (task.filename == taskToTest.filename) and |
250 and (task.filename == taskToTest.filename) |
249 (task.lineno == taskToTest.lineno) |
251 and (task.lineno == taskToTest.lineno) |
250 for task in self.tasks |
252 for task in self.tasks |
251 ) |
253 ) |
252 |
254 |
253 def __refreshDisplay(self): |
255 def __refreshDisplay(self): |
254 """ |
256 """ |
255 Private method to refresh the display. |
257 Private method to refresh the display. |
256 """ |
258 """ |
257 for task in self.tasks: |
259 for task in self.tasks: |
258 task.setHidden(not self.taskFilter.showTask(task)) |
260 task.setHidden(not self.taskFilter.showTask(task)) |
259 |
261 |
260 self.__checkTopLevelItems() |
262 self.__checkTopLevelItems() |
261 self.__resort() |
263 self.__resort() |
262 self.__resizeColumns() |
264 self.__resizeColumns() |
263 |
265 |
264 def __taskItemActivated(self, itm, col): |
266 def __taskItemActivated(self, itm, col): |
265 """ |
267 """ |
266 Private slot to handle the activation of an item. |
268 Private slot to handle the activation of an item. |
267 |
269 |
268 @param itm reference to the activated item (QTreeWidgetItem) |
270 @param itm reference to the activated item (QTreeWidgetItem) |
269 @param col column the item was activated in (integer) |
271 @param col column the item was activated in (integer) |
270 """ |
272 """ |
271 if ( |
273 if ( |
272 not self.__activating and |
274 not self.__activating |
273 itm is not self.__extractedItem and |
275 and itm is not self.__extractedItem |
274 itm is not self.__manualItem |
276 and itm is not self.__manualItem |
275 ): |
277 ): |
276 self.__activating = True |
278 self.__activating = True |
277 fn = itm.getFilename() |
279 fn = itm.getFilename() |
278 if fn: |
280 if fn: |
279 if os.path.exists(fn): |
281 if os.path.exists(fn): |
426 @param taskType type of the task |
445 @param taskType type of the task |
427 @type TaskType |
446 @type TaskType |
428 @param description explanatory text of the task |
447 @param description explanatory text of the task |
429 @type str |
448 @type str |
430 """ |
449 """ |
431 self.addTask(summary, filename=filename, lineno=lineno, |
450 self.addTask( |
432 isProjectTask=( |
451 summary, |
433 self.project and |
452 filename=filename, |
434 self.project.isProjectSource(filename)), |
453 lineno=lineno, |
435 taskType=TaskType(taskType), description=description) |
454 isProjectTask=(self.project and self.project.isProjectSource(filename)), |
436 |
455 taskType=TaskType(taskType), |
|
456 description=description, |
|
457 ) |
|
458 |
437 def getProjectTasks(self): |
459 def getProjectTasks(self): |
438 """ |
460 """ |
439 Public method to retrieve all project related tasks. |
461 Public method to retrieve all project related tasks. |
440 |
462 |
441 @return copy of tasks (list of Task) |
463 @return copy of tasks (list of Task) |
442 """ |
464 """ |
443 tasks = [task for task in self.tasks if task.isProjectTask()] |
465 tasks = [task for task in self.tasks if task.isProjectTask()] |
444 return tasks[:] |
466 return tasks[:] |
445 |
467 |
446 def getGlobalTasks(self): |
468 def getGlobalTasks(self): |
447 """ |
469 """ |
448 Public method to retrieve all non project related tasks. |
470 Public method to retrieve all non project related tasks. |
449 |
471 |
450 @return copy of tasks (list of Task) |
472 @return copy of tasks (list of Task) |
451 """ |
473 """ |
452 tasks = [task for task in self.tasks if not task.isProjectTask()] |
474 tasks = [task for task in self.tasks if not task.isProjectTask()] |
453 return tasks[:] |
475 return tasks[:] |
454 |
476 |
455 def clearTasks(self): |
477 def clearTasks(self): |
456 """ |
478 """ |
457 Public slot to clear all tasks from display. |
479 Public slot to clear all tasks from display. |
458 """ |
480 """ |
459 self.tasks = [] |
481 self.tasks = [] |
460 self.clear() |
482 self.clear() |
461 self.__generateTopLevelItems() |
483 self.__generateTopLevelItems() |
462 |
484 |
463 def clearProjectTasks(self, fileOnly=False): |
485 def clearProjectTasks(self, fileOnly=False): |
464 """ |
486 """ |
465 Public slot to clear project related tasks. |
487 Public slot to clear project related tasks. |
466 |
488 |
467 @param fileOnly flag indicating to clear only file related |
489 @param fileOnly flag indicating to clear only file related |
468 project tasks (boolean) |
490 project tasks (boolean) |
469 """ |
491 """ |
470 for task in reversed(self.tasks[:]): |
492 for task in reversed(self.tasks[:]): |
471 if ( |
493 if (fileOnly and task.isProjectFileTask()) or ( |
472 (fileOnly and task.isProjectFileTask()) or |
494 not fileOnly and task.isProjectTask() |
473 (not fileOnly and task.isProjectTask()) |
|
474 ): |
495 ): |
475 if self.copyTask == task: |
496 if self.copyTask == task: |
476 self.copyTask = None |
497 self.copyTask = None |
477 parent = task.parent() |
498 parent = task.parent() |
478 parent.removeChild(task) |
499 parent.removeChild(task) |
479 self.tasks.remove(task) |
500 self.tasks.remove(task) |
480 del task |
501 del task |
481 |
502 |
482 self.__checkTopLevelItems() |
503 self.__checkTopLevelItems() |
483 self.__resort() |
504 self.__resort() |
484 self.__resizeColumns() |
505 self.__resizeColumns() |
485 |
506 |
486 def clearFileTasks(self, filename, conditionally=False): |
507 def clearFileTasks(self, filename, conditionally=False): |
487 """ |
508 """ |
488 Public slot to clear all tasks related to a file. |
509 Public slot to clear all tasks related to a file. |
489 |
510 |
490 @param filename name of the file (string) |
511 @param filename name of the file (string) |
491 @param conditionally flag indicating to clear the tasks of the file |
512 @param conditionally flag indicating to clear the tasks of the file |
492 checking some conditions (boolean) |
513 checking some conditions (boolean) |
493 """ |
514 """ |
494 if conditionally: |
515 if conditionally: |
504 self.__extractedItem.removeChild(task) |
525 self.__extractedItem.removeChild(task) |
505 self.tasks.remove(task) |
526 self.tasks.remove(task) |
506 if task.isProjectTask: |
527 if task.isProjectTask: |
507 self.__projectTasksSaveTimer.changeOccurred() |
528 self.__projectTasksSaveTimer.changeOccurred() |
508 del task |
529 del task |
509 |
530 |
510 self.__checkTopLevelItems() |
531 self.__checkTopLevelItems() |
511 self.__resort() |
532 self.__resort() |
512 self.__resizeColumns() |
533 self.__resizeColumns() |
513 |
534 |
514 def __editTaskProperties(self): |
535 def __editTaskProperties(self): |
515 """ |
536 """ |
516 Private slot to handle the "Properties" context menu entry. |
537 Private slot to handle the "Properties" context menu entry. |
517 """ |
538 """ |
518 from .TaskPropertiesDialog import TaskPropertiesDialog |
539 from .TaskPropertiesDialog import TaskPropertiesDialog |
|
540 |
519 task = self.currentItem() |
541 task = self.currentItem() |
520 dlg = TaskPropertiesDialog(task, parent=self, |
542 dlg = TaskPropertiesDialog(task, parent=self, projectOpen=self.projectOpen) |
521 projectOpen=self.projectOpen) |
543 if dlg.exec() == QDialog.DialogCode.Accepted and dlg.isManualTaskMode(): |
522 if ( |
544 ( |
523 dlg.exec() == QDialog.DialogCode.Accepted and |
545 summary, |
524 dlg.isManualTaskMode() |
546 priority, |
525 ): |
547 taskType, |
526 (summary, priority, taskType, completed, isProjectTask, |
548 completed, |
527 description) = dlg.getData() |
549 isProjectTask, |
|
550 description, |
|
551 ) = dlg.getData() |
528 task.setSummary(summary) |
552 task.setSummary(summary) |
529 task.setPriority(priority) |
553 task.setPriority(priority) |
530 task.setTaskType(taskType) |
554 task.setTaskType(taskType) |
531 task.setCompleted(completed) |
555 task.setCompleted(completed) |
532 task.setProjectTask(isProjectTask) |
556 task.setProjectTask(isProjectTask) |
533 task.setDescription(description) |
557 task.setDescription(description) |
534 self.__projectTasksSaveTimer.changeOccurred() |
558 self.__projectTasksSaveTimer.changeOccurred() |
535 |
559 |
536 def __newTask(self): |
560 def __newTask(self): |
537 """ |
561 """ |
538 Private slot to handle the "New Task" context menu entry. |
562 Private slot to handle the "New Task" context menu entry. |
539 """ |
563 """ |
540 from .TaskPropertiesDialog import TaskPropertiesDialog |
564 from .TaskPropertiesDialog import TaskPropertiesDialog |
541 dlg = TaskPropertiesDialog(None, parent=self, |
565 |
542 projectOpen=self.projectOpen) |
566 dlg = TaskPropertiesDialog(None, parent=self, projectOpen=self.projectOpen) |
543 if dlg.exec() == QDialog.DialogCode.Accepted: |
567 if dlg.exec() == QDialog.DialogCode.Accepted: |
544 (summary, priority, taskType, completed, isProjectTask, |
568 ( |
545 description) = dlg.getData() |
569 summary, |
546 self.addTask(summary, priority, completed=completed, |
570 priority, |
547 isProjectTask=isProjectTask, taskType=taskType, |
571 taskType, |
548 description=description) |
572 completed, |
549 |
573 isProjectTask, |
|
574 description, |
|
575 ) = dlg.getData() |
|
576 self.addTask( |
|
577 summary, |
|
578 priority, |
|
579 completed=completed, |
|
580 isProjectTask=isProjectTask, |
|
581 taskType=taskType, |
|
582 description=description, |
|
583 ) |
|
584 |
550 def __newSubTask(self): |
585 def __newSubTask(self): |
551 """ |
586 """ |
552 Private slot to handle the "New Sub-Task" context menu entry. |
587 Private slot to handle the "New Sub-Task" context menu entry. |
553 """ |
588 """ |
554 parentTask = self.currentItem() |
589 parentTask = self.currentItem() |
555 projectTask = parentTask.isProjectTask() |
590 projectTask = parentTask.isProjectTask() |
556 |
591 |
557 from .TaskPropertiesDialog import TaskPropertiesDialog |
592 from .TaskPropertiesDialog import TaskPropertiesDialog |
558 dlg = TaskPropertiesDialog(None, parent=self, |
593 |
559 projectOpen=self.projectOpen) |
594 dlg = TaskPropertiesDialog(None, parent=self, projectOpen=self.projectOpen) |
560 dlg.setSubTaskMode(projectTask) |
595 dlg.setSubTaskMode(projectTask) |
561 if dlg.exec() == QDialog.DialogCode.Accepted: |
596 if dlg.exec() == QDialog.DialogCode.Accepted: |
562 (summary, priority, taskType, completed, isProjectTask, |
597 ( |
563 description) = dlg.getData() |
598 summary, |
564 self.addTask(summary, priority, completed=completed, |
599 priority, |
565 isProjectTask=isProjectTask, taskType=taskType, |
600 taskType, |
566 description=description, parentTask=parentTask) |
601 completed, |
567 |
602 isProjectTask, |
|
603 description, |
|
604 ) = dlg.getData() |
|
605 self.addTask( |
|
606 summary, |
|
607 priority, |
|
608 completed=completed, |
|
609 isProjectTask=isProjectTask, |
|
610 taskType=taskType, |
|
611 description=description, |
|
612 parentTask=parentTask, |
|
613 ) |
|
614 |
568 def __markCompleted(self): |
615 def __markCompleted(self): |
569 """ |
616 """ |
570 Private slot to handle the "Mark Completed" context menu entry. |
617 Private slot to handle the "Mark Completed" context menu entry. |
571 """ |
618 """ |
572 task = self.currentItem() |
619 task = self.currentItem() |
573 task.setCompleted(True) |
620 task.setCompleted(True) |
574 |
621 |
575 def __deleteCompleted(self): |
622 def __deleteCompleted(self): |
576 """ |
623 """ |
577 Private slot to handle the "Delete Completed Tasks" context menu entry. |
624 Private slot to handle the "Delete Completed Tasks" context menu entry. |
578 """ |
625 """ |
579 for task in reversed(self.tasks[:]): |
626 for task in reversed(self.tasks[:]): |
584 parent.removeChild(task) |
631 parent.removeChild(task) |
585 self.tasks.remove(task) |
632 self.tasks.remove(task) |
586 if task.isProjectTask: |
633 if task.isProjectTask: |
587 self.__projectTasksSaveTimer.changeOccurred() |
634 self.__projectTasksSaveTimer.changeOccurred() |
588 del task |
635 del task |
589 |
636 |
590 self.__checkTopLevelItems() |
637 self.__checkTopLevelItems() |
591 self.__resort() |
638 self.__resort() |
592 self.__resizeColumns() |
639 self.__resizeColumns() |
593 |
640 |
594 ci = self.currentItem() |
641 ci = self.currentItem() |
595 if ci: |
642 if ci: |
596 ind = self.indexFromItem(ci, self.currentColumn()) |
643 ind = self.indexFromItem(ci, self.currentColumn()) |
597 self.scrollTo(ind, QAbstractItemView.ScrollHint.PositionAtCenter) |
644 self.scrollTo(ind, QAbstractItemView.ScrollHint.PositionAtCenter) |
598 |
645 |
599 def __copyTask(self): |
646 def __copyTask(self): |
600 """ |
647 """ |
601 Private slot to handle the "Copy" context menu entry. |
648 Private slot to handle the "Copy" context menu entry. |
602 """ |
649 """ |
603 task = self.currentItem() |
650 task = self.currentItem() |
604 self.copyTask = task |
651 self.copyTask = task |
605 |
652 |
606 def __pasteTask(self): |
653 def __pasteTask(self): |
607 """ |
654 """ |
608 Private slot to handle the "Paste" context menu entry. |
655 Private slot to handle the "Paste" context menu entry. |
609 """ |
656 """ |
610 if self.copyTask: |
657 if self.copyTask: |
611 parent = self.copyTask.parent() |
658 parent = self.copyTask.parent() |
612 if not isinstance(parent, Task): |
659 if not isinstance(parent, Task): |
613 parent = None |
660 parent = None |
614 |
661 |
615 self.addTask(self.copyTask.summary, |
662 self.addTask( |
616 priority=self.copyTask.priority, |
663 self.copyTask.summary, |
617 completed=self.copyTask.completed, |
664 priority=self.copyTask.priority, |
618 description=self.copyTask.description, |
665 completed=self.copyTask.completed, |
619 isProjectTask=self.copyTask._isProjectTask, |
666 description=self.copyTask.description, |
620 parentTask=parent) |
667 isProjectTask=self.copyTask._isProjectTask, |
621 |
668 parentTask=parent, |
|
669 ) |
|
670 |
622 def __pasteMainTask(self): |
671 def __pasteMainTask(self): |
623 """ |
672 """ |
624 Private slot to handle the "Paste as Main Task" context menu entry. |
673 Private slot to handle the "Paste as Main Task" context menu entry. |
625 """ |
674 """ |
626 if self.copyTask: |
675 if self.copyTask: |
627 self.addTask(self.copyTask.summary, |
676 self.addTask( |
628 priority=self.copyTask.priority, |
677 self.copyTask.summary, |
629 completed=self.copyTask.completed, |
678 priority=self.copyTask.priority, |
630 description=self.copyTask.description, |
679 completed=self.copyTask.completed, |
631 isProjectTask=self.copyTask._isProjectTask) |
680 description=self.copyTask.description, |
632 |
681 isProjectTask=self.copyTask._isProjectTask, |
|
682 ) |
|
683 |
633 def __deleteSubTasks(self, task): |
684 def __deleteSubTasks(self, task): |
634 """ |
685 """ |
635 Private method to delete all sub-tasks. |
686 Private method to delete all sub-tasks. |
636 |
687 |
637 @param task task to delete sub-tasks of (Task) |
688 @param task task to delete sub-tasks of (Task) |
638 """ |
689 """ |
639 for subtask in task.takeChildren(): |
690 for subtask in task.takeChildren(): |
640 if self.copyTask == subtask: |
691 if self.copyTask == subtask: |
641 self.copyTask = None |
692 self.copyTask = None |
642 if subtask.childCount() > 0: |
693 if subtask.childCount() > 0: |
643 self.__deleteSubTasks(subtask) |
694 self.__deleteSubTasks(subtask) |
644 self.tasks.remove(subtask) |
695 self.tasks.remove(subtask) |
645 |
696 |
646 def __deleteTask(self, task=None): |
697 def __deleteTask(self, task=None): |
647 """ |
698 """ |
648 Private slot to delete a task. |
699 Private slot to delete a task. |
649 |
700 |
650 @param task task to be deleted |
701 @param task task to be deleted |
651 @type Task |
702 @type Task |
652 """ |
703 """ |
653 if task is None: |
704 if task is None: |
654 # called via "Delete Task" context menu entry |
705 # called via "Delete Task" context menu entry |
655 task = self.currentItem() |
706 task = self.currentItem() |
656 |
707 |
657 if self.copyTask is task: |
708 if self.copyTask is task: |
658 self.copyTask = None |
709 self.copyTask = None |
659 if task.childCount() > 0: |
710 if task.childCount() > 0: |
660 self.__deleteSubTasks(task) |
711 self.__deleteSubTasks(task) |
661 parent = task.parent() |
712 parent = task.parent() |
662 parent.removeChild(task) |
713 parent.removeChild(task) |
663 self.tasks.remove(task) |
714 self.tasks.remove(task) |
664 if task.isProjectTask: |
715 if task.isProjectTask: |
665 self.__projectTasksSaveTimer.changeOccurred() |
716 self.__projectTasksSaveTimer.changeOccurred() |
666 del task |
717 del task |
667 |
718 |
668 self.__checkTopLevelItems() |
719 self.__checkTopLevelItems() |
669 self.__resort() |
720 self.__resort() |
670 self.__resizeColumns() |
721 self.__resizeColumns() |
671 |
722 |
672 ci = self.currentItem() |
723 ci = self.currentItem() |
673 if ci: |
724 if ci: |
674 ind = self.indexFromItem(ci, self.currentColumn()) |
725 ind = self.indexFromItem(ci, self.currentColumn()) |
675 self.scrollTo(ind, QAbstractItemView.ScrollHint.PositionAtCenter) |
726 self.scrollTo(ind, QAbstractItemView.ScrollHint.PositionAtCenter) |
676 |
727 |
677 def __goToTask(self): |
728 def __goToTask(self): |
678 """ |
729 """ |
679 Private slot to handle the "Go To" context menu entry. |
730 Private slot to handle the "Go To" context menu entry. |
680 """ |
731 """ |
681 task = self.currentItem() |
732 task = self.currentItem() |
728 Private slot to configure scan options for project tasks. |
782 Private slot to configure scan options for project tasks. |
729 """ |
783 """ |
730 scanFilter, ok = QInputDialog.getText( |
784 scanFilter, ok = QInputDialog.getText( |
731 self, |
785 self, |
732 self.tr("Scan Filter Patterns"), |
786 self.tr("Scan Filter Patterns"), |
733 self.tr("Enter filename patterns of files" |
787 self.tr( |
734 " to be excluded separated by a comma:"), |
788 "Enter filename patterns of files" |
|
789 " to be excluded separated by a comma:" |
|
790 ), |
735 QLineEdit.EchoMode.Normal, |
791 QLineEdit.EchoMode.Normal, |
736 self.__projectTasksScanFilter) |
792 self.__projectTasksScanFilter, |
|
793 ) |
737 if ok: |
794 if ok: |
738 self.__projectTasksScanFilter = scanFilter |
795 self.__projectTasksScanFilter = scanFilter |
739 |
796 |
740 def regenerateProjectTasks(self, quiet=False): |
797 def regenerateProjectTasks(self, quiet=False): |
741 """ |
798 """ |
742 Public slot to regenerate project related tasks. |
799 Public slot to regenerate project related tasks. |
743 |
800 |
744 @param quiet flag indicating quiet operation |
801 @param quiet flag indicating quiet operation |
745 @type bool |
802 @type bool |
746 """ |
803 """ |
747 markers = { |
804 markers = { |
748 taskType: Preferences.getTasks(markersName).split() |
805 taskType: Preferences.getTasks(markersName).split() |
749 for taskType, markersName in Task.TaskType2MarkersName.items() |
806 for taskType, markersName in Task.TaskType2MarkersName.items() |
750 } |
807 } |
751 files = self.project.pdata["SOURCES"] |
808 files = self.project.pdata["SOURCES"] |
752 |
809 |
753 # apply file filter |
810 # apply file filter |
754 filterList = [f.strip() |
811 filterList = [ |
755 for f in self.__projectTasksScanFilter.split(",") |
812 f.strip() for f in self.__projectTasksScanFilter.split(",") if f.strip() |
756 if f.strip()] |
813 ] |
757 if filterList: |
814 if filterList: |
758 for scanFilter in filterList: |
815 for scanFilter in filterList: |
759 files = [f for f in files |
816 files = [f for f in files if not fnmatch.fnmatch(f, scanFilter)] |
760 if not fnmatch.fnmatch(f, scanFilter)] |
817 |
761 |
|
762 # remove all project tasks |
818 # remove all project tasks |
763 self.clearProjectTasks(fileOnly=True) |
819 self.clearProjectTasks(fileOnly=True) |
764 |
820 |
765 # now process them |
821 # now process them |
766 if quiet: |
822 if quiet: |
767 ppath = self.project.getProjectPath() |
823 ppath = self.project.getProjectPath() |
768 self.__projectTaskExtractionThread.scan( |
824 self.__projectTaskExtractionThread.scan( |
769 markers, [os.path.join(ppath, f) for f in files]) |
825 markers, [os.path.join(ppath, f) for f in files] |
|
826 ) |
770 else: |
827 else: |
771 progress = EricProgressDialog( |
828 progress = EricProgressDialog( |
772 self.tr("Extracting project tasks..."), |
829 self.tr("Extracting project tasks..."), |
773 self.tr("Abort"), 0, len(files), self.tr("%v/%m Files"), self) |
830 self.tr("Abort"), |
|
831 0, |
|
832 len(files), |
|
833 self.tr("%v/%m Files"), |
|
834 self, |
|
835 ) |
774 progress.setMinimumDuration(0) |
836 progress.setMinimumDuration(0) |
775 progress.setWindowTitle(self.tr("Tasks")) |
837 progress.setWindowTitle(self.tr("Tasks")) |
776 |
838 |
777 ppath = self.project.getProjectPath() |
839 ppath = self.project.getProjectPath() |
778 |
840 |
779 now = time.monotonic() |
841 now = time.monotonic() |
780 for count, file in enumerate(files): |
842 for count, file in enumerate(files): |
781 progress.setLabelText( |
843 progress.setLabelText( |
782 self.tr("Extracting project tasks...\n{0}").format(file)) |
844 self.tr("Extracting project tasks...\n{0}").format(file) |
|
845 ) |
783 progress.setValue(count) |
846 progress.setValue(count) |
784 if time.monotonic() - now > 0.01: |
847 if time.monotonic() - now > 0.01: |
785 QApplication.processEvents() |
848 QApplication.processEvents() |
786 now = time.monotonic() |
849 now = time.monotonic() |
787 if progress.wasCanceled(): |
850 if progress.wasCanceled(): |
788 break |
851 break |
789 |
852 |
790 fn = os.path.join(ppath, file) |
853 fn = os.path.join(ppath, file) |
791 # read the file and split it into textlines |
854 # read the file and split it into textlines |
792 try: |
855 try: |
793 text, encoding = Utilities.readEncodedFile(fn) |
856 text, encoding = Utilities.readEncodedFile(fn) |
794 lines = text.splitlines() |
857 lines = text.splitlines() |
795 except (UnicodeError, OSError): |
858 except (UnicodeError, OSError): |
796 count += 1 |
859 count += 1 |
797 progress.setValue(count) |
860 progress.setValue(count) |
798 continue |
861 continue |
799 |
862 |
800 # now search tasks and record them |
863 # now search tasks and record them |
801 for lineIndex, line in enumerate(lines, start=1): |
864 for lineIndex, line in enumerate(lines, start=1): |
802 shouldBreak = False |
865 shouldBreak = False |
803 |
866 |
804 if line.endswith("__NO-TASK__"): |
867 if line.endswith("__NO-TASK__"): |
805 # ignore potential task marker |
868 # ignore potential task marker |
806 continue |
869 continue |
807 |
870 |
808 for taskType, taskMarkers in markers.items(): |
871 for taskType, taskMarkers in markers.items(): |
809 for taskMarker in taskMarkers: |
872 for taskMarker in taskMarkers: |
810 index = line.find(taskMarker) |
873 index = line.find(taskMarker) |
811 if index > -1: |
874 if index > -1: |
812 task = line[index:] |
875 task = line[index:] |
813 self.addFileTask(task, fn, lineIndex, taskType) |
876 self.addFileTask(task, fn, lineIndex, taskType) |
814 shouldBreak = True |
877 shouldBreak = True |
815 break |
878 break |
816 if shouldBreak: |
879 if shouldBreak: |
817 break |
880 break |
818 |
881 |
819 progress.setValue(len(files)) |
882 progress.setValue(len(files)) |
820 |
883 |
821 def __configure(self): |
884 def __configure(self): |
822 """ |
885 """ |
823 Private method to open the configuration dialog. |
886 Private method to open the configuration dialog. |
824 """ |
887 """ |
825 ericApp().getObject("UserInterface").showPreferences("tasksPage") |
888 ericApp().getObject("UserInterface").showPreferences("tasksPage") |
826 |
889 |
827 def saveProjectTasks(self): |
890 def saveProjectTasks(self): |
828 """ |
891 """ |
829 Public method to write the project tasks. |
892 Public method to write the project tasks. |
830 """ |
893 """ |
831 if self.projectOpen and Preferences.getProject("TasksProjectAutoSave"): |
894 if self.projectOpen and Preferences.getProject("TasksProjectAutoSave"): |
832 self.project.writeTasks() |
895 self.project.writeTasks() |
833 |
896 |
834 def stopProjectTaskExtraction(self): |
897 def stopProjectTaskExtraction(self): |
835 """ |
898 """ |
836 Public method to stop the project task extraction thread. |
899 Public method to stop the project task extraction thread. |
837 """ |
900 """ |
838 self.__projectTaskExtractionThread.requestInterrupt() |
901 self.__projectTaskExtractionThread.requestInterrupt() |
839 self.__projectTaskExtractionThread.wait() |
902 self.__projectTaskExtractionThread.wait() |
840 |
903 |
841 def getTasksScanFilter(self) -> str: |
904 def getTasksScanFilter(self) -> str: |
842 """ |
905 """ |
843 Public method to get the project scan filter. |
906 Public method to get the project scan filter. |
844 |
907 |
845 @return project scan filter |
908 @return project scan filter |
846 @rtype str |
909 @rtype str |
847 """ |
910 """ |
848 return self.__projectTasksScanFilter.strip() |
911 return self.__projectTasksScanFilter.strip() |
849 |
912 |
850 def setTasksScanFilter(self, filterStr: str): |
913 def setTasksScanFilter(self, filterStr: str): |
851 """ |
914 """ |
852 Public method to set the project scan filter. |
915 Public method to set the project scan filter. |
853 |
916 |
854 @param filterStr project scan filter |
917 @param filterStr project scan filter |
855 @type str |
918 @type str |
856 """ |
919 """ |
857 self.__projectTasksScanFilter = filterStr |
920 self.__projectTasksScanFilter = filterStr |
858 |
921 |
859 |
922 |
860 class ProjectTaskExtractionThread(QThread): |
923 class ProjectTaskExtractionThread(QThread): |
861 """ |
924 """ |
862 Class implementing a thread to extract tasks related to a project. |
925 Class implementing a thread to extract tasks related to a project. |
863 |
926 |
864 @signal taskFound(str, str, int, TaskType) emitted with the task |
927 @signal taskFound(str, str, int, TaskType) emitted with the task |
865 description, the file name, the line number and task type to signal |
928 description, the file name, the line number and task type to signal |
866 the presence of a task |
929 the presence of a task |
867 """ |
930 """ |
|
931 |
868 taskFound = pyqtSignal(str, str, int, TaskType) |
932 taskFound = pyqtSignal(str, str, int, TaskType) |
869 |
933 |
870 def __init__(self, parent=None): |
934 def __init__(self, parent=None): |
871 """ |
935 """ |
872 Constructor |
936 Constructor |
873 |
937 |
874 @param parent reference to the parent object (QObject) |
938 @param parent reference to the parent object (QObject) |
875 """ |
939 """ |
876 super().__init__() |
940 super().__init__() |
877 |
941 |
878 self.__lock = threading.Lock() |
942 self.__lock = threading.Lock() |
879 self.__interrupt = False |
943 self.__interrupt = False |
880 |
944 |
881 def requestInterrupt(self): |
945 def requestInterrupt(self): |
882 """ |
946 """ |
883 Public method to request iterruption of the thread. |
947 Public method to request iterruption of the thread. |
884 """ |
948 """ |
885 if self.isRunning(): |
949 if self.isRunning(): |
886 self.__interrupt = True |
950 self.__interrupt = True |
887 |
951 |
888 def scan(self, markers, files): |
952 def scan(self, markers, files): |
889 """ |
953 """ |
890 Public method to scan the given list of files for tasks. |
954 Public method to scan the given list of files for tasks. |
891 |
955 |
892 @param markers dictionary of defined task markers |
956 @param markers dictionary of defined task markers |
893 @type dict of lists of str |
957 @type dict of lists of str |
894 @param files list of file names to be scanned |
958 @param files list of file names to be scanned |
895 @type list of str |
959 @type list of str |
896 """ |
960 """ |