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 }