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"