diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7fea4da..bf0d739 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -23,6 +23,7 @@ set(SRCS
gui/mainWindow.cpp
gui/settingsDialog.cpp
gui/taskWidget.cpp
+ gui/timeSheetItemDelegate.cpp
gui/timeSheetListWidgetItem.cpp
gui/timeSheetParamsItemDelegate.cpp
main.cpp
@@ -55,6 +56,7 @@ set(HDRS
gui/mainWindow.h
gui/settingsDialog.h
gui/taskWidget.h
+ gui/timeSheetItemDelegate.h
gui/timeSheetListWidgetItem.h
gui/timeSheetParamsItemDelegate.h
misc/customFmt.h
diff --git a/src/gui/activityWidget.cpp b/src/gui/activityWidget.cpp
index ff64ffb..192df2f 100644
--- a/src/gui/activityWidget.cpp
+++ b/src/gui/activityWidget.cpp
@@ -5,6 +5,7 @@
#include "misc/customFmt.h"
#include "misc/helpers.h"
+#include "timeSheetItemDelegate.h"
#include "timeSheetListWidgetItem.h"
#include "timeSheetParamsItemDelegate.h"
@@ -21,6 +22,10 @@ ActivityWidget::ActivityWidget(QWidget* parent) : QWidget(parent), mUi(std::make
mUi->cbTimeSheetParams->setItemDelegate(new TimeSheetParamsItemDelegate);
mUi->lwHistory->setModel(&mTimeSheetModel);
+ mUi->lwHistory->setItemDelegate(new TimeSheetItemDelegate);
+
+ auto scrollBarWidth = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+ mUi->lwHistoryOutsideLayout->setContentsMargins(scrollBarWidth, 0, 0, 0);
connect(mUi->btStartStop, &QPushButton::clicked, this, &ActivityWidget::onBtStartStopClicked);
connect(mUi->cbTimeSheetParams, &QComboBox::currentIndexChanged, this, &ActivityWidget::onTimeSheetParamsIndexChanged);
diff --git a/src/gui/activityWidget.ui b/src/gui/activityWidget.ui
index 5a78216..cc1673b 100644
--- a/src/gui/activityWidget.ui
+++ b/src/gui/activityWidget.ui
@@ -157,7 +157,33 @@
-
-
+
+
+ 0
+
+
-
+
+
+ true
+
+
+ QFrame::Shape::NoFrame
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOn
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+
+
+ false
+
+
+ 2
+
+
+
+
diff --git a/src/gui/timeSheetItemDelegate.cpp b/src/gui/timeSheetItemDelegate.cpp
new file mode 100644
index 0000000..e28e529
--- /dev/null
+++ b/src/gui/timeSheetItemDelegate.cpp
@@ -0,0 +1,113 @@
+#include "timeSheetItemDelegate.h"
+
+#include
+#include
+#include
+#include
+
+#include "models/timeSheetModel.h"
+
+using namespace kemai;
+
+static const auto gIndicatorWidth = 2;
+static const auto gTextSpacing = 2;
+static const auto gTextLeftOffset = gIndicatorWidth + gTextSpacing;
+static const auto gPadding = 4;
+static const auto gButtonWidth = 64;
+
+// gIndicatorWidth + gPadding (1 x blue rect + 1 x empty)
+// | |
+// || < gPadding >
+// || Customer name
+// || < gTextSpacing >
+// || Project name (on gProjectMaxWidth) < gTextSpacing > Activity name
+// || < gPadding >
+
+void TimeSheetItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ auto isHover = (option.state & QStyle::State_MouseOver) != 0;
+ auto timeSheet = reinterpret_cast(index.constInternalPointer());
+
+ /*
+ * Background frame
+ */
+ QStyleOptionFrame frameOption;
+ frameOption.frameShape = QFrame::StyledPanel;
+ frameOption.rect = option.rect;
+ if (isHover)
+ {
+ frameOption.palette.setColor(QPalette::Base, option.palette.color(QPalette::Base).lighter(120));
+ }
+ qApp->style()->drawPrimitive(QStyle::PE_Frame, &frameOption, painter);
+
+ /*
+ * Text
+ */
+ const auto textX = option.rect.left() + gTextLeftOffset;
+ const auto textH = option.fontMetrics.height();
+ const auto widthThird = option.rect.width() / 3;
+ const auto secondLineY = option.rect.top() + gPadding + option.fontMetrics.height() + gTextSpacing;
+
+ // Customer
+ auto customerRect = QRect(textX, option.rect.top() + gPadding, option.rect.width(), textH);
+ auto customerPalette = option.palette;
+ if (!timeSheet->project.customer.color.isEmpty())
+ {
+ customerPalette.setColor(QPalette::WindowText, timeSheet->project.customer.color);
+ }
+ qApp->style()->drawItemText(painter, customerRect, Qt::AlignLeft, customerPalette, isHover, timeSheet->project.customer.name, QPalette::WindowText);
+
+ // Project
+ auto projectRect = QRect(textX, secondLineY, widthThird, textH);
+ auto projectPalette = option.palette;
+ if (!timeSheet->project.color.isEmpty())
+ {
+ projectPalette.setColor(QPalette::WindowText, timeSheet->project.color);
+ }
+ qApp->style()->drawItemText(painter, projectRect, Qt::AlignLeft, projectPalette, isHover, timeSheet->project.name, QPalette::WindowText);
+
+ // Activity
+ auto activityRect = QRect(textX + widthThird, secondLineY, 2 * widthThird, textH);
+ qApp->style()->drawItemText(painter, activityRect, Qt::AlignLeft, option.palette, isHover, timeSheet->activity.name);
+
+ /*
+ * Buttons
+ */
+ const auto buttonY = option.rect.top() + gPadding;
+ const auto buttonH = option.rect.height() - 2 * gPadding;
+
+ // Reload button
+ QStyleOptionButton reloadButtonOption;
+ reloadButtonOption.features = QStyleOptionButton::Flat;
+ reloadButtonOption.text = "Reload";
+ reloadButtonOption.state = QStyle::State_Sunken;
+ reloadButtonOption.rect = QRect(option.rect.right() - 3 * gPadding - 2 * gButtonWidth, buttonY, gButtonWidth, buttonH);
+
+ qApp->style()->drawControl(QStyle::CE_PushButton, &reloadButtonOption, painter);
+
+ // Restart button
+ QStyleOptionButton restartButtonOption;
+ // buttonOption.features = QStyleOptionButton::DefaultButton;
+ restartButtonOption.text = "Restart";
+ restartButtonOption.state = QStyle::State_Sunken;
+ restartButtonOption.rect = QRect(option.rect.right() - gPadding - gButtonWidth, buttonY, gButtonWidth, buttonH);
+
+ qApp->style()->drawControl(QStyle::CE_PushButton, &restartButtonOption, painter);
+}
+
+QSize TimeSheetItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ return {option.rect.width(), option.fontMetrics.height() * 2 + gTextSpacing + 2 * gPadding};
+}
+
+bool TimeSheetItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
+{
+ switch (event->type())
+ {
+ case QEvent::MouseButtonPress:
+ return true;
+
+ default:
+ return false;
+ }
+}
diff --git a/src/gui/timeSheetItemDelegate.h b/src/gui/timeSheetItemDelegate.h
new file mode 100644
index 0000000..6cfa556
--- /dev/null
+++ b/src/gui/timeSheetItemDelegate.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+namespace kemai {
+
+class TimeSheetItemDelegate : public QStyledItemDelegate
+{
+public:
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+ bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override;
+};
+
+} // namespace kemai
diff --git a/src/models/timeSheetModel.cpp b/src/models/timeSheetModel.cpp
index 410d8a2..691a763 100644
--- a/src/models/timeSheetModel.cpp
+++ b/src/models/timeSheetModel.cpp
@@ -18,22 +18,14 @@ void TimeSheetModel::setFilterTerm(const QString& term)
resetModel();
}
-QVariant TimeSheetModel::data(const QModelIndex& index, int role) const
+QModelIndex TimeSheetModel::index(int row, int column, const QModelIndex& parent) const
{
- if (!index.isValid() || (index.row() > mTimeSheets.size()))
- {
- return {};
- }
-
- const auto& timeSheet = mTimeSheets.at(index.row());
- switch (role)
- {
- case Qt::DisplayRole:
- return QString("%1 / %2 / %3").arg(timeSheet.project.customer.name, timeSheet.project.name, timeSheet.activity.name);
+ return hasIndex(row, column, parent) ? createIndex(row, column, mTimeSheets.at(row).get()) : QModelIndex();
+}
- default:
- return {};
- }
+QVariant TimeSheetModel::data(const QModelIndex& index, int role) const
+{
+ return {};
}
int TimeSheetModel::rowCount(const QModelIndex& /*parent*/) const
@@ -55,7 +47,7 @@ void TimeSheetModel::fetchMore(const QModelIndex& /*parent*/)
auto timeSheets = timeSheetsResult->takeResult();
beginInsertRows({}, static_cast(mTimeSheets.size()), static_cast(mTimeSheets.size() + timeSheets.size() - 1));
- mTimeSheets.insert(mTimeSheets.end(), timeSheets.begin(), timeSheets.end());
+ std::ranges::transform(timeSheets, std::back_inserter(mTimeSheets), [](const auto& ts) { return std::make_unique(ts); });
endInsertRows();
++mLastPageFetched;
diff --git a/src/models/timeSheetModel.h b/src/models/timeSheetModel.h
index 68957ba..baef9b6 100644
--- a/src/models/timeSheetModel.h
+++ b/src/models/timeSheetModel.h
@@ -12,6 +12,7 @@ class TimeSheetModel : public QAbstractListModel
void setKemaiSession(const std::shared_ptr& kemaiSession);
void setFilterTerm(const QString& term);
+ QModelIndex index(int row, int column, const QModelIndex& parent = {}) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
void fetchMore(const QModelIndex& parent) override;
@@ -21,7 +22,7 @@ class TimeSheetModel : public QAbstractListModel
void resetModel();
std::shared_ptr mSession;
- std::vector mTimeSheets;
+ std::vector> mTimeSheets;
bool mEndReached = false;
bool mIsFetching = false;
int mLastPageFetched = 0;