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

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     const QList<KMyMoneyMVCCombo*> comboList = widget->findChildren<KMyMoneyMVCCombo*>();
0115     for (auto combo : comboList) {
0116         combo->setSubstringSearch(enabled);
0117     }
0118 }
0119 
0120 void KMyMoneyMVCCombo::setPlaceholderText(const QString& hint) const
0121 {
0122     KLineEdit* le = qobject_cast<KLineEdit*>(lineEdit());
0123     if (le) {
0124         le->setPlaceholderText(hint);
0125     }
0126 }
0127 
0128 QString KMyMoneyMVCCombo::selectedItem() const
0129 {
0130     Q_D(const KMyMoneyMVCCombo);
0131     auto dataVariant = itemData(currentIndex());
0132     if (dataVariant.isValid())
0133         d->m_id = dataVariant.toString();
0134     else
0135         d->m_id.clear();
0136     return d->m_id;
0137 }
0138 
0139 void KMyMoneyMVCCombo::setSelectedItem(const QString& id)
0140 {
0141     Q_D(KMyMoneyMVCCombo);
0142     d->m_id = id;
0143     setCurrentIndex(findData(QVariant(d->m_id)));
0144 }
0145 
0146 void KMyMoneyMVCCombo::activated(int index)
0147 {
0148     Q_D(KMyMoneyMVCCombo);
0149     auto dataVariant = itemData(index);
0150     if (dataVariant.isValid()) {
0151         d->m_id = dataVariant.toString();
0152         Q_EMIT itemSelected(d->m_id);
0153     }
0154 }
0155 
0156 void KMyMoneyMVCCombo::connectNotify(const QMetaMethod & signal)
0157 {
0158     Q_D(KMyMoneyMVCCombo);
0159     if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
0160         d->m_canCreateObjects = true;
0161     }
0162 }
0163 
0164 void KMyMoneyMVCCombo::disconnectNotify(const QMetaMethod & signal)
0165 {
0166     Q_D(KMyMoneyMVCCombo);
0167     if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
0168         d->m_canCreateObjects = false;
0169     }
0170 }
0171 
0172 void KMyMoneyMVCCombo::setCurrentText(const QString& txt)
0173 {
0174     KComboBox::setItemText(KComboBox::currentIndex(), txt);
0175 }
0176 
0177 void KMyMoneyMVCCombo::setCurrentText()
0178 {
0179     KComboBox::setItemText(KComboBox::currentIndex(), QString());
0180 }
0181 
0182 
0183 void KMyMoneyMVCCombo::focusOutEvent(QFocusEvent* e)
0184 {
0185     Q_D(KMyMoneyMVCCombo);
0186     // when showing m_completion we'll receive a focus out event even if the focus
0187     // will still remain at this widget since this widget is the completion's focus proxy
0188     // so ignore the focus out event caused by showing a widget of type Qt::Popup
0189     if (e->reason() == Qt::PopupFocusReason)
0190         return;
0191 
0192     if (d->m_inFocusOutEvent) {
0193         KComboBox::focusOutEvent(e);
0194         return;
0195     }
0196 
0197     //check if the focus went to a widget in TransactionFrom, in the Register, or on a WizardPage
0198     if (e->reason() == Qt::MouseFocusReason) {
0199         QObject *w = this->parent();
0200         QObject *q = qApp->focusWidget()->parent();
0201         // KMyMoneyTagCombo is inside KTagContainer, KMyMoneyPayeeCombo isn't it
0202         if (w->inherits("KTagContainer"))
0203             w = w->parent();
0204         while (q && q->objectName() != "qt_scrollarea_viewport")
0205             q = q->parent();
0206         if (q != w && qApp->focusWidget()->parent() != w && qApp->focusWidget()->objectName() != "register") {
0207             KComboBox::focusOutEvent(e);
0208             return;
0209         }
0210     }
0211 
0212     d->m_inFocusOutEvent = true;
0213     if (isEditable() && !currentText().isEmpty() && e->reason() != Qt::ActiveWindowFocusReason) {
0214         if (d->m_canCreateObjects) {
0215             // in case we tab out, we make sure that if the current completion
0216             // contains the current text that we set the current text to
0217             // the full completion text but only if the completion box is visible.
0218             // BUG 254984 is resolved with the visibility check
0219             if (e->reason() != Qt::MouseFocusReason) {
0220                 if (d->m_completer->popup() && d->m_completer->popup()->isVisible()
0221                         && d->m_completer->currentCompletion().contains(currentText(), Qt::CaseInsensitive)) {
0222                     lineEdit()->setText(d->m_completer->currentCompletion());
0223                 }
0224             }
0225 
0226             //check if the current text is contained in the internal list, if not ask the user if want to create a new item.
0227             checkCurrentText();
0228 
0229             // else if we cannot create objects, and the current text is not
0230             // in the list, then we clear the text and the selection.
0231         } else if (!contains(currentText())) {
0232             clearEditText();
0233         }
0234         //this is to cover the case when you highlight an item but don't activate it with Enter
0235         if (currentText() != itemText(currentIndex())) {
0236             setCurrentIndex(findText(currentText(), Qt::MatchExactly));
0237             Q_EMIT activated(currentIndex());
0238         }
0239     }
0240 
0241     KComboBox::focusOutEvent(e);
0242 
0243     // force update of hint and id if there is no text in the widget
0244     if (isEditable() && currentText().isEmpty()) {
0245         QString id = d->m_id;
0246         d->m_id.clear();
0247         setCurrentIndex(-1);
0248         if (!id.isEmpty())
0249             Q_EMIT itemSelected(d->m_id);
0250         update();
0251     }
0252 
0253     d->m_inFocusOutEvent = false;
0254     // This is used only be KMyMoneyTagCombo at this time
0255     Q_EMIT lostFocus();
0256 }
0257 
0258 void KMyMoneyMVCCombo::checkCurrentText()
0259 {
0260     Q_D(KMyMoneyMVCCombo);
0261     const auto payeeName = currentText();
0262     if (!contains(payeeName)) {
0263         QString id;
0264         // announce that we go into a possible dialog to create an object
0265         // This can be used by upstream widgets to disable filters etc.
0266         Q_EMIT objectCreation(true);
0267 
0268         Q_EMIT createItem(payeeName, id);
0269 
0270         // Announce that we return from object creation
0271         Q_EMIT objectCreation(false);
0272 
0273         // update the field to a possibly created object
0274         d->m_id = id;
0275         addEntry(payeeName, id);
0276         setCurrentTextById(id);
0277     }
0278 }
0279 
0280 void KMyMoneyMVCCombo::addEntry(const QString& newTxt, const QString& id)
0281 {
0282     // find the correct position in the list
0283     int idx;
0284     for(idx = 0; idx < model()->rowCount(); ++idx) {
0285         const QString txt = itemText(idx);
0286         if (txt.compare(newTxt) > 0) {
0287             break;
0288         }
0289     }
0290     // and insert the new item
0291     insertItem(idx - 1, QIcon(), newTxt, id);
0292 }
0293 
0294 void KMyMoneyMVCCombo::setCurrentTextById(const QString& id)
0295 {
0296     clearEditText();
0297     if (!id.isEmpty()) {
0298         int index = findData(QVariant(id), Qt::UserRole, Qt::MatchExactly);
0299         if (index > -1) {
0300             setCompletedText(itemText(index));
0301             setEditText(itemText(index));
0302             setCurrentIndex(index);
0303         }
0304     }
0305 }
0306 
0307 void KMyMoneyMVCCombo::protectItem(int id, bool protect)
0308 {
0309     QStandardItemModel* standardModel = qobject_cast<QStandardItemModel*> (model());
0310     QStandardItem* standardItem = standardModel->item(id);
0311     standardItem->setSelectable(!protect);
0312 }