TimeTracker/TimeTracker.py

Sun, 15 Jul 2018 19:33:21 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 15 Jul 2018 19:33:21 +0200
changeset 77
ade3656b8ad3
parent 75
d88d2d4943de
child 79
c363184776fd
permissions
-rw-r--r--

TimeTracker: fixed another issue switching to the time tracker.

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

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

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

from __future__ import unicode_literals

import os

from PyQt5.QtCore import Qt, QObject
from PyQt5.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):
        """
        Private 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.tr('Time Tracker'),
            self.tr('T&ime Tracker'),
            QKeySequence(self.tr("Alt+Shift+I")),
            0, self,
            'time_tracker_activate')
        self.__activateAct.setStatusTip(self.tr(
            "Switch the input focus to the Time Tracker window."))
        self.__activateAct.setWhatsThis(self.tr(
            """<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.tr("Read Time Tracker File"),
                    self.tr("""<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.tr("Read Time Tracker File"),
                    self.tr("""<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=None):
        """
        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.tr("Save Time Tracker File"),
                self.tr("""<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.tr("Import Time Tracker File"),
                self.tr("""<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 = [
                e.getStartDateTime() for e 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.tr("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.
        """
        try:
            uiLayoutType = self.__ui.getLayoutType()
        except AttributeError:
            # backward compatibility for eric < 18.08
            uiLayoutType = self.__ui.layoutType
        
        if uiLayoutType == "Toolboxes":
            self.__ui.hToolboxDock.show()
            self.__ui.hToolbox.setCurrentWidget(self.__widget)
        elif uiLayoutType == "Sidebars":
            self.__ui.bottomSidebar.show()
            self.__ui.bottomSidebar.setCurrentWidget(self.__widget)
        else:
            self.__widget.show()
        self.__widget.setFocus(Qt.ActiveWindowFocusReason)

eric ide

mercurial