diff --git a/plugins/akregator/konqfeedicon.cpp b/plugins/akregator/konqfeedicon.cpp index eccb426ff..dc0c0d62c 100644 --- a/plugins/akregator/konqfeedicon.cpp +++ b/plugins/akregator/konqfeedicon.cpp @@ -1,218 +1,220 @@ /* This file is part of Akregator. Copyright (C) 2004 Teemu Rytilahti This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "konqfeedicon.h" #include "feeddetector.h" #include "pluginutil.h" #include "akregatorplugindebug.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include +#include using namespace Akregator; K_PLUGIN_FACTORY(KonqFeedIconFactory, registerPlugin();) static QUrl baseUrl(KParts::ReadOnlyPart *part) { QUrl url; KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(part); if (ext) { url = ext->baseUrl(); } return url; } KonqFeedIcon::KonqFeedIcon(QObject *parent, const QVariantList &args) : KParts::Plugin(parent), m_part(nullptr), m_feedIcon(nullptr), m_statusBarEx(nullptr), m_menu(nullptr) { Q_UNUSED(args); // make our icon foundable by the KIconLoader KIconLoader::global()->addAppDir(QStringLiteral("akregator")); KParts::ReadOnlyPart *part = qobject_cast(parent); if (part) { KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(part); KParts::SelectorInterface *selectorInterface = qobject_cast(ext); if (selectorInterface) { m_part = part; connect(m_part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &KonqFeedIcon::addFeedIcon); connect(m_part, QOverload::of(&KParts::ReadOnlyPart::completed), this, &KonqFeedIcon::addFeedIcon); connect(m_part, &KParts::ReadOnlyPart::started, this, &KonqFeedIcon::removeFeedIcon); } } } KonqFeedIcon::~KonqFeedIcon() { m_statusBarEx = KParts::StatusBarExtension::childObject(m_part); if (m_statusBarEx) { m_statusBarEx->removeStatusBarItem(m_feedIcon); // if the statusbar extension is deleted, the icon is deleted as well (being the child of the status bar) delete m_feedIcon; } // the icon is deleted in every case m_feedIcon = nullptr; delete m_menu; m_menu = nullptr; } bool KonqFeedIcon::feedFound() { // Ensure that it is safe to use the URL, before doing anything else with it const QUrl partUrl(m_part->url()); if (!partUrl.isValid()) { return false; } // Since attempting to determine feed info for about:blank crashes khtml, // lets prevent such look up for local urls (about, file, man, etc...) if (KProtocolInfo::protocolClass(partUrl.scheme()).compare(QLatin1String(":local"), Qt::CaseInsensitive) == 0) { return false; } KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(m_part); KParts::SelectorInterface *selectorInterface = qobject_cast(ext); QString doc; if (selectorInterface) { QList linkNodes = selectorInterface->querySelectorAll(QStringLiteral("head > link[rel=\"alternate\"]"), KParts::SelectorInterface::EntireContent); for (int i = 0; i < linkNodes.count(); i++) { const KParts::SelectorInterface::Element element = linkNodes.at(i); // TODO parse the attributes directly here, rather than re-assembling // and then re-parsing in extractFromLinkTags! doc += QLatin1String(""); } qCDebug(AKREGATORPLUGIN_LOG) << doc; } m_feedList = FeedDetector::extractFromLinkTags(doc); return m_feedList.count() != 0; } void KonqFeedIcon::contextMenu() { delete m_menu; m_menu = new QMenu(m_part->widget()); if (m_feedList.count() == 1) { m_menu->setTitle(m_feedList.first().title()); - m_menu->addAction(SmallIcon("bookmark-new"), i18n("Add Feed to Akregator"), this, SLOT(addAllFeeds())); + m_menu->addAction(QIcon::fromTheme("bookmark-new"), i18n("Add Feed to Akregator"), this, SLOT(addAllFeeds())); } else { m_menu->setTitle(i18n("Add Feeds to Akregator")); int id = 0; for (FeedDetectorEntryList::Iterator it = m_feedList.begin(); it != m_feedList.end(); ++it) { QAction *action = m_menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), (*it).title(), this, SLOT(addFeed())); action->setData(QVariant::fromValue(id)); id++; } //disconnect(m_menu, SIGNAL(activated(int)), this, SLOT(addFeed(int))); m_menu->addSeparator(); m_menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add All Found Feeds to Akregator"), this, SLOT(addAllFeeds())); } m_menu->popup(QCursor::pos()); } void KonqFeedIcon::addFeedIcon() { if (!feedFound() || m_feedIcon) { return; } m_statusBarEx = KParts::StatusBarExtension::childObject(m_part); if (!m_statusBarEx) { return; } m_feedIcon = new KUrlLabel(m_statusBarEx->statusBar()); // from khtmlpart's ualabel - m_feedIcon->setFixedHeight(KIconLoader::global()->currentSize(KIconLoader::Small)); + m_feedIcon->setFixedHeight(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize)); m_feedIcon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); m_feedIcon->setUseCursor(false); m_feedIcon->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("feed"), KIconLoader::User)); m_feedIcon->setToolTip(i18n("Subscribe to site updates (using news feed)")); m_statusBarEx->addStatusBarItem(m_feedIcon, 0, true); connect(m_feedIcon, QOverload<>::of(&KUrlLabel::leftClickedUrl), this, &KonqFeedIcon::contextMenu); } void KonqFeedIcon::removeFeedIcon() { m_feedList.clear(); if (m_feedIcon && m_statusBarEx) { m_statusBarEx->removeStatusBarItem(m_feedIcon); delete m_feedIcon; m_feedIcon = nullptr; } // Close the popup if it's open, otherwise we crash delete m_menu; m_menu = nullptr; } void KonqFeedIcon::addFeed() { bool ok = false; const int id = sender() ? qobject_cast(sender())->data().toInt(&ok) : -1; if (!ok || id == -1) { return; } PluginUtil::addFeeds(QStringList(PluginUtil::fixRelativeURL(m_feedList[id].url(), baseUrl(m_part)))); } // from akregatorplugin.cpp void KonqFeedIcon::addAllFeeds() { QStringList list; foreach (const FeedDetectorEntry &it, m_feedList) { list.append(PluginUtil::fixRelativeURL(it.url(), baseUrl(m_part))); } PluginUtil::addFeeds(list); } #include "konqfeedicon.moc" diff --git a/plugins/searchbar/searchbar.cpp b/plugins/searchbar/searchbar.cpp index e3db7dd0f..85f53de69 100644 --- a/plugins/searchbar/searchbar.cpp +++ b/plugins/searchbar/searchbar.cpp @@ -1,747 +1,746 @@ /* This file is part of the KDE project Copyright (C) 2004 Arend van Beelen jr. Copyright (C) 2009 Fredy Yanardi This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "searchbar.h" #include "OpenSearchManager.h" #include "WebShortcutWidget.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(SearchBarPluginFactory, registerPlugin();) SearchBarPlugin::SearchBarPlugin(QObject *parent, const QVariantList &) : KParts::Plugin(parent), m_popupMenu(nullptr), m_addWSWidget(nullptr), m_searchMode(UseSearchProvider), m_urlEnterLock(false), m_openSearchManager(new OpenSearchManager(this)), m_reloadConfiguration(false) { m_searchCombo = new SearchBarCombo(nullptr); m_searchCombo->lineEdit()->installEventFilter(this); connect(m_searchCombo, SIGNAL(activated(QString)), SLOT(startSearch(QString))); connect(m_searchCombo, SIGNAL(iconClicked()), SLOT(showSelectionMenu())); m_searchCombo->setWhatsThis(i18n("Search Bar

" "Enter a search term. Click on the icon to change search mode or provider.

")); connect(m_searchCombo, SIGNAL(suggestionEnabled(bool)), this, SLOT(enableSuggestion(bool))); m_searchComboAction = new QWidgetAction(actionCollection()); actionCollection()->addAction(QStringLiteral("toolbar_search_bar"), m_searchComboAction); m_searchComboAction->setText(i18n("Search Bar")); m_searchComboAction->setDefaultWidget(m_searchCombo); actionCollection()->setShortcutsConfigurable(m_searchComboAction, false); QAction *a = actionCollection()->addAction(QStringLiteral("focus_search_bar")); a->setText(i18n("Focus Searchbar")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_S)); connect(a, SIGNAL(triggered()), this, SLOT(focusSearchbar())); m_searchProvidersDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kde5/services/searchproviders/"; QDir().mkpath(m_searchProvidersDir); configurationChanged(); m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(requestSuggestion())); // parent is the KonqMainWindow and we want to listen to PartActivateEvent events. parent->installEventFilter(this); connect(m_searchCombo->lineEdit(), SIGNAL(textEdited(QString)), SLOT(searchTextChanged(QString))); connect(m_openSearchManager, SIGNAL(suggestionReceived(QStringList)), SLOT(addSearchSuggestion(QStringList))); connect(m_openSearchManager, SIGNAL(openSearchEngineAdded(QString,QString,QString)), SLOT(openSearchEngineAdded(QString,QString,QString))); QDBusConnection::sessionBus().connect(QString(), QString(), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"), this, SLOT(reloadConfiguration())); } SearchBarPlugin::~SearchBarPlugin() { KConfigGroup config(KSharedConfig::openConfig(), "SearchBar"); config.writeEntry("Mode", (int) m_searchMode); config.writeEntry("CurrentEngine", m_currentEngine); config.writeEntry("SuggestionEnabled", m_suggestionEnabled); delete m_searchCombo; m_searchCombo = nullptr; } bool SearchBarPlugin::eventFilter(QObject *o, QEvent *e) { if (qobject_cast(o) && KParts::PartActivateEvent::test(e)) { KParts::PartActivateEvent *partEvent = static_cast(e); KParts::ReadOnlyPart *part = qobject_cast(partEvent->part()); //kDebug() << "Embedded part changed to " << part; if (part && (!m_part || part != m_part.data())) { m_part = part; // Delete the popup menu so a new one can be created with the // appropriate entries the next time it is shown... // ######## TODO: This loses the opensearch entries for the old part!!! if (m_popupMenu) { delete m_popupMenu; m_popupMenu = nullptr; m_addSearchActions.clear(); // the actions had the menu as parent, so they're deleted now } // Change the search mode if it is set to FindInThisPage since // that feature is currently KHTML specific. It is also completely // redundant and unnecessary. if (m_searchMode == FindInThisPage && enableFindInPage()) { nextSearchEntry(); } connect(part, SIGNAL(completed()), this, SLOT(HTMLDocLoaded())); connect(part, SIGNAL(started(KIO::Job*)), this, SLOT(HTMLLoadingStarted())); } // Delay since when destroying tabs part 0 gets activated for a bit, before the proper part QTimer::singleShot(0, this, SLOT(updateComboVisibility())); } else if (o == m_searchCombo->lineEdit() && e->type() == QEvent::KeyPress) { QKeyEvent *k = (QKeyEvent *)e; if (k->modifiers() & Qt::ControlModifier) { if (k->key() == Qt::Key_Down) { nextSearchEntry(); return true; } if (k->key() == Qt::Key_Up) { previousSearchEntry(); return true; } } } return KParts::Plugin::eventFilter(o, e); } void SearchBarPlugin::nextSearchEntry() { if (m_searchMode == FindInThisPage) { m_searchMode = UseSearchProvider; if (m_searchEngines.isEmpty()) { m_currentEngine = QStringLiteral("google"); } else { m_currentEngine = m_searchEngines.first(); } } else { const int index = m_searchEngines.indexOf(m_currentEngine) + 1; if (index >= m_searchEngines.count()) { m_searchMode = FindInThisPage; } else { m_currentEngine = m_searchEngines.at(index); } } setIcon(); } void SearchBarPlugin::previousSearchEntry() { if (m_searchMode == FindInThisPage) { m_searchMode = UseSearchProvider; if (m_searchEngines.isEmpty()) { m_currentEngine = QStringLiteral("google"); } else { m_currentEngine = m_searchEngines.last(); } } else { const int index = m_searchEngines.indexOf(m_currentEngine) - 1; if (index <= 0) { m_searchMode = FindInThisPage; } else { m_currentEngine = m_searchEngines.at(index); } } setIcon(); } // Called when activating the combobox (Key_Return, or item in popup or in completionbox) void SearchBarPlugin::startSearch(const QString &search) { if (m_urlEnterLock || search.isEmpty() || !m_part) { return; } m_timer->stop(); m_lastSearch = search; if (m_searchMode == FindInThisPage) { KParts::TextExtension *textExt = KParts::TextExtension::childObject(m_part.data()); if (textExt) { textExt->findText(search, nullptr); } } else if (m_searchMode == UseSearchProvider) { m_urlEnterLock = true; const KUriFilterSearchProvider &provider = m_searchProviders.value(m_currentEngine); KUriFilterData data; data.setData(provider.defaultKey() + m_delimiter + search); //kDebug() << "Query:" << (provider.defaultKey() + m_delimiter + search); if (!KUriFilter::self()->filterSearchUri(data, KUriFilter::WebShortcutFilter)) { qWarning() << "Failed to filter using web shortcut:" << provider.defaultKey(); return; } KParts::BrowserExtension *ext = KParts::BrowserExtension::childObject(m_part.data()); if (QApplication::keyboardModifiers() & Qt::ControlModifier) { KParts::OpenUrlArguments arguments; KParts::BrowserArguments browserArguments; browserArguments.setNewTab(true); if (ext) { emit ext->createNewWindow(data.uri(), arguments, browserArguments); } } else { if (ext) { emit ext->openUrlRequest(data.uri()); if (m_part) { m_part.data()->widget()->setFocus(); // #152923 } } } } m_searchCombo->addToHistory(search); m_searchCombo->setItemIcon(0, m_searchIcon); m_urlEnterLock = false; } void SearchBarPlugin::setIcon() { if (m_searchMode == FindInThisPage) { - m_searchIcon = SmallIcon(QStringLiteral("edit-find")); + m_searchIcon = QIcon::fromTheme(QStringLiteral("edit-find")).pixmap(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize)); } else { const QString engine = (m_currentEngine.isEmpty() ? m_searchEngines.first() : m_currentEngine); //kDebug() << "Icon Name:" << m_searchProviders.value(engine).iconName(); const QString iconName = m_searchProviders.value(engine).iconName(); if (iconName.startsWith(QLatin1Char('/'))) { m_searchIcon = QPixmap(iconName); } else { - m_searchIcon = SmallIcon(iconName); + m_searchIcon = QIcon::fromTheme(iconName).pixmap(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize)); } } // Create a bit wider icon with arrow QPixmap arrowmap = QPixmap(m_searchIcon.width() + 5, m_searchIcon.height() + 5); arrowmap.fill(m_searchCombo->lineEdit()->palette().color(m_searchCombo->lineEdit()->backgroundRole())); QPainter p(&arrowmap); p.drawPixmap(0, 2, m_searchIcon); QStyleOption opt; opt.state = QStyle::State_None; opt.rect = QRect(arrowmap.width() - 6, arrowmap.height() - 5, 6, 5); m_searchCombo->style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, &p, m_searchCombo); p.end(); m_searchIcon = arrowmap; m_searchCombo->setIcon(m_searchIcon); // Set the placeholder text to be the search engine name... if (m_searchProviders.contains(m_currentEngine)) { m_searchCombo->lineEdit()->setPlaceholderText(m_searchProviders.value(m_currentEngine).name()); } } void SearchBarPlugin::showSelectionMenu() { // Update the configuration, if needed, before showing the menu items... if (m_reloadConfiguration) { configurationChanged(); } if (!m_popupMenu) { m_popupMenu = new QMenu(m_searchCombo); m_popupMenu->setObjectName(QStringLiteral("search selection menu")); if (enableFindInPage()) { m_popupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Find in This Page"), this, SLOT(useFindInThisPage())); m_popupMenu->addSeparator(); } for (int i = 0, count = m_searchEngines.count(); i != count; ++i) { const KUriFilterSearchProvider &provider = m_searchProviders.value(m_searchEngines.at(i)); QAction *action = m_popupMenu->addAction(QIcon::fromTheme(provider.iconName()), provider.name()); action->setData(QVariant::fromValue(i)); } m_popupMenu->addSeparator(); m_popupMenu->addAction(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")), i18n("Select Search Engines..."), this, SLOT(selectSearchEngines())); connect(m_popupMenu, SIGNAL(triggered(QAction*)), SLOT(menuActionTriggered(QAction*))); } else { Q_FOREACH (QAction *action, m_addSearchActions) { m_popupMenu->removeAction(action); delete action; } m_addSearchActions.clear(); } QList actions = m_popupMenu->actions(); QAction *before = nullptr; if (actions.size() > 1) { before = actions[actions.size() - 2]; } Q_FOREACH (const QString &title, m_openSearchDescs.keys()) { QAction *addSearchAction = new QAction(m_popupMenu); addSearchAction->setText(i18n("Add %1...", title)); m_addSearchActions.append(addSearchAction); addSearchAction->setData(QVariant::fromValue(title)); m_popupMenu->insertAction(before, addSearchAction); } m_popupMenu->popup(m_searchCombo->mapToGlobal(QPoint(0, m_searchCombo->height() + 1))); } void SearchBarPlugin::useFindInThisPage() { m_searchMode = FindInThisPage; setIcon(); } void SearchBarPlugin::menuActionTriggered(QAction *action) { bool ok = false; const int id = action->data().toInt(&ok); if (ok) { m_searchMode = UseSearchProvider; m_currentEngine = m_searchEngines.at(id); setIcon(); m_openSearchManager->setSearchProvider(m_currentEngine); m_searchCombo->lineEdit()->selectAll(); return; } m_searchCombo->lineEdit()->setPlaceholderText(QString()); const QString openSearchTitle = action->data().toString(); if (!openSearchTitle.isEmpty()) { const QString openSearchHref = m_openSearchDescs.value(openSearchTitle); QUrl url; QUrl openSearchUrl = QUrl(openSearchHref); if (openSearchUrl.isRelative()) { const QUrl docUrl = m_part ? m_part.data()->url() : QUrl(); QString host = docUrl.scheme() + QLatin1String("://") + docUrl.host(); if (docUrl.port() != -1) { host += QLatin1String(":") + QString::number(docUrl.port()); } url = docUrl.resolved(QUrl(openSearchHref)); } else { url = QUrl(openSearchHref); } //kDebug() << "Adding open search Engine: " << openSearchTitle << " : " << openSearchHref; m_openSearchManager->addOpenSearchEngine(url, openSearchTitle); } } void SearchBarPlugin::selectSearchEngines() { KRun::runCommand(QStringLiteral("kcmshell5 webshortcuts"), (m_part ? m_part.data()->widget() : nullptr)); } void SearchBarPlugin::configurationChanged() { delete m_popupMenu; m_popupMenu = nullptr; m_addSearchActions.clear(); m_searchEngines.clear(); m_searchProviders.clear(); KUriFilterData data; data.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); data.setAlternateDefaultSearchProvider(QStringLiteral("google")); if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) { m_delimiter = data.searchTermSeparator(); Q_FOREACH (const QString &engine, data.preferredSearchProviders()) { //kDebug() << "Found search provider:" << engine; const KUriFilterSearchProvider &provider = data.queryForSearchProvider(engine); m_searchProviders.insert(provider.desktopEntryName(), provider); m_searchEngines << provider.desktopEntryName(); } } //kDebug() << "Found search engines:" << m_searchEngines; KConfigGroup config = KConfigGroup(KSharedConfig::openConfig(), "SearchBar"); m_searchMode = (SearchModes) config.readEntry("Mode", static_cast(UseSearchProvider)); const QString defaultSearchEngine((m_searchEngines.isEmpty() ? QStringLiteral("google") : m_searchEngines.first())); m_currentEngine = config.readEntry("CurrentEngine", defaultSearchEngine); m_suggestionEnabled = config.readEntry("SuggestionEnabled", true); m_searchCombo->setSuggestionEnabled(m_suggestionEnabled); m_openSearchManager->setSearchProvider(m_currentEngine); m_reloadConfiguration = false; setIcon(); } void SearchBarPlugin::reloadConfiguration() { // NOTE: We do not directly connect the dbus signal to the configurationChanged // slot because our slot my be called before the filter plugins, in which case we // simply end up retrieving the same configuration information from the plugin. m_reloadConfiguration = true; } void SearchBarPlugin::updateComboVisibility() { if (!m_part) { return; } // NOTE: We hide the search combobox if the embedded kpart is ReadWrite // because web browsers by their very nature are ReadOnly kparts... m_searchComboAction->setVisible((!m_part.data()->inherits("ReadWritePart") && !m_searchComboAction->associatedWidgets().isEmpty())); m_openSearchDescs.clear(); } void SearchBarPlugin::focusSearchbar() { m_searchCombo->setFocus(Qt::ShortcutFocusReason); } void SearchBarPlugin::searchTextChanged(const QString &text) { // Don't do anything if the user just activated the search for this text // Popping up suggestions again would just lead to an annoying popup (#231213) if (m_lastSearch == text) { return; } // Don't do anything if the user is still pressing on the mouse button if (qApp->mouseButtons()) { return; } // 400 ms delay before requesting for suggestions, so we don't flood the provider with suggestion request m_timer->start(400); } void SearchBarPlugin::requestSuggestion() { m_searchCombo->clearSuggestions(); if (m_suggestionEnabled && m_searchMode != FindInThisPage && m_openSearchManager->isSuggestionAvailable() && !m_searchCombo->lineEdit()->text().isEmpty()) { m_openSearchManager->requestSuggestion(m_searchCombo->lineEdit()->text()); } } void SearchBarPlugin::enableSuggestion(bool enable) { m_suggestionEnabled = enable; } void SearchBarPlugin::HTMLDocLoaded() { if (!m_part || m_part.data()->url().host().isEmpty()) { return; } // Testcase for this code: http://search.iwsearch.net KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(m_part.data()); KParts::SelectorInterface *selectorInterface = qobject_cast(ext); if (selectorInterface) { //if (headElelement.getAttribute("profile") != "http://a9.com/-/spec/opensearch/1.1/") { // kWarning() << "Warning: there is no profile attribute or wrong profile attribute in , as specified by open search specification 1.1"; //} const QString query(QStringLiteral("head > link[rel=\"search\"][type=\"application/opensearchdescription+xml\"]")); const QList linkNodes = selectorInterface->querySelectorAll(query, KParts::SelectorInterface::EntireContent); //kDebug() << "Found" << linkNodes.length() << "links in" << m_part->url(); Q_FOREACH (const KParts::SelectorInterface::Element &link, linkNodes) { const QString title = link.attribute(QStringLiteral("title")); const QString href = link.attribute(QStringLiteral("href")); //kDebug() << "Found opensearch" << title << href; m_openSearchDescs.insert(title, href); // TODO associate this with m_part; we can get descs from multiple tabs here... } } } void SearchBarPlugin::openSearchEngineAdded(const QString &name, const QString &searchUrl, const QString &fileName) { //kDebug() << "New Open Search Engine Added: " << name << ", searchUrl " << searchUrl; KConfig _service(m_searchProvidersDir + fileName + ".desktop", KConfig::SimpleConfig); KConfigGroup service(&_service, "Desktop Entry"); service.writeEntry("Type", "Service"); service.writeEntry("ServiceTypes", "SearchProvider"); service.writeEntry("Name", name); service.writeEntry("Query", searchUrl); service.writeEntry("Keys", fileName); // TODO service.writeEntry("Charset", "" /* provider->charset() */); // we might be overwriting a hidden entry service.writeEntry("Hidden", false); // Show the add web shortcut widget if (!m_addWSWidget) { m_addWSWidget = new WebShortcutWidget(m_searchCombo); m_addWSWidget->setWindowFlags(Qt::Popup); connect(m_addWSWidget, SIGNAL(webShortcutSet(QString,QString,QString)), this, SLOT(webShortcutSet(QString,QString,QString))); } QPoint pos = m_searchCombo->mapToGlobal(QPoint(m_searchCombo->width() - m_addWSWidget->width(), m_searchCombo->height() + 1)); m_addWSWidget->setGeometry(QRect(pos, m_addWSWidget->size())); m_addWSWidget->show(name, fileName); } void SearchBarPlugin::webShortcutSet(const QString &name, const QString &webShortcut, const QString &fileName) { Q_UNUSED(name); KConfig _service(m_searchProvidersDir + fileName + ".desktop", KConfig::SimpleConfig); KConfigGroup service(&_service, "Desktop Entry"); service.writeEntry("Keys", webShortcut); _service.sync(); // Update filters in running applications including ourselves... QDBusConnection::sessionBus().send(QDBusMessage::createSignal(QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure"))); // If the providers changed, tell sycoca to rebuild its database... KBuildSycocaProgressDialog::rebuildKSycoca(m_searchCombo); } void SearchBarPlugin::HTMLLoadingStarted() { // reset the open search availability, so that if there is previously detected engine, // it will not be shown m_openSearchDescs.clear(); } void SearchBarPlugin::addSearchSuggestion(const QStringList &suggestions) { m_searchCombo->setSuggestionItems(suggestions); } SearchBarCombo::SearchBarCombo(QWidget *parent) : KHistoryComboBox(true, parent) { setDuplicatesEnabled(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setMaximumWidth(300); connect(this, SIGNAL(cleared()), SLOT(historyCleared())); Q_ASSERT(useCompletion()); KConfigGroup config(KSharedConfig::openConfig(), "SearchBar"); setCompletionMode(static_cast(config.readEntry("CompletionMode", static_cast(KCompletion::CompletionPopup)))); const QStringList list = config.readEntry("History list", QStringList()); setHistoryItems(list, true); Q_ASSERT(currentText().isEmpty()); // KHistoryComboBox calls clearEditText m_enableAction = new QAction(i18n("Enable Suggestion"), this); m_enableAction->setCheckable(true); connect(m_enableAction, SIGNAL(toggled(bool)), this, SIGNAL(suggestionEnabled(bool))); connect(this, SIGNAL(aboutToShowContextMenu(QMenu*)), SLOT(addEnableMenuItem(QMenu*))); // use our own item delegate to display our fancy stuff :D KCompletionBox *box = completionBox(); box->setItemDelegate(new SearchBarItemDelegate(this)); connect(lineEdit(), SIGNAL(textEdited(QString)), box, SLOT(setCancelledText(QString))); } SearchBarCombo::~SearchBarCombo() { KConfigGroup config(KSharedConfig::openConfig(), "SearchBar"); config.writeEntry("History list", historyItems()); const int mode = completionMode(); config.writeEntry("CompletionMode", mode); delete m_enableAction; } const QPixmap &SearchBarCombo::icon() const { return m_icon; } void SearchBarCombo::setIcon(const QPixmap &icon) { m_icon = icon; const QString editText = currentText(); if (count() == 0) { insertItem(0, m_icon, nullptr); } else { for (int i = 0; i < count(); i++) { setItemIcon(i, m_icon); } } setEditText(editText); } void SearchBarCombo::setSuggestionEnabled(bool enable) { m_enableAction->setChecked(enable); } int SearchBarCombo::findHistoryItem(const QString &searchText) { for (int i = 0; i < count(); i++) { if (itemText(i) == searchText) { return i; } } return -1; } void SearchBarCombo::mousePressEvent(QMouseEvent *e) { QStyleOptionComplex opt; int x0 = QStyle::visualRect(layoutDirection(), style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this), rect()).x(); if (e->x() > x0 + 2 && e->x() < lineEdit()->x()) { emit iconClicked(); e->accept(); } else { KHistoryComboBox::mousePressEvent(e); } } void SearchBarCombo::historyCleared() { setIcon(m_icon); } void SearchBarCombo::setSuggestionItems(const QStringList &suggestions) { if (!m_suggestions.isEmpty()) { clearSuggestions(); } m_suggestions = suggestions; if (!suggestions.isEmpty()) { const int size = completionBox()->count(); QListWidgetItem *item = new QListWidgetItem(suggestions.at(0)); item->setData(Qt::UserRole, "suggestion"); completionBox()->insertItem(size + 1, item); const int suggestionCount = suggestions.count(); for (int i = 1; i < suggestionCount; i++) { completionBox()->insertItem(size + 1 + i, suggestions.at(i)); } completionBox()->popup(); } } void SearchBarCombo::clearSuggestions() { // Removing items can change the current item in completion box, // which makes the lineEdit emit textEdited, and we would then // re-enter this method, so block lineEdit signals. const bool oldBlock = lineEdit()->blockSignals(true); int size = completionBox()->count(); if (!m_suggestions.isEmpty() && size >= m_suggestions.count()) { for (int i = size - 1; i >= size - m_suggestions.size(); i--) { completionBox()->takeItem(i); } } m_suggestions.clear(); lineEdit()->blockSignals(oldBlock); } void SearchBarCombo::addEnableMenuItem(QMenu *menu) { if (menu) { menu->addAction(m_enableAction); } } SearchBarItemDelegate::SearchBarItemDelegate(QObject *parent) : QItemDelegate(parent) { } void SearchBarItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QString userText = index.data(Qt::UserRole).toString(); QString text = index.data(Qt::DisplayRole).toString(); // Get item data if (!userText.isEmpty()) { // This font is for the "information" text, small size + italic + gray in color QFont usrTxtFont = option.font; usrTxtFont.setItalic(true); usrTxtFont.setPointSize(6); QFontMetrics usrTxtFontMetrics(usrTxtFont); int width = usrTxtFontMetrics.width(userText); QRect rect(option.rect.x(), option.rect.y(), option.rect.width() - width, option.rect.height()); QFontMetrics textFontMetrics(option.font); QString elidedText = textFontMetrics.elidedText(text, Qt::ElideRight, option.rect.width() - width - option.decorationSize.width()); QAbstractItemModel *itemModel = const_cast(index.model()); itemModel->setData(index, elidedText, Qt::DisplayRole); QItemDelegate::paint(painter, option, index); itemModel->setData(index, text, Qt::DisplayRole); painter->setFont(usrTxtFont); painter->setPen(QPen(QColor(Qt::gray))); painter->drawText(option.rect, Qt::AlignRight, userText); // Draw a separator above this item if (index.row() > 0) { painter->drawLine(option.rect.x(), option.rect.y(), option.rect.x() + option.rect.width(), option.rect.y()); } } else { QItemDelegate::paint(painter, option, index); } } bool SearchBarPlugin::enableFindInPage() const { return true; } #include "searchbar.moc" diff --git a/src/konqcombo.cpp b/src/konqcombo.cpp index 358f878aa..c04667819 100644 --- a/src/konqcombo.cpp +++ b/src/konqcombo.cpp @@ -1,906 +1,907 @@ /* This file is part of the KDE project Copyright (C) 2001 Carsten Pfeiffer Copyright (C) 2007 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "konqcombo.h" // Qt #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include "konqdebug.h" #include #include #include #include #include #include #include #include // Local #include "konqview.h" #include "KonquerorAdaptor.h" #include "konqueror_interface.h" #include "konqhistorymanager.h" KConfig *KonqCombo::s_config = nullptr; const int KonqCombo::temporary = 0; static QString titleOfURL(const QString &urlStr) { QUrl url(QUrl::fromUserInput(urlStr)); const KonqHistoryList &historylist = KonqHistoryManager::kself()->entries(); KonqHistoryList::const_iterator historyentry = historylist.constFindEntry(url); if (historyentry == historylist.constEnd() && !url.url().endsWith('/')) { if (!url.path().endsWith('/')) { url.setPath(url.path() + '/'); } historyentry = historylist.constFindEntry(url); } return historyentry != historylist.end() ? (*historyentry).title : QString(); } /////////////////////////////////////////////////////////////////////////////// class KonqListWidgetItem : public QListWidgetItem { public: enum { KonqItemType = 0x1845D5CC }; KonqListWidgetItem(QListWidget *parent = nullptr); KonqListWidgetItem(const QString &text, QListWidget *parent = nullptr); QVariant data(int role) const override; bool reuse(const QString &newText); private: mutable bool lookupPending; }; /////////////////////////////////////////////////////////////////////////////// class KonqComboItemDelegate : public QItemDelegate { public: KonqComboItemDelegate(QObject *parent) : QItemDelegate(parent) {} QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; /////////////////////////////////////////////////////////////////////////////// class KonqComboLineEdit : public KLineEdit { public: KonqComboLineEdit(QWidget *parent = nullptr); KCompletionBox *completionBox(bool create) override; protected: void mouseDoubleClickEvent(QMouseEvent *e) override; }; class KonqComboCompletionBox : public KCompletionBox { public: KonqComboCompletionBox(QWidget *parent); void setItems(const QStringList &items); }; KonqCombo::KonqCombo(QWidget *parent) : KHistoryComboBox(parent), m_returnPressed(false), m_permanent(false), m_pageSecurity(KonqMainWindow::NotCrypted) { setLayoutDirection(Qt::LeftToRight); setInsertPolicy(NoInsert); setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); Q_ASSERT(s_config); KConfigGroup locationBarGroup(s_config, "Location Bar"); setMaxCount(locationBarGroup.readEntry("Maximum of URLs in combo", 20)); // We should also connect the completionBox' highlighted signal to // our setEditText() slot, because we're handling the signals ourselves. // But we're lazy and let KCompletionBox do this and simply switch off // handling of signals later. setHandleSignals(true); KonqComboLineEdit *edit = new KonqComboLineEdit(this); edit->setHandleSignals(true); edit->setCompletionBox(new KonqComboCompletionBox(edit)); setLineEdit(edit); setItemDelegate(new KonqComboItemDelegate(this)); connect(edit, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString))); completionBox()->setTabHandling(true); // #167135 completionBox()->setItemDelegate(new KonqComboItemDelegate(this)); // Make the lineedit consume the Qt::Key_Enter event... setTrapReturnKey(true); // Connect to the returnPressed signal when completionMode == CompletionNone. #314736 slotCompletionModeChanged(completionMode()); connect(KonqHistoryManager::kself(), SIGNAL(cleared()), SLOT(slotCleared())); connect(this, &KonqCombo::cleared, this, &KonqCombo::slotCleared); connect(this, static_cast(&KonqCombo::highlighted), this, &KonqCombo::slotSetIcon); // WARNING! has to be the old style connect below, otherwise location bar doesn't work! //connect(this, &KonqCombo::activated, this, &KonqCombo::slotActivated); connect(this, SIGNAL(activated(QString)), this, SLOT(slotActivated(QString))); connect(this, SIGNAL(completionModeChanged(KCompletion::CompletionMode)), this, SLOT(slotCompletionModeChanged(KCompletion::CompletionMode))); } KonqCombo::~KonqCombo() { } void KonqCombo::init(KCompletion *completion) { setCompletionObject(completion, false); //KonqMainWindow handles signals setAutoDeleteCompletionObject(false); setCompletionMode(completion->completionMode()); // We use Ctrl+T for new tab, so we need something else for substring completion // TODO: how to make that shortcut configurable? If we add a QAction we need to // call the KLineEdit code, which we can't do. Well, we could send a keyevent... setKeyBinding(KCompletionBase::SubstringCompletion, QList() << QKeySequence(Qt::Key_F7)); loadItems(); } void KonqCombo::slotTextEdited(const QString &text) { QString txt = text; txt.remove(QChar('\n')); txt.remove(QChar(QChar::LineSeparator)); txt.remove(QChar(QChar::ParagraphSeparator)); if (txt != text) { lineEdit()->setText(txt); } } void KonqCombo::setURL(const QString &url) { //qCDebug(KONQUEROR_LOG) << url << "returnPressed=" << m_returnPressed; setTemporary(url); if (m_returnPressed) { // Really insert... m_returnPressed = false; QDBusMessage message = QDBusMessage::createSignal(KONQ_MAIN_PATH, QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("addToCombo")); message << url; QDBusConnection::sessionBus().send(message); } // important security consideration: always display the beginning // of the url rather than its end to prevent spoofing attempts. lineEdit()->setCursorPosition(0); } void KonqCombo::setTemporary(const QString &text) { setTemporary(text, KonqPixmapProvider::self()->pixmapFor(text, KIconLoader::SizeSmall)); } void KonqCombo::setTemporary(const QString &url, const QPixmap &pix) { //qCDebug(KONQUEROR_LOG) << url << "temporary=" << temporary; // Insert a temporary item when we don't have one yet if (count() == 0) { insertItem(pix, url, temporary, titleOfURL(url)); } else { if (url != temporaryItem()) { applyPermanent(); } updateItem(pix, url, temporary, titleOfURL(url)); } setCurrentIndex(temporary); } void KonqCombo::removeDuplicates(int index) { //qCDebug(KONQUEROR_LOG) << "starting index= " << index; QString url(temporaryItem()); if (url.endsWith('/')) { url.truncate(url.length() - 1); } // Remove all dupes, if available... for (int i = index; i < count(); i++) { QString item(itemText(i)); if (item.endsWith('/')) { item.truncate(item.length() - 1); } if (item == url) { removeItem(i); } } } // called via DBUS in all instances void KonqCombo::insertPermanent(const QString &url) { //qCDebug(KONQUEROR_LOG) << "url=" << url; saveState(); setTemporary(url); m_permanent = true; restoreState(); } // called right before a new (different!) temporary item will be set. So we // insert an item that was marked permanent properly at position 1. void KonqCombo::applyPermanent() { if (m_permanent && !temporaryItem().isEmpty()) { // Remove as many items as needed to honor maxCount() int index = count(); while (count() >= maxCount()) { removeItem(--index); } QString item = temporaryItem(); insertItem(KonqPixmapProvider::self()->pixmapFor(item, KIconLoader::SizeSmall), item, 1, titleOfURL(item)); //qCDebug(KONQUEROR_LOG) << url; // Remove all duplicates starting from index = 2 removeDuplicates(2); m_permanent = false; } } void KonqCombo::insertItem(const QString &text, int index, const QString &title) { KHistoryComboBox::insertItem(index, text, title); } void KonqCombo::insertItem(const QPixmap &pixmap, const QString &text, int index, const QString &title) { KHistoryComboBox::insertItem(index, pixmap, text, title); } void KonqCombo::updateItem(const QPixmap &pix, const QString &t, int index, const QString &title) { // No need to flicker if (itemText(index) == t && (!itemIcon(index).isNull() && itemIcon(index).pixmap(iconSize()).cacheKey() == pix.cacheKey())) { return; } // qCDebug(KONQUEROR_LOG) << "item=" << t << "index=" << index; setItemText(index, t); setItemIcon(index, pix); setItemData(index, title); update(); } void KonqCombo::saveState() { m_cursorPos = cursorPosition(); m_currentText = currentText(); m_selectedText = lineEdit()->selectedText(); m_currentIndex = currentIndex(); } void KonqCombo::restoreState() { setTemporary(m_currentText); if (m_selectedText.isEmpty()) { lineEdit()->setCursorPosition(m_cursorPos); } else { const int index = m_currentText.indexOf(m_selectedText); if (index == -1) { lineEdit()->setCursorPosition(m_cursorPos); } else { lineEdit()->setSelection(index, m_selectedText.length()); } } } void KonqCombo::updatePixmaps() { saveState(); setUpdatesEnabled(false); KonqPixmapProvider *prov = KonqPixmapProvider::self(); for (int i = 1; i < count(); i++) { setItemIcon(i, prov->pixmapFor(itemText(i), KIconLoader::SizeSmall)); } setUpdatesEnabled(true); repaint(); restoreState(); } void KonqCombo::loadItems() { clear(); int i = 0; KConfigGroup historyConfigGroup(s_config, "History"); // delete the old 2.0.x completion historyConfigGroup.writeEntry("CompletionItems", "unused"); KConfigGroup locationBarGroup(s_config, "Location Bar"); const QStringList items = locationBarGroup.readPathEntry("ComboContents", QStringList()); for (const QString &item : items) { if (!item.isEmpty()) { // only insert non-empty items insertItem(KonqPixmapProvider::self()->pixmapFor(item, KIconLoader::SizeSmall), item, i++, titleOfURL(item)); } } if (count() > 0) { m_permanent = true; // we want the first loaded item to stay } } void KonqCombo::slotSetIcon(int index) { if (itemIcon(index).isNull()) // on-demand icon loading setItemIcon(index, KonqPixmapProvider::self()->pixmapFor(itemText(index), KIconLoader::SizeSmall)); update(); } void KonqCombo::getStyleOption(QStyleOptionComboBox *comboOpt) { //We only use this for querying metrics,so it's rough.. comboOpt->init(this); comboOpt->editable = isEditable(); comboOpt->frame = hasFrame(); comboOpt->iconSize = iconSize(); comboOpt->currentIcon = itemIcon(currentIndex()); comboOpt->currentText = currentText(); } void KonqCombo::popup() { for (int i = 0; i < count(); ++i) { if (itemIcon(i).isNull()) { // on-demand icon loading setItemIcon(i, KonqPixmapProvider::self()->pixmapFor(itemText(i), KIconLoader::SizeSmall)); } } KHistoryComboBox::showPopup(); } void KonqCombo::saveItems() { QStringList items; int i = m_permanent ? 0 : 1; for (; i < count(); i++) { items.append(itemText(i)); } KConfigGroup locationBarGroup(s_config, "Location Bar"); locationBarGroup.writePathEntry("ComboContents", items); KonqPixmapProvider::self()->save(locationBarGroup, QStringLiteral("ComboIconCache"), items); s_config->sync(); } void KonqCombo::clearTemporary(bool makeCurrent) { applyPermanent(); setItemText(temporary, QString()); // ### default pixmap? if (makeCurrent) { setCurrentIndex(temporary); } } bool KonqCombo::eventFilter(QObject *o, QEvent *ev) { // Handle Ctrl+Del/Backspace etc better than the Qt widget, which always // jumps to the next whitespace. QLineEdit *edit = lineEdit(); if (o == edit) { const int type = ev->type(); if (type == QEvent::KeyPress) { QKeyEvent *e = static_cast(ev); QKeySequence key(e->key() | e->modifiers()); if (KStandardShortcut::deleteWordBack().contains(key) || KStandardShortcut::deleteWordForward().contains(key) || ((e->modifiers() & Qt::ControlModifier) && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right))) { selectWord(e); e->accept(); return true; } } else if (type == QEvent::MouseButtonDblClick) { edit->selectAll(); return true; } } return KComboBox::eventFilter(o, ev); } void KonqCombo::keyPressEvent(QKeyEvent *e) { KHistoryComboBox::keyPressEvent(e); // we have to set it as temporary, otherwise we wouldn't get our nice // pixmap. Yes, QComboBox still sucks. QList key{QKeySequence(e->key() | e->modifiers())}; if (key == KStandardShortcut::rotateUp() || key == KStandardShortcut::rotateDown()) { setTemporary(currentText()); } } /* Handle Ctrl+Cursor etc better than the Qt widget, which always jumps to the next whitespace. This code additionally jumps to the next [/#?:], which makes more sense for URLs. The list of chars that will stop the cursor are '/', '.', '?', '#', ':'. */ void KonqCombo::selectWord(QKeyEvent *e) { QLineEdit *edit = lineEdit(); QString text = edit->text(); int pos = edit->cursorPosition(); int pos_old = pos; int count = 0; // TODO: make these a parameter when in kdelibs/kdeui... QList chars; chars << QChar('/') << QChar('.') << QChar('?') << QChar('#') << QChar(':'); bool allow_space_break = true; if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace) { do { pos--; count++; if (allow_space_break && text[pos].isSpace() && count > 1) { break; } } while (pos >= 0 && (chars.indexOf(text[pos]) == -1 || count <= 1)); if (e->modifiers() & Qt::ShiftModifier) { edit->cursorForward(true, 1 - count); } else if (e->key() == Qt::Key_Backspace) { edit->cursorForward(false, 1 - count); QString text = edit->text(); int pos_to_right = edit->text().length() - pos_old; QString cut = text.left(edit->cursorPosition()) + text.right(pos_to_right); edit->setText(cut); edit->setCursorPosition(pos_old - count + 1); } else { edit->cursorForward(false, 1 - count); } } else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Delete) { do { pos++; count++; if (allow_space_break && text[pos].isSpace()) { break; } } while (pos < text.length() && chars.indexOf(text[pos]) == -1); if (e->modifiers() & Qt::ShiftModifier) { edit->cursorForward(true, count + 1); } else if (e->key() == Qt::Key_Delete) { edit->cursorForward(false, -count - 1); QString text = edit->text(); int pos_to_right = text.length() - pos - 1; QString cut = text.left(pos_old) + (pos_to_right > 0 ? text.right(pos_to_right) : QString()); edit->setText(cut); edit->setCursorPosition(pos_old); } else { edit->cursorForward(false, count + 1); } } } void KonqCombo::slotCleared() { QDBusMessage message = QDBusMessage::createSignal(KONQ_MAIN_PATH, QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("comboCleared")); QDBusConnection::sessionBus().send(message); } void KonqCombo::removeURL(const QString &url) { setUpdatesEnabled(false); lineEdit()->setUpdatesEnabled(false); removeFromHistory(url); applyPermanent(); setTemporary(currentText()); setUpdatesEnabled(true); lineEdit()->setUpdatesEnabled(true); update(); } void KonqCombo::mousePressEvent(QMouseEvent *e) { m_dragStart = QPoint(); // null QPoint if (e->button() == Qt::LeftButton && !itemIcon(currentIndex()).isNull()) { // check if the pixmap was clicked int x = e->pos().x(); QStyleOptionComboBox comboOpt; getStyleOption(&comboOpt); int x0 = QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x(); if (x > x0 + 2 && x < lineEdit()->x()) { m_dragStart = e->pos(); return; // don't call KComboBox::mousePressEvent! } } QStyleOptionComboBox optCombo; optCombo.initFrom(this); if (e->button() == Qt::LeftButton && m_pageSecurity != KonqMainWindow::NotCrypted && style()->subElementRect(QStyle::SE_ComboBoxFocusRect, &optCombo, this).contains(e->pos())) { emit showPageSecurity(); } KComboBox::mousePressEvent(e); } void KonqCombo::mouseMoveEvent(QMouseEvent *e) { KComboBox::mouseMoveEvent(e); if (m_dragStart.isNull() || currentText().isEmpty()) { return; } if (e->buttons() & Qt::LeftButton && (e->pos() - m_dragStart).manhattanLength() > QApplication::startDragDistance()) { QUrl url(QUrl::fromUserInput(currentText())); if (url.isValid()) { QDrag *drag = new QDrag(this); QMimeData *mime = new QMimeData; mime->setUrls(QList() << url); drag->setMimeData(mime); QPixmap pix = KonqPixmapProvider::self()->pixmapFor(currentText(), KIconLoader::SizeMedium); if (!pix.isNull()) { drag->setPixmap(pix); } drag->start(); } } } void KonqCombo::slotActivated(const QString &text) { applyPermanent(); m_returnPressed = true; emit activated(text, qApp->keyboardModifiers()); } void KonqCombo::setConfig(KConfig *kc) { s_config = kc; } void KonqCombo::paintEvent(QPaintEvent *pe) { QComboBox::paintEvent(pe); QLineEdit *edit = lineEdit(); QStyleOptionComboBox comboOpt; getStyleOption(&comboOpt); QRect re = style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this); re = QStyle::visualRect(layoutDirection(), rect(), re); if (m_pageSecurity != KonqMainWindow::NotCrypted) { QPainter p(this); p.setClipRect(re); - QPixmap pix = SmallIcon(QLatin1String(m_pageSecurity == KonqMainWindow::Encrypted ? "security-high" : "security-medium")); + const auto icon = QIcon::fromTheme(QLatin1String(m_pageSecurity == KonqMainWindow::Encrypted ? "security-high" : "security-medium")); + QPixmap pix = icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); QRect r = edit->geometry(); r.setRight(re.right() - pix.width() - 2); if (r != edit->geometry()) { edit->setGeometry(r); } p.drawPixmap(re.right() - pix.width() - 1, re.y() + (re.height() - pix.height()) / 2, pix); p.setClipping(false); } else { QRect r = edit->geometry(); r.setRight(re.right()); if (r != edit->geometry()) { edit->setGeometry(r); } } } void KonqCombo::setPageSecurity(int pageSecurity) { int ops = m_pageSecurity; m_pageSecurity = pageSecurity; if (ops != pageSecurity) { update(); } } void KonqCombo::slotReturnPressed() { slotActivated(currentText()); } void KonqCombo::slotCompletionModeChanged(KCompletion::CompletionMode mode) { if (mode == KCompletion::CompletionNone) { connect(this, static_cast(&KonqCombo::returnPressed), this, &KonqCombo::slotReturnPressed); } else { disconnect(this, static_cast(&KonqCombo::returnPressed), this, &KonqCombo::slotReturnPressed); } } /////////////////////////////////////////////////////////////////////////////// KonqListWidgetItem::KonqListWidgetItem(QListWidget *parent) : QListWidgetItem(parent, KonqItemType), lookupPending(true) { } KonqListWidgetItem::KonqListWidgetItem(const QString &text, QListWidget *parent) : QListWidgetItem(text, parent, KonqItemType), lookupPending(true) { } QVariant KonqListWidgetItem::data(int role) const { if (lookupPending && role != Qt::DisplayRole) { QString title = titleOfURL(text()); QPixmap pixmap; KonqPixmapProvider *provider = KonqPixmapProvider::self(); if (!title.isEmpty()) { pixmap = provider->pixmapFor(text(), KIconLoader::SizeSmall); } else if (!text().contains(QLatin1String("://"))) { title = titleOfURL(QLatin1String("http://") + text()); if (!title.isEmpty()) { pixmap = provider->pixmapFor(QLatin1String("http://") + text(), KIconLoader::SizeSmall); } else { pixmap = provider->pixmapFor(text(), KIconLoader::SizeSmall); } } const_cast(this)->setIcon(pixmap); const_cast(this)->setData(Qt::UserRole, title); lookupPending = false; } return QListWidgetItem::data(role); } bool KonqListWidgetItem::reuse(const QString &newText) { if (text() == newText) { return false; } lookupPending = true; setText(newText); return true; } /////////////////////////////////////////////////////////////////////////////// QSize KonqComboItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); int vMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin); QSize size(1, qMax(option.fontMetrics.lineSpacing(), option.decorationSize.height())); size.rheight() += vMargin * 2; return size.expandedTo(QApplication::globalStrut()); } void KonqComboItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); QString url = index.data(Qt::DisplayRole).toString(); QString title = index.data(Qt::UserRole).toString(); QIcon::Mode mode = option.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled; const QSize size = icon.actualSize(option.decorationSize, mode); QPixmap pixmap = icon.pixmap(size, mode); QStyleOptionViewItem opt(option); painter->save(); // Draw the item background // ### When Qt 4.4 is released we need to change this code to draw the background // by calling QStyle::drawPrimitive() with PE_PanelItemViewRow. if (opt.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.brush(QPalette::Highlight)); painter->setPen(QPen(option.palette.brush(QPalette::HighlightedText), 0)); } int hMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin); int vMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin); const QRect bounding = option.rect.adjusted(hMargin, vMargin, -hMargin, -vMargin); const QSize textSize(bounding.width() - pixmap.width() - 2, bounding.height()); const QRect pixmapRect = QStyle::alignedRect(option.direction, Qt::AlignLeft | Qt::AlignVCenter, pixmap.size(), bounding); const QRect textRect = QStyle::alignedRect(option.direction, Qt::AlignRight, textSize, bounding); if (!pixmap.isNull()) { painter->drawPixmap(pixmapRect.topLeft(), pixmap); } QSize titleSize((bounding.width() / 3) - 1, textRect.height()); if (title.isEmpty()) { // Don't truncate the urls when there is no title to show (e.g. local files) // Ideally we would do this globally for all items - reserve space for a title for all, or for none titleSize = QSize(); } const QSize urlSize(textRect.width() - titleSize.width() - 2, textRect.height()); const QRect titleRect = QStyle::alignedRect(option.direction, Qt::AlignRight, titleSize, textRect); const QRect urlRect = QStyle::alignedRect(option.direction, Qt::AlignLeft, urlSize, textRect); if (!url.isEmpty()) { QString squeezedText = option.fontMetrics.elidedText(url, Qt::ElideMiddle, urlRect.width()); painter->drawText(urlRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText); } if (!title.isEmpty()) { QString squeezedText = option.fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width()); QFont font = painter->font(); font.setItalic(true); painter->setFont(font); QColor color = painter->pen().color(); color.setAlphaF(.75); painter->setPen(color); painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText); } painter->restore(); } /////////////////////////////////////////////////////////////////////////////// KonqComboLineEdit::KonqComboLineEdit(QWidget *parent) : KLineEdit(parent) { setClearButtonEnabled(true); } void KonqComboLineEdit::mouseDoubleClickEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { selectAll(); return; } KLineEdit::mouseDoubleClickEvent(e); } KCompletionBox *KonqComboLineEdit::completionBox(bool create) { KCompletionBox *box = KLineEdit::completionBox(false); if (create && !box) { KonqComboCompletionBox *konqBox = new KonqComboCompletionBox(this); setCompletionBox(konqBox); konqBox->setObjectName(QStringLiteral("completion box")); konqBox->setFont(font()); return konqBox; } return box; } /////////////////////////////////////////////////////////////////////////////// KonqComboCompletionBox::KonqComboCompletionBox(QWidget *parent) : KCompletionBox(parent) { setLayoutDirection(Qt::LeftToRight); } void KonqComboCompletionBox::setItems(const QStringList &items) { bool block = signalsBlocked(); blockSignals(true); int rowIndex = 0; if (count() == 0) { for (const QString &text : items) { insertItem(rowIndex++, new KonqListWidgetItem(text)); } } else { //Keep track of whether we need to change anything, //so we can avoid a repaint for identical updates, //to reduce flicker bool dirty = false; for (const QString &text : items) { if (rowIndex < count()) { const bool changed = (static_cast(item(rowIndex)))->reuse(text); dirty = dirty || changed; } else { dirty = true; //Inserting an item is a way of making this dirty addItem(new KonqListWidgetItem(text)); } rowIndex++; } //If there is an unused item, mark as dirty -> less items now if (rowIndex < count()) { dirty = true; } while (rowIndex < count()) { delete item(rowIndex); } //TODO KDE 4 - Port this //if ( dirty ) // triggerUpdate( false ); } if (isVisible() && size().height() != sizeHint().height()) { resizeAndReposition(); } blockSignals(block); } diff --git a/src/konqstatusbarmessagelabel.cpp b/src/konqstatusbarmessagelabel.cpp index 7903bd33a..2f5942bd8 100644 --- a/src/konqstatusbarmessagelabel.cpp +++ b/src/konqstatusbarmessagelabel.cpp @@ -1,406 +1,405 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "konqstatusbarmessagelabel.h" #include #include #include -#include #include #include #include "konqdebug.h" #include #include #include #include #include enum { GeometryTimeout = 100 }; enum { BorderGap = 2 }; class KonqStatusBarMessageLabel::Private { public: Private() : m_type(Default), m_state(DefaultState), m_illumination(0), m_minTextHeight(-1), m_timer(nullptr), m_closeButton(nullptr) {} bool isRichText() const { return m_text.startsWith(QLatin1String("")) || m_text.startsWith(QLatin1String("")); } KonqStatusBarMessageLabel::Type m_type; KonqStatusBarMessageLabel::State m_state; int m_illumination; int m_minTextHeight; QTimer *m_timer; QString m_text; QString m_defaultText; QTextDocument m_textDocument; QList m_pendingMessages; QPixmap m_pixmap; QToolButton *m_closeButton; }; KonqStatusBarMessageLabel::KonqStatusBarMessageLabel(QWidget *parent) : QWidget(parent), d(new Private) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum /*the sizeHint is the max*/); d->m_timer = new QTimer(this); connect(d->m_timer, &QTimer::timeout, this, &KonqStatusBarMessageLabel::timerDone); d->m_closeButton = new QToolButton(this); d->m_closeButton->setAutoRaise(true); d->m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); d->m_closeButton->setToolTip(i18nc("@info", "Close")); d->m_closeButton->setAccessibleName(i18n("Close")); d->m_closeButton->hide(); connect(d->m_closeButton, &QToolButton::clicked, this, &KonqStatusBarMessageLabel::closeErrorMessage); } KonqStatusBarMessageLabel::~KonqStatusBarMessageLabel() { delete d; } void KonqStatusBarMessageLabel::setMessage(const QString &text, Type type) { if ((text == d->m_text) && (type == d->m_type)) { return; } if (d->m_type == Error) { if (type == Error) { d->m_pendingMessages.insert(0, d->m_text); } else if ((d->m_state != DefaultState) || !d->m_pendingMessages.isEmpty()) { // a non-error message should not be shown, as there // are other pending error messages in the queue return; } } d->m_text = text; d->m_type = type; if (d->isRichText()) { d->m_textDocument.setTextWidth(-1); d->m_textDocument.setDefaultFont(font()); QString html = QStringLiteral(""); html += d->m_text; d->m_textDocument.setHtml(html); } d->m_timer->stop(); d->m_illumination = 0; d->m_state = DefaultState; const char *iconName = nullptr; QPixmap pixmap; switch (type) { case OperationCompleted: iconName = "dialog-ok"; // "ok" icon should probably be "dialog-success", but we don't have that icon in KDE 4.0 d->m_closeButton->hide(); break; case Information: iconName = "dialog-information"; d->m_closeButton->hide(); break; case Error: d->m_timer->start(100); d->m_state = Illuminate; updateCloseButtonPosition(); d->m_closeButton->show(); updateGeometry(); break; case Default: default: d->m_closeButton->hide(); updateGeometry(); break; } - d->m_pixmap = (iconName == nullptr) ? QPixmap() : SmallIcon(iconName); + d->m_pixmap = (iconName == nullptr) ? QPixmap() : QIcon::fromTheme(iconName).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText())); if (type == Error) { setAccessibleName(i18n("Error: %1", text)); } else { setAccessibleName(text); } update(); } KonqStatusBarMessageLabel::Type KonqStatusBarMessageLabel::type() const { return d->m_type; } QString KonqStatusBarMessageLabel::text() const { return d->m_text; } void KonqStatusBarMessageLabel::setDefaultText(const QString &text) { d->m_defaultText = text; } QString KonqStatusBarMessageLabel::defaultText() const { return d->m_defaultText; } void KonqStatusBarMessageLabel::setMinimumTextHeight(int min) { if (min != d->m_minTextHeight) { d->m_minTextHeight = min; setMinimumHeight(min); if (d->m_closeButton->height() > min) { d->m_closeButton->setFixedHeight(min); } } } int KonqStatusBarMessageLabel::minimumTextHeight() const { return d->m_minTextHeight; } void KonqStatusBarMessageLabel::paintEvent(QPaintEvent * /* event */) { QPainter painter(this); if (d->m_illumination > 0) { // at this point, a: we are a second label being drawn over the already // painted status area, so we can be translucent, and b: our palette's // window color (bg only) seems to be wrong (always black) KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window); QColor backgroundColor = scheme.background(KColorScheme::NegativeBackground).color(); backgroundColor.setAlpha(qMin(255, d->m_illumination * 2)); painter.setBrush(backgroundColor); painter.setPen(Qt::NoPen); painter.drawRect(QRect(0, 0, width(), height())); } // draw pixmap int x = BorderGap; const int y = (d->m_minTextHeight - d->m_pixmap.height()) / 2; if (!d->m_pixmap.isNull()) { painter.drawPixmap(x, y, d->m_pixmap); x += d->m_pixmap.width() + BorderGap; } // draw text const QRect availTextRect(x, 0, availableTextWidth(), height()); if (d->isRichText()) { const QSize sz = d->m_textDocument.size().toSize(); // Vertical centering const QRect textRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignLeft | Qt::AlignVCenter, sz, availTextRect); //qCDebug(KONQUEROR_LOG) << d->m_text << " sz=" << sz << textRect; // What about wordwrap here? painter.translate(textRect.left(), textRect.top()); d->m_textDocument.drawContents(&painter); } else { // plain text painter.setPen(palette().windowText().color()); int flags = Qt::AlignVCenter; if (height() > d->m_minTextHeight) { flags = flags | Qt::TextWordWrap; } painter.drawText(availTextRect, flags, d->m_text); } painter.end(); } void KonqStatusBarMessageLabel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateCloseButtonPosition(); QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText())); } void KonqStatusBarMessageLabel::timerDone() { switch (d->m_state) { case Illuminate: { // increase the illumination const int illumination_max = 128; if (d->m_illumination < illumination_max) { d->m_illumination += 32; if (d->m_illumination > illumination_max) { d->m_illumination = illumination_max; } update(); } else { d->m_state = Illuminated; d->m_timer->start(5000); } break; } case Illuminated: { // start desaturation d->m_state = Desaturate; d->m_timer->start(100); break; } case Desaturate: { // desaturate if (d->m_illumination > 0) { d->m_illumination -= 5; update(); } else { d->m_state = DefaultState; d->m_timer->stop(); } break; } default: break; } } void KonqStatusBarMessageLabel::assureVisibleText() { if (d->m_text.isEmpty()) { return; } int requiredHeight = d->m_minTextHeight; if (d->m_type != Default) { // Calculate the required height of the widget thats // needed for having a fully visible text. Note that for the default // statusbar type (e. g. hover information) increasing the text height // is not wanted, as this might rearrange the layout of items. QFontMetrics fontMetrics(font()); const QRect bounds(fontMetrics.boundingRect(0, 0, availableTextWidth(), height(), Qt::AlignVCenter | Qt::TextWordWrap, d->m_text)); requiredHeight = bounds.height(); if (requiredHeight < d->m_minTextHeight) { requiredHeight = d->m_minTextHeight; } } // Increase/decrease the current height of the widget to the // required height. The increasing/decreasing is done in several // steps to have an animation if the height is modified // (see KonqStatusBarMessageLabel::resizeEvent()) const int gap = d->m_minTextHeight / 2; int minHeight = minimumHeight(); if (minHeight < requiredHeight) { minHeight += gap; if (minHeight > requiredHeight) { minHeight = requiredHeight; } setMinimumHeight(minHeight); updateGeometry(); } else if (minHeight > requiredHeight) { minHeight -= gap; if (minHeight < requiredHeight) { minHeight = requiredHeight; } setMinimumHeight(minHeight); updateGeometry(); } updateCloseButtonPosition(); } int KonqStatusBarMessageLabel::availableTextWidth() const { const int buttonWidth = (d->m_type == Error) ? d->m_closeButton->width() + BorderGap : 0; return width() - d->m_pixmap.width() - (BorderGap * 4) - buttonWidth; } void KonqStatusBarMessageLabel::updateCloseButtonPosition() { const int x = width() - d->m_closeButton->width() - BorderGap; d->m_closeButton->move(x, 0); } void KonqStatusBarMessageLabel::closeErrorMessage() { if (!showPendingMessage()) { d->m_state = DefaultState; setMessage(d->m_defaultText, Default); } } bool KonqStatusBarMessageLabel::showPendingMessage() { if (!d->m_pendingMessages.isEmpty()) { reset(); setMessage(d->m_pendingMessages.takeFirst(), Error); return true; } return false; } void KonqStatusBarMessageLabel::reset() { d->m_text.clear(); d->m_type = Default; } QSize KonqStatusBarMessageLabel::sizeHint() const { return minimumSizeHint(); } QSize KonqStatusBarMessageLabel::minimumSizeHint() const { const int fontHeight = fontMetrics().height(); QSize sz(100, fontHeight); if (d->m_closeButton->isVisible()) { const QSize toolButtonSize = d->m_closeButton->sizeHint(); sz = toolButtonSize.expandedTo(sz); } return sz; }