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 }