File indexing completed on 2024-11-24 04:48:41

0001 /*
0002  * Copyright (c) 2019 Alexander Potashev <aspotashev@gmail.com>
0003  *
0004  * This program is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU General Public License as
0006  * published by the Free Software Foundation; either version 2 of
0007  * the License or (at your option) version 3 or any later version
0008  * accepted by the membership of KDE e.V. (or its successor approved
0009  * by the membership of KDE e.V.), which shall act as a proxy
0010  * defined in Section 14 of version 3 of the license.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU General Public License
0018  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019  */
0020 
0021 #include "taskswidget.h"
0022 
0023 #include <QApplication>
0024 #include <QMenu>
0025 #include <QMouseEvent>
0026 #include <QPainter>
0027 #include <QSortFilterProxyModel>
0028 #include <QStyledItemDelegate>
0029 
0030 #include <KConfigGroup>
0031 #include <KLocalizedString>
0032 #include <KSharedConfig>
0033 
0034 #include "ktimetracker.h"
0035 #include "ktt_debug.h"
0036 #include "model/task.h"
0037 #include "model/tasksmodel.h"
0038 
0039 bool readBoolEntry(const QString &key)
0040 {
0041     return KSharedConfig::openConfig()->group(QString()).readEntry(key, true);
0042 }
0043 
0044 void writeEntry(const QString &key, bool value)
0045 {
0046     KConfigGroup config = KSharedConfig::openConfig()->group(QString());
0047     config.writeEntry(key, value);
0048     config.sync();
0049 }
0050 
0051 //BEGIN ProgressColumnDelegate (custom painting of the progress column)
0052 class ProgressColumnDelegate : public QStyledItemDelegate {
0053 public:
0054     explicit ProgressColumnDelegate(QObject *parent)
0055         : QStyledItemDelegate(parent)
0056     {
0057     }
0058 
0059     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0060     {
0061         if (index.column() == 6) {
0062             QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter);
0063             int rX = option.rect.x() + 2;
0064             int rY = option.rect.y() + 2;
0065             int rWidth = option.rect.width() - 4;
0066             int rHeight = option.rect.height() - 4;
0067             int value = index.model()->data(index).toInt();
0068             int newWidth = (int)(rWidth * (value / 100.));
0069 
0070             if (QApplication::isLeftToRight()) {
0071                 int mid = rY + rHeight / 2;
0072                 int width = rWidth / 2;
0073                 QLinearGradient gradient1(rX, mid, rX + width, mid);
0074                 gradient1.setColorAt(0, Qt::red);
0075                 gradient1.setColorAt(1, Qt::yellow);
0076                 painter->fillRect(rX, rY, (newWidth < width) ? newWidth : width, rHeight, gradient1);
0077 
0078                 if (newWidth > width) {
0079                     QLinearGradient gradient2(rX + width, mid, rX + 2 * width, mid);
0080                     gradient2.setColorAt(0, Qt::yellow);
0081                     gradient2.setColorAt(1, Qt::green);
0082                     painter->fillRect(rX + width, rY, newWidth - width, rHeight, gradient2);
0083                 }
0084 
0085                 painter->setPen(option.state & QStyle::State_Selected ? option.palette.highlight().color()
0086                                                                       : option.palette.window().color());
0087                 for (int x = rHeight; x < newWidth; x += rHeight) {
0088                     painter->drawLine(rX + x, rY, rX + x, rY + rHeight - 1);
0089                 }
0090             } else {
0091                 int mid = option.rect.height() - rHeight / 2;
0092                 int width = rWidth / 2;
0093                 QLinearGradient gradient1(rX, mid, rX + width, mid);
0094                 gradient1.setColorAt(0, Qt::red);
0095                 gradient1.setColorAt(1, Qt::yellow);
0096                 painter->fillRect(option.rect.height(), rY, (newWidth < width) ? newWidth : width, rHeight, gradient1);
0097 
0098                 if (newWidth > width) {
0099                     QLinearGradient gradient2(rX + width, mid, rX + 2 * width, mid);
0100                     gradient2.setColorAt(0, Qt::yellow);
0101                     gradient2.setColorAt(1, Qt::green);
0102                     painter->fillRect(rX + width, rY, newWidth - width, rHeight, gradient2);
0103                 }
0104 
0105                 painter->setPen(option.state & QStyle::State_Selected ? option.palette.highlight().color()
0106                                                                       : option.palette.window().color());
0107                 for (int x = rWidth - rHeight; x > newWidth; x -= rHeight) {
0108                     painter->drawLine(rWidth - x, rY, rWidth - x, rY + rHeight - 1);
0109                 }
0110             }
0111             painter->setPen(Qt::black);
0112             painter->drawText(option.rect, Qt::AlignCenter | Qt::AlignVCenter, QString::number(value) + QString::fromLatin1(" %"));
0113         } else {
0114             QStyledItemDelegate::paint(painter, option, index);
0115         }
0116     }
0117 };
0118 //END
0119 
0120 TasksWidget::TasksWidget(QWidget *parent, QSortFilterProxyModel *filterProxyModel, TasksModel *tasksModel)
0121     : QTreeView(parent)
0122     , m_filterProxyModel(filterProxyModel)
0123     , m_tasksModel(tasksModel)
0124     , m_popupPercentageMenu(nullptr)
0125     , m_popupPriorityMenu(nullptr)
0126 {
0127     setModel(filterProxyModel);
0128 
0129     connect(this, &QTreeView::expanded, this, &TasksWidget::itemStateChanged);
0130     connect(this, &QTreeView::collapsed, this, &TasksWidget::itemStateChanged);
0131 
0132     setWindowFlags(windowFlags() | Qt::WindowContextHelpButtonHint);
0133 
0134     setAllColumnsShowFocus(true);
0135     setSortingEnabled(true);
0136     setAlternatingRowColors(true);
0137     setDragDropMode(QAbstractItemView::InternalMove);
0138     setItemDelegateForColumn(6, new ProgressColumnDelegate(this));
0139 
0140     // Context menu for task progress percentage
0141     m_popupPercentageMenu = new QMenu(this);
0142     for (int i = 0; i <= 100; i += 5) {
0143         QString label = i18nc("@item:inmenu Task progress", "%1 %", i);
0144         m_percentage[m_popupPercentageMenu->addAction(label)] = i;
0145     }
0146     connect(m_popupPercentageMenu, &QMenu::triggered, this, &TasksWidget::slotSetPercentage);
0147 
0148     // Context menu for task priority
0149     m_popupPriorityMenu = new QMenu(this);
0150     for (int i = 0; i <= 9; ++i) {
0151         QString label;
0152         switch (i) {
0153         case 0:
0154             label = i18nc("@item:inmenu Task priority", "unspecified");
0155             break;
0156         case 1:
0157             label = i18nc("@item:inmenu Task priority", "1 (highest)");
0158             break;
0159         case 5:
0160             label = i18nc("@item:inmenu Task priority", "5 (medium)");
0161             break;
0162         case 9:
0163             label = i18nc("@item:inmenu Task priority", "9 (lowest)");
0164             break;
0165         default:
0166             label = QStringLiteral("%1").arg(i);
0167             break;
0168         }
0169         m_priority[m_popupPriorityMenu->addAction(label)] = i;
0170     }
0171     connect(m_popupPriorityMenu, &QMenu::triggered, this, &TasksWidget::slotSetPriority);
0172 
0173     setContextMenuPolicy(Qt::CustomContextMenu);
0174     connect(this, &TasksWidget::customContextMenuRequested, this, &TasksWidget::slotCustomContextMenuRequested);
0175 
0176     sortByColumn(0, Qt::AscendingOrder);
0177 }
0178 
0179 void TasksWidget::itemStateChanged(const QModelIndex &index)
0180 {
0181     Task *task = taskAtViewIndex(index);
0182     if (!task) {
0183         return;
0184     }
0185 
0186     qCDebug(KTT_LOG) << "TaskView::itemStateChanged()"
0187                      << " uid=" << task->uid() << " state=" << isExpanded(index);
0188     writeEntry(task->uid(), isExpanded(index));
0189 }
0190 
0191 void TasksWidget::slotCustomContextMenuRequested(const QPoint &pos)
0192 {
0193     QPoint newPos = viewport()->mapToGlobal(pos);
0194     int column = columnAt(pos.x());
0195 
0196     switch (column) {
0197     case 6: /* percentage */
0198         m_popupPercentageMenu->popup(newPos);
0199         break;
0200 
0201     case 5: /* priority */
0202         m_popupPriorityMenu->popup(newPos);
0203         break;
0204 
0205     default:
0206         Q_EMIT contextMenuRequested(newPos);
0207         break;
0208     }
0209 }
0210 
0211 void TasksWidget::slotSetPercentage(QAction *action)
0212 {
0213     if (currentItem()) {
0214         currentItem()->setPercentComplete(m_percentage[action]);
0215         Q_EMIT updateButtons();
0216     }
0217 }
0218 
0219 void TasksWidget::slotSetPriority(QAction *action)
0220 {
0221     if (currentItem()) {
0222         currentItem()->setPriority(m_priority[action]);
0223     }
0224 }
0225 
0226 void TasksWidget::mouseMoveEvent(QMouseEvent *event)
0227 {
0228     QModelIndex index = indexAt(event->pos());
0229     if (index.isValid() && index.column() == 6 && event->buttons() == Qt::LeftButton) {
0230         int newValue = (int)((event->pos().x() - visualRect(index).x()) / (double)(visualRect(index).width()) * 101);
0231         if (newValue > 100) {
0232             newValue = 100;
0233         }
0234 
0235         if (event->modifiers() & Qt::ShiftModifier) {
0236             int delta = newValue % 10;
0237             if (delta >= 5) {
0238                 newValue += (10 - delta);
0239             } else {
0240                 newValue -= delta;
0241             }
0242         }
0243         if (selectionModel()->isSelected(index)) {
0244             Task *task = taskAtViewIndex(index);
0245             if (task) {
0246                 task->setPercentComplete(newValue);
0247                 Q_EMIT updateButtons();
0248             }
0249         }
0250     } else {
0251         QTreeView::mouseMoveEvent(event);
0252     }
0253 }
0254 
0255 bool TasksWidget::mousePositionInsideCheckbox(QMouseEvent *event) const
0256 {
0257     QModelIndex index = indexAt(event->pos());
0258     return index.isValid() && index.column() == 0 && visualRect(index).x() <= event->pos().x()
0259         && event->pos().x() < visualRect(index).x() + 19;
0260 }
0261 
0262 void TasksWidget::mousePressEvent(QMouseEvent *event)
0263 {
0264     qCDebug(KTT_LOG) << "Entering function, event->button()=" << event->button();
0265 
0266     if (mousePositionInsideCheckbox(event) && event->buttons() == Qt::LeftButton) {
0267         // if the user toggles a task as complete/incomplete
0268         QModelIndex index = indexAt(event->pos());
0269         Task *task = taskAtViewIndex(index);
0270         if (task) {
0271             if (task->isComplete()) {
0272                 task->setPercentComplete(0);
0273             } else {
0274                 task->setPercentComplete(100);
0275             }
0276             Q_EMIT updateButtons();
0277         }
0278     } else {
0279         // the user did not mark a task as complete/incomplete
0280         if (KTimeTrackerSettings::configPDA()) {
0281             // if you have a touchscreen, you cannot right-click. So, display context menu on any click.
0282             QPoint newPos = viewport()->mapToGlobal(event->pos());
0283             Q_EMIT contextMenuRequested(newPos);
0284         }
0285 
0286         QTreeView::mousePressEvent(event);
0287     }
0288 }
0289 
0290 void TasksWidget::mouseDoubleClickEvent(QMouseEvent *event)
0291 {
0292     qCDebug(KTT_LOG) << "Entering function, event->button()=" << event->button();
0293     QModelIndex index = indexAt(event->pos());
0294 
0295     // if the user toggles a task as complete/incomplete
0296     if (index.isValid() && !mousePositionInsideCheckbox(event)) {
0297         Task *task = taskAtViewIndex(index);
0298         if (task) {
0299             Q_EMIT taskDoubleClicked(task);
0300         }
0301     } else {
0302         QTreeView::mouseDoubleClickEvent(event);
0303     }
0304 }
0305 
0306 void TasksWidget::currentChanged(const QModelIndex & /*current*/, const QModelIndex & /*previous*/)
0307 {
0308     Q_EMIT updateButtons();
0309 }
0310 
0311 void TasksWidget::restoreItemState()
0312 {
0313     qCDebug(KTT_LOG) << "Entering function";
0314 
0315     if (m_tasksModel->topLevelItemCount() > 0) {
0316         for (auto *item : m_tasksModel->getAllItems()) {
0317             auto *task = dynamic_cast<Task *>(item);
0318             setExpanded(m_filterProxyModel->mapFromSource(m_tasksModel->index(task, 0)), readBoolEntry(task->uid()));
0319         }
0320     }
0321     qCDebug(KTT_LOG) << "Leaving function";
0322 }
0323 
0324 Task *TasksWidget::taskAtViewIndex(QModelIndex viewIndex)
0325 {
0326 //    if (!m_storage->isLoaded()) {
0327 //        return nullptr;
0328 //    }
0329     if (!m_tasksModel) {
0330         return nullptr;
0331     }
0332 
0333     QModelIndex index = m_filterProxyModel->mapToSource(viewIndex);
0334     return dynamic_cast<Task *>(m_tasksModel->item(index));
0335 }
0336 
0337 void TasksWidget::setSourceModel(TasksModel *tasksModel)
0338 {
0339     m_tasksModel = tasksModel;
0340 }
0341 
0342 Task *TasksWidget::currentItem()
0343 {
0344     return taskAtViewIndex(QTreeView::currentIndex());
0345 }
0346 
0347 void TasksWidget::setFilterText(const QString &text)
0348 {
0349     m_filterProxyModel->setFilterFixedString(text);
0350 }
0351 
0352 void TasksWidget::refresh()
0353 {
0354     // remove root decoration if there is no more child.
0355 //    int i = 0;
0356 //    while (itemAt(++i) && itemAt(i)->depth() == 0){};
0357     //setRootIsDecorated( itemAt( i ) && ( itemAt( i )->depth() != 0 ) );
0358     // FIXME workaround? seems that the QItemDelegate for the percent column only
0359     // works properly if rootIsDecorated == true.
0360     setRootIsDecorated(true);
0361 
0362     Q_EMIT updateButtons();
0363     qCDebug(KTT_LOG) << "exiting TaskView::refresh()";
0364 }
0365 
0366 void TasksWidget::reconfigure()
0367 {
0368     /* Adapt columns */
0369     setColumnHidden(1, !KTimeTrackerSettings::displaySessionTime());
0370     setColumnHidden(2, !KTimeTrackerSettings::displayTime());
0371     setColumnHidden(3, !KTimeTrackerSettings::displayTotalSessionTime());
0372     setColumnHidden(4, !KTimeTrackerSettings::displayTotalTime());
0373     setColumnHidden(5, !KTimeTrackerSettings::displayPriority());
0374     setColumnHidden(6, !KTimeTrackerSettings::displayPercentComplete());
0375 
0376     refresh();
0377 }
0378 
0379 #include "moc_taskswidget.cpp"