TimeTracker/TimeTracker.py

Sun, 21 Oct 2012 19:23:08 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 21 Oct 2012 19:23:08 +0200
changeset 15
645506ab3376
parent 12
6c91abc72022
child 21
28b7956c9608
permissions
-rw-r--r--

Implemented the 'add entry' action.

# -*- coding: utf-8 -*-

# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the time tracker object.
"""

import os

from PyQt4.QtCore import QObject

from E5Gui.E5Application import e5App
from E5Gui import E5MessageBox

from .TimeTrackEntry import TimeTrackEntry
from .TimeTrackerWidget import TimeTrackerWidget

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")
        
        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.__initialize()
    
    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 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.
        """
        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
            
            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
        
        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
        
        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.
        """
        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 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)

eric ide

mercurial