File indexing completed on 2024-05-12 16:44:03

0001 /*
0002     SPDX-FileCopyrightText: 2010-2018 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2010-2016 Cristian Oneț <onet.cristian@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Alvaro Soliverez <asoliverez@gmail.com>
0005     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0006     SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kmymoneymvccombo.h"
0011 #include "kmymoneymvccombo_p.h"
0012 
0013 // ----------------------------------------------------------------------------
0014 // QT Includes
0015 
0016 #include <QStandardItemModel>
0017 #include <QAbstractItemView>
0018 #include <QCompleter>
0019 #include <QFocusEvent>
0020 #include <QApplication>
0021 #include <QMetaMethod>
0022 
0023 // ----------------------------------------------------------------------------
0024 // KDE Includes
0025 
0026 #include <KLineEdit>
0027 
0028 // ----------------------------------------------------------------------------
0029 // Project Includes
0030 
0031 
0032 KMyMoneyMVCCombo::KMyMoneyMVCCombo(QWidget* parent) :
0033     KComboBox(parent),
0034     d_ptr(new KMyMoneyMVCComboPrivate)
0035 {
0036     view()->setAlternatingRowColors(true);
0037     connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
0038 }
0039 
0040 KMyMoneyMVCCombo::KMyMoneyMVCCombo(bool editable, QWidget* parent) :
0041     KComboBox(editable, parent),
0042     d_ptr(new KMyMoneyMVCComboPrivate)
0043 {
0044     Q_D(KMyMoneyMVCCombo);
0045     d->m_completer = new QCompleter(this);
0046     d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
0047     d->m_completer->setModel(model());
0048     setCompleter(d->m_completer);
0049 
0050     // setSubstringSearch(!KMyMoneySettings::stringMatchFromStart());
0051 
0052     view()->setAlternatingRowColors(true);
0053     setInsertPolicy(QComboBox::NoInsert); // don't insert new objects due to object creation
0054     connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
0055 }
0056 
0057 KMyMoneyMVCCombo::KMyMoneyMVCCombo(KMyMoneyMVCComboPrivate &dd, QWidget* parent) :
0058     KComboBox(parent),
0059     d_ptr(&dd)
0060 {
0061     view()->setAlternatingRowColors(true);
0062     connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
0063 }
0064 
0065 KMyMoneyMVCCombo::KMyMoneyMVCCombo(KMyMoneyMVCComboPrivate &dd, bool editable, QWidget* parent) :
0066     KComboBox(editable, parent),
0067     d_ptr(&dd)
0068 {
0069     Q_D(KMyMoneyMVCCombo);
0070     d->m_completer = new QCompleter(this);
0071     d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
0072     d->m_completer->setModel(model());
0073     setCompleter(d->m_completer);
0074 
0075     // setSubstringSearch(!KMyMoneySettings::stringMatchFromStart());
0076 
0077     view()->setAlternatingRowColors(true);
0078     setInsertPolicy(QComboBox::NoInsert); // don't insert new objects due to object creation
0079     connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
0080 }
0081 
0082 KMyMoneyMVCCombo::~KMyMoneyMVCCombo()
0083 {
0084     Q_D(KMyMoneyMVCCombo);
0085     delete d;
0086 }
0087 
0088 void KMyMoneyMVCCombo::setEditable(bool editable)
0089 {
0090     Q_D(KMyMoneyMVCCombo);
0091     KComboBox::setEditable(editable);
0092 
0093     if(editable) {
0094         if(!d->m_completer) {
0095             d->m_completer = new QCompleter(this);
0096             d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
0097             d->m_completer->setModel(model());
0098         }
0099         setCompleter(d->m_completer);
0100     }
0101 }
0102 
0103 void KMyMoneyMVCCombo::setSubstringSearch(bool enabled)
0104 {
0105     Q_D(KMyMoneyMVCCombo);
0106     d->m_completer->setCompletionMode(QCompleter::PopupCompletion);
0107     d->m_completer->setModel(model());
0108     d->m_completer->setFilterMode(enabled ? Qt::MatchContains : Qt::MatchStartsWith);
0109 }
0110 
0111 void KMyMoneyMVCCombo::setSubstringSearchForChildren(QWidget*const widget, bool enabled)
0112 {
0113     Q_CHECK_PTR(widget);
0114     QList<KMyMoneyMVCCombo *> comboList;
0115     comboList = widget->findChildren<KMyMoneyMVCCombo *>();
0116     foreach (KMyMoneyMVCCombo *combo, comboList) {
0117         combo->setSubstringSearch(enabled);
0118     }
0119 }
0120 
0121 void KMyMoneyMVCCombo::setPlaceholderText(const QString& hint) const
0122 {
0123     KLineEdit* le = qobject_cast<KLineEdit*>(lineEdit());
0124     if (le) {
0125         le->setPlaceholderText(hint);
0126     }
0127 }
0128 
0129 QString KMyMoneyMVCCombo::selectedItem() const
0130 {
0131     Q_D(const KMyMoneyMVCCombo);
0132     auto dataVariant = itemData(currentIndex());
0133     if (dataVariant.isValid())
0134         d->m_id = dataVariant.toString();
0135     else
0136         d->m_id.clear();
0137     return d->m_id;
0138 }
0139 
0140 void KMyMoneyMVCCombo::setSelectedItem(const QString& id)
0141 {
0142     Q_D(KMyMoneyMVCCombo);
0143     d->m_id = id;
0144     setCurrentIndex(findData(QVariant(d->m_id)));
0145 }
0146 
0147 void KMyMoneyMVCCombo::activated(int index)
0148 {
0149     Q_D(KMyMoneyMVCCombo);
0150     auto dataVariant = itemData(index);
0151     if (dataVariant.isValid()) {
0152         d->m_id = dataVariant.toString();
0153         emit itemSelected(d->m_id);
0154     }
0155 }
0156 
0157 void KMyMoneyMVCCombo::connectNotify(const QMetaMethod & signal)
0158 {
0159     Q_D(KMyMoneyMVCCombo);
0160     if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
0161         d->m_canCreateObjects = true;
0162     }
0163 }
0164 
0165 void KMyMoneyMVCCombo::disconnectNotify(const QMetaMethod & signal)
0166 {
0167     Q_D(KMyMoneyMVCCombo);
0168     if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
0169         d->m_canCreateObjects = false;
0170     }
0171 }
0172 
0173 void KMyMoneyMVCCombo::setCurrentText(const QString& txt)
0174 {
0175     KComboBox::setItemText(KComboBox::currentIndex(), txt);
0176 }
0177 
0178 void KMyMoneyMVCCombo::setCurrentText()
0179 {
0180     KComboBox::setItemText(KComboBox::currentIndex(), QString());
0181 }
0182 
0183 
0184 void KMyMoneyMVCCombo::focusOutEvent(QFocusEvent* e)
0185 {
0186     Q_D(KMyMoneyMVCCombo);
0187     // when showing m_completion we'll receive a focus out event even if the focus
0188     // will still remain at this widget since this widget is the completion's focus proxy
0189     // so ignore the focus out event caused by showing a widget of type Qt::Popup
0190     if (e->reason() == Qt::PopupFocusReason)
0191         return;
0192 
0193     if (d->m_inFocusOutEvent) {
0194         KComboBox::focusOutEvent(e);
0195         return;
0196     }
0197 
0198     //check if the focus went to a widget in TransactionFrom, in the Register, or on a WizardPage
0199     if (e->reason() == Qt::MouseFocusReason) {
0200         QObject *w = this->parent();
0201         QObject *q = qApp->focusWidget()->parent();
0202         // KMyMoneyTagCombo is inside KTagContainer, KMyMoneyPayeeCombo isn't it
0203         if (w->inherits("KTagContainer"))
0204             w = w->parent();
0205         while (q && q->objectName() != "qt_scrollarea_viewport")
0206             q = q->parent();
0207         if (q != w && qApp->focusWidget()->parent() != w && qApp->focusWidget()->objectName() != "register") {
0208             KComboBox::focusOutEvent(e);
0209             return;
0210         }
0211     }
0212 
0213     d->m_inFocusOutEvent = true;
0214     if (isEditable() && !currentText().isEmpty() && e->reason() != Qt::ActiveWindowFocusReason) {
0215         if (d->m_canCreateObjects) {
0216             // in case we tab out, we make sure that if the current completion
0217             // contains the current text that we set the current text to
0218             // the full completion text but only if the completion box is visible.
0219             // BUG 254984 is resolved with the visibility check
0220             if (e->reason() != Qt::MouseFocusReason) {
0221                 if (d->m_completer->popup() && d->m_completer->popup()->isVisible()
0222                         && d->m_completer->currentCompletion().contains(currentText(), Qt::CaseInsensitive)) {
0223                     lineEdit()->setText(d->m_completer->currentCompletion());
0224                 }
0225             }
0226 
0227             //check if the current text is contained in the internal list, if not ask the user if want to create a new item.
0228             checkCurrentText();
0229 
0230             // else if we cannot create objects, and the current text is not
0231             // in the list, then we clear the text and the selection.
0232         } else if (!contains(currentText())) {
0233             clearEditText();
0234         }
0235         //this is to cover the case when you highlight an item but don't activate it with Enter
0236         if (currentText() != itemText(currentIndex())) {
0237             setCurrentIndex(findText(currentText(), Qt::MatchExactly));
0238             emit activated(currentIndex());
0239         }
0240     }
0241 
0242     KComboBox::focusOutEvent(e);
0243 
0244     // force update of hint and id if there is no text in the widget
0245     if (isEditable() && currentText().isEmpty()) {
0246         QString id = d->m_id;
0247         d->m_id.clear();
0248         setCurrentIndex(-1);
0249         if (!id.isEmpty())
0250             emit itemSelected(d->m_id);
0251         update();
0252     }
0253 
0254     d->m_inFocusOutEvent = false;
0255     // This is used only be KMyMoneyTagCombo at this time
0256     emit lostFocus();
0257 }
0258 
0259 void KMyMoneyMVCCombo::checkCurrentText()
0260 {
0261     Q_D(KMyMoneyMVCCombo);
0262     const auto payeeName = currentText();
0263     if (!contains(payeeName)) {
0264         QString id;
0265         // announce that we go into a possible dialog to create an object
0266         // This can be used by upstream widgets to disable filters etc.
0267         emit objectCreation(true);
0268 
0269         emit createItem(payeeName, id);
0270 
0271         // Announce that we return from object creation
0272         emit objectCreation(false);
0273 
0274         // update the field to a possibly created object
0275         d->m_id = id;
0276         addEntry(payeeName, id);
0277         setCurrentTextById(id);
0278     }
0279 }
0280 
0281 void KMyMoneyMVCCombo::addEntry(const QString& newTxt, const QString& id)
0282 {
0283     // find the correct position in the list
0284     int idx;
0285     for(idx = 0; idx < model()->rowCount(); ++idx) {
0286         const QString txt = itemText(idx);
0287         if (txt.compare(newTxt) > 0) {
0288             break;
0289         }
0290     }
0291     // and insert the new item
0292     insertItem(idx - 1, QIcon(), newTxt, id);
0293 }
0294 
0295 void KMyMoneyMVCCombo::setCurrentTextById(const QString& id)
0296 {
0297     clearEditText();
0298     if (!id.isEmpty()) {
0299         int index = findData(QVariant(id), Qt::UserRole, Qt::MatchExactly);
0300         if (index > -1) {
0301             setCompletedText(itemText(index));
0302             setEditText(itemText(index));
0303             setCurrentIndex(index);
0304         }
0305     }
0306 }
0307 
0308 void KMyMoneyMVCCombo::protectItem(int id, bool protect)
0309 {
0310     QStandardItemModel* standardModel = qobject_cast<QStandardItemModel*> (model());
0311     QStandardItem* standardItem = standardModel->item(id);
0312     standardItem->setSelectable(!protect);
0313 }