TimeTracker/TimeTrackerWidget.py

Mon, 24 Oct 2022 18:11:15 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 24 Oct 2022 18:11:15 +0200
branch
eric7
changeset 109
3d6e8bb07779
parent 108
702f47d3f794
child 111
e0bf42d8474e
permissions
-rw-r--r--

Adapted the import statements to the new structure.

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

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

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

import os

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

from eric7 import Preferences, Utilities
from eric7.EricWidgets import EricMessageBox, EricFileDialog

from .Ui_TimeTrackerWidget import Ui_TimeTrackerWidget


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
        @type TimeTracker
        @param parent reference to the parent widget
        @type QWidget
        """
        super().__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
        @type str
        """
        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
        @type str
        """
        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
        @type bool
        """
        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.ItemDataRole.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
        @type 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.ItemDataRole.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.ItemDataRole.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)
        act = menu.addAction(self.tr("Export Selected"), self.__exportSelectedEntries)
        act.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 = [
            self.taskCombo.itemText(index) for index in range(self.taskCombo.count())
        ]
        comments = [
            self.commentCombo.itemText(index)
            for index in range(self.commentCombo.count())
        ]
        dlg = TimeTrackerEntryDialog(self.__tracker, None, tasks, comments)
        if dlg.exec() == QDialog.DialogCode.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.ItemDataRole.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 = [
                    self.taskCombo.itemText(index)
                    for index in range(self.taskCombo.count())
                ]
                comments = [
                    self.commentCombo.itemText(index)
                    for index in range(self.commentCombo.count())
                ]
                dlg = TimeTrackerEntryDialog(self.__tracker, entry, tasks, comments)
                if dlg.exec() == QDialog.DialogCode.Accepted:
                    start, duration, task, comment = dlg.getData()

                    entry.setStartDateTime(start)
                    entry.setDuration(duration)
                    entry.setTask(task)
                    entry.setComment(comment)
                    self.__tracker.entryChanged()

                    data = entry.getEntryData()
                    itm.setText(
                        0,
                        self.tr("{0}, {1}", "date, time").format(
                            data["start_date"], data["start_time"]
                        ),
                    )
                    itm.setText(1, self.tr("{0} min").format(data["duration"]))
                    itm.setText(2, data["task"])
                    itm.setText(3, data["comment"])
                    self.__resizeColumns()

    def __deleteSelectedEntries(self):
        """
        Private slot to delete the selected tracker entries.
        """
        res = EricMessageBox.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.ItemDataRole.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 = EricFileDialog.getOpenFileName(
            None,
            self.tr("Import Time Tracker Entries"),
            path,
            self.tr("Time Tracker Files (*.ttj);;All Files (*)"),
        )
        if fname:
            fname = Utilities.toNativeSeparators(fname)
            if not os.path.exists(fname):
                EricMessageBox.critical(
                    self,
                    self.tr("Import Time 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.

        @param ids list of IDs to export or all if empty
        @type list of int
        """
        path = Preferences.getMultiProject("Workspace") or Utilities.getHomeDir()
        fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Export Time Tracker Entries"),
            path,
            self.tr("Time Tracker Files (*.ttj);;All Files (*)"),
            None,
            EricFileDialog.Options(EricFileDialog.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 = EricMessageBox.yesNo(
                    self,
                    self.tr("Export Time Tracker Entries"),
                    self.tr(
                        "<p>The file <b>{0}</b> already exists." " Overwrite it?</p>"
                    ).format(fname),
                    icon=EricMessageBox.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.ItemDataRole.UserRole)
            if eid > -1:
                ids.append(eid)

        if ids:
            self.__exportEntries(ids=ids)

    def __removeDuplicates(self):
        """
        Private slot to remove duplicate entries.
        """
        res = EricMessageBox.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 = EricMessageBox.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
        @type TimeTrackEntry
        @param index index the entry is to be inserted; -1 for at the end
        @type int
        """
        data = entry.getEntryData()
        itm = QTreeWidgetItem(
            [
                self.tr("{0}, {1}", "date, time").format(
                    data["start_date"], data["start_time"]
                ),
                self.tr("{0} min").format(data["duration"]),
                data["task"],
                data["comment"],
            ]
        )
        itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight)
        itm.setData(0, Qt.ItemDataRole.UserRole, data["id"])
        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
        @type 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
        @type TimeTrackEntry
        """
        self.__insertEntry(entry, 0)
        self.__resizeColumns()

        data = entry.getEntryData()
        self.startDateTimeEdit.setDateTime(entry.getStartDateTime())
        self.durationSpinBox.setValue(data["duration"])
        self.taskCombo.setEditText(data["task"])
        self.commentCombo.setEditText(data["comment"])

    def clear(self):
        """
        Public method to clear all the data.
        """
        self.entriesList.clear()
        self.startDateTimeEdit.setDate(QDate(2020, 1, 1))
        self.startDateTimeEdit.setTime(QTime(0, 0, 0))
        self.durationSpinBox.setValue(0)
        self.taskCombo.clear()
        self.commentCombo.clear()

eric ide

mercurial