File indexing completed on 2024-06-16 04:51:16

0001 /*
0002   This file is part of KOrganizer.
0003 
0004   SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at>
0005 
0006   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0007 */
0008 
0009 #include "todoviewview.h"
0010 
0011 #include <KDatePickerPopup>
0012 #include <KLocalizedString>
0013 #include <QMenu>
0014 
0015 #include <QAction>
0016 #include <QContextMenuEvent>
0017 #include <QEvent>
0018 #include <QHeaderView>
0019 #include <QMouseEvent>
0020 #include <chrono>
0021 
0022 using namespace std::chrono_literals;
0023 
0024 TodoViewView::TodoViewView(QWidget *parent)
0025     : QTreeView(parent)
0026 {
0027     header()->installEventFilter(this);
0028     setAlternatingRowColors(true);
0029     connect(&mExpandTimer, &QTimer::timeout, this, &TodoViewView::expandParent);
0030     mExpandTimer.setInterval(1s);
0031     header()->setStretchLastSection(false);
0032     mStartPopupMenu = new KDatePickerPopup(KDatePickerPopup::NoDate | KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), this);
0033 }
0034 
0035 bool TodoViewView::isEditing(const QModelIndex &index) const
0036 {
0037     return state() & QAbstractItemView::EditingState && currentIndex() == index;
0038 }
0039 
0040 bool TodoViewView::eventFilter(QObject *watched, QEvent *event)
0041 {
0042     Q_UNUSED(watched)
0043     if (event->type() == QEvent::ContextMenu) {
0044         auto e = static_cast<QContextMenuEvent *>(event);
0045 
0046         if (!mHeaderPopup) {
0047             mHeaderPopup = new QMenu(this);
0048             mHeaderPopup->setTitle(i18n("View Columns"));
0049             // First entry can't be disabled
0050             for (int i = 1; i < model()->columnCount(); ++i) {
0051                 QAction *tmp = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
0052                 tmp->setData(QVariant(i));
0053                 tmp->setCheckable(true);
0054                 mColumnActions << tmp;
0055             }
0056 
0057             connect(mHeaderPopup, &QMenu::triggered, this, &TodoViewView::toggleColumnHidden);
0058         }
0059 
0060         for (QAction *action : std::as_const(mColumnActions)) {
0061             int column = action->data().toInt();
0062             action->setChecked(!isColumnHidden(column));
0063         }
0064 
0065         mHeaderPopup->popup(mapToGlobal(e->pos()));
0066         return true;
0067     }
0068 
0069     return false;
0070 }
0071 
0072 KDatePickerPopup *TodoViewView::startPopupMenu()
0073 {
0074     return mStartPopupMenu;
0075 }
0076 
0077 void TodoViewView::toggleColumnHidden(QAction *action)
0078 {
0079     if (action->isChecked()) {
0080         showColumn(action->data().toInt());
0081     } else {
0082         hideColumn(action->data().toInt());
0083     }
0084 
0085     Q_EMIT visibleColumnCountChanged();
0086 }
0087 
0088 QModelIndex TodoViewView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
0089 {
0090     QModelIndex current = currentIndex();
0091     if (!current.isValid()) {
0092         return QTreeView::moveCursor(cursorAction, modifiers);
0093     }
0094 
0095     switch (cursorAction) {
0096     case MoveNext: {
0097         // try to find an editable item right of the current one
0098         QModelIndex tmp = getNextEditableIndex(current.sibling(current.row(), current.column() + 1), 1);
0099         if (tmp.isValid()) {
0100             return tmp;
0101         }
0102 
0103         // check if the current item is expanded, and find an editable item
0104         // just below it if so
0105         current = current.sibling(current.row(), 0);
0106         if (isExpanded(current)) {
0107             tmp = getNextEditableIndex(model()->index(0, 0, current), 1);
0108             if (tmp.isValid()) {
0109                 return tmp;
0110             }
0111         }
0112 
0113         // find an editable item in the item below the currently edited one
0114         tmp = getNextEditableIndex(current.sibling(current.row() + 1, 0), 1);
0115         if (tmp.isValid()) {
0116             return tmp;
0117         }
0118 
0119         // step back a hierarchy level, and search for an editable item there
0120         while (current.isValid()) {
0121             current = current.parent();
0122             tmp = getNextEditableIndex(current.sibling(current.row() + 1, 0), 1);
0123             if (tmp.isValid()) {
0124                 return tmp;
0125             }
0126         }
0127         return {};
0128     }
0129     case MovePrevious: {
0130         // try to find an editable item left of the current one
0131         QModelIndex tmp = getNextEditableIndex(current.sibling(current.row(), current.column() - 1), -1);
0132         if (tmp.isValid()) {
0133             return tmp;
0134         }
0135 
0136         int lastCol = model()->columnCount(QModelIndex()) - 1;
0137 
0138         // search on top of the item, also include expanded items
0139         tmp = current.sibling(current.row() - 1, 0);
0140         while (tmp.isValid() && isExpanded(tmp)) {
0141             tmp = model()->index(model()->rowCount(tmp) - 1, 0, tmp);
0142         }
0143         if (tmp.isValid()) {
0144             tmp = getNextEditableIndex(tmp.sibling(tmp.row(), lastCol), -1);
0145             if (tmp.isValid()) {
0146                 return tmp;
0147             }
0148         }
0149 
0150         // step back a hierarchy level, and search for an editable item there
0151         current = current.parent();
0152         return getNextEditableIndex(current.sibling(current.row(), lastCol), -1);
0153     }
0154     default:
0155         break;
0156     }
0157 
0158     return QTreeView::moveCursor(cursorAction, modifiers);
0159 }
0160 
0161 QModelIndex TodoViewView::getNextEditableIndex(const QModelIndex &cur, int inc)
0162 {
0163     if (!cur.isValid()) {
0164         return {};
0165     }
0166 
0167     QModelIndex tmp;
0168     int colCount = model()->columnCount(QModelIndex());
0169     int end = inc == 1 ? colCount : -1;
0170 
0171     for (int c = cur.column(); c != end; c += inc) {
0172         tmp = cur.sibling(cur.row(), c);
0173         if ((tmp.flags() & Qt::ItemIsEditable) && !isIndexHidden(tmp)) {
0174             return tmp;
0175         }
0176     }
0177     return {};
0178 }
0179 
0180 void TodoViewView::mouseReleaseEvent(QMouseEvent *event)
0181 {
0182     mExpandTimer.stop();
0183 
0184     if (mIgnoreNextMouseRelease) {
0185         mIgnoreNextMouseRelease = false;
0186         return;
0187     }
0188 
0189     if (!indexAt(event->pos()).isValid()) {
0190         clearSelection();
0191         event->accept();
0192     } else {
0193         QTreeView::mouseReleaseEvent(event);
0194     }
0195 }
0196 
0197 void TodoViewView::mouseMoveEvent(QMouseEvent *event)
0198 {
0199     mExpandTimer.stop();
0200     QTreeView::mouseMoveEvent(event);
0201 }
0202 
0203 void TodoViewView::mousePressEvent(QMouseEvent *event)
0204 {
0205     mExpandTimer.stop();
0206     QModelIndex index = indexAt(event->pos());
0207     if (index.isValid() && event->button() == Qt::LeftButton) {
0208         mExpandTimer.start();
0209     }
0210 
0211     QTreeView::mousePressEvent(event);
0212 }
0213 
0214 void TodoViewView::expandParent()
0215 {
0216     QModelIndex index = indexAt(viewport()->mapFromGlobal(QCursor::pos()));
0217     if (index.isValid()) {
0218         mIgnoreNextMouseRelease = true;
0219         QKeyEvent keyEvent = QKeyEvent(QEvent::KeyPress, Qt::Key_Asterisk, Qt::NoModifier);
0220         QTreeView::keyPressEvent(&keyEvent);
0221     }
0222 }
0223 
0224 #include "moc_todoviewview.cpp"