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

0001 /*
0002     SPDX-FileCopyrightText: 2001 Felix Rodriguez <frodriguez@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002-2011 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kmymoneycombo.h"
0009 #include "kmymoneycombo_p.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QRect>
0015 #include <QStyle>
0016 #include <QPainter>
0017 #include <QKeyEvent>
0018 #include <QList>
0019 #include <QFocusEvent>
0020 #include <QMouseEvent>
0021 #include <QMetaMethod>
0022 
0023 // ----------------------------------------------------------------------------
0024 // KDE Includes
0025 
0026 #include <KConfig>
0027 #include <KConfigGroup>
0028 
0029 // ----------------------------------------------------------------------------
0030 // Project Includes
0031 
0032 #include "kmymoneyselector.h"
0033 #include "kmymoneycompletion.h"
0034 #include "kmymoneylineedit.h"
0035 
0036 KMyMoneyCombo::KMyMoneyCombo(QWidget *parent) :
0037     KComboBox(parent),
0038     d_ptr(new KMyMoneyComboPrivate)
0039 {
0040 }
0041 
0042 KMyMoneyCombo::KMyMoneyCombo(bool rw, QWidget *parent) :
0043     KComboBox(rw, parent),
0044     d_ptr(new KMyMoneyComboPrivate)
0045 {
0046     Q_D(KMyMoneyCombo);
0047     if (rw) {
0048         d->m_edit = new KMyMoneyLineEdit(this, true);
0049         setLineEdit(d->m_edit);
0050     }
0051 }
0052 
0053 KMyMoneyCombo::KMyMoneyCombo(KMyMoneyComboPrivate &dd, bool rw, QWidget *parent) :
0054     KComboBox(rw, parent),
0055     d_ptr(&dd)
0056 {
0057     Q_D(KMyMoneyCombo);
0058     if (rw) {
0059         d->m_edit = new KMyMoneyLineEdit(this, true);
0060         setLineEdit(d->m_edit);
0061     }
0062 }
0063 
0064 KMyMoneyCombo::~KMyMoneyCombo()
0065 {
0066     Q_D(KMyMoneyCombo);
0067     delete d;
0068 }
0069 
0070 void KMyMoneyCombo::setCurrentTextById(const QString& id)
0071 {
0072     clearEditText();
0073     if (!id.isEmpty()) {
0074         QTreeWidgetItem* item = selector()->item(id);
0075         if (item) {
0076             setCompletedText(item->text(0));
0077             setEditText(item->text(0));
0078         }
0079     }
0080 }
0081 
0082 void KMyMoneyCombo::slotItemSelected(const QString& id)
0083 {
0084     Q_D(KMyMoneyCombo);
0085     if (isEditable()) {
0086         bool blocked = signalsBlocked();
0087         blockSignals(true);
0088         setCurrentTextById(id);
0089         blockSignals(blocked);
0090     }
0091 
0092     d->m_completion->hide();
0093 
0094     if (d->m_id != id) {
0095         d->m_id = id;
0096         emit itemSelected(id);
0097     }
0098 }
0099 
0100 void KMyMoneyCombo::setEditable(bool y)
0101 {
0102     Q_D(KMyMoneyCombo);
0103     if (y == isEditable())
0104         return;
0105 
0106     KComboBox::setEditable(y);
0107 
0108     // make sure we use our own line edit style
0109     if (y) {
0110         d->m_edit = new KMyMoneyLineEdit(this, true);
0111         setLineEdit(d->m_edit);
0112         d->m_edit->setPalette(palette());
0113     } else {
0114         d->m_edit = 0;
0115     }
0116 }
0117 
0118 void KMyMoneyCombo::setPlaceholderText(const QString& hint) const
0119 {
0120     Q_D(const KMyMoneyCombo);
0121     if (d->m_edit)
0122         d->m_edit->setPlaceholderText(hint);
0123 }
0124 
0125 void KMyMoneyCombo::paintEvent(QPaintEvent* ev)
0126 {
0127     Q_D(KMyMoneyCombo);
0128     KComboBox::paintEvent(ev);
0129     // if we don't have an edit field, we need to paint the text onto the button
0130     if (!d->m_edit) {
0131         if (d->m_completion) {
0132             QStringList list;
0133             selector()->selectedItems(list);
0134             if (!list.isEmpty()) {
0135                 QString str = selector()->item(list[0])->text(0);
0136                 // we only paint, if the text is longer than 1 char. Assumption
0137                 // is that length 1 is the blank case so no need to do painting
0138                 if (str.length() > 1) {
0139                     QPainter p(this);
0140                     p.setPen(palette().text().color());
0141                     QStyleOptionComboBox opt;
0142                     initStyleOption(&opt);
0143                     QRect re = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
0144                     p.setClipRect(re);
0145                     p.save();
0146                     p.setFont(font());
0147                     QFontMetrics fm(font());
0148                     int x = re.x(), y = re.y() + fm.ascent();
0149                     p.drawText(x, y, str);
0150                     p.restore();
0151                 }
0152             }
0153         }
0154     }
0155 }
0156 
0157 void KMyMoneyCombo::mousePressEvent(QMouseEvent *e)
0158 {
0159     Q_D(KMyMoneyCombo);
0160     // mostly copied from QCombo::mousePressEvent() and adjusted for our needs
0161     if (e->button() != Qt::LeftButton)
0162         return;
0163 
0164     if (((!isEditable() || isInArrowArea(e->globalPos())) && selector()->itemList().count()) && !d->m_completion->isVisible()) {
0165         d->m_completion->setVisible(true);
0166     }
0167 
0168     if (d->m_timer.isActive()) {
0169         d->m_timer.stop();
0170         d->m_completion->slotMakeCompletion(QString());
0171         // the above call clears the selection in the selector but maintains the current index, use that index to restore the selection
0172         QTreeWidget* listView = selector()->listView();
0173         QModelIndex currentIndex = listView->currentIndex();
0174         if (currentIndex.isValid()) {
0175             listView->selectionModel()->select(currentIndex, QItemSelectionModel::Select);
0176             listView->scrollToItem(listView->currentItem());
0177         }
0178     } else {
0179         KConfig config("kcminputrc");
0180         KConfigGroup grp = config.group("KDE");
0181         d->m_timer.setSingleShot(true);
0182         d->m_timer.start(grp.readEntry("DoubleClickInterval", 400));
0183     }
0184 }
0185 
0186 bool KMyMoneyCombo::isInArrowArea(const QPoint& pos) const
0187 {
0188     QStyleOptionComboBox opt;
0189     initStyleOption(&opt);
0190     QRect arrowRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, this);
0191 
0192     // Correction for motif style, where arrow is smaller
0193     // and thus has a rect that doesn't fit the button.
0194     arrowRect.setHeight(qMax(height() - (2 * arrowRect.y()), arrowRect.height()));
0195 
0196     // if the button is not isEditable, it covers the whole widget
0197     if (!isEditable())
0198         arrowRect = rect();
0199 
0200     return arrowRect.contains(mapFromGlobal(pos));
0201 }
0202 
0203 
0204 void KMyMoneyCombo::setSuppressObjectCreation(bool suppress)
0205 {
0206     Q_D(KMyMoneyCombo);
0207     d->m_canCreateObjects = !suppress;
0208 }
0209 
0210 void KMyMoneyCombo::setCurrentText(const QString& txt)
0211 {
0212     KComboBox::setItemText(KComboBox::currentIndex(), txt);
0213 }
0214 
0215 void KMyMoneyCombo::setCurrentText()
0216 {
0217     KComboBox::setItemText(KComboBox::currentIndex(), QString());
0218 }
0219 
0220 void KMyMoneyCombo::keyPressEvent(QKeyEvent* e)
0221 {
0222     Q_D(KMyMoneyCombo);
0223     if ((e->key() == Qt::Key_F4 && e->modifiers() == 0) ||
0224             (e->key() == Qt::Key_Down && (e->modifiers() & Qt::AltModifier)) ||
0225             (!isEditable() && e->key() == Qt::Key_Space)) {
0226         // if we have at least one item in the list, we open the dropdown
0227         if (selector()->listView()->itemAt(0, 0))
0228             d->m_completion->setVisible(true);
0229         e->ignore();
0230         return;
0231     }
0232     KComboBox::keyPressEvent(e);
0233 }
0234 
0235 void KMyMoneyCombo::connectNotify(const QMetaMethod & signal)
0236 {
0237     Q_D(KMyMoneyCombo);
0238     if (signal != QMetaMethod::fromSignal(&KMyMoneyCombo::createItem)) {
0239         d->m_canCreateObjects = true;
0240     }
0241 }
0242 
0243 void KMyMoneyCombo::disconnectNotify(const QMetaMethod & signal)
0244 {
0245     Q_D(KMyMoneyCombo);
0246     if (signal != QMetaMethod::fromSignal(&KMyMoneyCombo::createItem)) {
0247         d->m_canCreateObjects = false;
0248     }
0249 }
0250 
0251 void KMyMoneyCombo::focusOutEvent(QFocusEvent* e)
0252 {
0253     Q_D(KMyMoneyCombo);
0254     // don't do anything if the focus is lost due to window activation, this way switching
0255     // windows while typing a category will not popup the category creation dialog
0256     // also ignore the fact that the focus is lost because of Qt::PopupFocusReason (context menu)
0257     if (e->reason() == Qt::ActiveWindowFocusReason || e->reason() == Qt::PopupFocusReason)
0258         return;
0259 
0260     if (d->m_inFocusOutEvent) {
0261         KComboBox::focusOutEvent(e);
0262         return;
0263     }
0264 
0265     d->m_inFocusOutEvent = true;
0266     if (isEditable() && !currentText().isEmpty()) {
0267         if (d->m_canCreateObjects) {
0268             if (!d->m_completion->selector()->contains(currentText())) {
0269                 QString id;
0270                 // announce that we go into a possible dialog to create an object
0271                 // This can be used by upstream widgets to disable filters etc.
0272                 emit objectCreation(true);
0273 
0274                 emit createItem(currentText(), id);
0275 
0276                 // Announce that we return from object creation
0277                 emit objectCreation(false);
0278 
0279                 // update the field to a possibly created object
0280                 d->m_id = id;
0281                 setCurrentTextById(id);
0282 
0283                 // make sure the completion does not show through
0284                 d->m_completion->hide();
0285             }
0286 
0287             // else if we cannot create objects, and the current text is not
0288             // in the list, then we clear the text and the selection.
0289         } else if (!d->m_completion->selector()->contains(currentText())) {
0290             clearEditText();
0291         }
0292     }
0293 
0294     KComboBox::focusOutEvent(e);
0295 
0296     // force update of hint and id if there is no text in the widget
0297     if (isEditable() && currentText().isEmpty()) {
0298         QString id = d->m_id;
0299         d->m_id.clear();
0300         if (!id.isEmpty())
0301             emit itemSelected(d->m_id);
0302         update();
0303     }
0304     d->m_inFocusOutEvent = false;
0305 }
0306 
0307 KMyMoneySelector* KMyMoneyCombo::selector() const
0308 {
0309     Q_D(const KMyMoneyCombo);
0310     return d->m_completion->selector();
0311 }
0312 
0313 KMyMoneyCompletion* KMyMoneyCombo::completion() const
0314 {
0315     Q_D(const KMyMoneyCombo);
0316     return d->m_completion;
0317 }
0318 
0319 void KMyMoneyCombo::selectedItems(QStringList& list) const
0320 {
0321     Q_D(const KMyMoneyCombo);
0322     if (lineEdit() && lineEdit()->text().length() == 0) {
0323         list.clear();
0324     } else {
0325         d->m_completion->selector()->selectedItems(list);
0326     }
0327 }
0328 
0329 QString KMyMoneyCombo::selectedItem() const
0330 {
0331     Q_D(const KMyMoneyCombo);
0332     return d->m_id;
0333 }
0334 
0335 void KMyMoneyCombo::setSelectedItem(const QString& id)
0336 {
0337     Q_D(KMyMoneyCombo);
0338     d->m_completion->selector()->setSelected(id, true);
0339     blockSignals(true);
0340     slotItemSelected(id);
0341     blockSignals(false);
0342     update();
0343 }
0344 
0345 QSize KMyMoneyCombo::sizeHint() const
0346 {
0347     return KComboBox::sizeHint();
0348 
0349     // I wanted to use the code below to adjust the size of the combo box
0350     // according to the largest item in the selector list. Apparently that
0351     // does not work too well in the enter and edit schedule dialog for
0352     // the category combo box. So we just use the standard implementation for now.
0353 #if 0
0354     constPolish();
0355     int i, w;
0356     QFontMetrics fm = fontMetrics();
0357 
0358     int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18;
0359     int maxH = qMax(fm.lineSpacing(), 14) + 2;
0360 
0361     w = selector()->optimizedWidth();
0362     if (w > maxW)
0363         maxW = w;
0364 
0365     QSize sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, this,
0366                       QSize(maxW, maxH)).
0367                       expandedTo(QApplication::globalStrut()));
0368 
0369     return sizeHint;
0370 #endif
0371 }