File indexing completed on 2024-05-12 05:07:59

0001 /*
0002     SPDX-FileCopyrightText: 2006-2018 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kmymoneyselector_p.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QApplication>
0013 #include <QRegularExpression>
0014 #include <QStyle>
0015 
0016 // ----------------------------------------------------------------------------
0017 // KDE Includes
0018 
0019 // ----------------------------------------------------------------------------
0020 // Project Includes
0021 
0022 #include "kmymoneysettings.h"
0023 #include "widgetenums.h"
0024 
0025 using namespace eWidgets;
0026 
0027 KMyMoneySelector::KMyMoneySelector(QWidget *parent, Qt::WindowFlags flags) :
0028     QWidget(parent, flags),
0029     d_ptr(new KMyMoneySelectorPrivate(this))
0030 {
0031     Q_D(KMyMoneySelector);
0032     d->init();
0033 }
0034 
0035 KMyMoneySelector::KMyMoneySelector(KMyMoneySelectorPrivate &dd, QWidget* parent, Qt::WindowFlags flags) :
0036     QWidget(parent, flags),
0037     d_ptr(&dd)
0038 {
0039     Q_D(KMyMoneySelector);
0040     d->init();
0041 }
0042 
0043 KMyMoneySelector::~KMyMoneySelector()
0044 {
0045     Q_D(KMyMoneySelector);
0046     delete d;
0047 }
0048 
0049 void KMyMoneySelector::clear()
0050 {
0051     Q_D(KMyMoneySelector);
0052     d->m_treeWidget->clear();
0053 }
0054 
0055 void KMyMoneySelector::setSelectable(QTreeWidgetItem *item, bool selectable)
0056 {
0057     if (selectable) {
0058         item->setFlags(item->flags() | Qt::ItemIsSelectable);
0059     } else {
0060         item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
0061     }
0062 }
0063 
0064 void KMyMoneySelector::slotSelectAllItems()
0065 {
0066     selectAllItems(true);
0067 }
0068 
0069 void KMyMoneySelector::slotDeselectAllItems()
0070 {
0071     selectAllItems(false);
0072 }
0073 
0074 void KMyMoneySelector::setSelectionMode(const QTreeWidget::SelectionMode mode)
0075 {
0076     Q_D(KMyMoneySelector);
0077     if (d->m_selMode != mode) {
0078         d->m_selMode = mode;
0079         clear();
0080 
0081         // make sure, it's either Multi or Single
0082         if (mode != QTreeWidget::MultiSelection) {
0083             d->m_selMode = QTreeWidget::SingleSelection;
0084             connect(d->m_treeWidget, &QTreeWidget::itemSelectionChanged, this, &KMyMoneySelector::stateChanged);
0085             connect(d->m_treeWidget, &QTreeWidget::itemActivated, this, &KMyMoneySelector::slotItemSelected);
0086             connect(d->m_treeWidget, &QTreeWidget::itemClicked, this, &KMyMoneySelector::slotItemSelected);
0087         } else {
0088             disconnect(d->m_treeWidget, &QTreeWidget::itemSelectionChanged, this, &KMyMoneySelector::stateChanged);
0089             disconnect(d->m_treeWidget, &QTreeWidget::itemActivated, this, &KMyMoneySelector::slotItemSelected);
0090             disconnect(d->m_treeWidget, &QTreeWidget::itemClicked, this, &KMyMoneySelector::slotItemSelected);
0091         }
0092     }
0093     QWidget::update();
0094 }
0095 
0096 QTreeWidget::SelectionMode KMyMoneySelector::selectionMode() const
0097 {
0098     Q_D(const KMyMoneySelector);
0099     return d->m_selMode;
0100 }
0101 
0102 void KMyMoneySelector::slotItemSelected(QTreeWidgetItem *item)
0103 {
0104     Q_D(KMyMoneySelector);
0105     if (d->m_selMode == QTreeWidget::SingleSelection) {
0106         if (item && item->flags().testFlag(Qt::ItemIsSelectable)) {
0107             Q_EMIT itemSelected(item->data(0, (int)Selector::Role::Id).toString());
0108         }
0109     }
0110 }
0111 
0112 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name, QTreeWidgetItem* after, const QString& key, const QString& id)
0113 {
0114     Q_D(KMyMoneySelector);
0115     QTreeWidgetItem* item = new QTreeWidgetItem(d->m_treeWidget, after);
0116 
0117     item->setText(0, name);
0118     item->setData(0, (int)Selector::Role::Key, key);
0119     item->setData(0, (int)Selector::Role::Id, id);
0120     item->setText(1, key); // hidden, but used for sorting
0121     item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
0122 
0123     if (id.isEmpty()) {
0124         QFont font = item->font(0);
0125         font.setBold(true);
0126         item->setFont(0, font);
0127         setSelectable(item, false);
0128     }
0129     item->setExpanded(true);
0130     return item;
0131 }
0132 
0133 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name, QTreeWidgetItem* after, const QString& key)
0134 {
0135     return newItem(name, after, key, QString());
0136 }
0137 
0138 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name, QTreeWidgetItem* after)
0139 {
0140     return newItem(name, after, QString(), QString());
0141 }
0142 
0143 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name, const QString& key, const QString& id)
0144 {
0145     return newItem(name, 0, key, id);
0146 }
0147 
0148 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name, const QString& key)
0149 {
0150     return newItem(name, 0, key, QString());
0151 }
0152 
0153 QTreeWidgetItem* KMyMoneySelector::newItem(const QString& name)
0154 {
0155     return newItem(name, 0, QString(), QString());
0156 }
0157 
0158 QTreeWidgetItem* KMyMoneySelector::newTopItem(const QString& name, const QString& key, const QString& id)
0159 {
0160     Q_D(KMyMoneySelector);
0161     QTreeWidgetItem* item = new QTreeWidgetItem(d->m_treeWidget);
0162 
0163     item->setText(0, name);
0164     item->setData(0, (int)Selector::Role::Key, key);
0165     item->setData(0, (int)Selector::Role::Id, id);
0166     item->setText(1, key); // hidden, but used for sorting
0167     item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
0168 
0169     if (d->m_selMode == QTreeWidget::MultiSelection) {
0170         item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
0171         item->setCheckState(0, Qt::Checked);
0172     }
0173     return item;
0174 }
0175 
0176 QTreeWidgetItem* KMyMoneySelector::newItem(QTreeWidgetItem* parent, const QString& name, const QString& key, const QString& id)
0177 {
0178     Q_D(KMyMoneySelector);
0179     QTreeWidgetItem* item = new QTreeWidgetItem(parent);
0180 
0181     item->setText(0, name);
0182     item->setData(0, (int)Selector::Role::Key, key);
0183     item->setData(0, (int)Selector::Role::Id, id);
0184     item->setText(1, key); // hidden, but used for sorting
0185     item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
0186 
0187     if (d->m_selMode == QTreeWidget::MultiSelection) {
0188         item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
0189         item->setCheckState(0, Qt::Checked);
0190     }
0191     return item;
0192 }
0193 
0194 void KMyMoneySelector::protectItem(const QString& itemId, const bool protect)
0195 {
0196     Q_D(KMyMoneySelector);
0197     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0198     QTreeWidgetItem* it_v;
0199 
0200     // scan items
0201     while ((it_v = *it) != 0) {
0202         if (it_v->data(0, (int)Selector::Role::Id).toString() == itemId) {
0203             setSelectable(it_v, !protect);
0204             break;
0205         }
0206         ++it;
0207     }
0208 }
0209 
0210 QTreeWidgetItem* KMyMoneySelector::item(const QString& id) const
0211 {
0212     Q_D(const KMyMoneySelector);
0213     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0214     QTreeWidgetItem* it_v;
0215 
0216     while ((it_v = *it) != 0) {
0217         if (it_v->data(0, (int)Selector::Role::Id).toString() == id)
0218             break;
0219         ++it;
0220     }
0221     return it_v;
0222 }
0223 
0224 bool KMyMoneySelector::allItemsSelected() const
0225 {
0226     Q_D(const KMyMoneySelector);
0227     QTreeWidgetItem* rootItem = d->m_treeWidget->invisibleRootItem();
0228 
0229     if (d->m_selMode == QTreeWidget::SingleSelection)
0230         return false;
0231 
0232     for (auto i = 0; i < rootItem->childCount(); ++i) {
0233         QTreeWidgetItem* item = rootItem->child(i);
0234         if (item->flags().testFlag(Qt::ItemIsUserCheckable)) {
0235             if (!(item->checkState(0) == Qt::Checked && allItemsSelected(item)))
0236                 return false;
0237         } else {
0238             if (!allItemsSelected(item))
0239                 return false;
0240         }
0241     }
0242     return true;
0243 }
0244 
0245 bool KMyMoneySelector::allItemsSelected(const QTreeWidgetItem *item) const
0246 {
0247     for (auto i = 0; i < item->childCount(); ++i) {
0248         QTreeWidgetItem* child = item->child(i);
0249         if (child->flags().testFlag(Qt::ItemIsUserCheckable)) {
0250             if (!(child->checkState(0) == Qt::Checked && allItemsSelected(child)))
0251                 return false;
0252         }
0253     }
0254     return true;
0255 }
0256 
0257 void KMyMoneySelector::removeItem(const QString& id)
0258 {
0259     Q_D(KMyMoneySelector);
0260     QTreeWidgetItem* it_v;
0261     QTreeWidgetItemIterator it(d->m_treeWidget);
0262 
0263     while ((it_v = *it) != 0) {
0264         if (id == it_v->data(0, (int)Selector::Role::Id).toString()) {
0265             if (it_v->childCount() > 0) {
0266                 setSelectable(it_v, false);
0267             } else {
0268                 delete it_v;
0269             }
0270         }
0271         it++;
0272     }
0273 
0274     // get rid of top items that just lost the last children (e.g. Favorites)
0275     it = QTreeWidgetItemIterator(d->m_treeWidget, QTreeWidgetItemIterator::NotSelectable);
0276     while ((it_v = *it) != 0) {
0277         if (it_v->childCount() == 0)
0278             delete it_v;
0279         it++;
0280     }
0281 }
0282 
0283 
0284 void KMyMoneySelector::selectAllItems(const bool state)
0285 {
0286     Q_D(KMyMoneySelector);
0287     selectAllSubItems(d->m_treeWidget->invisibleRootItem(), state);
0288     Q_EMIT stateChanged();
0289 }
0290 
0291 void KMyMoneySelector::selectItems(const QStringList& itemList, const bool state)
0292 {
0293     Q_D(KMyMoneySelector);
0294     selectSubItems(d->m_treeWidget->invisibleRootItem(), itemList, state);
0295     Q_EMIT stateChanged();
0296 }
0297 
0298 void KMyMoneySelector::selectSubItems(QTreeWidgetItem* item, const QStringList& itemList, const bool state)
0299 {
0300     for (auto i = 0; i < item->childCount(); ++i) {
0301         QTreeWidgetItem* child = item->child(i);
0302         if (child->flags().testFlag(Qt::ItemIsUserCheckable) && itemList.contains(child->data(0, (int)Selector::Role::Id).toString())) {
0303             child->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
0304         }
0305         selectSubItems(child, itemList, state);
0306     }
0307     Q_EMIT stateChanged();
0308 }
0309 
0310 void KMyMoneySelector::selectAllSubItems(QTreeWidgetItem* item, const bool state)
0311 {
0312     for (auto i = 0; i < item->childCount(); ++i) {
0313         QTreeWidgetItem* child = item->child(i);
0314         if (child->flags().testFlag(Qt::ItemIsUserCheckable)) {
0315             child->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
0316         }
0317         selectAllSubItems(child, state);
0318     }
0319     Q_EMIT stateChanged();
0320 }
0321 
0322 void KMyMoneySelector::selectedItems(QStringList& list) const
0323 {
0324     Q_D(const KMyMoneySelector);
0325     list.clear();
0326     if (d->m_selMode == QTreeWidget::SingleSelection) {
0327         QTreeWidgetItem* it_c = d->m_treeWidget->currentItem();
0328         if (it_c != 0 && it_c->isSelected())
0329             list << it_c->data(0, (int)Selector::Role::Id).toString();
0330     } else {
0331         QTreeWidgetItem* rootItem = d->m_treeWidget->invisibleRootItem();
0332         for (auto i = 0; i < rootItem->childCount(); ++i) {
0333             QTreeWidgetItem* child = rootItem->child(i);
0334             if (child->flags().testFlag(Qt::ItemIsUserCheckable)) {
0335                 if (child->checkState(0) == Qt::Checked)
0336                     list << child->data(0, (int)Selector::Role::Id).toString();
0337             }
0338             selectedItems(list, child);
0339         }
0340     }
0341 }
0342 
0343 void KMyMoneySelector::selectedItems(QStringList& list, QTreeWidgetItem* item) const
0344 {
0345     for (auto i = 0; i < item->childCount(); ++i) {
0346         QTreeWidgetItem* child = item->child(i);
0347         if (child->flags().testFlag(Qt::ItemIsUserCheckable)) {
0348             if (child->checkState(0) == Qt::Checked)
0349                 list << child->data(0, (int)Selector::Role::Id).toString();
0350         }
0351         selectedItems(list, child);
0352     }
0353 }
0354 
0355 void KMyMoneySelector::itemList(QStringList& list) const
0356 {
0357     Q_D(const KMyMoneySelector);
0358     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0359     QTreeWidgetItem* it_v;
0360 
0361     while ((it_v = *it) != 0) {
0362         list << it_v->data(0, (int)Selector::Role::Id).toString();
0363         it++;
0364     }
0365 }
0366 
0367 void KMyMoneySelector::setSelected(const QString& id, const bool state)
0368 {
0369     Q_D(const KMyMoneySelector);
0370     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0371     QTreeWidgetItem* item;
0372     QTreeWidgetItem* it_visible = 0;
0373 
0374     while ((item = *it) != 0) {
0375         if (item->data(0, (int)Selector::Role::Id).toString() == id) {
0376             if (item->flags().testFlag(Qt::ItemIsUserCheckable)) {
0377                 item->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
0378             }
0379             d->m_treeWidget->setCurrentItem(item);
0380             if (!it_visible)
0381                 it_visible = item;
0382         }
0383         it++;
0384     }
0385 
0386     // make sure the first one found is visible
0387     if (it_visible)
0388         d->m_treeWidget->scrollToItem(it_visible);
0389 }
0390 
0391 QTreeWidget* KMyMoneySelector::listView() const
0392 {
0393     Q_D(const KMyMoneySelector);
0394     return d->m_treeWidget;
0395 }
0396 
0397 int KMyMoneySelector::slotMakeCompletion(const QString& _txt)
0398 {
0399     QString txt(QRegularExpression::escape(_txt));
0400     if (KMyMoneySettings::stringMatchFromStart() && QLatin1String(this->metaObject()->className()) == QLatin1String("KMyMoneySelector"))
0401         txt.prepend('^');
0402     return slotMakeCompletion(QRegularExpression(txt, QRegularExpression::CaseInsensitiveOption));
0403 }
0404 
0405 bool KMyMoneySelector::match(const QRegularExpression& exp, QTreeWidgetItem* item) const
0406 {
0407     return exp.match(item->text(0)).hasMatch();
0408 }
0409 
0410 int KMyMoneySelector::slotMakeCompletion(const QRegularExpression& _exp)
0411 {
0412     Q_D(KMyMoneySelector);
0413     auto exp(_exp);
0414     auto pattern = exp.pattern();
0415     auto replacement = QStringLiteral(".*:");
0416     if (!KMyMoneySettings::stringMatchFromStart() || QLatin1String(this->metaObject()->className()) != QLatin1String("KMyMoneySelector")) {
0417         replacement.append(QLatin1String(".*"));
0418     }
0419     pattern.replace(QLatin1String(":"), replacement);
0420     exp.setPattern(pattern);
0421 
0422     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0423 
0424     QTreeWidgetItem* it_v;
0425 
0426     // The logic used here seems to be awkward. The problem is, that
0427     // QListViewItem::setVisible works recursively on all it's children
0428     // and grand-children.
0429     //
0430     // The way out of this is as follows: Make all items visible.
0431     // Then go through the list again and perform the checks.
0432     // If an item does not have any children (last leaf in the tree view)
0433     // perform the check. Then check recursively on the parent of this
0434     // leaf that it has no visible children. If that is the case, make the
0435     // parent invisible and continue this check with it's parent.
0436     while ((it_v = *it) != 0) {
0437         it_v->setHidden(false);
0438         ++it;
0439     }
0440 
0441     QTreeWidgetItem* firstMatch = 0;
0442 
0443     if (!exp.pattern().isEmpty()) {
0444         it = QTreeWidgetItemIterator(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0445         while ((it_v = *it) != 0) {
0446             if (it_v->childCount() == 0) {
0447                 if (!match(exp, it_v)) {
0448                     // this is a node which does not contain the
0449                     // text and does not have children. So we can
0450                     // safely hide it. Then we check, if the parent
0451                     // has more children which are still visible. If
0452                     // none are found, the parent node is hidden also. We
0453                     // continue until the top of the tree or until we
0454                     // find a node that still has visible children.
0455                     bool hide = true;
0456                     while (hide) {
0457                         it_v->setHidden(true);
0458                         it_v = it_v->parent();
0459                         if (it_v && (it_v->flags() & Qt::ItemIsSelectable)) {
0460                             hide = !match(exp, it_v);
0461                             for (auto i = 0; hide && i < it_v->childCount(); ++i) {
0462                                 if (!it_v->child(i)->isHidden())
0463                                     hide = false;
0464                             }
0465                         } else
0466                             hide = false;
0467                     }
0468                 } else if (!firstMatch) {
0469                     firstMatch = it_v;
0470                 }
0471                 ++it;
0472 
0473             } else if (match(exp, it_v)) {
0474                 if (!firstMatch) {
0475                     firstMatch = it_v;
0476                 }
0477                 // a node with children contains the text. We want
0478                 // to display all child nodes in this case, so we need
0479                 // to advance the iterator to the next sibling of the
0480                 // current node. This could well be the sibling of a
0481                 // parent or grandparent node.
0482                 QTreeWidgetItem* curr = it_v;
0483                 QTreeWidgetItem* item;
0484                 while ((item = curr->treeWidget()->itemBelow(curr)) == 0) {
0485                     curr = curr->parent();
0486                     if (curr == 0)
0487                         break;
0488                     if (match(exp, curr))
0489                         firstMatch = curr;
0490                 }
0491                 do {
0492                     ++it;
0493                 } while (*it && *it != item);
0494             } else {
0495                 // It's a node with children that does not match. We don't
0496                 // change it's status here.
0497                 ++it;
0498             }
0499         }
0500     }
0501 
0502     // make the first match the one that is selected
0503     // if we have no match, make sure none is selected
0504     if (d->m_selMode == QTreeWidget::SingleSelection) {
0505         if (firstMatch) {
0506             d->m_treeWidget->setCurrentItem(firstMatch);
0507             d->m_treeWidget->scrollToItem(firstMatch);
0508         } else
0509             d->m_treeWidget->clearSelection();
0510     }
0511 
0512     // Get the number of visible nodes for the return code
0513     auto cnt = 0;
0514 
0515     it = QTreeWidgetItemIterator(d->m_treeWidget, QTreeWidgetItemIterator::Selectable | QTreeWidgetItemIterator::NotHidden);
0516     while ((*it) != 0) {
0517         cnt++;
0518         it++;
0519     }
0520     return cnt;
0521 }
0522 
0523 bool KMyMoneySelector::contains(const QString& txt) const
0524 {
0525     Q_D(const KMyMoneySelector);
0526     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0527     QTreeWidgetItem* it_v;
0528     while ((it_v = *it) != 0) {
0529         if (it_v->text(0) == txt) {
0530             return true;
0531         }
0532         it++;
0533     }
0534     return false;
0535 }
0536 
0537 void KMyMoneySelector::slotItemPressed(QTreeWidgetItem* item, int /* col */)
0538 {
0539     Q_D(KMyMoneySelector);
0540     if (QApplication::mouseButtons() != Qt::RightButton)
0541         return;
0542 
0543     if (item->flags().testFlag(Qt::ItemIsUserCheckable)) {
0544         QStyleOptionButton opt;
0545         opt.rect = d->m_treeWidget->visualItemRect(item);
0546         QRect rect = d->m_treeWidget->style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, d->m_treeWidget);
0547         if (rect.contains(d->m_treeWidget->mapFromGlobal(QCursor::pos()))) {
0548             // we get down here, if we have a right click onto the checkbox
0549             item->setCheckState(0, item->checkState(0) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
0550             selectAllSubItems(item, item->checkState(0) == Qt::Checked);
0551         }
0552     }
0553 }
0554 
0555 QStringList KMyMoneySelector::selectedItems() const
0556 {
0557     QStringList list;
0558     selectedItems(list);
0559     return list;
0560 }
0561 
0562 QStringList KMyMoneySelector::itemList() const
0563 {
0564     QStringList list;
0565     itemList(list);
0566     return list;
0567 }