File indexing completed on 2024-04-14 04:53:07

0001 /* This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0003     SPDX-FileCopyrightText: 2007 Fredrik Höglund <fredrik@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // Own
0009 #include "konqcombo.h"
0010 
0011 // Qt
0012 #include <QPainter>
0013 #include <QStyle>
0014 #include <QDrag>
0015 #include <QPixmap>
0016 #include <QKeyEvent>
0017 #include <QItemDelegate>
0018 #include <QListWidgetItem>
0019 #include <QEvent>
0020 #include <QMimeData>
0021 
0022 // KDE
0023 #include <kconfig.h>
0024 #include <kconfiggroup.h>
0025 #include <kcompletionbox.h>
0026 #include "konqdebug.h"
0027 #include <kiconloader.h>
0028 #include <kicontheme.h>
0029 #include <klineedit.h>
0030 #include <konqpixmapprovider.h>
0031 #include <kstandardshortcut.h>
0032 #include <konqmainwindow.h>
0033 #include <kstringhandler.h>
0034 #include <QApplication>
0035 
0036 // Local
0037 #include "konqview.h"
0038 #include "KonquerorAdaptor.h"
0039 #include "konqueror_interface.h"
0040 #include "konqhistorymanager.h"
0041 
0042 KConfig *KonqCombo::s_config = nullptr;
0043 const int KonqCombo::temporary = 0;
0044 
0045 static QString titleOfURL(const QString &urlStr)
0046 {
0047     QUrl url(QUrl::fromUserInput(urlStr));
0048     const KonqHistoryList &historylist = KonqHistoryManager::kself()->entries();
0049     KonqHistoryList::const_iterator historyentry = historylist.constFindEntry(url);
0050     if (historyentry == historylist.constEnd() && !url.url().endsWith('/')) {
0051         if (!url.path().endsWith('/')) {
0052             url.setPath(url.path() + '/');
0053         }
0054         historyentry = historylist.constFindEntry(url);
0055     }
0056     return historyentry != historylist.end() ? (*historyentry).title : QString();
0057 }
0058 
0059 ///////////////////////////////////////////////////////////////////////////////
0060 
0061 class KonqListWidgetItem : public QListWidgetItem
0062 {
0063 public:
0064     enum { KonqItemType = 0x1845D5CC };
0065 
0066     KonqListWidgetItem(QListWidget *parent = nullptr);
0067     KonqListWidgetItem(const QString &text, QListWidget *parent = nullptr);
0068 
0069     QVariant data(int role) const override;
0070 
0071     bool reuse(const QString &newText);
0072 
0073 private:
0074     mutable bool lookupPending;
0075 };
0076 
0077 ///////////////////////////////////////////////////////////////////////////////
0078 
0079 class KonqComboItemDelegate : public QItemDelegate
0080 {
0081 public:
0082     KonqComboItemDelegate(QObject *parent) : QItemDelegate(parent) {}
0083     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
0084     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
0085 };
0086 
0087 ///////////////////////////////////////////////////////////////////////////////
0088 
0089 class KonqComboLineEdit : public KLineEdit
0090 {
0091 public:
0092     KonqComboLineEdit(QWidget *parent = nullptr);
0093     KCompletionBox *completionBox(bool create) override;
0094 
0095 protected:
0096     void mouseDoubleClickEvent(QMouseEvent *e) override;
0097 };
0098 
0099 class KonqComboCompletionBox : public KCompletionBox
0100 {
0101 public:
0102     KonqComboCompletionBox(QWidget *parent);
0103     void setItems(const QStringList &items);
0104 };
0105 
0106 KonqCombo::KonqCombo(QWidget *parent)
0107     : KHistoryComboBox(parent),
0108       m_returnPressed(false),
0109       m_permanent(false),
0110       m_pageSecurity(KonqMainWindow::NotCrypted)
0111 {
0112     setLayoutDirection(Qt::LeftToRight);
0113     setInsertPolicy(NoInsert);
0114     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0115     setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
0116 
0117     Q_ASSERT(s_config);
0118 
0119     KConfigGroup locationBarGroup(s_config, "Location Bar");
0120     setMaxCount(locationBarGroup.readEntry("Maximum of URLs in combo", 20));
0121 
0122     // We should also connect the completionBox' highlighted signal to
0123     // our setEditText() slot, because we're handling the signals ourselves.
0124     // But we're lazy and let KCompletionBox do this and simply switch off
0125     // handling of signals later.
0126     setHandleSignals(true);
0127 
0128     KonqComboLineEdit *edit = new KonqComboLineEdit(this);
0129     edit->setHandleSignals(true);
0130     edit->setCompletionBox(new KonqComboCompletionBox(edit));
0131     setLineEdit(edit);
0132     setItemDelegate(new KonqComboItemDelegate(this));
0133     connect(edit, &QLineEdit::textEdited, this, &KonqCombo::slotTextEdited);
0134 
0135     completionBox()->setTabHandling(true); // #167135
0136     completionBox()->setItemDelegate(new KonqComboItemDelegate(this));
0137 
0138     // Make the lineedit consume the Qt::Key_Enter event...
0139     setTrapReturnKey(true);
0140 
0141     // Connect to the returnPressed signal when completionMode == CompletionNone. #314736
0142     slotCompletionModeChanged(completionMode());
0143 
0144     connect(KonqHistoryManager::kself(), &HistoryProvider::cleared, this, &KonqCombo::slotCleared);
0145     connect(this, &KHistoryComboBox::cleared, this, &KonqCombo::slotCleared);
0146     // The overload resolution is still needed until QComboBox::highlight(QString)
0147     // is either removed or hidden.
0148     connect(this, QOverload<int>::of(&QComboBox::highlighted), this, &KonqCombo::slotSetIcon);
0149 
0150 #if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
0151     connect(this, &QComboBox::textActivated, this, &KonqCombo::slotActivated);
0152 #else
0153     connect(this, SIGNAL(activated(QString)), this, SLOT(slotActivated(QString)));
0154 #endif
0155     connect(this, &KComboBox::completionModeChanged, this, &KonqCombo::slotCompletionModeChanged);
0156 }
0157 
0158 KonqCombo::~KonqCombo()
0159 {
0160 }
0161 
0162 void KonqCombo::init(KCompletion *completion)
0163 {
0164     setCompletionObject(completion, false);   //KonqMainWindow handles signals
0165     setAutoDeleteCompletionObject(false);
0166     setCompletionMode(completion->completionMode());
0167 
0168     // We use Ctrl+T for new tab, so we need something else for substring completion
0169     // TODO: how to make that shortcut configurable? If we add a QAction we need to
0170     // call the KLineEdit code, which we can't do. Well, we could send a keyevent...
0171     setKeyBinding(KCompletionBase::SubstringCompletion, QList<QKeySequence>() << QKeySequence(Qt::Key_F7));
0172 
0173     loadItems();
0174 }
0175 
0176 void KonqCombo::slotTextEdited(const QString &text)
0177 {
0178     QString txt = text;
0179     txt.remove(QChar('\n'));
0180     txt.remove(QChar(QChar::LineSeparator));
0181     txt.remove(QChar(QChar::ParagraphSeparator));
0182 
0183     if (txt != text) {
0184         lineEdit()->setText(txt);
0185     }
0186 }
0187 
0188 void KonqCombo::setURL(const QString &url)
0189 {
0190     //qCDebug(KONQUEROR_LOG) << url << "returnPressed=" << m_returnPressed;
0191     setTemporary(url);
0192 
0193     if (m_returnPressed) {   // Really insert...
0194         m_returnPressed = false;
0195         QDBusMessage message = QDBusMessage::createSignal(KONQ_MAIN_PATH, QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("addToCombo"));
0196         message << url;
0197         QDBusConnection::sessionBus().send(message);
0198     }
0199     // important security consideration: always display the beginning
0200     // of the url rather than its end to prevent spoofing attempts.
0201     lineEdit()->setCursorPosition(0);
0202 }
0203 
0204 void KonqCombo::setTemporary(const QString &text)
0205 {
0206     setTemporary(text, KonqPixmapProvider::self()->pixmapFor(text, KIconLoader::SizeSmall));
0207 }
0208 
0209 void KonqCombo::setTemporary(const QString &url, const QPixmap &pix)
0210 {
0211     //qCDebug(KONQUEROR_LOG) << url << "temporary=" << temporary;
0212 
0213     // Insert a temporary item when we don't have one yet
0214     if (count() == 0) {
0215         insertItem(pix, url, temporary, titleOfURL(url));
0216     } else {
0217         if (url != temporaryItem()) {
0218             applyPermanent();
0219         }
0220 
0221         updateItem(pix, url, temporary, titleOfURL(url));
0222     }
0223 
0224     setCurrentIndex(temporary);
0225 }
0226 
0227 void KonqCombo::removeDuplicates(int index)
0228 {
0229     //qCDebug(KONQUEROR_LOG) << "starting index= " << index;
0230 
0231     QString url(temporaryItem());
0232     if (url.endsWith('/')) {
0233         url.truncate(url.length() - 1);
0234     }
0235 
0236     // Remove all dupes, if available...
0237     for (int i = index; i < count(); i++) {
0238         QString item(itemText(i));
0239         if (item.endsWith('/')) {
0240             item.truncate(item.length() - 1);
0241         }
0242 
0243         if (item == url) {
0244             removeItem(i);
0245         }
0246     }
0247 }
0248 
0249 // called via DBUS in all instances
0250 void KonqCombo::insertPermanent(const QString &url)
0251 {
0252     //qCDebug(KONQUEROR_LOG) << "url=" << url;
0253     saveState();
0254     setTemporary(url);
0255     m_permanent = true;
0256     restoreState();
0257 }
0258 
0259 // called right before a new (different!) temporary item will be set. So we
0260 // insert an item that was marked permanent properly at position 1.
0261 void KonqCombo::applyPermanent()
0262 {
0263     if (m_permanent && !temporaryItem().isEmpty()) {
0264 
0265         // Remove as many items as needed to honor maxCount()
0266         int index = count();
0267         while (count() >= maxCount()) {
0268             removeItem(--index);
0269         }
0270 
0271         QString item = temporaryItem();
0272         insertItem(KonqPixmapProvider::self()->pixmapFor(item, KIconLoader::SizeSmall), item, 1, titleOfURL(item));
0273         //qCDebug(KONQUEROR_LOG) << url;
0274 
0275         // Remove all duplicates starting from index = 2
0276         removeDuplicates(2);
0277         m_permanent = false;
0278     }
0279 }
0280 
0281 void KonqCombo::insertItem(const QString &text, int index, const QString &title)
0282 {
0283     KHistoryComboBox::insertItem(index, text, title);
0284 }
0285 
0286 void KonqCombo::insertItem(const QPixmap &pixmap, const QString &text, int index, const QString &title)
0287 {
0288     KHistoryComboBox::insertItem(index, pixmap, text, title);
0289 }
0290 
0291 void KonqCombo::updateItem(const QPixmap &pix, const QString &t, int index, const QString &title)
0292 {
0293     // No need to flicker
0294     if (itemText(index) == t &&
0295             (!itemIcon(index).isNull() && itemIcon(index).pixmap(iconSize()).cacheKey() == pix.cacheKey())) {
0296         return;
0297     }
0298 
0299     // qCDebug(KONQUEROR_LOG) << "item=" << t << "index=" << index;
0300 
0301     setItemText(index, t);
0302     setItemIcon(index, pix);
0303     setItemData(index, title);
0304 
0305     update();
0306 }
0307 
0308 void KonqCombo::saveState()
0309 {
0310     m_cursorPos = cursorPosition();
0311     m_currentText = currentText();
0312     m_selectedText = lineEdit()->selectedText();
0313     m_currentIndex = currentIndex();
0314 }
0315 
0316 void KonqCombo::restoreState()
0317 {
0318     setTemporary(m_currentText);
0319     if (m_selectedText.isEmpty()) {
0320         lineEdit()->setCursorPosition(m_cursorPos);
0321     } else {
0322         const int index = m_currentText.indexOf(m_selectedText);
0323         if (index == -1) {
0324             lineEdit()->setCursorPosition(m_cursorPos);
0325         } else {
0326             lineEdit()->setSelection(index, m_selectedText.length());
0327         }
0328     }
0329 }
0330 
0331 void KonqCombo::updatePixmaps()
0332 {
0333     saveState();
0334 
0335     setUpdatesEnabled(false);
0336     KonqPixmapProvider *prov = KonqPixmapProvider::self();
0337     for (int i = 1; i < count(); i++) {
0338         setItemIcon(i, prov->pixmapFor(itemText(i), KIconLoader::SizeSmall));
0339     }
0340     setUpdatesEnabled(true);
0341     repaint();
0342 
0343     restoreState();
0344 }
0345 
0346 void KonqCombo::loadItems()
0347 {
0348     clear();
0349     int i = 0;
0350 
0351     KConfigGroup historyConfigGroup(s_config, "History");   // delete the old 2.0.x completion
0352     historyConfigGroup.writeEntry("CompletionItems", "unused");
0353 
0354     KConfigGroup locationBarGroup(s_config, "Location Bar");
0355     const QStringList items = locationBarGroup.readPathEntry("ComboContents", QStringList());
0356     for (const QString &item : items) {
0357         if (!item.isEmpty()) {   // only insert non-empty items
0358             insertItem(KonqPixmapProvider::self()->pixmapFor(item, KIconLoader::SizeSmall),
0359                        item, i++, titleOfURL(item));
0360         }
0361     }
0362 
0363     if (count() > 0) {
0364         m_permanent = true;    // we want the first loaded item to stay
0365     }
0366 }
0367 
0368 void KonqCombo::slotSetIcon(int index)
0369 {
0370     if (itemIcon(index).isNull())
0371         // on-demand icon loading
0372         setItemIcon(index, KonqPixmapProvider::self()->pixmapFor(itemText(index),
0373                     KIconLoader::SizeSmall));
0374     update();
0375 }
0376 
0377 void KonqCombo::getStyleOption(QStyleOptionComboBox *comboOpt)
0378 {
0379     //We only use this for querying metrics,so it's rough..
0380     comboOpt->initFrom(this);
0381     comboOpt->editable = isEditable();
0382     comboOpt->frame    = hasFrame();
0383     comboOpt->iconSize = iconSize();
0384     comboOpt->currentIcon = itemIcon(currentIndex());
0385     comboOpt->currentText = currentText();
0386 }
0387 
0388 void KonqCombo::popup()
0389 {
0390     for (int i = 0; i < count(); ++i) {
0391         if (itemIcon(i).isNull()) {
0392             // on-demand icon loading
0393             setItemIcon(i, KonqPixmapProvider::self()->pixmapFor(itemText(i),
0394                         KIconLoader::SizeSmall));
0395         }
0396     }
0397     KHistoryComboBox::showPopup();
0398 }
0399 
0400 void KonqCombo::saveItems()
0401 {
0402     QStringList items;
0403     int i = m_permanent ? 0 : 1;
0404 
0405     for (; i < count(); i++) {
0406         items.append(itemText(i));
0407     }
0408 
0409     KConfigGroup locationBarGroup(s_config, "Location Bar");
0410     locationBarGroup.writePathEntry("ComboContents", items);
0411     KonqPixmapProvider::self()->save(locationBarGroup, QStringLiteral("ComboIconCache"), items);
0412 
0413     s_config->sync();
0414 }
0415 
0416 void KonqCombo::clearTemporary(bool makeCurrent)
0417 {
0418     applyPermanent();
0419     setItemText(temporary, QString());   // ### default pixmap?
0420     if (makeCurrent) {
0421         setCurrentIndex(temporary);
0422     }
0423 }
0424 
0425 bool KonqCombo::eventFilter(QObject *o, QEvent *ev)
0426 {
0427     // Handle Ctrl+Del/Backspace etc better than the Qt widget, which always
0428     // jumps to the next whitespace.
0429     QLineEdit *edit = lineEdit();
0430     if (o == edit) {
0431         const int type = ev->type();
0432         if (type == QEvent::KeyPress) {
0433             QKeyEvent *e = static_cast<QKeyEvent *>(ev);
0434 
0435             QKeySequence key(e->key() | e->modifiers());
0436 
0437             if (KStandardShortcut::deleteWordBack().contains(key) ||
0438                     KStandardShortcut::deleteWordForward().contains(key) ||
0439                     ((e->modifiers() & Qt::ControlModifier) &&
0440                      (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right))) {
0441                 selectWord(e);
0442                 e->accept();
0443                 return true;
0444             }
0445         } else if (type == QEvent::MouseButtonDblClick) {
0446             edit->selectAll();
0447             return true;
0448         }
0449     }
0450     return KComboBox::eventFilter(o, ev);
0451 }
0452 
0453 void KonqCombo::keyPressEvent(QKeyEvent *e)
0454 {
0455     KHistoryComboBox::keyPressEvent(e);
0456     // we have to set it as temporary, otherwise we wouldn't get our nice
0457     // pixmap. Yes, QComboBox still sucks.
0458     QList<QKeySequence> key{QKeySequence(e->key() | e->modifiers())};
0459     if (key == KStandardShortcut::rotateUp() ||
0460             key == KStandardShortcut::rotateDown()) {
0461         setTemporary(currentText());
0462     }
0463 }
0464 
0465 /*
0466    Handle Ctrl+Cursor etc better than the Qt widget, which always
0467    jumps to the next whitespace. This code additionally jumps to
0468    the next [/#?:], which makes more sense for URLs. The list of
0469    chars that will stop the cursor are '/', '.', '?', '#', ':'.
0470 */
0471 void KonqCombo::selectWord(QKeyEvent *e)
0472 {
0473     QLineEdit *edit = lineEdit();
0474     QString text = edit->text();
0475     int pos = edit->cursorPosition();
0476     int pos_old = pos;
0477     int count = 0;
0478 
0479     // TODO: make these a parameter when in kdelibs/kdeui...
0480     QList<QChar> chars;
0481     chars << QChar('/') << QChar('.') << QChar('?') << QChar('#') << QChar(':');
0482     bool allow_space_break = true;
0483 
0484     if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) {
0485         do {
0486             pos--;
0487             count++;
0488             if (allow_space_break && text[pos].isSpace() && count > 1) {
0489                 break;
0490             }
0491         } while (pos >= 0 && (chars.indexOf(text[pos]) == -1 || count <= 1));
0492 
0493         if (e->modifiers() & Qt::ShiftModifier) {
0494             edit->cursorForward(true, 1 - count);
0495         } else if (e->key() == Qt::Key_Backspace) {
0496             edit->cursorForward(false, 1 - count);
0497             QString text = edit->text();
0498             int pos_to_right = edit->text().length() - pos_old;
0499             QString cut = text.left(edit->cursorPosition()) + text.right(pos_to_right);
0500             edit->setText(cut);
0501             edit->setCursorPosition(pos_old - count + 1);
0502         } else {
0503             edit->cursorForward(false, 1 - count);
0504         }
0505     } else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Delete) {
0506         do {
0507             pos++;
0508             count++;
0509             if (allow_space_break && text[pos].isSpace()) {
0510                 break;
0511             }
0512         } while (pos < text.length() && chars.indexOf(text[pos]) == -1);
0513 
0514         if (e->modifiers() & Qt::ShiftModifier) {
0515             edit->cursorForward(true, count + 1);
0516         } else if (e->key() == Qt::Key_Delete) {
0517             edit->cursorForward(false, -count - 1);
0518             QString text = edit->text();
0519             int pos_to_right = text.length() - pos - 1;
0520             QString cut = text.left(pos_old) +
0521                           (pos_to_right > 0 ? text.right(pos_to_right) : QString());
0522             edit->setText(cut);
0523             edit->setCursorPosition(pos_old);
0524         } else {
0525             edit->cursorForward(false, count + 1);
0526         }
0527     }
0528 }
0529 
0530 void KonqCombo::slotCleared()
0531 {
0532     QDBusMessage message = QDBusMessage::createSignal(KONQ_MAIN_PATH, QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("comboCleared"));
0533     QDBusConnection::sessionBus().send(message);
0534 }
0535 
0536 void KonqCombo::removeURL(const QString &url)
0537 {
0538     setUpdatesEnabled(false);
0539     lineEdit()->setUpdatesEnabled(false);
0540 
0541     removeFromHistory(url);
0542     applyPermanent();
0543     setTemporary(currentText());
0544 
0545     setUpdatesEnabled(true);
0546     lineEdit()->setUpdatesEnabled(true);
0547     update();
0548 }
0549 
0550 void KonqCombo::mousePressEvent(QMouseEvent *e)
0551 {
0552     m_dragStart = QPoint(); // null QPoint
0553 
0554     if (e->button() == Qt::LeftButton && !itemIcon(currentIndex()).isNull()) {
0555         // check if the pixmap was clicked
0556         int x = e->pos().x();
0557         QStyleOptionComboBox comboOpt;
0558         getStyleOption(&comboOpt);
0559         int x0 = QStyle::visualRect(layoutDirection(), rect(),
0560                                     style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField,
0561                                             this)).x();
0562 
0563         if (x > x0 + 2 && x < lineEdit()->x()) {
0564             m_dragStart = e->pos();
0565             return; // don't call KComboBox::mousePressEvent!
0566         }
0567     }
0568 
0569     QStyleOptionComboBox optCombo;
0570     optCombo.initFrom(this);
0571     if (e->button() == Qt::LeftButton && m_pageSecurity != KonqMainWindow::NotCrypted &&
0572             style()->subElementRect(QStyle::SE_ComboBoxFocusRect, &optCombo, this).contains(e->pos())) {
0573         emit showPageSecurity();
0574     }
0575 
0576     KComboBox::mousePressEvent(e);
0577 }
0578 
0579 void KonqCombo::mouseMoveEvent(QMouseEvent *e)
0580 {
0581     KComboBox::mouseMoveEvent(e);
0582     if (m_dragStart.isNull() || currentText().isEmpty()) {
0583         return;
0584     }
0585 
0586     if (e->buttons() & Qt::LeftButton &&
0587             (e->pos() - m_dragStart).manhattanLength() >
0588             QApplication::startDragDistance()) {
0589         QUrl url(QUrl::fromUserInput(currentText()));
0590         if (url.isValid()) {
0591             QDrag *drag = new QDrag(this);
0592             QMimeData *mime = new QMimeData;
0593             mime->setUrls(QList<QUrl>() << url);
0594             drag->setMimeData(mime);
0595             QPixmap pix = KonqPixmapProvider::self()->pixmapFor(currentText(),
0596                           KIconLoader::SizeMedium);
0597             if (!pix.isNull()) {
0598                 drag->setPixmap(pix);
0599             }
0600             drag->exec(Qt::CopyAction);
0601         }
0602     }
0603 }
0604 
0605 void KonqCombo::slotActivated(const QString &text)
0606 {
0607     applyPermanent();
0608     m_returnPressed = true;
0609     emit activated(text, qApp->keyboardModifiers());
0610 }
0611 
0612 void KonqCombo::setConfig(KConfig *kc)
0613 {
0614     s_config = kc;
0615 }
0616 
0617 void KonqCombo::paintEvent(QPaintEvent *pe)
0618 {
0619     QComboBox::paintEvent(pe);
0620 
0621     QLineEdit *edit = lineEdit();
0622 
0623     QStyleOptionComboBox comboOpt;
0624     getStyleOption(&comboOpt);
0625     QRect re = style()->subControlRect(QStyle::CC_ComboBox, &comboOpt,
0626                                        QStyle::SC_ComboBoxEditField, this);
0627     re = QStyle::visualRect(layoutDirection(), rect(), re);
0628 
0629     if (m_pageSecurity != KonqMainWindow::NotCrypted) {
0630         QPainter p(this);
0631         p.setClipRect(re);
0632 
0633         const auto icon = QIcon::fromTheme(QLatin1String(m_pageSecurity == KonqMainWindow::Encrypted ? "security-high" : "security-medium"));
0634         QPixmap pix = icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize));
0635 
0636         QRect r = edit->geometry();
0637         r.setRight(re.right() - pix.width() - 2);
0638         if (r != edit->geometry()) {
0639             edit->setGeometry(r);
0640         }
0641 
0642         p.drawPixmap(re.right() - pix.width() - 1, re.y() + (re.height() - pix.height()) / 2, pix);
0643         p.setClipping(false);
0644     } else {
0645         QRect r = edit->geometry();
0646         r.setRight(re.right());
0647         if (r != edit->geometry()) {
0648             edit->setGeometry(r);
0649         }
0650     }
0651 }
0652 
0653 void KonqCombo::setPageSecurity(int pageSecurity)
0654 {
0655     int ops = m_pageSecurity;
0656     m_pageSecurity = pageSecurity;
0657     if (ops != pageSecurity) {
0658         update();
0659     }
0660 }
0661 
0662 void KonqCombo::slotReturnPressed()
0663 {
0664     slotActivated(currentText());
0665 }
0666 
0667 void KonqCombo::slotCompletionModeChanged(KCompletion::CompletionMode mode)
0668 {
0669     if (mode == KCompletion::CompletionNone) {
0670         connect(this, QOverload<const QString &>::of(&KComboBox::returnPressed), this, &KonqCombo::slotReturnPressed);
0671     } else {
0672         disconnect(this, QOverload<const QString &>::of(&KComboBox::returnPressed), this, &KonqCombo::slotReturnPressed);
0673     }
0674 }
0675 
0676 ///////////////////////////////////////////////////////////////////////////////
0677 
0678 KonqListWidgetItem::KonqListWidgetItem(QListWidget *parent)
0679     : QListWidgetItem(parent, KonqItemType), lookupPending(true)
0680 {
0681 }
0682 
0683 KonqListWidgetItem::KonqListWidgetItem(const QString &text, QListWidget *parent)
0684     : QListWidgetItem(text, parent, KonqItemType), lookupPending(true)
0685 {
0686 }
0687 
0688 QVariant KonqListWidgetItem::data(int role) const
0689 {
0690     if (lookupPending && role != Qt::DisplayRole) {
0691         QString title = titleOfURL(text());
0692         QPixmap pixmap;
0693 
0694         KonqPixmapProvider *provider = KonqPixmapProvider::self();
0695 
0696         if (!title.isEmpty()) {
0697             pixmap = provider->pixmapFor(text(), KIconLoader::SizeSmall);
0698         } else if (!text().contains(QLatin1String("://"))) {
0699             title = titleOfURL(QLatin1String("http://") + text());
0700             if (!title.isEmpty()) {
0701                 pixmap = provider->pixmapFor(QLatin1String("http://") + text(), KIconLoader::SizeSmall);
0702             } else {
0703                 pixmap = provider->pixmapFor(text(), KIconLoader::SizeSmall);
0704             }
0705         }
0706 
0707         const_cast<KonqListWidgetItem *>(this)->setIcon(pixmap);
0708         const_cast<KonqListWidgetItem *>(this)->setData(Qt::UserRole, title);
0709 
0710         lookupPending = false;
0711     }
0712 
0713     return QListWidgetItem::data(role);
0714 }
0715 
0716 bool KonqListWidgetItem::reuse(const QString &newText)
0717 {
0718     if (text() == newText) {
0719         return false;
0720     }
0721 
0722     lookupPending = true;
0723     setText(newText);
0724     return true;
0725 }
0726 
0727 ///////////////////////////////////////////////////////////////////////////////
0728 
0729 QSize KonqComboItemDelegate::sizeHint(const QStyleOptionViewItem &option,
0730                                       const QModelIndex &index) const
0731 {
0732     Q_UNUSED(index);
0733     int vMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
0734 
0735     QSize size(1, qMax(option.fontMetrics.lineSpacing(), option.decorationSize.height()));
0736     size.rheight() += vMargin * 2;
0737 
0738     //TODO KF6: QApplication::globalStrut doesn't exist in Qt6, so we're treating as
0739     //if it were QSize(0,0). Check whether the assumption is correct
0740     return size;
0741 }
0742 
0743 void KonqComboItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
0744                                   const QModelIndex &index) const
0745 {
0746     QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
0747     QString url = index.data(Qt::DisplayRole).toString();
0748     QString title = index.data(Qt::UserRole).toString();
0749 
0750     QIcon::Mode mode = option.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled;
0751     const QSize size = icon.actualSize(option.decorationSize, mode);
0752     QPixmap pixmap = icon.pixmap(size, mode);
0753 
0754     QStyleOptionViewItem opt(option);
0755 
0756     painter->save();
0757 
0758     // Draw the item background
0759 
0760     // ### When Qt 4.4 is released we need to change this code to draw the background
0761     //     by calling QStyle::drawPrimitive() with PE_PanelItemViewRow.
0762     if (opt.state & QStyle::State_Selected) {
0763         painter->fillRect(option.rect, option.palette.brush(QPalette::Highlight));
0764         painter->setPen(QPen(option.palette.brush(QPalette::HighlightedText), 0));
0765     }
0766 
0767     int hMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
0768     int vMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin);
0769 
0770     const QRect bounding = option.rect.adjusted(hMargin, vMargin, -hMargin, -vMargin);
0771     const QSize textSize(bounding.width() - pixmap.width() - 2, bounding.height());
0772     const QRect pixmapRect = QStyle::alignedRect(option.direction, Qt::AlignLeft | Qt::AlignVCenter,
0773                              pixmap.size(), bounding);
0774     const QRect textRect = QStyle::alignedRect(option.direction, Qt::AlignRight, textSize, bounding);
0775 
0776     if (!pixmap.isNull()) {
0777         painter->drawPixmap(pixmapRect.topLeft(), pixmap);
0778     }
0779 
0780     QSize titleSize((bounding.width() / 3) - 1, textRect.height());
0781     if (title.isEmpty()) {
0782         // Don't truncate the urls when there is no title to show (e.g. local files)
0783         // Ideally we would do this globally for all items - reserve space for a title for all, or for none
0784         titleSize = QSize();
0785     }
0786     const QSize urlSize(textRect.width() - titleSize.width() - 2, textRect.height());
0787     const QRect titleRect = QStyle::alignedRect(option.direction, Qt::AlignRight, titleSize, textRect);
0788     const QRect urlRect   = QStyle::alignedRect(option.direction, Qt::AlignLeft, urlSize, textRect);
0789 
0790     if (!url.isEmpty()) {
0791         QString squeezedText = option.fontMetrics.elidedText(url, Qt::ElideMiddle, urlRect.width());
0792         painter->drawText(urlRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText);
0793     }
0794 
0795     if (!title.isEmpty()) {
0796         QString squeezedText = option.fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width());
0797         QFont font = painter->font();
0798         font.setItalic(true);
0799         painter->setFont(font);
0800         QColor color = painter->pen().color();
0801         color.setAlphaF(.75);
0802         painter->setPen(color);
0803         painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText);
0804     }
0805 
0806     painter->restore();
0807 }
0808 
0809 ///////////////////////////////////////////////////////////////////////////////
0810 
0811 KonqComboLineEdit::KonqComboLineEdit(QWidget *parent)
0812     : KLineEdit(parent)
0813 {
0814     setClearButtonEnabled(true);
0815 }
0816 
0817 void KonqComboLineEdit::mouseDoubleClickEvent(QMouseEvent *e)
0818 {
0819     if (e->button() == Qt::LeftButton) {
0820         selectAll();
0821         return;
0822     }
0823     KLineEdit::mouseDoubleClickEvent(e);
0824 }
0825 
0826 KCompletionBox *KonqComboLineEdit::completionBox(bool create)
0827 {
0828     KCompletionBox *box = KLineEdit::completionBox(false);
0829     if (create && !box) {
0830         KonqComboCompletionBox *konqBox = new KonqComboCompletionBox(this);
0831         setCompletionBox(konqBox);
0832         konqBox->setObjectName(QStringLiteral("completion box"));
0833         konqBox->setFont(font());
0834         return konqBox;
0835     }
0836 
0837     return box;
0838 }
0839 
0840 ///////////////////////////////////////////////////////////////////////////////
0841 
0842 KonqComboCompletionBox::KonqComboCompletionBox(QWidget *parent)
0843     : KCompletionBox(parent)
0844 {
0845     setLayoutDirection(Qt::LeftToRight);
0846 }
0847 
0848 void KonqComboCompletionBox::setItems(const QStringList &items)
0849 {
0850     bool block = signalsBlocked();
0851     blockSignals(true);
0852 
0853     int rowIndex = 0;
0854 
0855     if (count() == 0) {
0856         for (const QString &text : items) {
0857             insertItem(rowIndex++, new KonqListWidgetItem(text));
0858         }
0859     } else {
0860         //Keep track of whether we need to change anything,
0861         //so we can avoid a repaint for identical updates,
0862         //to reduce flicker
0863         bool dirty = false;
0864 
0865         for (const QString &text : items) {
0866             if (rowIndex < count()) {
0867                 const bool changed = (static_cast<KonqListWidgetItem *>(item(rowIndex)))->reuse(text);
0868                 dirty = dirty || changed;
0869             } else {
0870                 dirty = true;
0871                 //Inserting an item is a way of making this dirty
0872                 addItem(new KonqListWidgetItem(text));
0873             }
0874             rowIndex++;
0875         }
0876 
0877         //If there is an unused item, mark as dirty -> less items now
0878         if (rowIndex < count()) {
0879             dirty = true;
0880         }
0881 
0882         while (rowIndex < count()) {
0883             delete item(rowIndex);
0884         }
0885 
0886         //TODO KDE 4 - Port this
0887         //if ( dirty )
0888         //    triggerUpdate( false );
0889     }
0890 
0891     if (isVisible() && size().height() != sizeHint().height()) {
0892         resizeAndReposition();
0893     }
0894 
0895     blockSignals(block);
0896 }