TimeTracker/TimeTracker.py

branch
eric7
changeset 98
693e42176007
parent 94
a3d936304e51
child 104
2ae9ad969598
equal deleted inserted replaced
97:44ec4168b442 98:693e42176007
5 5
6 """ 6 """
7 Module implementing the time tracker object. 7 Module implementing the time tracker object.
8 """ 8 """
9 9
10 import json
10 import os 11 import os
11 12
12 from PyQt5.QtCore import Qt, QObject 13 from PyQt6.QtCore import Qt, QObject
13 from PyQt5.QtGui import QKeySequence 14 from PyQt6.QtGui import QKeySequence
14 15
15 from E5Gui.E5Application import e5App 16 from EricWidgets.EricApplication import ericApp
16 from E5Gui import E5MessageBox 17 from EricWidgets import EricMessageBox
17 from E5Gui.E5Action import E5Action 18 from EricGui.EricAction import EricAction
18 19
19 import UI.PixmapCache 20 import UI.PixmapCache
20 21
21 22
22 class TimeTracker(QObject): 23 class TimeTracker(QObject):
23 """ 24 """
24 Class implementing the time tracker object. 25 Class implementing the time tracker object.
25 """ 26 """
26 FileName = "TimeTracker.txt" 27 FileName = "TimeTracker.ttj"
27 28
28 def __init__(self, plugin, iconSuffix, parent=None): 29 def __init__(self, plugin, iconSuffix, parent=None):
29 """ 30 """
30 Constructor 31 Constructor
31 32
40 41
41 self.__plugin = plugin 42 self.__plugin = plugin
42 self.__iconSuffix = iconSuffix 43 self.__iconSuffix = iconSuffix
43 self.__ui = parent 44 self.__ui = parent
44 45
45 self.__e5project = e5App().getObject("Project") 46 self.__ericProject = ericApp().getObject("Project")
46 47
47 def __initialize(self): 48 def __initialize(self):
48 """ 49 """
49 Private slot to initialize some member variables. 50 Private slot to initialize some member variables.
50 """ 51 """
71 os.path.join("TimeTracker", "icons", 72 os.path.join("TimeTracker", "icons",
72 "clock-{0}".format(self.__iconSuffix)) 73 "clock-{0}".format(self.__iconSuffix))
73 ), 74 ),
74 self.tr("Time Tracker")) 75 self.tr("Time Tracker"))
75 76
76 self.__activateAct = E5Action( 77 self.__activateAct = EricAction(
77 self.tr('Time Tracker'), 78 self.tr('Time Tracker'),
78 self.tr('T&ime Tracker'), 79 self.tr('T&ime Tracker'),
79 QKeySequence(self.tr("Alt+Shift+I")), 80 QKeySequence(self.tr("Alt+Shift+I")),
80 0, self, 81 0, self,
81 'time_tracker_activate') 82 'time_tracker_activate')
86 """<p>This switches the input focus to the Time Tracker""" 87 """<p>This switches the input focus to the Time Tracker"""
87 """ window.</p>""" 88 """ window.</p>"""
88 )) 89 ))
89 self.__activateAct.triggered.connect(self.__activateWidget) 90 self.__activateAct.triggered.connect(self.__activateWidget)
90 91
91 self.__ui.addE5Actions([self.__activateAct], 'ui') 92 self.__ui.addEricActions([self.__activateAct], 'ui')
92 menu = self.__ui.getMenu("subwindow") 93 menu = self.__ui.getMenu("subwindow")
93 menu.addAction(self.__activateAct) 94 menu.addAction(self.__activateAct)
94 95
95 self.__initialize() 96 self.__initialize()
96 97
98 """ 99 """
99 Public method to deactivate the time tracker. 100 Public method to deactivate the time tracker.
100 """ 101 """
101 menu = self.__ui.getMenu("subwindow") 102 menu = self.__ui.getMenu("subwindow")
102 menu.removeAction(self.__activateAct) 103 menu.removeAction(self.__activateAct)
103 self.__ui.removeE5Actions([self.__activateAct], 'ui') 104 self.__ui.removeEricActions([self.__activateAct], 'ui')
104 self.__ui.removeSideWidget(self.__widget) 105 self.__ui.removeSideWidget(self.__widget)
105 106
106 def projectOpened(self): 107 def projectOpened(self):
107 """ 108 """
108 Public slot to handle the projectOpened signal. 109 Public slot to handle the projectOpened signal.
109 """ 110 """
110 if self.__projectOpen: 111 if self.__projectOpen:
111 self.projectClosed() 112 self.projectClosed()
112 113
113 self.__projectOpen = True 114 self.__projectOpen = True
114 self.__projectPath = self.__e5project.getProjectPath() 115 self.__projectPath = self.__ericProject.getProjectPath()
115 self.__trackerFilePath = os.path.join( 116 self.__trackerFilePath = os.path.join(
116 self.__e5project.getProjectManagementDir(), 117 self.__ericProject.getProjectManagementDir(),
117 TimeTracker.FileName) 118 TimeTracker.FileName)
118 119
119 self.__readTrackerEntries() 120 self.__readTrackerEntries()
120 self.__widget.showTrackerEntries(sorted(self.__entries.values(), 121 self.__widget.showTrackerEntries(sorted(self.__entries.values(),
121 reverse=True)) 122 reverse=True))
136 """ 137 """
137 Private slot to read the time tracker entries from a file. 138 Private slot to read the time tracker entries from a file.
138 """ 139 """
139 if os.path.exists(self.__trackerFilePath): 140 if os.path.exists(self.__trackerFilePath):
140 try: 141 try:
141 with open(self.__trackerFilePath, "r", encoding="utf-8") as f: 142 with open(self.__trackerFilePath, "r") as f:
142 data = f.read() 143 jsonString = f.read()
143 except OSError as err: 144 entriesDataList = json.loads(jsonString)
144 E5MessageBox.critical( 145 except (OSError, json.JSONDecodeError) as err:
146 EricMessageBox.critical(
145 self.__ui, 147 self.__ui,
146 self.tr("Read Time Tracker File"), 148 self.tr("Read Time Tracker File"),
147 self.tr("""<p>The time tracker file <b>{0}</b> could""" 149 self.tr("""<p>The time tracker file <b>{0}</b> could"""
148 """ not be read.</p><p>Reason: {1}</p>""") 150 """ not be read.</p><p>Reason: {1}</p>""")
149 .format(self.__trackerFilePath, str(err))) 151 .format(self.__trackerFilePath, str(err)))
150 return 152 return
151 153
152 from .TimeTrackEntry import TimeTrackEntry 154 from .TimeTrackEntry import TimeTrackEntry
153 155
154 invalidCount = 0 156 invalidCount = 0
155 for line in data.splitlines(): 157 for data in entriesDataList:
156 entry = TimeTrackEntry(self.__plugin) 158 entry = TimeTrackEntry(self.__plugin)
157 eid = entry.fromString(line.strip()) 159 eid = entry.fromDict(data)
158 if eid > -1: 160 if eid > -1:
159 self.__entries[eid] = entry 161 self.__entries[eid] = entry
160 else: 162 else:
161 invalidCount += 1 163 invalidCount += 1
162 164
163 if invalidCount: 165 if invalidCount:
164 E5MessageBox.information( 166 EricMessageBox.information(
165 self.__ui, 167 self.__ui,
166 self.tr("Read Time Tracker File"), 168 self.tr("Read Time Tracker File"),
167 self.tr("""<p>The time tracker file <b>{0}</b>""" 169 self.tr("""<p>The time tracker file <b>{0}</b>"""
168 """ contained %n invalid entries. These""" 170 """ contained %n invalid entries. These"""
169 """ have been discarded.</p>""", "", 171 """ have been discarded.</p>""", "",
171 173
172 def saveTrackerEntries(self, filePath="", ids=None): 174 def saveTrackerEntries(self, filePath="", ids=None):
173 """ 175 """
174 Public slot to save the tracker entries to a file. 176 Public slot to save the tracker entries to a file.
175 177
176 @keyparam filePath path and name of the file to write the entries to 178 @param filePath path and name of the file to write the entries to
177 (string) 179 @type str
178 @keyparam ids list of entry IDs to be written (list of integer) 180 @param ids list of entry IDs to be written
181 @type list of int
179 """ 182 """
180 if not filePath: 183 if not filePath:
181 filePath = self.__trackerFilePath 184 filePath = self.__trackerFilePath
182 entriesList = ( 185 entriesDataList = (
183 [self.__entries[eid] for eid in ids if eid in self.__entries] 186 [self.__entries[eid].toDict()
187 for eid in ids if eid in self.__entries]
184 if ids else 188 if ids else
185 self.__entries.values() 189 [e.toDict() for e in self.__entries.values()]
186 ) 190 )
187 try: 191 try:
188 with open(filePath, "w", encoding="utf-8") as f: 192 jsonString = json.dumps(entriesDataList, indent=2)
189 for entry in entriesList: 193 with open(filePath, "w") as f:
190 if entry.isValid(): 194 f.write(jsonString)
191 f.write(entry.toString() + "\n") 195 except (TypeError, OSError) as err:
192 except OSError as err: 196 EricMessageBox.critical(
193 E5MessageBox.critical(
194 self.__ui, 197 self.__ui,
195 self.tr("Save Time Tracker File"), 198 self.tr("Save Time Tracker File"),
196 self.tr("""<p>The time tracker file <b>{0}</b> could""" 199 self.tr("""<p>The time tracker file <b>{0}</b> could"""
197 """ not be saved.</p><p>Reason: {1}</p>""") 200 """ not be saved.</p><p>Reason: {1}</p>""")
198 .format(self.__trackerFilePath, str(err))) 201 .format(self.__trackerFilePath, str(err)))
203 206
204 @param fname name of the file to import (string) 207 @param fname name of the file to import (string)
205 """ 208 """
206 try: 209 try:
207 with open(fname, "r", encoding="utf-8") as f: 210 with open(fname, "r", encoding="utf-8") as f:
208 data = f.read() 211 jsonString = f.read()
209 except OSError as err: 212 entriesDataList = json.loads(jsonString)
210 E5MessageBox.critical( 213 except (OSError, json.JSONDecodeError) as err:
214 EricMessageBox.critical(
211 self.__ui, 215 self.__ui,
212 self.tr("Import Time Tracker File"), 216 self.tr("Import Time Tracker File"),
213 self.tr("""<p>The time tracker file <b>{0}</b> could""" 217 self.tr("""<p>The time tracker file <b>{0}</b> could"""
214 """ not be read.</p><p>Reason: {1}</p>""") 218 """ not be read.</p><p>Reason: {1}</p>""")
215 .format(fname, str(err))) 219 .format(fname, str(err)))
218 from .TimeTrackEntry import TimeTrackEntry 222 from .TimeTrackEntry import TimeTrackEntry
219 223
220 invalidCount = 0 224 invalidCount = 0
221 duplicateCount = 0 225 duplicateCount = 0
222 entries = [] 226 entries = []
223 for line in data.splitlines(): 227
228 for data in entriesDataList:
224 entry = TimeTrackEntry(self.__plugin) 229 entry = TimeTrackEntry(self.__plugin)
225 eid = entry.fromString(line.strip()) 230 eid = entry.fromDict(data)
226 if eid > -1: 231 if eid > -1:
227 entries.append(entry) 232 entries.append(entry)
228 else: 233 else:
229 invalidCount += 1 234 invalidCount += 1
230 235
268 """ %n invalid entries.""", 273 """ %n invalid entries.""",
269 "", invalidCount).format(fname) 274 "", invalidCount).format(fname)
270 msg += " " + self.tr( 275 msg += " " + self.tr(
271 """ %n entries have been ignored.</p>""", 276 """ %n entries have been ignored.</p>""",
272 "", invalidCount + duplicateCount) 277 "", invalidCount + duplicateCount)
273 E5MessageBox.information( 278 EricMessageBox.information(
274 self.__ui, 279 self.__ui,
275 self.tr("Import Time Tracker File"), 280 self.tr("Import Time Tracker File"),
276 msg) 281 msg)
277 282
278 self.__widget.clear() 283 self.__widget.clear()
282 287
283 def addTrackerEntry(self, startDateTime, duration, task, comment): 288 def addTrackerEntry(self, startDateTime, duration, task, comment):
284 """ 289 """
285 Public method to add a new tracker entry based on the given data. 290 Public method to add a new tracker entry based on the given data.
286 291
287 @param startDateTime start date and time (QDateTime) 292 @param startDateTime start date and time
288 @param duration duration in minutes (integer) 293 @type QDateTime
289 @param task task description (string) 294 @param duration duration in minutes
290 @param comment comment (string) 295 @type int
296 @param task task description
297 @type str
298 @param comment comment
299 @type str
291 """ 300 """
292 if not self.__plugin.getPreferences("AllowDuplicates"): 301 if not self.__plugin.getPreferences("AllowDuplicates"):
293 startDateTimes = [ 302 startDateTimes = [
294 entry.getStartDateTime() for entry in self.__entries.values()] 303 entry.getStartDateTime() for entry in self.__entries.values()]
295 if startDateTime in startDateTimes: 304 if startDateTime in startDateTimes:
313 entry.setTask(task) 322 entry.setTask(task)
314 entry.setComment(comment) 323 entry.setComment(comment)
315 self.__entries[nextID] = entry 324 self.__entries[nextID] = entry
316 325
317 self.__widget.clear() 326 self.__widget.clear()
318 self.__widget.showTrackerEntries(sorted(self.__entries.values(), 327 self.__widget.showTrackerEntries(
319 reverse=True)) 328 sorted(self.__entries.values(), reverse=True))
320 self.__widget.setCurrentEntry(self.__currentEntry) 329 self.__widget.setCurrentEntry(self.__currentEntry)
321 330
322 def pauseTrackerEntry(self): 331 def pauseTrackerEntry(self):
323 """ 332 """
324 Public method to pause the current tracker entry. 333 Public method to pause the current tracker entry.
334 def stopTrackerEntry(self): 343 def stopTrackerEntry(self):
335 """ 344 """
336 Public method to stop the current tracker entry. 345 Public method to stop the current tracker entry.
337 346
338 @return tuple of the ID assigned to the stopped tracker entry and 347 @return tuple of the ID assigned to the stopped tracker entry and
339 the duration (integer, integer) 348 the duration
349 @rtype tuple of (int, int)
340 """ 350 """
341 duration = 0 351 duration = 0
342 nextID = -1 352 nextID = -1
343 if self.__currentEntry is not None: 353 if self.__currentEntry is not None:
344 self.__currentEntry.stop() 354 self.__currentEntry.stop()
345 if self.__currentEntry.isValid(): 355 if self.__currentEntry.isValid():
346 if len(self.__entries.keys()): 356 nextID = (
347 nextID = max(self.__entries.keys()) + 1 357 max(self.__entries.keys()) + 1
348 else: 358 if len(self.__entries.keys()) else
349 nextID = 0 359 0
360 )
350 self.__currentEntry.setID(nextID) 361 self.__currentEntry.setID(nextID)
351 self.__entries[nextID] = self.__currentEntry 362 self.__entries[nextID] = self.__currentEntry
352 if self.__plugin.getPreferences("AutoSave"): 363 if self.__plugin.getPreferences("AutoSave"):
353 self.saveTrackerEntries() 364 self.saveTrackerEntries()
354 duration = self.__currentEntry.getDuration() 365 duration = self.__currentEntry.getDuration()
368 379
369 def getCurrentEntry(self): 380 def getCurrentEntry(self):
370 """ 381 """
371 Public method to get a reference to the current tracker entry. 382 Public method to get a reference to the current tracker entry.
372 383
373 @return reference to the current entry (TimeTrackEntry) 384 @return reference to the current entry
385 @rtype TimeTrackEntry
374 """ 386 """
375 return self.__currentEntry 387 return self.__currentEntry
376 388
377 def getEntry(self, eid): 389 def getEntry(self, eid):
378 """ 390 """
379 Public method to get a tracker entry given its ID. 391 Public method to get a tracker entry given its ID.
380 392
381 @param eid ID of the tracker entry (integer) 393 @param eid ID of the tracker entry
382 @return entry for the given ID (TimeTrackEntry) or None 394 @type int
395 @return entry for the given ID or None
396 @rtype TimeTrackEntry
383 """ 397 """
384 if eid in self.__entries: 398 if eid in self.__entries:
385 return self.__entries[eid] 399 return self.__entries[eid]
386 else: 400 else:
387 return None 401 return None
388 402
389 def deleteTrackerEntry(self, eid): 403 def deleteTrackerEntry(self, eid):
390 """ 404 """
391 Public method to delete a tracker entry given its ID. 405 Public method to delete a tracker entry given its ID.
392 406
393 @param eid ID of the tracker entry (integer) 407 @param eid ID of the tracker entry
408 @type int
394 """ 409 """
395 if eid in self.__entries: 410 if eid in self.__entries:
396 del self.__entries[eid] 411 del self.__entries[eid]
397 412
398 def removeDuplicateTrackerEntries(self): 413 def removeDuplicateTrackerEntries(self):
418 433
419 if self.__plugin.getPreferences("AutoSave"): 434 if self.__plugin.getPreferences("AutoSave"):
420 self.saveTrackerEntries() 435 self.saveTrackerEntries()
421 436
422 self.__widget.clear() 437 self.__widget.clear()
423 self.__widget.showTrackerEntries(sorted(self.__entries.values(), 438 self.__widget.showTrackerEntries(
424 reverse=True)) 439 sorted(self.__entries.values(), reverse=True))
425 self.__widget.setCurrentEntry(self.__currentEntry) 440 self.__widget.setCurrentEntry(self.__currentEntry)
426 441
427 def mergeDuplicateTrackerEntries(self): 442 def mergeDuplicateTrackerEntries(self):
428 """ 443 """
429 Public slot to merge duplicate time tracker entries. 444 Public slot to merge duplicate time tracker entries.
446 461
447 if self.__plugin.getPreferences("AutoSave"): 462 if self.__plugin.getPreferences("AutoSave"):
448 self.saveTrackerEntries() 463 self.saveTrackerEntries()
449 464
450 self.__widget.clear() 465 self.__widget.clear()
451 self.__widget.showTrackerEntries(sorted(self.__entries.values(), 466 self.__widget.showTrackerEntries(
452 reverse=True)) 467 sorted(self.__entries.values(), reverse=True))
453 self.__widget.setCurrentEntry(self.__currentEntry) 468 self.__widget.setCurrentEntry(self.__currentEntry)
454 469
455 def entryChanged(self): 470 def entryChanged(self):
456 """ 471 """
457 Public method to indicate an external change to any of the entries. 472 Public method to indicate an external change to any of the entries.
461 476
462 def getPreferences(self, key): 477 def getPreferences(self, key):
463 """ 478 """
464 Public method to retrieve the various settings. 479 Public method to retrieve the various settings.
465 480
466 @param key the key of the value to get 481 @param key key of the value to get
467 @return the requested setting 482 @type str
483 @return value of the requested setting
484 @rtype Any
468 """ 485 """
469 return self.__plugin.getPreferences(key) 486 return self.__plugin.getPreferences(key)
470 487
471 def __activateWidget(self): 488 def __activateWidget(self):
472 """ 489 """
473 Private slot to handle the activation of the project browser. 490 Private slot to handle the activation of the time tracker widget.
474 """ 491 """
475 uiLayoutType = self.__ui.getLayoutType() 492 uiLayoutType = self.__ui.getLayoutType()
476 493
477 if uiLayoutType == "Toolboxes": 494 if uiLayoutType == "Toolboxes":
478 self.__ui.hToolboxDock.show() 495 self.__ui.hToolboxDock.show()
480 elif uiLayoutType == "Sidebars": 497 elif uiLayoutType == "Sidebars":
481 self.__ui.bottomSidebar.show() 498 self.__ui.bottomSidebar.show()
482 self.__ui.bottomSidebar.setCurrentWidget(self.__widget) 499 self.__ui.bottomSidebar.setCurrentWidget(self.__widget)
483 else: 500 else:
484 self.__widget.show() 501 self.__widget.show()
485 self.__widget.setFocus(Qt.ActiveWindowFocusReason) 502 self.__widget.setFocus(Qt.FocusReason.ActiveWindowFocusReason)

eric ide

mercurial