File indexing completed on 2025-04-27 03:58:21

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-03-14
0007  * Description : User interface for searches
0008  *
0009  * SPDX-FileCopyrightText: 2008-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "comboboxutilities.h"
0016 
0017 // Qt includes
0018 
0019 #include <QAbstractItemView>
0020 #include <QAbstractListModel>
0021 #include <QMouseEvent>
0022 #include <QPointer>
0023 #include <QPainter>
0024 #include <QPen>
0025 #include <QStyle>
0026 #include <QStyleOption>
0027 #include <QTreeView>
0028 #include <QVBoxLayout>
0029 
0030 // Local includes
0031 
0032 #include "digikam_debug.h"
0033 
0034 namespace Digikam
0035 {
0036 
0037 ProxyLineEdit::ProxyLineEdit(QWidget* const parent)
0038     : QLineEdit(parent),
0039       m_widget (nullptr)
0040 {
0041     m_layout = new QVBoxLayout;
0042     m_layout->setSpacing(0);
0043     m_layout->setContentsMargins(QMargins());
0044     setLayout(m_layout);
0045 
0046     // unset text edit cursor
0047 
0048     unsetCursor();
0049 
0050     // unset clear button per default
0051 
0052     setClearButtonShown(false);
0053 
0054     connect(this, SIGNAL(textChanged(QString)),
0055             this, SLOT(slotTextChanged(QString)));
0056 }
0057 
0058 void ProxyLineEdit::setWidget(QWidget* widget)
0059 {
0060     if (m_widget)
0061     {
0062         delete m_widget;
0063     }
0064 
0065     m_widget = widget;
0066     m_widget->setParent(this);
0067     m_layout->addWidget(m_widget);
0068 }
0069 
0070 void ProxyLineEdit::setClearButtonShown(bool show)
0071 {
0072     setClearButtonEnabled(show);
0073 
0074     int rightMargin = show ? height() : 0;
0075     m_layout->setContentsMargins(0, 0, rightMargin, 0);
0076 }
0077 
0078 void ProxyLineEdit::slotTextChanged(const QString& text)
0079 {
0080     if (text.isEmpty() && isClearButtonEnabled())
0081     {
0082         Q_EMIT signalClearButtonPressed();
0083     }
0084 }
0085 
0086 /**
0087  * NOTE: see bug #326718: We need to use QLineEdit parent class with these methods
0088  * to have clear button working fine.
0089  */
0090 void ProxyLineEdit::mousePressEvent(QMouseEvent* event)
0091 {
0092     QLineEdit::mousePressEvent(event);
0093 }
0094 
0095 void ProxyLineEdit::mouseReleaseEvent(QMouseEvent* event)
0096 {
0097     QLineEdit::mouseReleaseEvent(event);
0098 }
0099 
0100 /**
0101  * We just re-implement all relevant QWidget event handlers and call
0102  * the QWidget implementation, not the QLineEdit one.
0103  */
0104 void ProxyLineEdit::mouseMoveEvent(QMouseEvent* event)
0105 {
0106     QWidget::mouseMoveEvent(event);
0107 }
0108 
0109 void ProxyLineEdit::mouseDoubleClickEvent(QMouseEvent* event)
0110 {
0111     QWidget::mouseDoubleClickEvent(event);
0112 }
0113 
0114 void ProxyLineEdit::keyPressEvent(QKeyEvent* event)
0115 {
0116     QWidget::keyPressEvent(event);
0117 }
0118 
0119 void ProxyLineEdit::focusInEvent(QFocusEvent* event)
0120 {
0121     QWidget::focusInEvent(event);
0122 }
0123 
0124 void ProxyLineEdit::focusOutEvent(QFocusEvent* event)
0125 {
0126     QWidget::focusOutEvent(event);
0127 }
0128 
0129 void ProxyLineEdit::paintEvent(QPaintEvent* event)
0130 {
0131     QWidget::paintEvent(event);
0132 }
0133 
0134 void ProxyLineEdit::dragEnterEvent(QDragEnterEvent* event)
0135 {
0136     QWidget::dragEnterEvent(event);
0137 }
0138 
0139 void ProxyLineEdit::dragMoveEvent(QDragMoveEvent* event)
0140 {
0141     QWidget::dragMoveEvent(event);
0142 }
0143 
0144 void ProxyLineEdit::dragLeaveEvent(QDragLeaveEvent* event)
0145 {
0146     QWidget::dragLeaveEvent(event);
0147 }
0148 
0149 void ProxyLineEdit::dropEvent(QDropEvent* event)
0150 {
0151     QWidget::dropEvent(event);
0152 }
0153 
0154 void ProxyLineEdit::changeEvent(QEvent* event)
0155 {
0156     QWidget::changeEvent(event);
0157 }
0158 
0159 void ProxyLineEdit::contextMenuEvent(QContextMenuEvent* event)
0160 {
0161     QWidget::contextMenuEvent(event);
0162 }
0163 
0164 void ProxyLineEdit::inputMethodEvent(QInputMethodEvent* event)
0165 {
0166     QWidget::inputMethodEvent(event);
0167 }
0168 
0169 QSize ProxyLineEdit::minimumSizeHint() const
0170 {
0171     return QWidget::minimumSizeHint();
0172 }
0173 
0174 QSize ProxyLineEdit::sizeHint() const
0175 {
0176     return QWidget::sizeHint();
0177 }
0178 
0179 // -------------------------------------------------------------------------
0180 
0181 ProxyClickLineEdit::ProxyClickLineEdit(QWidget* const parent)
0182     : ProxyLineEdit(parent)
0183 {
0184 }
0185 
0186 void ProxyClickLineEdit::mouseReleaseEvent(QMouseEvent* event)
0187 {
0188     ProxyLineEdit::mouseReleaseEvent(event);
0189 
0190     if (event->button() == Qt::LeftButton)
0191     {
0192         Q_EMIT leftClicked();
0193         event->accept();
0194     }
0195 }
0196 
0197 // -------------------------------------------------------------------------
0198 
0199 ModelIndexBasedComboBox::ModelIndexBasedComboBox(QWidget* const parent)
0200     : QComboBox(parent)
0201 {
0202 }
0203 
0204 void ModelIndexBasedComboBox::hidePopup()
0205 {
0206     m_currentIndex = view()->selectionModel()->currentIndex();
0207     QComboBox::hidePopup();
0208 }
0209 
0210 void ModelIndexBasedComboBox::showPopup()
0211 {
0212     QComboBox::showPopup();
0213 
0214     if (m_currentIndex.isValid())
0215     {
0216         view()->selectionModel()->setCurrentIndex(m_currentIndex, QItemSelectionModel::ClearAndSelect);
0217     }
0218 }
0219 
0220 QModelIndex ModelIndexBasedComboBox::currentIndex() const
0221 {
0222     return m_currentIndex;
0223 }
0224 
0225 void ModelIndexBasedComboBox::setCurrentIndex(const QModelIndex& index)
0226 {
0227     m_currentIndex = index;
0228     view()->selectionModel()->setCurrentIndex(m_currentIndex, QItemSelectionModel::ClearAndSelect);
0229 }
0230 
0231 // -------------------------------------------------------------------------
0232 
0233 StayPoppedUpComboBox::StayPoppedUpComboBox(QWidget* const parent)
0234     : ModelIndexBasedComboBox(parent)
0235 {
0236     m_view = nullptr;
0237 }
0238 
0239 void StayPoppedUpComboBox::installView(QAbstractItemView* view)
0240 {
0241     if (m_view)
0242     {
0243         return;
0244     }
0245 
0246     // Create view
0247 
0248     m_view = view;
0249 
0250     // set on combo box
0251 
0252     setView(m_view);
0253 
0254     // Removing these event filters works just as the eventFilter() solution below,
0255     // but is much more dependent on Qt internals and not guaranteed to work in the future.
0256 /*
0257     m_view->removeEventFilter(m_view->parent());
0258     m_view->viewport()->removeEventFilter(m_view->parent());
0259 */
0260     // Install event filters, _after_ setView() is called
0261 
0262     m_view->installEventFilter(this);
0263     m_view->viewport()->installEventFilter(this);
0264 }
0265 
0266 bool StayPoppedUpComboBox::eventFilter(QObject* o, QEvent* e)
0267 {
0268     // The combo box has installed an event filter on the view.
0269     // If it catches a valid mouse button release there, it will hide the popup.
0270     // Here we prevent this by eating the event ourselves,
0271     // and then dispatching it to its destination.
0272 
0273     if ((o == m_view) || (o == m_view->viewport()))
0274     {
0275         switch (e->type())
0276         {
0277             case QEvent::MouseButtonRelease:
0278             {
0279                 QMouseEvent* m = static_cast<QMouseEvent*>(e);
0280 
0281                 if (m_view->isVisible() && m_view->rect().contains(m->pos()))
0282                 {
0283                     if (o == m_view)
0284                     {
0285                         o->event(e);
0286                     }
0287                     else
0288                     {
0289                         // Viewport: Calling event() does not work, viewportEvent() is needed.
0290                         // This is the event that gets redirected to the QTreeView finally!
0291 
0292                         sendViewportEventToView(e);
0293                     }
0294 
0295                     // we have dispatched the event privately; we filter it out from the main dispatching
0296 
0297                     return true;
0298                 }
0299 
0300                 break;
0301             }
0302 
0303             case QEvent::ContextMenu:
0304             {
0305                 if (o != m_view)
0306                 {
0307                     // for whatever reason, the position of the event is slightly wrong
0308 
0309                     QContextMenuEvent* m = static_cast<QContextMenuEvent*>(e);
0310                     QPoint correctPos    = m_view->viewport()->mapFromGlobal(m->globalPos());
0311                     QContextMenuEvent corrected(m->reason(), correctPos, m->globalPos(), m->modifiers());
0312                     sendViewportEventToView(&corrected);
0313 
0314                     return true;
0315                 }
0316 
0317                 break;
0318             }
0319 
0320             default:
0321                 break;
0322         }
0323     }
0324 
0325     return QComboBox::eventFilter(o, e);
0326 }
0327 
0328 // -------------------------------------------------------------------------
0329 
0330 class Q_DECL_HIDDEN TreeViewComboBoxTreeView : public QTreeView
0331 {
0332     Q_OBJECT
0333 
0334 public:
0335 
0336     // Needed to make viewportEvent() public
0337 
0338     TreeViewComboBoxTreeView(QWidget* const parent = nullptr)
0339         : QTreeView(parent)
0340     {
0341     }
0342 
0343     bool viewportEvent(QEvent* event) override
0344     {
0345         return QTreeView::viewportEvent(event);
0346     }
0347 };
0348 
0349 TreeViewComboBox::TreeViewComboBox(QWidget* const parent)
0350     : StayPoppedUpComboBox(parent)
0351 {
0352 }
0353 
0354 void TreeViewComboBox::installView(QAbstractItemView* view)
0355 {
0356     // parent does the heavy work
0357 
0358     if (view)
0359     {
0360         StayPoppedUpComboBox::installView(view);
0361     }
0362     else
0363     {
0364         QPointer<TreeViewComboBoxTreeView> tview = new TreeViewComboBoxTreeView;
0365         StayPoppedUpComboBox::installView(tview);
0366     }
0367 }
0368 
0369 void TreeViewComboBox::sendViewportEventToView(QEvent* e)
0370 {
0371     static_cast<TreeViewComboBoxTreeView*>(m_view)->viewportEvent(e);
0372 }
0373 
0374 QTreeView* TreeViewComboBox::view() const
0375 {
0376     return static_cast<QTreeView*>(m_view);
0377 }
0378 
0379 // -------------------------------------------------------------------------
0380 
0381 class Q_DECL_HIDDEN ListViewComboBoxListView : public QListView
0382 {
0383     Q_OBJECT
0384 
0385 public:
0386 
0387     // Needed to make viewportEvent() public
0388 
0389     ListViewComboBoxListView(QWidget* const parent = nullptr)
0390         : QListView(parent)
0391     {
0392     }
0393 
0394     bool viewportEvent(QEvent* event) override
0395     {
0396         return QListView::viewportEvent(event);
0397     }
0398 
0399 private:
0400 
0401     // Disable
0402     explicit ListViewComboBoxListView(QObject*);
0403 };
0404 
0405 ListViewComboBox::ListViewComboBox(QWidget* const parent)
0406     : StayPoppedUpComboBox(parent)
0407 {
0408 }
0409 
0410 void ListViewComboBox::installView(QAbstractItemView* view)
0411 {
0412     // parent does the heavy work
0413 
0414     if (view)
0415     {
0416         StayPoppedUpComboBox::installView(view);
0417     }
0418     else
0419     {
0420         QPointer<ListViewComboBoxListView> lview = new ListViewComboBoxListView;
0421         StayPoppedUpComboBox::installView(lview);
0422     }
0423 }
0424 
0425 void ListViewComboBox::sendViewportEventToView(QEvent* e)
0426 {
0427     static_cast<ListViewComboBoxListView*>(m_view)->viewportEvent(e);
0428 }
0429 
0430 QListView* ListViewComboBox::view() const
0431 {
0432     return static_cast<QListView*>(m_view);
0433 }
0434 
0435 // -------------------------------------------------------------------------
0436 
0437 class Q_DECL_HIDDEN TreeViewComboBoxLineEdit : public QLineEdit
0438 {
0439     Q_OBJECT
0440 
0441 public:
0442 
0443     // This line edit works like a weblink:
0444     // Readonly; A mouse press shows the popup; Cursor is the pointing hand.
0445 
0446     explicit TreeViewComboBoxLineEdit(QComboBox* const box)
0447         : QLineEdit(box),
0448           m_box    (box)
0449     {
0450         setReadOnly(true);
0451         setCursor(Qt::PointingHandCursor);
0452     }
0453 
0454     void mouseReleaseEvent(QMouseEvent* event) override
0455     {
0456         QLineEdit::mouseReleaseEvent(event);
0457         m_box->showPopup();
0458     }
0459 
0460     void wheelEvent(QWheelEvent* /*event*/) override
0461     {
0462         m_box->showPopup();
0463     }
0464 
0465 public:
0466 
0467     QComboBox* m_box;
0468 };
0469 
0470 // -------------------------------------------------------------------------
0471 
0472 TreeViewLineEditComboBox::TreeViewLineEditComboBox(QWidget* const parent)
0473     : TreeViewComboBox(parent),
0474       m_comboLineEdit (nullptr)
0475 {
0476 }
0477 
0478 void TreeViewLineEditComboBox::setLineEditText(const QString& text)
0479 {
0480     if (m_comboLineEdit)
0481     {
0482         m_comboLineEdit->setText(text);
0483     }
0484 }
0485 
0486 void TreeViewLineEditComboBox::installView(QAbstractItemView* view)
0487 {
0488     // parent does the heavy work
0489 
0490     TreeViewComboBox::installView(view);
0491 
0492     installLineEdit();
0493 }
0494 
0495 void TreeViewLineEditComboBox::installLineEdit()
0496 {
0497     if (!m_comboLineEdit)
0498     {
0499         setLineEdit(new TreeViewComboBoxLineEdit(this));
0500     }
0501 }
0502 
0503 void TreeViewLineEditComboBox::setLineEdit(QLineEdit* edit)
0504 {
0505     m_comboLineEdit = edit;
0506     TreeViewComboBox::setLineEdit(edit);
0507 }
0508 
0509 } // namespace Digikam
0510 
0511 #include "comboboxutilities.moc"
0512 
0513 #include "moc_comboboxutilities.cpp"