TimeTracker/TimeTrackerWidget.py

Mon, 27 Mar 2017 19:10:33 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 27 Mar 2017 19:10:33 +0200
changeset 69
021ac1e44e32
parent 68
cf8b446f7691
child 73
9c5a741085c3
permissions
-rw-r--r--

Fixed some code style issues.

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

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

"""
Module implementing the time tracker widget.
"""

from __future__ import unicode_literals

import os

from PyQt5.QtCore import pyqtSlot, QPoint, Qt, QDate, QTime, QFileInfo
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QWidget, QMenu, QTreeWidgetItem, QDialog

from E5Gui import E5MessageBox, E5FileDialog

from .Ui_TimeTrackerWidget import Ui_TimeTrackerWidget

import Preferences
import Utilities


class TimeTrackerWidget(QWidget, Ui_TimeTrackerWidget):
    """
    Class implementing the time tracker widget.
    """
    DurationColumn = 1
    TaskColumn = 2
    CommentColumn = 3
    
    def __init__(self, tracker, parent=None):
        """
        Constructor
        
        @param tracker reference to the time tracker (TimeTracker)
        @param parent reference to the parent widget (QWidget)
        """
        super(TimeTrackerWidget, self).__init__(parent)
        self.setupUi(self)
        
        self.__tracker = tracker
    
    @pyqtSlot(str)
    def on_taskCombo_editTextChanged(self, txt):
        """
        Private slot handling changes of the task description of the current
        entry.
        
        @param txt new task description (string)
        """
        itm = self.entriesList.topLevelItem(0)
        if itm:
            itm.setText(self.TaskColumn, txt)
            self.entriesList.resizeColumnToContents(self.TaskColumn)
        
        entry = self.__tracker.getCurrentEntry()
        if entry:
            entry.setTask(txt)
    
    @pyqtSlot(str)
    def on_commentCombo_editTextChanged(self, txt):
        """
        Private slot handling changes of the comment of the current entry.
        
        @param txt new comment (string)
        """
        itm = self.entriesList.topLevelItem(0)
        if itm:
            itm.setText(self.CommentColumn, txt)
            self.entriesList.resizeColumnToContents(self.CommentColumn)
        
        entry = self.__tracker.getCurrentEntry()
        if entry:
            entry.setComment(txt)
    
    @pyqtSlot(bool)
    def on_pauseButton_toggled(self, checked):
        """
        Private slot to pause the current timing.
        
        @param checked flag indicating the checked status of the button
            (boolean)
        """
        if checked:
            self.__tracker.pauseTrackerEntry()
            
            entry = self.__tracker.getCurrentEntry()
            duration = entry.getDuration()
            
            itm = self.entriesList.topLevelItem(0)
            itm.setText(self.DurationColumn,
                        self.tr("{0} min").format(duration))
            self.entriesList.resizeColumnToContents(self.DurationColumn)
            
            self.durationSpinBox.setValue(duration)
        else:
            self.__tracker.continueTrackerEntry()
    
    @pyqtSlot()
    def on_newButton_clicked(self):
        """
        Private slot to end the current timer and start a new one.
        """
        # stop the current tracker
        eid, duration = self.__tracker.stopTrackerEntry()
        if eid > -1:
            itm = self.entriesList.topLevelItem(0)
            itm.setText(self.DurationColumn,
                        self.tr("{0} min").format(duration))
            itm.setData(0, Qt.UserRole, eid)
        else:
            itm = self.entriesList.takeTopLevelItem(0)
            del itm
        self.__resizeColumns()
        
        # start a new one
        self.__tracker.startTrackerEntry()
    
    @pyqtSlot(QPoint)
    def on_entriesList_customContextMenuRequested(self, pos):
        """
        Private slot to create the context menu and show it.
        
        @param pos position the menu should be shown at (QPoint)
        """
        menu = QMenu()
        
        act = menu.addAction(self.tr("Edit"), self.__editEntry)
        act.setEnabled(
            len(self.entriesList.selectedItems()) == 1 and
            self.entriesList.selectedItems()[0].data(0, Qt.UserRole) > -1
        )
        menu.addAction(self.tr("Add"), self.__addEntry)
        act = menu.addAction(self.tr("Delete"), self.__deleteSelectedEntries)
        act.setEnabled(
            (len(self.entriesList.selectedItems()) == 1 and
             self.entriesList.selectedItems()[0].data(0, Qt.UserRole) > -1) or
            len(self.entriesList.selectedItems()) > 1
        )
        menu.addSeparator()
        menu.addAction(self.tr("Save"), self.__saveEntries)
        menu.addSeparator()
        menu.addAction(self.tr("Import"), self.__importEntries)
        menu.addAction(self.tr("Export Selected"),
                       self.__exportSelectedEntries)\
            .setEnabled(len(self.entriesList.selectedItems()) != 0)
        menu.addAction(self.tr("Export All"), self.__exportEntries)
        menu.addSeparator()
        menu.addAction(self.tr("Remove duplicates"), self.__removeDuplicates)
        menu.addAction(self.tr("Merge duplicates"), self.__mergeDuplicates)
        menu.exec_(QCursor.pos())
    
    def __addEntry(self):
        """
        Private slot to manually add an entry.
        """
        from .TimeTrackerEntryDialog import TimeTrackerEntryDialog
        
        tasks = []
        for index in range(self.taskCombo.count()):
            tasks.append(self.taskCombo.itemText(index))
        comments = []
        for index in range(self.commentCombo.count()):
            comments.append(self.commentCombo.itemText(index))
        dlg = TimeTrackerEntryDialog(self.__tracker, None, tasks, comments)
        if dlg.exec_() == QDialog.Accepted:
            self.__tracker.addTrackerEntry(*dlg.getData())
    
    def __editEntry(self):
        """
        Private slot to edit the selected tracker entry.
        """
        itm = self.entriesList.selectedItems()[0]
        eid = itm.data(0, Qt.UserRole)
        if eid > -1:
            # the current entry is edited via the elements of this widget
            entry = self.__tracker.getEntry(eid)
            if entry is not None:
                from .TimeTrackerEntryDialog import TimeTrackerEntryDialog
                
                tasks = []
                for index in range(self.taskCombo.count()):
                    tasks.append(self.taskCombo.itemText(index))
                comments = []
                for index in range(self.commentCombo.count()):
                    comments.append(self.commentCombo.itemText(index))
                dlg = TimeTrackerEntryDialog(self.__tracker, entry, tasks,
                                             comments)
                if dlg.exec_() == QDialog.Accepted:
                    start, duration, task, comment = dlg.getData()
                    
                    entry.setStartDateTime(start)
                    entry.setDuration(duration)
                    entry.setTask(task)
                    entry.setComment(comment)
                    self.__tracker.entryChanged()
                    
                    date, time, duration, task, comment = \
                        entry.getEntryData()[1:-1]
                    itm.setText(0, self.tr("{0}, {1}", "date, time")
                                .format(date, time))
                    itm.setText(1, self.tr("{0} min").format(duration))
                    itm.setText(2, task)
                    itm.setText(3, comment)
                    self.__resizeColumns()
    
    def __deleteSelectedEntries(self):
        """
        Private slot to delete the selected tracker entries.
        """
        res = E5MessageBox.yesNo(
            self,
            self.tr("Delete Selected Entries"),
            self.tr("""Do you really want to delete the selected"""
                    """ entries?"""))
        if res:
            for item in self.entriesList.selectedItems():
                eid = item.data(0, Qt.UserRole)
                if eid > -1:
                    # the current entry must not be deleted
                    self.entriesList.takeTopLevelItem(
                        self.entriesList.indexOfTopLevelItem(item))
                    self.__tracker.deleteTrackerEntry(eid)
                    del item
    
    def __saveEntries(self):
        """
        Private slot to save the tracker entries.
        """
        self.__tracker.saveTrackerEntries()
    
    def __importEntries(self):
        """
        Private slot to import tracker entries.
        """
        path = Preferences.getMultiProject("Workspace") or \
            Utilities.getHomeDir()
        fname = E5FileDialog.getOpenFileName(
            None,
            self.tr("Import Tracker Entries"),
            path,
            self.tr("Text Files (*.txt);;All Files (*)"))
        if fname:
            fname = Utilities.toNativeSeparators(fname)
            if not os.path.exists(fname):
                E5MessageBox.critical(
                    self,
                    self.tr("Import Tracker Entries"),
                    self.tr("<p>The file <b>{0}</b> does not exist.</p>")
                    .format(fname))
                return
            
            self.__tracker.importTrackerEntries(fname)
    
    def __exportEntries(self, ids=None):
        """
        Private method to export all or selected entries.
        
        @keyparam ids list of IDs to export or all if empty (list of integer)
        """
        path = Preferences.getMultiProject("Workspace") or \
            Utilities.getHomeDir()
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Export Tracker Entries"),
            path,
            self.tr("Text Files (*.txt);;All Files (*)"),
            None,
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        if fname:
            ext = QFileInfo(fname).suffix()
            if not ext:
                ex = selectedFilter.split("(*")[1].split(")")[0]
                if ex:
                    fname += ex
            if QFileInfo(fname).exists():
                res = E5MessageBox.yesNo(
                    self,
                    self.tr("Export Tracker Entries"),
                    self.tr("<p>The file <b>{0}</b> already exists."
                            " Overwrite it?</p>").format(fname),
                    icon=E5MessageBox.Warning)
                if not res:
                    return
                fname = Utilities.toNativeSeparators(fname)
            self.__tracker.saveTrackerEntries(filePath=fname, ids=ids)
    
    def __exportSelectedEntries(self):
        """
        Private slot to export the selected tracker entries.
        """
        ids = []
        for itm in self.entriesList.selectedItems():
            eid = itm.data(0, Qt.UserRole)
            if eid > -1:
                ids.append(eid)
        
        if ids:
            self.__exportEntries(ids=ids)
    
    def __removeDuplicates(self):
        """
        Private slot to remove duplicate entries.
        """
        res = E5MessageBox.yesNo(
            self,
            self.tr("Remove Duplicate Tracker Entries"),
            self.tr("""Are you sure you want to remove duplicate"""
                    """ tracker entries? Only the one with the longest"""
                    """ duration will be kept."""))
        if res:
            self.__tracker.removeDuplicateTrackerEntries()
    
    def __mergeDuplicates(self):
        """
        Private slot to merge duplicate entries.
        """
        res = E5MessageBox.yesNo(
            self,
            self.tr("Merge Duplicate Tracker Entries"),
            self.tr("""Are you sure you want to merge duplicate"""
                    """ tracker entries? The durations of duplicate"""
                    """ ones will be added."""))
        if res:
            self.__tracker.mergeDuplicateTrackerEntries()
    
    def __insertEntry(self, entry, index=-1):
        """
        Private method to insert a tracker entry into the list.
        
        @param entry reference to the tracker entry (TimeTrackEntry)
        @param index index the entry is to be inserted; -1 for at the end
            (integer)
        """
        eid, date, time, duration, task, comment, paused = entry.getEntryData()
        itm = QTreeWidgetItem(
            [self.tr("{0}, {1}", "date, time").format(date, time),
             self.tr("{0} min").format(duration), task, comment])
        itm.setTextAlignment(1, Qt.AlignRight)
        itm.setData(0, Qt.UserRole, eid)
        if index == -1:
            self.entriesList.addTopLevelItem(itm)
        else:
            self.entriesList.insertTopLevelItem(index, itm)
    
    def __resizeColumns(self):
        """
        Private slot to resize the columns of the entries list.
        """
        for column in range(self.entriesList.columnCount()):
            self.entriesList.resizeColumnToContents(column)
    
    def showTrackerEntries(self, entries):
        """
        Public method to show the tracker entries of the current project.
        
        @param entries list of tracker entries (list of TimeTrackEntry)
        """
        self.taskCombo.addItem("")
        self.commentCombo.addItem("")
        
        tasks = []
        comments = []
        
        for entry in entries:
            self.__insertEntry(entry)
            task = entry.getTask()
            if task and task not in tasks:
                tasks.append(task)
            comment = entry.getComment()
            if comment and comment not in comments:
                comments.append(comment)
        
        self.__resizeColumns()
        if tasks:
            self.taskCombo.addItems(sorted(tasks))
        if comments:
            self.commentCombo.addItems(sorted(comments))
    
    def setCurrentEntry(self, entry):
        """
        Public method to set the current entry.
        
        @param entry current entry (TimeTrackEntry)
        """
        self.__insertEntry(entry, 0)
        self.__resizeColumns()
        
        eid, date, time, duration, task, comment, paused = entry.getEntryData()
        self.startDateTimeEdit.setDateTime(entry.getStartDateTime())
        self.durationSpinBox.setValue(duration)
        self.taskCombo.setEditText(task)
        self.commentCombo.setEditText(comment)
    
    def clear(self):
        """
        Public method to clear all the data.
        """
        self.entriesList.clear()
        self.startDateTimeEdit.setDate(QDate(2000, 1, 1))
        self.startDateTimeEdit.setTime(QTime(0, 0, 0))
        self.durationSpinBox.setValue(0)
        self.taskCombo.clear()
        self.commentCombo.clear()

eric ide

mercurial