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

0001 /*
0002     SPDX-FileCopyrightText: 2004-2011 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 "kmymoneycompletion.h"
0008 #include "kmymoneycompletion_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QApplication>
0014 #include <QKeyEvent>
0015 #include <QEvent>
0016 #include <QDesktopWidget>
0017 #include <QLineEdit>
0018 #include <QVBoxLayout>
0019 
0020 // ----------------------------------------------------------------------------
0021 // KDE Includes
0022 
0023 // ----------------------------------------------------------------------------
0024 // Project Includes
0025 
0026 #include <kmymoneyselector.h>
0027 #include "kmymoneycombo.h"
0028 #include "widgetenums.h"
0029 
0030 KMyMoneyCompletion::KMyMoneyCompletion(QWidget *parent) :
0031     QWidget(parent),
0032     d_ptr(new KMyMoneyCompletionPrivate)
0033 {
0034     Q_D(KMyMoneyCompletion);
0035     setWindowFlags(Qt::ToolTip);
0036     // make it look like the Qt completer
0037     QVBoxLayout *completionLayout = new QVBoxLayout(this);
0038     completionLayout->setContentsMargins(0, 0, 0, 0);
0039     completionLayout->setSpacing(0);
0040 
0041     d->m_parent = parent;
0042     d->m_selector = new KMyMoneySelector(this);
0043     d->m_selector->listView()->setFocusProxy(parent);
0044     completionLayout->addWidget(d->m_selector);
0045 
0046     // to handle the keyboard events received by this widget in the same way as
0047     // the keyboard events received by the other widgets
0048     installEventFilter(this);
0049 
0050     connectSignals(d->m_selector, d->m_selector->listView());
0051 }
0052 
0053 void KMyMoneyCompletion::connectSignals(QWidget* widget, QTreeWidget* lv)
0054 {
0055     Q_D(KMyMoneyCompletion);
0056     d->m_widget = widget;
0057     d->m_lv = lv;
0058     connect(lv, &QTreeWidget::itemActivated, this, &KMyMoneyCompletion::slotItemSelected);
0059     connect(lv, &QTreeWidget::itemClicked, this, &KMyMoneyCompletion::slotItemSelected);
0060 }
0061 
0062 KMyMoneyCompletion::KMyMoneyCompletion(KMyMoneyCompletionPrivate &dd, QWidget* parent) :
0063     QWidget(parent),
0064     d_ptr(&dd)
0065 {
0066 }
0067 
0068 KMyMoneyCompletion::~KMyMoneyCompletion()
0069 {
0070     Q_D(KMyMoneyCompletion);
0071     delete d;
0072 }
0073 
0074 void KMyMoneyCompletion::adjustSize()
0075 {
0076     Q_D(KMyMoneyCompletion);
0077     QTreeWidgetItemIterator it(d->m_lv, QTreeWidgetItemIterator::NotHidden);
0078     int count = 0;
0079     while (*it) {
0080         ++count;
0081         ++it;
0082     }
0083     adjustSize(count);
0084 }
0085 
0086 void KMyMoneyCompletion::adjustSize(const int count)
0087 {
0088     Q_D(KMyMoneyCompletion);
0089     int w = d->m_widget->sizeHint().width();
0090     if (d->m_parent && w < d->m_parent->width())
0091         w = d->m_parent->width();
0092 
0093     const int minimumWidth = fontMetrics().width(QLatin1Char('W')) * 15;
0094     w = qMax(w, minimumWidth);
0095 
0096     int h = 0;
0097     QTreeWidgetItemIterator it(d->m_lv, QTreeWidgetItemIterator::NotHidden);
0098     QTreeWidgetItem* item = *it;
0099     if (item)
0100         // the +1 in the next statement avoids the display of a scroll bar if count < MAX_ITEMS.
0101         h = item->treeWidget()->visualItemRect(item).height() * (count > KMyMoneyCompletionPrivate::MAX_ITEMS - 1 ? KMyMoneyCompletionPrivate::MAX_ITEMS : count + 1);
0102 
0103     resize(w, h);
0104 
0105     if (d->m_parent) {
0106         // the code of this basic block is taken from KCompletionBox::show()
0107         // and modified to our local needs
0108 
0109         QRect screenSize = QApplication::desktop()->availableGeometry(parentWidget());
0110 
0111         QPoint orig = d->m_parent->mapToGlobal(QPoint(0, d->m_parent->height()));
0112         int x = orig.x();
0113         int y = orig.y();
0114 
0115         if (x + width() > screenSize.right())
0116             x = screenSize.right() - width();
0117 
0118         // check for the maximum height here to avoid flipping
0119         // of the completion box from top to bottom of the
0120         // edit widget. The offset (y) is certainly based
0121         // on the actual height.
0122         if (item) {
0123             if ((y + item->treeWidget()->visualItemRect(item).height() * KMyMoneyCompletionPrivate::MAX_ITEMS) > screenSize.bottom())
0124                 y = y - height() - d->m_parent->height();
0125         }
0126 
0127         move(x, y);
0128     }
0129 }
0130 
0131 void KMyMoneyCompletion::showEvent(QShowEvent* e)
0132 {
0133     show(true);
0134     QWidget::showEvent(e);
0135 }
0136 
0137 void KMyMoneyCompletion::show(bool presetSelected)
0138 {
0139     Q_D(KMyMoneyCompletion);
0140     if (!d->m_id.isEmpty() && presetSelected)
0141         d->m_selector->setSelected(d->m_id);
0142 
0143     adjustSize();
0144 
0145     if (d->m_parent) {
0146         d->m_parent->installEventFilter(this);
0147         // make sure to install the filter for the combobox lineedit as well
0148         // We have do this here because QObject::installEventFilter() is not
0149         // declared virtual and we have no chance to override it in KMyMoneyCombo
0150         KMyMoneyCombo* c = dynamic_cast<KMyMoneyCombo*>(d->m_parent);
0151         if (c && c->lineEdit()) {
0152             c->lineEdit()->installEventFilter(this);
0153         }
0154     }
0155     QWidget::show();
0156 
0157     // make sure that the parent is the input context's focus widget instead of the selector's list
0158     //if (qApp->inputContext()->focusWidget() == m_selector->listView())
0159     //qApp->inputContext()->setFocusWidget(m_parent);
0160 }
0161 
0162 void KMyMoneyCompletion::hide()
0163 {
0164     Q_D(KMyMoneyCompletion);
0165     if (d->m_parent) {
0166         d->m_parent->removeEventFilter(this);
0167         // make sure to uninstall the filter for the combobox lineedit as well
0168         // We have do this here because QObject::installEventFilter() is not
0169         // declared virtual and we have no chance to override it in KMyMoneyCombo
0170         KMyMoneyCombo* c = dynamic_cast<KMyMoneyCombo*>(d->m_parent);
0171         if (c && c->lineEdit()) {
0172             c->lineEdit()->removeEventFilter(this);
0173         }
0174     }
0175     QWidget::hide();
0176 }
0177 
0178 bool KMyMoneyCompletion::eventFilter(QObject* o, QEvent* e)
0179 {
0180     Q_D(KMyMoneyCompletion);
0181     KMyMoneyCombo *c = dynamic_cast<KMyMoneyCombo*>(d->m_parent);
0182     if (o == d->m_parent || (c && o == c->lineEdit()) || o == this) {
0183         if (isVisible()) {
0184 #ifdef Q_OS_WIN32                   //krazy:exclude=cpp 
0185             // hide the completer only if the focus was not lost because of windows activation or the activated window is not an application window
0186             if (e->type() == QEvent::FocusOut && (static_cast<QFocusEvent*>(e)->reason() != Qt::ActiveWindowFocusReason || QApplication::activeWindow() == 0)) {
0187 #else
0188             if (e->type() == QEvent::FocusOut) {
0189 #endif
0190                 hide();
0191             }
0192             if (e->type() == QEvent::KeyPress) {
0193                 QTreeWidgetItem* item = 0;
0194                 QKeyEvent* ev = static_cast<QKeyEvent*>(e);
0195                 switch (ev->key()) {
0196                 case Qt::Key_Tab:
0197                 case Qt::Key_Backtab:
0198                     slotItemSelected(d->m_lv->currentItem(), 0);
0199                     break;
0200 
0201                 case Qt::Key_Down:
0202                 case Qt::Key_PageDown:
0203                     item = d->m_lv->currentItem();
0204                     while (item) {
0205                         item = d->m_lv->itemBelow(item);
0206                         if (item && selector()->match(d->m_lastCompletion, item))
0207                             break;
0208                     }
0209                     if (item) {
0210                         d->m_lv->setCurrentItem(item);
0211                         d->m_lv->scrollToItem(item);
0212                     }
0213                     ev->accept();
0214                     return true;
0215 
0216                 case Qt::Key_Up:
0217                 case Qt::Key_PageUp:
0218                     item = d->m_lv->currentItem();
0219                     while (item) {
0220                         item = d->m_lv->itemAbove(item);
0221                         if (item && selector()->match(d->m_lastCompletion, item))
0222                             break;
0223                     }
0224                     if (item) {
0225                         d->m_lv->setCurrentItem(item);
0226                         // make sure, we always see a possible (non-selectable) group item
0227                         if (d->m_lv->itemAbove(item))
0228                             item = d->m_lv->itemAbove(item);
0229                         d->m_lv->scrollToItem(item);
0230                     }
0231                     ev->accept();
0232                     return true;
0233 
0234                 case Qt::Key_Escape:
0235                     hide();
0236                     ev->accept();
0237                     return true;
0238 
0239                 case Qt::Key_Enter:
0240                 case Qt::Key_Return:
0241                     slotItemSelected(d->m_lv->currentItem(), 0);
0242                     ev->accept();
0243                     return true;
0244 
0245                 case Qt::Key_Home:
0246                 case Qt::Key_End:
0247                     if (ev->modifiers() & Qt::ControlModifier) {
0248                         item = d->m_lv->currentItem();
0249                         if (ev->key() == Qt::Key_Home) {
0250                             while (item && d->m_lv->itemAbove(item)) {
0251                                 item = d->m_lv->itemAbove(item);
0252                             }
0253                             while (item && !selector()->match(d->m_lastCompletion, item)) {
0254                                 item = d->m_lv->itemBelow(item);
0255                             }
0256                         } else {
0257                             while (item && d->m_lv->itemBelow(item)) {
0258                                 item = d->m_lv->itemBelow(item);
0259                             }
0260                             while (item && !selector()->match(d->m_lastCompletion, item)) {
0261                                 item = d->m_lv->itemAbove(item);
0262                             }
0263                         }
0264                         if (item) {
0265                             d->m_lv->setCurrentItem(item);
0266                             // make sure, we always see a possible (non-selectable) group item
0267                             if (d->m_lv->itemAbove(item))
0268                                 item = d->m_lv->itemAbove(item);
0269                             d->m_lv->scrollToItem(item);
0270                         }
0271                         ev->accept();
0272                         return true;
0273                     }
0274                     break;
0275 
0276                 default:
0277                     break;
0278                 }
0279             }
0280         }
0281     }
0282     return QWidget::eventFilter(o, e);
0283 }
0284 
0285 void KMyMoneyCompletion::slotMakeCompletion(const QString& txt)
0286 {
0287     Q_D(KMyMoneyCompletion);
0288     auto cnt = selector()->slotMakeCompletion(txt.trimmed());
0289 
0290     if (d->m_parent && d->m_parent->isVisible() && !isVisible() && cnt)
0291         show(false);
0292     else {
0293         if (cnt != 0) {
0294             adjustSize();
0295         } else {
0296             hide();
0297         }
0298     }
0299 }
0300 
0301 void KMyMoneyCompletion::slotItemSelected(QTreeWidgetItem *item, int)
0302 {
0303     Q_D(KMyMoneyCompletion);
0304     if (item && item->flags().testFlag(Qt::ItemIsSelectable)) {
0305         QString id = item->data(0, (int)eWidgets::Selector::Role::Id).toString();
0306         // hide the widget, so we can debug the slots that are connect
0307         // to the signal we emit very soon
0308         hide();
0309         d->m_id = id;
0310         emit itemSelected(id);
0311     }
0312 }
0313 
0314 void KMyMoneyCompletion::setSelected(const QString& id)
0315 {
0316     Q_D(KMyMoneyCompletion);
0317     d->m_id = id;
0318     d->m_selector->setSelected(id, true);
0319 }
0320 
0321 KMyMoneySelector* KMyMoneyCompletion::selector() const
0322 {
0323     Q_D(const KMyMoneyCompletion);
0324     return d->m_selector;
0325 }