File indexing completed on 2024-12-01 04:36:37

0001 /*
0002    SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "completionlistview.h"
0008 
0009 #include <QApplication>
0010 #include <QDebug>
0011 #include <QKeyEvent>
0012 #include <QScreen>
0013 #include <QScrollBar>
0014 
0015 CompletionListView::CompletionListView()
0016     : QListView()
0017 {
0018     setUniformItemSizes(true);
0019     setWindowFlag(Qt::Popup);
0020     hide();
0021 
0022     connect(this, &QListView::clicked, this, &CompletionListView::complete);
0023 }
0024 
0025 CompletionListView::~CompletionListView() = default;
0026 
0027 void CompletionListView::setTextWidget(QWidget *textWidget)
0028 {
0029     mTextWidget = textWidget;
0030     const Qt::FocusPolicy origPolicy = textWidget->focusPolicy();
0031     setFocusPolicy(Qt::NoFocus);
0032     textWidget->setFocusPolicy(origPolicy);
0033     setFocusProxy(textWidget);
0034 }
0035 
0036 // Typically called with one of the 3 models returned by InputTextManager:
0037 // inputCompleterModel() for users and channels, emojiCompleterModel(), or commandModel()
0038 void CompletionListView::setModel(QAbstractItemModel *model)
0039 {
0040     QAbstractItemModel *oldModel = QListView::model();
0041     if (model != oldModel) {
0042         if (oldModel) {
0043             disconnect(oldModel, &QAbstractItemModel::rowsInserted, this, &CompletionListView::slotCompletionAvailable);
0044             disconnect(oldModel, &QAbstractItemModel::rowsRemoved, this, &CompletionListView::slotCompletionAvailable);
0045         }
0046         connect(model, &QAbstractItemModel::rowsInserted, this, &CompletionListView::slotCompletionAvailable);
0047         connect(model, &QAbstractItemModel::rowsRemoved, this, &CompletionListView::slotCompletionAvailable);
0048         QListView::setModel(model);
0049     }
0050 }
0051 
0052 void CompletionListView::keyPressEvent(QKeyEvent *event)
0053 {
0054     const int key = event->key();
0055     if (key == Qt::Key_Escape) {
0056         hide();
0057         event->accept();
0058         return;
0059     } else if (key == Qt::Key_Return || key == Qt::Key_Enter) {
0060         Q_EMIT complete(currentIndex());
0061         event->accept();
0062         return;
0063     } else if (key != Qt::Key_Down && key != Qt::Key_Up && key != Qt::Key_PageDown && key != Qt::Key_PageUp && key != Qt::Key_Home && key != Qt::Key_End
0064                && key != Qt::Key_Left && key != Qt::Key_Right) {
0065         // send keypresses to the linedit
0066         qApp->sendEvent(mTextWidget, event);
0067         return;
0068     }
0069 
0070     QListView::keyPressEvent(event);
0071 }
0072 
0073 bool CompletionListView::event(QEvent *event)
0074 {
0075     // QAbstractScrollArea::event doesn't call QWidget::mousePressEvent, which leads to
0076     // https://bugs.kde.org/show_bug.cgi?id=434473 when making the scrollarea a Qt::Popup
0077     if (event->type() == QEvent::MouseButtonPress) {
0078         QWidget::mousePressEvent(static_cast<QMouseEvent *>(event));
0079     }
0080     return QListView::event(event);
0081 }
0082 
0083 void CompletionListView::slotCompletionAvailable()
0084 {
0085     const int rowCount = model()->rowCount();
0086     if (rowCount == 0) {
0087         hide();
0088         return;
0089     }
0090     const int maxVisibleItems = 15;
0091 
0092     // Not entirely unlike QCompletionPrivate::showPopup
0093     const QRect screenRect = mTextWidget->screen()->availableGeometry();
0094     int h = (sizeHintForRow(0) * qMin(maxVisibleItems, rowCount) + 3) + 3;
0095     QScrollBar *hsb = horizontalScrollBar();
0096     if (hsb && hsb->isVisible()) {
0097         h += horizontalScrollBar()->sizeHint().height();
0098     }
0099 
0100     const int rh = mTextWidget->height();
0101     QPoint pos = mTextWidget->mapToGlobal(QPoint(0, rh - 2));
0102     int w = mTextWidget->width();
0103 
0104     if (w > screenRect.width()) {
0105         w = screenRect.width();
0106     }
0107     if ((pos.x() + w) > (screenRect.x() + screenRect.width())) {
0108         pos.setX(screenRect.x() + screenRect.width() - w);
0109     }
0110     if (pos.x() < screenRect.x()) {
0111         pos.setX(screenRect.x());
0112     }
0113 
0114     int top = pos.y() - rh - screenRect.top() + 2;
0115     int bottom = screenRect.bottom() - pos.y();
0116     h = qMax(h, minimumHeight());
0117     if (h > bottom) {
0118         h = qMin(qMax(top, bottom), h);
0119 
0120         if (top > bottom) {
0121             pos.setY(pos.y() - h - rh + 2);
0122         }
0123     }
0124 
0125     // qDebug() << "showing at" << pos << "size" << w << "x" << h;
0126     setGeometry(pos.x(), pos.y(), w, h);
0127 
0128     if (!isVisible()) {
0129         if (!currentIndex().isValid()) {
0130             setCurrentIndex(model()->index(0, 0));
0131         }
0132         show();
0133     }
0134 }
0135 
0136 #include "moc_completionlistview.cpp"