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;