diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 233f08d7a..5c994a25a 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -438,6 +438,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/project/projectpropertieshandler.cpp ${QET_DIR}/sources/project/projectpropertieshandler.h + ${QET_DIR}/sources/project/projectusagetracker.cpp + ${QET_DIR}/sources/project/projectusagetracker.h ${QET_DIR}/sources/properties/elementdata.cpp ${QET_DIR}/sources/properties/elementdata.h diff --git a/sources/project/projectpropertieshandler.cpp b/sources/project/projectpropertieshandler.cpp index 4d924de37..10b021148 100644 --- a/sources/project/projectpropertieshandler.cpp +++ b/sources/project/projectpropertieshandler.cpp @@ -28,3 +28,8 @@ TerminalStripLayoutsHandler &ProjectPropertiesHandler::terminalStripLayoutHandle { return m_terminal_strip_layout_handler; } + +ProjectUsageTracker &ProjectPropertiesHandler::usageTracker() +{ + return m_usage_tracker; +} diff --git a/sources/project/projectpropertieshandler.h b/sources/project/projectpropertieshandler.h index 29413d3f0..b78153fbb 100644 --- a/sources/project/projectpropertieshandler.h +++ b/sources/project/projectpropertieshandler.h @@ -21,6 +21,7 @@ #include #include "../TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.h" +#include "projectusagetracker.h" class QETProject; @@ -43,11 +44,13 @@ class ProjectPropertiesHandler ProjectPropertiesHandler(QETProject *project); TerminalStripLayoutsHandler& terminalStripLayoutHandler(); + ProjectUsageTracker& usageTracker(); private: QPointer m_project; TerminalStripLayoutsHandler m_terminal_strip_layout_handler; + ProjectUsageTracker m_usage_tracker; }; #endif // PROJECTPROPERTIESHANDLER_H diff --git a/sources/project/projectusagetracker.cpp b/sources/project/projectusagetracker.cpp new file mode 100644 index 000000000..1e2a5743a --- /dev/null +++ b/sources/project/projectusagetracker.cpp @@ -0,0 +1,121 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "projectusagetracker.h" + +#include + +/** + @brief ProjectUsageTracker::secondsSpent + @return the total accumulated time, in seconds, including the time + elapsed since the tracker was last made active (if it currently is). +*/ +qint64 ProjectUsageTracker::secondsSpent() const +{ + qint64 total = m_seconds_spent; + if (m_elapsed_timer.isValid()) { + total += m_elapsed_timer.elapsed() / 1000; + } + return total; +} + +/** + @brief ProjectUsageTracker::resetSecondsSpent + Zero the accumulated time. If the tracker is currently timing, it keeps + timing from this point on. +*/ +void ProjectUsageTracker::resetSecondsSpent() +{ + m_seconds_spent = 0; + if (m_elapsed_timer.isValid()) { + m_elapsed_timer.restart(); + } +} + +/** + @brief ProjectUsageTracker::setEnabled + Enable or disable time tracking for this project. Disabling while + currently timing flushes the elapsed time first; re-enabling while + the project is the active tab resumes timing immediately. + @param enabled +*/ +void ProjectUsageTracker::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + + if (!enabled && m_elapsed_timer.isValid()) { + m_seconds_spent += m_elapsed_timer.elapsed() / 1000; + m_elapsed_timer.invalidate(); + } else if (enabled && m_active) { + m_elapsed_timer.start(); + } + m_enabled = enabled; +} + +/** + @brief ProjectUsageTracker::setActive + Tell the tracker whether this project's tab is currently the active + one. No-op if tracking is disabled or the state doesn't change. + @param active +*/ +void ProjectUsageTracker::setActive(bool active) +{ + if (m_active == active) { + return; + } + + if (!active && m_elapsed_timer.isValid()) { + m_seconds_spent += m_elapsed_timer.elapsed() / 1000; + m_elapsed_timer.invalidate(); + } else if (active && m_enabled) { + m_elapsed_timer.start(); + } + m_active = active; +} + +/** + @brief ProjectUsageTracker::toXml + Append a child element to @p parent_element with the current + accumulated time and enabled state. + @param parent_element +*/ +void ProjectUsageTracker::toXml(QDomElement &parent_element) const +{ + QDomElement usage_element = + parent_element.ownerDocument().createElement(QStringLiteral("usage")); + usage_element.setAttribute(QStringLiteral("time_spent"), QString::number(secondsSpent())); + usage_element.setAttribute(QStringLiteral("enabled"), m_enabled ? QStringLiteral("true") : QStringLiteral("false")); + parent_element.appendChild(usage_element); +} + +/** + @brief ProjectUsageTracker::fromXml + Read the child element of @p parent_element, if any. + @param parent_element +*/ +void ProjectUsageTracker::fromXml(const QDomElement &parent_element) +{ + const QDomElement usage_element = parent_element.firstChildElement(QStringLiteral("usage")); + if (usage_element.isNull()) { + return; + } + + m_seconds_spent = usage_element.attribute(QStringLiteral("time_spent"), QStringLiteral("0")).toLongLong(); + m_enabled = usage_element.attribute(QStringLiteral("enabled"), QStringLiteral("true")) != QStringLiteral("false"); +} diff --git a/sources/project/projectusagetracker.h b/sources/project/projectusagetracker.h new file mode 100644 index 000000000..fb805a30b --- /dev/null +++ b/sources/project/projectusagetracker.h @@ -0,0 +1,58 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef PROJECTUSAGETRACKER_H +#define PROJECTUSAGETRACKER_H + +#include + +class QDomElement; + +/** + @brief The ProjectUsageTracker class + Accumulates how long a project has been the active tab, purely locally: + the value lives only in the project's own file (a element next + to ) and is never transmitted anywhere. + + The owning code (QETDiagramEditor) is expected to call setActive(true) + when this project's tab becomes the current one, and setActive(false) + when it stops being current (tab switch, project closed, ...). +*/ +class ProjectUsageTracker +{ + public: + ProjectUsageTracker() = default; + + qint64 secondsSpent() const; + void resetSecondsSpent(); + + bool isEnabled() const {return m_enabled;} + void setEnabled(bool enabled); + + void setActive(bool active); + + void toXml(QDomElement &parent_element) const; + void fromXml(const QDomElement &parent_element); + + private: + qint64 m_seconds_spent = 0; + bool m_enabled = true; + bool m_active = false; + QElapsedTimer m_elapsed_timer; +}; + +#endif // PROJECTUSAGETRACKER_H diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index da9c09083..88c239d6e 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -29,6 +29,9 @@ #include "elementspanelwidget.h" #include "factory/qetgraphicstablefactory.h" #include "print/projectprintwindow.h" +#include "project/projectpropertieshandler.h" +#include "projectview.h" +#include "qetproject.h" #include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h" #include "qetgraphicsitem/conductortextitem.h" #include "qetgraphicsitem/dynamicelementtextitem.h" @@ -2466,6 +2469,30 @@ void QETDiagramEditor::subWindowActivated(QMdiSubWindow *subWindows) slot_updateActions(); slot_updateWindowsMenu(); emit syncElementsPanel(); + updateUsageTrackersActiveState(); +} + +/** + @brief QETDiagramEditor::updateUsageTrackersActiveState + Mark the currently active project's usage tracker (time spent on this + project) as active, and every other opened project's tracker as + inactive. Called whenever the current MDI subwindow changes. + + Known limitation: this only accounts for tab switches within this + QETDiagramEditor window. If the same project were ever shown as the + active tab in two different windows at once, its tracked time could be + double-counted -- today QETApp only ever gives a project one ProjectView, + so this doesn't happen in practice. +*/ +void QETDiagramEditor::updateUsageTrackersActiveState() +{ + QETProject *active_project = currentProject(); + const QList project_views = openedProjects(); + for (ProjectView *project_view : project_views) { + if (QETProject *project = project_view->project()) { + project->projectPropertiesHandler().usageTracker().setActive(project == active_project); + } + } } /** diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 9daf6a122..98a60a967 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -97,6 +97,7 @@ class QETDiagramEditor : public QETMainWindow ProjectView *findProject(QETProject *) const; ProjectView *findProject(const QString &) const; QMdiSubWindow *subWindowForWidget(QWidget *) const; + void updateUsageTrackersActiveState(); signals: void syncElementsPanel(); diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index cf7dfc799..f64440a09 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -996,6 +996,9 @@ QDomDocument QETProject::toXml() writeProjectPropertiesXml(project_properties); project_root.appendChild(project_properties); + // local, non-transmitted usage tracking (time spent on this project) + writeUsageXml(project_root); + // Properties for news diagrams QDomElement new_diagrams_properties = xml_doc.createElement("newdiagrams"); writeDefaultPropertiesXml(new_diagrams_properties); @@ -1496,6 +1499,9 @@ void QETProject::readProjectXml(QDomDocument &xml_project) //Load the project-wide properties readProjectPropertiesXml(xml_project); + //Load the local, non-transmitted usage tracking + readUsageXml(xml_project); + //Load the default properties for the new diagrams readDefaultPropertiesXml(xml_project); @@ -1649,6 +1655,17 @@ void QETProject::readProjectPropertiesXml(QDomDocument &xml_project) m_project_properties.fromXml(dom_elmt); } +/** + @brief QETProject::readUsageXml + Load the local, non-transmitted usage tracking (time spent on this + project) from the XML description of the project. + @param xml_project : the xml description of the project +*/ +void QETProject::readUsageXml(QDomDocument &xml_project) +{ + m_project_properties_handler.usageTracker().fromXml(xml_project.documentElement()); +} + /** @brief QETProject::readDefaultPropertiesXml load default properties for new diagram, found in the xml of this project @@ -1784,6 +1801,15 @@ void QETProject::writeProjectPropertiesXml(QDomElement &xml_element) { m_project_properties.toXml(xml_element); } +/** + @brief QETProject::writeUsageXml + Export the local, non-transmitted usage tracking (time spent on this + project) as a child of \a xml_element. +*/ +void QETProject::writeUsageXml(QDomElement &xml_element) { + m_project_properties_handler.usageTracker().toXml(xml_element); +} + /** @brief QETProject::writeDefaultPropertiesXml Export all defaults properties used by a new diagram and his content diff --git a/sources/qetproject.h b/sources/qetproject.h index 6e96c1e6a..5e1a1a368 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -251,9 +251,11 @@ class QETProject : public QObject void readProjectPropertiesXml(QDomDocument &xml_project); void readDefaultPropertiesXml(QDomDocument &xml_project); void readTerminalStripXml(const QDomDocument &xml_project); + void readUsageXml(QDomDocument &xml_project); void writeProjectPropertiesXml(QDomElement &); void writeDefaultPropertiesXml(QDomElement &); + void writeUsageXml(QDomElement &); void addDiagram(Diagram *diagram, int pos = -1); void writeBackup(); void init(); diff --git a/sources/ui/configpage/projectconfigpages.cpp b/sources/ui/configpage/projectconfigpages.cpp index 422e27109..e4fcb69ef 100644 --- a/sources/ui/configpage/projectconfigpages.cpp +++ b/sources/ui/configpage/projectconfigpages.cpp @@ -22,6 +22,7 @@ #include "../autoNum/ui/folioautonumbering.h" #include "../autoNum/ui/formulaautonumberingw.h" #include "../autoNum/ui/selectautonumw.h" +#include "../project/projectpropertieshandler.h" #include "../qeticons.h" #include "../qetproject.h" #include "../borderpropertieswidget.h" @@ -161,6 +162,13 @@ void ProjectMainConfigPage::applyProjectConf() m_project -> setProjectProperties(new_properties); modified_project = true; } + + ProjectUsageTracker &usage_tracker = m_project -> projectPropertiesHandler().usageTracker(); + if (usage_tracker.isEnabled() != usage_enabled_cb_ -> isChecked()) { + usage_tracker.setEnabled(usage_enabled_cb_ -> isChecked()); + modified_project = true; + } + if (modified_project) { m_project -> setModified(true); } @@ -191,6 +199,12 @@ void ProjectMainConfigPage::initWidgets() project_variables_label_ -> setWordWrap(true); project_variables_ = new DiagramContextWidget(); project_variables_ -> setContext(DiagramContext()); + + usage_label_ = new QLabel(tr("Temps passé sur ce projet :", "label when configuring")); + usage_value_ = new QLabel(); + usage_enabled_cb_ = new QCheckBox(tr("Suivre le temps passé sur ce projet (uniquement enregistré localement dans ce fichier)", "checkbox label")); + usage_reset_pb_ = new QPushButton(tr("Réinitialiser", "button label")); + connect(usage_reset_pb_, &QPushButton::clicked, this, &ProjectMainConfigPage::resetUsageTracker); } /** @@ -207,6 +221,16 @@ void ProjectMainConfigPage::initLayout() main_layout0 -> addSpacing(10); main_layout0 -> addWidget(project_variables_label_); main_layout0 -> addWidget(project_variables_); + main_layout0 -> addSpacing(10); + + QHBoxLayout *usage_layout0 = new QHBoxLayout(); + usage_layout0 -> addWidget(usage_label_); + usage_layout0 -> addWidget(usage_value_); + usage_layout0 -> addStretch(); + usage_layout0 -> addWidget(usage_reset_pb_); + main_layout0 -> addLayout(usage_layout0); + main_layout0 -> addWidget(usage_enabled_cb_); + setLayout(main_layout0); this -> setMinimumWidth(680); @@ -219,6 +243,27 @@ void ProjectMainConfigPage::readValuesFromProject() { title_value_ -> setText(m_project -> title()); project_variables_ -> setContext(m_project -> projectProperties()); + + const ProjectUsageTracker &usage_tracker = m_project -> projectPropertiesHandler().usageTracker(); + const qint64 total_seconds = usage_tracker.secondsSpent(); + usage_value_ -> setText(tr("%1 h %2 min", "hours and minutes of time spent on a project") + .arg(total_seconds / 3600) + .arg((total_seconds % 3600) / 60)); + usage_enabled_cb_ -> setChecked(usage_tracker.isEnabled()); +} + +/** + @brief ProjectMainConfigPage::resetUsageTracker + Reset the accumulated "time spent on this project" counter to zero and + refresh its displayed value. Applies immediately (not staged behind + OK/Cancel like the other fields on this page), since it isn't + destructive to any actual project content. +*/ +void ProjectMainConfigPage::resetUsageTracker() +{ + m_project -> projectPropertiesHandler().usageTracker().resetSecondsSpent(); + m_project -> setModified(true); + usage_value_ -> setText(tr("%1 h %2 min", "hours and minutes of time spent on a project").arg(0).arg(0)); } /** @@ -229,6 +274,8 @@ void ProjectMainConfigPage::adjustReadOnly() { bool is_read_only = m_project -> isReadOnly(); title_value_ -> setReadOnly(is_read_only); + usage_enabled_cb_ -> setDisabled(is_read_only); + usage_reset_pb_ -> setDisabled(is_read_only); } //######################################################################################// diff --git a/sources/ui/configpage/projectconfigpages.h b/sources/ui/configpage/projectconfigpages.h index 7fd7f19f3..79649689a 100644 --- a/sources/ui/configpage/projectconfigpages.h +++ b/sources/ui/configpage/projectconfigpages.h @@ -21,6 +21,8 @@ class QLabel; class QLineEdit; +class QCheckBox; +class QPushButton; class QETProject; class BorderPropertiesWidget; class ConductorPropertiesWidget; @@ -108,7 +110,10 @@ class ProjectMainConfigPage : public ProjectConfigPage { void initLayout() override; void readValuesFromProject() override; void adjustReadOnly() override; - + + private slots: + void resetUsageTracker(); + // attributes protected: QLabel *title_label_; @@ -116,6 +121,10 @@ class ProjectMainConfigPage : public ProjectConfigPage { QLabel *title_information_; QLabel *project_variables_label_; DiagramContextWidget *project_variables_; + QLabel *usage_label_; + QLabel *usage_value_; + QCheckBox *usage_enabled_cb_; + QPushButton *usage_reset_pb_; }; class ProjectAutoNumConfigPage : public ProjectConfigPage {