Wed, 23 Apr 2014 23:25:17 +0200
python2Compatible flag added.
# -*- coding: utf-8 -*- # Copyright (c) 2012 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the time tracker object. """ from __future__ import unicode_literals import os from PyQt4.QtCore import Qt, QObject from PyQt4.QtGui import QKeySequence from E5Gui.E5Application import e5App from E5Gui import E5MessageBox from E5Gui.E5Action import E5Action import UI.PixmapCache class TimeTracker(QObject): """ Class implementing the time tracker object. """ FileName = "TimeTracker.txt" def __init__(self, plugin, parent=None): """ Constructor @param plugin reference to the plugin object (TimeTrackerPlugin) @param parent parent (QObject) """ QObject.__init__(self, parent) self.__plugin = plugin self.__ui = parent self.__e5project = e5App().getObject("Project") def __initialize(self): """ Public slot to initialize some member variables. """ self.__projectPath = '' self.__trackerFilePath = '' self.__projectOpen = False self.__entries = {} # key: entry ID, value tracker entry self.__currentEntry = None self.__widget.clear() self.__widget.setEnabled(False) def activate(self): """ Public method to activate the time tracker. """ from .TimeTrackerWidget import TimeTrackerWidget self.__widget = TimeTrackerWidget(self) self.__ui.addSideWidget( self.__ui.BottomSide, self.__widget, UI.PixmapCache.getIcon( os.path.join("TimeTracker", "icons", "clock.png")), self.tr("Time Tracker")) self.__activateAct = E5Action( self.trUtf8('Time Tracker'), self.trUtf8('T&ime Tracker'), QKeySequence(self.trUtf8("Alt+Shift+I")), 0, self, 'time_tracker_activate') self.__activateAct.setStatusTip(self.trUtf8( "Switch the input focus to the Time Tracker window.")) self.__activateAct.setWhatsThis(self.trUtf8( """<b>Activate Time Tracker</b>""" """<p>This switches the input focus to the Time Tracker""" """ window.</p>""" )) self.__activateAct.triggered[()].connect(self.__activateWidget) self.__ui.addE5Actions([self.__activateAct], 'ui') menu = self.__ui.getMenu("subwindow") menu.addAction(self.__activateAct) self.__initialize() def deactivate(self): """ Public method to deactivate the time tracker. """ menu = self.__ui.getMenu("subwindow") menu.removeAction(self.__activateAct) self.__ui.removeE5Actions([self.__activateAct], 'ui') self.__ui.removeSideWidget(self.__widget) def projectOpened(self): """ Public slot to handle the projectOpened signal. """ if self.__projectOpen: self.projectClosed() self.__projectOpen = True self.__projectPath = self.__e5project.getProjectPath() self.__trackerFilePath = os.path.join( self.__e5project.getProjectManagementDir(), TimeTracker.FileName) self.__readTrackerEntries() self.__widget.showTrackerEntries(sorted(self.__entries.values(), reverse=True)) self.__widget.setEnabled(True) self.startTrackerEntry() def projectClosed(self): """ Public slot to handle the projectClosed signal. """ if self.__projectOpen: self.stopTrackerEntry() self.saveTrackerEntries() self.__initialize() def __readTrackerEntries(self): """ Private slot to read the time tracker entries from a file. """ if os.path.exists(self.__trackerFilePath): try: f = open(self.__trackerFilePath, "r", encoding="utf-8") data = f.read() f.close() except (IOError, OSError) as err: E5MessageBox.critical( self.__ui, self.trUtf8("Read Time Tracker File"), self.trUtf8("""<p>The time tracker file <b>{0}</b> could""" """ not be read.</p><p>Reason: {1}</p>""") .format(self.__trackerFilePath, str(err))) return from .TimeTrackEntry import TimeTrackEntry invalidCount = 0 for line in data.splitlines(): entry = TimeTrackEntry(self.__plugin) eid = entry.fromString(line.strip()) if eid > -1: self.__entries[eid] = entry else: invalidCount += 1 if invalidCount: E5MessageBox.information( self.__ui, self.trUtf8("Read Time Tracker File"), self.trUtf8("""<p>The time tracker file <b>{0}</b>""" """ contained %n invalid entries. These""" """ have been discarded.</p>""", "", invalidCount).format(self.__trackerFilePath)) def saveTrackerEntries(self, filePath="", ids=[]): """ Public slot to save the tracker entries to a file. @keyparam filePath path and name of the file to write the entries to (string) @keyparam ids list of entry IDs to be written (list of integer) """ if not filePath: filePath = self.__trackerFilePath if ids: entriesList = [self.__entries[eid] for eid in ids if eid in self.__entries] else: entriesList = self.__entries.values() try: f = open(filePath, "w", encoding="utf-8") for entry in entriesList: if entry.isValid(): f.write(entry.toString() + "\n") f.close() except (IOError, OSError) as err: E5MessageBox.critical( self.__ui, self.trUtf8("Save Time Tracker File"), self.trUtf8("""<p>The time tracker file <b>{0}</b> could""" """ not be saved.</p><p>Reason: {1}</p>""") .format(self.__trackerFilePath, str(err))) def importTrackerEntries(self, fname): """ Public slot to import tracker entries from a file. @param fname name of the file to import (string) """ try: f = open(fname, "r", encoding="utf-8") data = f.read() f.close() except (IOError, OSError) as err: E5MessageBox.critical( self.__ui, self.trUtf8("Import Time Tracker File"), self.trUtf8("""<p>The time tracker file <b>{0}</b> could""" """ not be read.</p><p>Reason: {1}</p>""") .format(fname, str(err))) return from .TimeTrackEntry import TimeTrackEntry invalidCount = 0 duplicateCount = 0 entries = [] for line in data.splitlines(): entry = TimeTrackEntry(self.__plugin) eid = entry.fromString(line.strip()) if eid > -1: entries.append(entry) else: invalidCount += 1 if not self.__plugin.getPreferences("AllowDuplicates"): startDateTimes = [ entry.getStartDateTime() for entry in self.__entries.values()] for entry in entries[:]: if entry.getStartDateTime() in startDateTimes: entries.remove(entry) duplicateCount += 1 if len(self.__entries.keys()): nextID = max(self.__entries.keys()) + 1 else: nextID = 0 for entry in entries: entry.setID(nextID) self.__entries[nextID] = entry nextID += 1 if self.__plugin.getPreferences("AutoSave"): self.saveTrackerEntries() if invalidCount != 0 or duplicateCount != 0: if invalidCount != 0 and duplicateCount != 0: msg = self.tr( """<p>The time tracker file <b>{0}</b> contained""" """ %n invalid entries.""", "", invalidCount).format(fname) msg += " " + self.tr( """ %n duplicate entries were detected.""", "", duplicateCount) elif duplicateCount != 0: msg = self.tr( """<p>The time tracker file <b>{0}</b> contained""" """ %n duplicate entries.""", "", duplicateCount).format(fname) elif invalidCount != 0: msg = self.tr( """<p>The time tracker file <b>{0}</b> contained""" """ %n invalid entries.""", "", invalidCount).format(fname) msg += " " + self.tr( """ %n entries have been ignored.</p>""", "", invalidCount + duplicateCount) E5MessageBox.information( self.__ui, self.trUtf8("Import Time Tracker File"), msg) self.__widget.clear() self.__widget.showTrackerEntries(sorted(self.__entries.values(), reverse=True)) self.__widget.setCurrentEntry(self.__currentEntry) def addTrackerEntry(self, startDateTime, duration, task, comment): """ Public method to add a new tracker entry based on the given data. @param startDateTime start date and time (QDateTime) @param duration duration in minutes (integer) @param task task description (string) @param comment comment (string) """ if not self.__plugin.getPreferences("AllowDuplicates"): startDateTimes = [ entry.getStartDateTime() for entry in self.__entries.values()] if startDateTime in startDateTimes: return if duration < self.__plugin.getPreferences("MinimumDuration"): return if len(self.__entries.keys()): nextID = max(self.__entries.keys()) + 1 else: nextID = 0 from .TimeTrackEntry import TimeTrackEntry entry = TimeTrackEntry(self.__plugin) entry.setID(nextID) entry.setStartDateTime(startDateTime) entry.setDuration(duration) entry.setTask(task) entry.setComment(comment) self.__entries[nextID] = entry self.__widget.clear() self.__widget.showTrackerEntries(sorted(self.__entries.values(), reverse=True)) self.__widget.setCurrentEntry(self.__currentEntry) def pauseTrackerEntry(self): """ Public method to pause the current tracker entry. """ self.__currentEntry.pause() def continueTrackerEntry(self): """ Public method to continue the current tracker entry. """ self.__currentEntry.continue_() def stopTrackerEntry(self): """ Public method to stop the current tracker entry. @return tuple of the ID assigned to the stopped tracker entry and the duration (integer, integer) """ duration = 0 nextID = -1 if self.__currentEntry is not None: self.__currentEntry.stop() if self.__currentEntry.isValid(): if len(self.__entries.keys()): nextID = max(self.__entries.keys()) + 1 else: nextID = 0 self.__currentEntry.setID(nextID) self.__entries[nextID] = self.__currentEntry if self.__plugin.getPreferences("AutoSave"): self.saveTrackerEntries() duration = self.__currentEntry.getDuration() self.__currentEntry = None return nextID, duration def startTrackerEntry(self): """ Public method to start a new tracker entry. """ from .TimeTrackEntry import TimeTrackEntry self.__currentEntry = TimeTrackEntry(self.__plugin) self.__currentEntry.start() self.__widget.setCurrentEntry(self.__currentEntry) def getCurrentEntry(self): """ Public method to get a reference to the current tracker entry. @return reference to the current entry (TimeTrackEntry) """ return self.__currentEntry def getEntry(self, eid): """ Public method to get a tracker entry given its ID. @param eid ID of the tracker entry (integer) @return entry for the given ID (TimeTrackEntry) or None """ if eid in self.__entries: return self.__entries[eid] else: return None def deleteTrackerEntry(self, eid): """ Public method to delete a tracker entry given its ID. @param eid ID of the tracker entry (integer) """ if eid in self.__entries: del self.__entries[eid] def removeDuplicateTrackerEntries(self): """ Public slot to remove duplicate time tracker entries. If entries with the identical start date and time are found, the one with the longest duration is kept. """ entries = {} for entry in self.__entries.values(): dt = entry.getStartDateTime() if dt in entries: if entry.getDuration() > entries[dt].getDuration(): entries[dt] = entry else: entries[dt] = entry self.__entries = {} nextID = 0 for entry in sorted(entries.values()): entry.setID(nextID) self.__entries[nextID] = entry nextID += 1 if self.__plugin.getPreferences("AutoSave"): self.saveTrackerEntries() self.__widget.clear() self.__widget.showTrackerEntries(sorted(self.__entries.values(), reverse=True)) self.__widget.setCurrentEntry(self.__currentEntry) def mergeDuplicateTrackerEntries(self): """ Public slot to merge duplicate time tracker entries. If entries with the identical start date and time are found, the durations of these entries are added. """ entries = {} for entry in self.__entries.values(): dt = entry.getStartDateTime() if dt in entries: entries[dt].addDuration(entry.getDuration()) else: entries[dt] = entry self.__entries = {} nextID = 0 for entry in sorted(entries.values()): entry.setID(nextID) self.__entries[nextID] = entry nextID += 1 if self.__plugin.getPreferences("AutoSave"): self.saveTrackerEntries() self.__widget.clear() self.__widget.showTrackerEntries(sorted(self.__entries.values(), reverse=True)) self.__widget.setCurrentEntry(self.__currentEntry) def entryChanged(self): """ Public method to indicate an external change to any of the entries. """ if self.__plugin.getPreferences("AutoSave"): self.saveTrackerEntries() def getPreferences(self, key): """ Public method to retrieve the various settings. @param key the key of the value to get @return the requested setting """ return self.__plugin.getPreferences(key) def __activateWidget(self): """ Private slot to handle the activation of the project browser. """ if self.__ui.layout == "Toolboxes": self.__ui.hToolboxDock.show() self.__ui.hToolboxDock.setCurrentWidget(self.__widget) elif self.__ui.layout == "Sidebars": self.__ui.bottomSidebar.show() self.__ui.bottomSidebar.setCurrentWidget(self.__widget) else: self.__widget.show() self.__widget.setFocus(Qt.ActiveWindowFocusReason)