/* ============================================================ * * This file is a part of the rekonq project * * Copyright (C) 2009-2012 by Andrea Diamantini * * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * * ============================================================ */ // Self Includes #include "urlsuggester.h" #include "urlsuggester.moc" // Local Includes #include "historymanager.h" #include "bookmarkmanager.h" #include "searchengine.h" // KDE Includes #include #include #include // Qt Includes #include // NOTE // default kurifilter plugin list (at least in my box): // 1. "kshorturifilter" // 2. "kurisearchfilter" // 3. "localdomainurifilter" // 4 ."kuriikwsfilter" // 5. "fixhosturifilter" // ------------------------------------------------------------------------ // NOTE // Induce an order in the history items bool isHistoryItemRelevant(const HistoryItem &a, const HistoryItem &b) { return a.relevance() > b.relevance(); } // ------------------------------------------------------------------------ QRegExp UrlSuggester::_browseRegexp; QRegExp UrlSuggester::_searchEnginesRegexp; UrlSuggester::UrlSuggester(const QString &typedUrl) : QObject() , _typedString(typedUrl.trimmed()) , _isKDEShortUrl(false) { if (_browseRegexp.isEmpty()) { QString protocol = QString("^(%1)").arg(KProtocolInfo::protocols().join("|")); QString localhost = QL1S("^localhost"); QString local = QL1S("^/"); QString ipv4 = QL1S("^0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])"\ "\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])"); QString ipv6 = QL1S("^([0-9a-fA-F]{4}|0)(\\:([0-9a-fA-F]{4}|0)){7}"); QString address = QL1S("[\\d\\w-.]+\\.(a[cdefgilmnoqrstuwz]|b[abdefghijmnorstvwyz]|"\ "c[acdfghiklmnoruvxyz]|d[ejkmnoz]|e[ceghrstu]|f[ijkmnor]|g[abdefghilmnpqrstuwy]|"\ "h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|"\ "m[acdghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eouw]|"\ "s[abcdeghijklmnortuvyz]|t[cdfghjkmnoprtvwz]|u[augkmsyz]|v[aceginu]|w[fs]|"\ "y[etu]|z[amw]|aero|arpa|biz|com|coop|edu|info|int|gov|local|mil|museum|name|net|org|"\ "pro)"); QString joiner = QL1S(")|("); _browseRegexp = QRegExp(QL1C('(') + protocol + joiner + localhost + joiner + local + joiner + address + joiner + ipv6 + joiner + ipv4 + QL1C(')') ); } if (_searchEnginesRegexp.isEmpty()) { QString reg; QString engineUrl; Q_FOREACH(KService::Ptr s, SearchEngine::favorites()) { engineUrl = QRegExp::escape(s->property("Query").toString()).replace(QL1S("\\\\\\{@\\}"), QL1S("[\\d\\w-.]+")); if (reg.isEmpty()) reg = QL1C('(') + engineUrl + QL1C(')'); else reg = reg + QL1S("|(") + engineUrl + QL1C(')'); } _searchEnginesRegexp = QRegExp(reg); } } UrlSuggestionList UrlSuggester::orderedSearchItems() { if (_typedString.startsWith(QL1S("about:"))) { QStringList aboutUrlList; aboutUrlList << QL1S("about:home") << QL1S("about:favorites") // << QL1S("about:closedTabs") << QL1S("about:bookmarks") << QL1S("about:history") << QL1S("about:downloads") // << QL1S("about:tabs") // << QL1S("about:info") ; QStringList aboutUrlResults = aboutUrlList.filter(_typedString, Qt::CaseInsensitive); UrlSuggestionList list; if (aboutUrlResults.isEmpty()) { UrlSuggestionItem info(UrlSuggestionItem::Browse, QL1S("about:info"), QL1S("info")); list << info; return list; } Q_FOREACH(const QString & urlResult, aboutUrlResults) { QString name = urlResult; name.remove(0, 6); UrlSuggestionItem item(UrlSuggestionItem::Browse, urlResult, name); list << item; } return list; } // NOTE: this sets _isKDEShortUrl. // IF it is true we can just suggest it computeWebSearches(); if (_isKDEShortUrl) { return _webSearches; } //compute lists computeHistory(); computeQurlFromUserInput(); computeBookmarks(); return orderLists(); } UrlSuggestionList UrlSuggester::orderLists() { // NOTE // The const int here decides the number of proper suggestions, taken from history & bookmarks // You have to add here the "browse & search" options, always available. const int availableEntries = 8; // Browse & Search results UrlSuggestionList browseSearch; QString lowerTypedString = _typedString.toLower(); bool textIsUrl = (_browseRegexp.indexIn(lowerTypedString) != -1); if (textIsUrl) { // browse url case (typed kde.org): show resolved url before browseSearch << _qurlFromUserInput; browseSearch << _webSearches; } else { // NON url case: propose web search before browseSearch << _webSearches; browseSearch << _qurlFromUserInput; } // find relevant items (the one you are more probably searching...) UrlSuggestionList relevant; // history Q_FOREACH(const UrlSuggestionItem & item, _history) { QString hst = KUrl(item.url).host(); if (item.url.startsWith(_typedString) || hst.startsWith(_typedString) || hst.remove("www.").startsWith(_typedString)) { relevant << item; _history.removeOne(item); break; } } // just add this is relevant is Null if (relevant.count() == 0) { // bookmarks Q_FOREACH(const UrlSuggestionItem & item, _bookmarks) { QString hst = KUrl(item.url).host(); if (item.url.startsWith(_typedString) || hst.startsWith(_typedString) || hst.remove("www.").startsWith(_typedString)) { relevant << item; _bookmarks.removeOne(item); break; } } } // decide history & bookmarks number int historyCount = _history.count(); int bookmarksCount = _bookmarks.count(); int relevantCount = relevant.count(); const int historyEntries = (availableEntries - relevantCount) / 2; const int bookmarksEntries = availableEntries - relevantCount - historyEntries; if (historyCount >= historyEntries && bookmarksCount >= bookmarksEntries) { _history = _history.mid(0, historyEntries); _bookmarks = _bookmarks.mid(0, bookmarksEntries); } else if (historyCount < historyEntries && bookmarksCount >= bookmarksEntries) { if (historyCount + bookmarksCount > availableEntries) { _bookmarks = _bookmarks.mid(0, availableEntries - historyCount); } } else if (historyCount >= historyEntries && bookmarksCount < bookmarksEntries) { if (historyCount + bookmarksCount > availableEntries) { _history = _history.mid(0, availableEntries - bookmarksCount); } } if (_typedString.count() > 1) removeBookmarksDuplicates(); // and finally, results UrlSuggestionList list; if (textIsUrl) list += browseSearch + relevant + _history + _bookmarks; else list += relevant + browseSearch + _history + _bookmarks; return list; } ////////////////////////////////////////////////////////////////////////// // PRIVATE ENGINES // QUrl from User Input (easily the best solution... ) void UrlSuggester::computeQurlFromUserInput() { QString url = _typedString; QUrl urlFromUserInput = QUrl::fromUserInput(url); if (urlFromUserInput.isValid()) { // ensure http(s) hosts are lower cases if (urlFromUserInput.scheme().startsWith(QL1S("http"))) { QString hst = urlFromUserInput.host(); urlFromUserInput.setHost(hst.toLower()); } QString urlString = urlFromUserInput.toString(); QString gTitle = i18nc("Browse a website", "Browse"); UrlSuggestionItem gItem(UrlSuggestionItem::Browse, urlString, gTitle); _qurlFromUserInput << gItem; } } // webSearches void UrlSuggester::computeWebSearches() { QString query = _typedString; // this result is generated when an user types something like gg:kde KService::Ptr engine = SearchEngine::fromString(_typedString); if (engine) { query = query.remove(0, _typedString.indexOf(SearchEngine::delimiter()) + 1); _isKDEShortUrl = true; } else { engine = SearchEngine::defaultEngine(); } if (engine) { UrlSuggestionItem item = UrlSuggestionItem(UrlSuggestionItem::Search, SearchEngine::buildQuery(engine, query), query, engine->name()); UrlSuggestionList list; list << item; _webSearches = list; } } // history void UrlSuggester::computeHistory() { QList found = HistoryManager::self()->find(_typedString); // FIXME: profiling computeHistory, this seems too much expensive (around 1 second for) // doing it just from second time... if (_typedString.count() > 1) qSort(found.begin(), found.end(), isHistoryItemRelevant); Q_FOREACH(const HistoryItem & i, found) { if (_searchEnginesRegexp.isEmpty() || _searchEnginesRegexp.indexIn(i.url) == -1) //filter all urls that are search engine results { UrlSuggestionItem gItem(UrlSuggestionItem::History, i.url, i.title); _history << gItem; } } } // bookmarks void UrlSuggester::computeBookmarks() { QList found = BookmarkManager::self()->find(_typedString); Q_FOREACH(const KBookmark & b, found) { UrlSuggestionItem gItem(UrlSuggestionItem::Bookmark, b.url().url(), b.fullText()); _bookmarks << gItem; } } // opensearch suggestion void UrlSuggester::computeSuggestions() { // NOTE // This attempt basically cuts out open search suggestions. UrlSuggestionList list; emit suggestionsReady(list, _typedString); return; // // if a string startsWith /, it is probably a local path // // so, no need for suggestions... // if (_typedString.startsWith('/') || !rApp->opensearchManager()->isSuggestionAvailable()) // { // UrlSuggestionList list; // emit suggestionsReady(list, _typedString); // return; // } // // QString query = _typedString; // KService::Ptr engine = SearchEngine::fromString(_typedString); // if (engine) // { // query = query.remove(0, _typedString.indexOf(SearchEngine::delimiter()) + 1); // setSearchEngine(engine); // } // // connect(rApp->opensearchManager(), // SIGNAL(suggestionsReceived(QString,ResponseList)), // this, // SLOT(suggestionsReceived(QString,ResponseList))); // // _typedQuery = query; // rApp->opensearchManager()->requestSuggestion(query); } // void UrlSuggester::suggestionsReceived(const QString &text, const ResponseList &suggestions) // { // if (text != _typedString) // return; // // UrlSuggestionList sugList; // QString urlString; // Q_FOREACH(const Response & i, suggestions) // { // if (text == i.title) // continue; // // urlString = i.url; // if (urlString.isEmpty()) // { // urlString = SearchEngine::buildQuery(UrlSuggester::searchEngine(), i.title); // } // // UrlSuggestionItem gItem(UrlSuggestionItem::Suggestion, urlString, i.title, i.description, i.image, i.image_width, i.image_height); // sugList << gItem; // } // emit suggestionsReady(sugList, _typedString); // this->deleteLater(); // } ////////////////////////////////////////////////////////////////////////// // WARNING: this seems A LOT expensive and has to be done just // when the two groups (history & bookmarks) have just been "restricted".. void UrlSuggester::removeBookmarksDuplicates() { Q_FOREACH(const UrlSuggestionItem & item, _history) { QString hu = item.url; Q_FOREACH(const UrlSuggestionItem & item, _bookmarks) { if (hu == item.url) { _bookmarks.removeOne(item); break; } } } }