X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/bd30bb6ca98374b37db20d14a41542c21acdd5e0..2849f71ba04025abcc9fa6b25bd95b36fec09280:/src/search/dolphinsearchbox.cpp diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 4375974a4..46ca01a4c 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -1,297 +1,515 @@ /*************************************************************************** - * Copyright (C) 2009 by Peter Penz * - * Copyright (C) 2009 by Matthias Fuchs * - * * - * 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 * - ***************************************************************************/ +* Copyright (C) 2010 by Peter Penz * +* * +* 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 "dolphinsearchbox.h" -#include +#include "dolphin_searchsettings.h" +#include "dolphinfacetswidget.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 -#ifdef HAVE_NEPOMUK -#include -#include +#include +#ifdef HAVE_BALOO + #include + #include + #include #endif -DolphinSearchCompleter::DolphinSearchCompleter(KLineEdit* linedit) : - QObject(0), - q(linedit), - m_completer(0), - m_completionModel(0), - m_wordStart(-1), - m_wordEnd(-1) +DolphinSearchBox::DolphinSearchBox(QWidget* parent) : + QWidget(parent), + m_startedSearching(false), + m_active(true), + m_topLayout(0), + m_searchLabel(0), + m_searchInput(0), + m_optionsScrollArea(0), + m_fileNameButton(0), + m_contentButton(0), + m_separator(0), + m_fromHereButton(0), + m_everywhereButton(0), + m_facetsToggleButton(0), + m_facetsWidget(0), + m_searchPath(), + m_startSearchTimer(0) { - m_completionModel = new QStandardItemModel(this); - -#ifdef HAVE_NEPOMUK - if (!Nepomuk::ResourceManager::instance()->init()) { - //read all currently set tags - //NOTE if the user changes tags elsewhere they won't get updated here - QList tags = Nepomuk::Tag::allTags(); - foreach (const Nepomuk::Tag& tag, tags) { - const QString tagText = tag.label(); - addCompletionItem(tagText, - "tag:\"" + tagText + '\"', - i18nc("Tag as in Nepomuk::Tag", "Tag"), - QString(), - KIcon("mail-tagged")); - } - } -#endif //HAVE_NEPOMUK - - // load the completions stored in the desktop file - KDesktopFile file(KStandardDirs::locate("data", "dolphin/dolphinsearchcommands.desktop")); - foreach (const QString &group, file.groupList()) { - KConfigGroup cg(&file, group); - const QString displayed = cg.readEntry("Name", QString()); - const QString usedForCompletition = cg.readEntry("Completion", QString()); - const QString description = cg.readEntry("Comment", QString()); - const QString toolTip = cg.readEntry("GenericName", QString()); - const QString icon = cg.readEntry("Icon", QString()); - - if (icon.isEmpty()) { - addCompletionItem(displayed, usedForCompletition, description, toolTip); +} + +DolphinSearchBox::~DolphinSearchBox() +{ + saveSettings(); +} + +void DolphinSearchBox::setText(const QString& text) +{ + m_searchInput->setText(text); +} + +QString DolphinSearchBox::text() const +{ + return m_searchInput->text(); +} + +void DolphinSearchBox::setSearchPath(const KUrl& url) +{ + m_searchPath = url; + + QFontMetrics metrics(m_fromHereButton->font()); + const int maxWidth = metrics.height() * 8; + + QString location = url.fileName(); + if (location.isEmpty()) { + if (url.isLocalFile()) { + location = QLatin1String("/"); } else { - addCompletionItem(displayed, usedForCompletition, description, toolTip, KIcon(icon)); + location = url.protocol() + QLatin1String(" - ") + url.host(); } } - m_completionModel->sort(0, Qt::AscendingOrder); + const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth); + m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation)); - m_completer = new QCompleter(m_completionModel, this); - m_completer->setWidget(q); - m_completer->setCaseSensitivity(Qt::CaseInsensitive); - QTreeView *view = new QTreeView; - m_completer->setPopup(view); - view->setRootIsDecorated(false); - view->setHeaderHidden(true); + const bool showSearchFromButtons = url.isLocalFile(); + m_separator->setVisible(showSearchFromButtons); + m_fromHereButton->setVisible(showSearchFromButtons); + m_everywhereButton->setVisible(showSearchFromButtons); - connect(q, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString))); - connect(m_completer, SIGNAL(highlighted(QModelIndex)), this, SLOT(highlighted(QModelIndex))); + bool hasFacetsSupport = false; +#ifdef HAVE_BALOO + const Baloo::IndexerConfig searchInfo; + hasFacetsSupport = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); +#endif + m_facetsWidget->setEnabled(hasFacetsSupport); +} + +KUrl DolphinSearchBox::searchPath() const +{ + return m_searchPath; } -void DolphinSearchCompleter::addCompletionItem(const QString& displayed, const QString& usedForCompletition, const QString& description, const QString& toolTip, const KIcon& icon) +KUrl DolphinSearchBox::urlForSearching() const { - if (displayed.isEmpty() || usedForCompletition.isEmpty()) { - return; + KUrl url; + bool useBalooSearch = false; +#ifdef HAVE_BALOO + const Baloo::IndexerConfig searchInfo; + useBalooSearch = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); +#endif + if (useBalooSearch) { + url = balooUrlForSearching(); + } else { + url.setProtocol("filenamesearch"); + url.addQueryItem("search", m_searchInput->text()); + if (m_contentButton->isChecked()) { + url.addQueryItem("checkContent", "yes"); + } + + QString encodedUrl; + if (m_everywhereButton->isChecked()) { + // It is very unlikely, that the majority of Dolphins target users + // mean "the whole harddisk" instead of "my home folder" when + // selecting the "Everywhere" button. + encodedUrl = QDir::homePath(); + } else { + encodedUrl = m_searchPath.url(); + } + url.addQueryItem("url", encodedUrl); } - QList items; - QStandardItem *item = new QStandardItem(); - item->setData(QVariant(displayed), Qt::DisplayRole); - item->setData(QVariant(usedForCompletition), Qt::UserRole); - item->setData(QVariant(toolTip), Qt::ToolTipRole); - items << item; + return url; +} - item = new QStandardItem(description); - if (!icon.isNull()) { - item->setIcon(icon); +void DolphinSearchBox::fromSearchUrl(const KUrl& url) +{ + if (url.protocol() == "baloosearch") { + fromBalooSearchUrl(url); + } else if (url.protocol() == "filenamesearch") { + const QMap& queryItems = url.queryItems(); + setText(queryItems.value("search")); + setSearchPath(queryItems.value("url")); + m_contentButton->setChecked(queryItems.value("checkContent") == "yes"); + } else { + setText(QString()); + setSearchPath(url); } - item->setData(QVariant(toolTip), Qt::ToolTipRole); - items << item; +} - m_completionModel->insertRow(m_completionModel->rowCount(), items); +void DolphinSearchBox::selectAll() +{ + m_searchInput->selectAll(); } -void DolphinSearchCompleter::findText(int* wordStart, int* wordEnd, QString* newWord, int cursorPos, const QString &input) +void DolphinSearchBox::setActive(bool active) { - --cursorPos;//decrease to get a useful position (not the end of the word e.g.) + if (active != m_active) { + m_active = active; - if (!wordStart || !wordEnd) { - return; + if (active) { + emit activated(); + } } +} - *wordStart = -1; - *wordEnd = -1; - - // the word might contain "" and thus maybe spaces - if (input.contains('\"')) { - int tempStart = -1; - int tempEnd = -1; - - do { - tempStart = input.indexOf('\"', tempEnd + 1); - tempEnd = input.indexOf('\"', tempStart + 1); - if ((cursorPos >= tempStart) && (cursorPos <= tempEnd)) { - *wordStart = tempStart; - *wordEnd = tempEnd; - break; - } else if ((tempEnd == -1) && (cursorPos >= tempStart)) { - //one " found, so probably the beginning of the new word - *wordStart = tempStart; - break; - } - } while ((tempStart != -1) && (tempEnd != -1)); - } +bool DolphinSearchBox::isActive() const +{ + return m_active; +} - if (*wordEnd > -1) { - *wordEnd = input.indexOf(' ', *wordEnd) - 1; - } else { - *wordEnd = input.indexOf(' ', cursorPos) - 1; +bool DolphinSearchBox::event(QEvent* event) +{ + if (event->type() == QEvent::Polish) { + init(); } - if (*wordEnd < 0) { - *wordEnd = input.length() - 1; + return QWidget::event(event); +} + +void DolphinSearchBox::showEvent(QShowEvent* event) +{ + if (!event->spontaneous()) { + m_searchInput->setFocus(); + m_startedSearching = false; } +} - if (*wordStart > -1) { - *wordStart = input.lastIndexOf(' ', *wordStart + 1) + 1; - } else { - *wordStart = input.lastIndexOf(' ', cursorPos) + 1; +void DolphinSearchBox::keyReleaseEvent(QKeyEvent* event) +{ + QWidget::keyReleaseEvent(event); + if (event->key() == Qt::Key_Escape) { + if (m_searchInput->text().isEmpty()) { + emit closeRequest(); + } else { + m_searchInput->clear(); + } } - if (*wordStart < 0) { - *wordStart = 0; +} + +bool DolphinSearchBox::eventFilter(QObject* obj, QEvent* event) +{ + switch (event->type()) { + case QEvent::FocusIn: + setActive(true); + setFocus(); + break; + + default: + break; } + return QObject::eventFilter(obj, event); +} + +void DolphinSearchBox::emitSearchRequest() +{ + m_startSearchTimer->stop(); + m_startedSearching = true; + emit searchRequest(); +} - QString word = input.mid(*wordStart, *wordEnd - *wordStart + 1); +void DolphinSearchBox::emitCloseRequest() +{ + m_startSearchTimer->stop(); + m_startedSearching = false; + emit closeRequest(); +} - //remove opening braces or negations ('-' = not) at the beginning - while (word.count() && ((word[0] == '(') || (word[0] == '-'))) { - word.remove(0, 1); - ++(*wordStart); +void DolphinSearchBox::slotConfigurationChanged() +{ + saveSettings(); + if (m_startedSearching) { + emitSearchRequest(); } +} - //remove ending braces at the end - while (word.count() && (word[word.count() - 1] == ')')) { - word.remove(word.count() - 1, 1); - --(*wordEnd); +void DolphinSearchBox::slotSearchTextChanged(const QString& text) +{ + if (text.isEmpty()) { + m_startSearchTimer->stop(); + } else { + m_startSearchTimer->start(); } + emit searchTextChanged(text); +} - if (newWord) { - *newWord = word; - } +void DolphinSearchBox::slotReturnPressed(const QString& text) +{ + emitSearchRequest(); + emit returnPressed(text); } -void DolphinSearchCompleter::slotTextEdited(const QString& text) +void DolphinSearchBox::slotFacetsButtonToggled() { - findText(&m_wordStart, &m_wordEnd, &m_userText, q->cursorPosition(), text); - - if (!m_userText.isEmpty()) { - const int role = m_completer->completionRole(); - - //change the role used for comparison depending on what the user entered - if (m_userText.contains(':') || m_userText.contains('\"')) { - //assume that m_userText contains searchinformation like 'tag:"..."' - if (role != Qt::UserRole) { - m_completer->setCompletionRole(Qt::UserRole); - } - } else if (role != Qt::EditRole) { - m_completer->setCompletionRole(Qt::EditRole); - } + const bool facetsIsVisible = !m_facetsWidget->isVisible(); + m_facetsWidget->setVisible(facetsIsVisible); + updateFacetsToggleButton(); +} - m_completer->setCompletionPrefix(m_userText); - m_completer->complete(); - } +void DolphinSearchBox::slotFacetChanged() +{ + m_startedSearching = true; + m_startSearchTimer->stop(); + emit searchRequest(); } -void DolphinSearchCompleter::highlighted(const QModelIndex& index) +void DolphinSearchBox::initButton(QToolButton* button) { - QString text = q->text(); - int wordStart; - int wordEnd; + button->installEventFilter(this); + button->setAutoExclusive(true); + button->setAutoRaise(true); + button->setCheckable(true); + connect(button, SIGNAL(clicked(bool)), this, SLOT(slotConfigurationChanged())); +} - findText(&wordStart, &wordEnd, 0, q->cursorPosition(), text); +void DolphinSearchBox::loadSettings() +{ + if (SearchSettings::location() == QLatin1String("Everywhere")) { + m_everywhereButton->setChecked(true); + } else { + m_fromHereButton->setChecked(true); + } - QString replace = index.sibling(index.row(), 0).data(Qt::UserRole).toString(); - //show the originally entered text - if (replace.isEmpty()) { - replace = m_userText; + if (SearchSettings::what() == QLatin1String("Content")) { + m_contentButton->setChecked(true); + } else { + m_fileNameButton->setChecked(true); } - text.replace(wordStart, wordEnd - wordStart + 1, replace); - q->setText(text); - q->setCursorPosition(wordStart + replace.length()); + m_facetsWidget->setVisible(SearchSettings::showFacetsWidget()); } -DolphinSearchBox::DolphinSearchBox(QWidget* parent) : - QWidget(parent), - m_searchInput(0), - m_completer(0) +void DolphinSearchBox::saveSettings() { - QHBoxLayout* hLayout = new QHBoxLayout(this); - hLayout->setMargin(0); - hLayout->setSpacing(0); + SearchSettings::setLocation(m_fromHereButton->isChecked() ? "FromHere" : "Everywhere"); + SearchSettings::setWhat(m_fileNameButton->isChecked() ? "FileName" : "Content"); + SearchSettings::setShowFacetsWidget(m_facetsToggleButton->isChecked()); + SearchSettings::self()->writeConfig(); +} + +void DolphinSearchBox::init() +{ + // Create close button + QToolButton* closeButton = new QToolButton(this); + closeButton->setAutoRaise(true); + closeButton->setIcon(KIcon("dialog-close")); + closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching")); + connect(closeButton, SIGNAL(clicked()), this, SLOT(emitCloseRequest())); + + // Create search label + m_searchLabel = new QLabel(this); + // Create search box m_searchInput = new KLineEdit(this); - m_searchInput->setClearButtonShown(true); - m_searchInput->setMinimumWidth(150); - m_searchInput->setClickMessage(i18nc("@label:textbox", "Search...")); m_searchInput->installEventFilter(this); - hLayout->addWidget(m_searchInput); - connect(m_searchInput, SIGNAL(returnPressed()), - this, SLOT(emitSearchSignal())); + m_searchInput->setClearButtonShown(true); + m_searchInput->setFont(KGlobalSettings::generalFont()); + setFocusProxy(m_searchInput); + connect(m_searchInput, SIGNAL(returnPressed(QString)), + this, SLOT(slotReturnPressed(QString))); + connect(m_searchInput, SIGNAL(textChanged(QString)), + this, SLOT(slotSearchTextChanged(QString))); + + // Apply layout for the search input + QHBoxLayout* searchInputLayout = new QHBoxLayout(); + searchInputLayout->setMargin(0); + searchInputLayout->addWidget(closeButton); + searchInputLayout->addWidget(m_searchLabel); + searchInputLayout->addWidget(m_searchInput); + + // Create "Filename" and "Content" button + m_fileNameButton = new QToolButton(this); + m_fileNameButton->setText(i18nc("action:button", "Filename")); + initButton(m_fileNameButton); + + m_contentButton = new QToolButton(); + m_contentButton->setText(i18nc("action:button", "Content")); + initButton(m_contentButton); + + QButtonGroup* searchWhatGroup = new QButtonGroup(this); + searchWhatGroup->addButton(m_fileNameButton); + searchWhatGroup->addButton(m_contentButton); + + m_separator = new KSeparator(Qt::Vertical, this); + + // Create "From Here" and "Everywhere"button + m_fromHereButton = new QToolButton(this); + m_fromHereButton->setText(i18nc("action:button", "From Here")); + initButton(m_fromHereButton); + + m_everywhereButton = new QToolButton(this); + m_everywhereButton->setText(i18nc("action:button", "Everywhere")); + initButton(m_everywhereButton); + + QButtonGroup* searchLocationGroup = new QButtonGroup(this); + searchLocationGroup->addButton(m_fromHereButton); + searchLocationGroup->addButton(m_everywhereButton); + + // Create "Facets" widgets + m_facetsToggleButton = new QToolButton(this); + m_facetsToggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + initButton(m_facetsToggleButton); + connect(m_facetsToggleButton, SIGNAL(clicked()), this, SLOT(slotFacetsButtonToggled())); + + m_facetsWidget = new DolphinFacetsWidget(this); + m_facetsWidget->installEventFilter(this); + m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + connect(m_facetsWidget, SIGNAL(facetChanged()), this, SLOT(slotFacetChanged())); + + // Apply layout for the options + QHBoxLayout* optionsLayout = new QHBoxLayout(); + optionsLayout->setMargin(0); + optionsLayout->addWidget(m_fileNameButton); + optionsLayout->addWidget(m_contentButton); + optionsLayout->addWidget(m_separator); + optionsLayout->addWidget(m_fromHereButton); + optionsLayout->addWidget(m_everywhereButton); + optionsLayout->addStretch(1); + optionsLayout->addWidget(m_facetsToggleButton); + + // Put the options into a QScrollArea. This prevents increasing the view width + // in case that not enough width for the options is available. + QWidget* optionsContainer = new QWidget(this); + optionsContainer->setLayout(optionsLayout); + + m_optionsScrollArea = new QScrollArea(this); + m_optionsScrollArea->setFrameShape(QFrame::NoFrame); + m_optionsScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_optionsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_optionsScrollArea->setMaximumHeight(optionsContainer->sizeHint().height()); + m_optionsScrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_optionsScrollArea->setWidget(optionsContainer); + m_optionsScrollArea->setWidgetResizable(true); + + m_topLayout = new QVBoxLayout(this); + m_topLayout->setMargin(0); + m_topLayout->addLayout(searchInputLayout); + m_topLayout->addWidget(m_optionsScrollArea); + m_topLayout->addWidget(m_facetsWidget); + + loadSettings(); + + // The searching should be started automatically after the user did not change + // the text within one second + m_startSearchTimer = new QTimer(this); + m_startSearchTimer->setSingleShot(true); + m_startSearchTimer->setInterval(1000); + connect(m_startSearchTimer, SIGNAL(timeout()), this, SLOT(emitSearchRequest())); + + updateFacetsToggleButton(); } -DolphinSearchBox::~DolphinSearchBox() +KUrl DolphinSearchBox::balooUrlForSearching() const { -} +#ifdef HAVE_BALOO + const QString text = m_searchInput->text(); -QString DolphinSearchBox::text() const -{ - return m_searchInput->text(); -} + Baloo::Query query; + query.addType("File"); + query.addType(m_facetsWidget->facetType()); -bool DolphinSearchBox::event(QEvent* event) -{ - if (event->type() == QEvent::Polish) { - m_searchInput->setFont(KGlobalSettings::generalFont()); - } else if (event->type() == QEvent::KeyPress) { - if (static_cast(event)->key() == Qt::Key_Escape) { - m_searchInput->clear(); - } + Baloo::Term term(Baloo::Term::And); + + Baloo::Term ratingTerm = m_facetsWidget->ratingTerm(); + if (ratingTerm.isValid()) { + term.addSubTerm(ratingTerm); } - return QWidget::event(event); + + if (m_contentButton->isChecked()) { + query.setSearchString(text); + } else if (!text.isEmpty()) { + term.addSubTerm(Baloo::Term(QLatin1String("filename"), text)); + } + + if (m_fromHereButton->isChecked()) { + query.addCustomOption("includeFolder", m_searchPath.toLocalFile()); + } + + query.setTerm(term); + + return query.toSearchUrl(i18nc("@title UDS_DISPLAY_NAME for a KIO directory listing. %1 is the query the user entered.", + "Query Results from '%1'", text)); +#else + return KUrl(); +#endif } -bool DolphinSearchBox::eventFilter(QObject* watched, QEvent* event) +void DolphinSearchBox::fromBalooSearchUrl(const KUrl& url) { - if ((watched == m_searchInput) && (event->type() == QEvent::FocusIn)) { - // Postpone the creation of the search completer until - // the search box is used. This decreases the startup time - // of Dolphin. - if (m_completer == 0) { - m_completer = new DolphinSearchCompleter(m_searchInput); +#ifdef HAVE_BALOO + const Baloo::Query query = Baloo::Query::fromSearchUrl(url); + const Baloo::Term term = query.term(); + + // Block all signals to avoid unnecessary "searchRequest" signals + // while we adjust the search text and the facet widget. + blockSignals(true); + + const QVariantHash customOptions = query.customOptions(); + if (customOptions.contains("includeFolder")) { + setSearchPath(customOptions.value("includeFolder").toString()); + } else { + setSearchPath(QDir::homePath()); + } + + if (!query.searchString().isEmpty()) { + setText(query.searchString()); + } + + QStringList types = query.types(); + types.removeOne("File"); // We are only interested in facet widget types + if (!types.isEmpty()) { + m_facetsWidget->setFacetType(types.first()); + } + + foreach (const Baloo::Term& subTerm, term.subTerms()) { + const QString property = subTerm.property(); + + if (property == QLatin1String("filename")) { + setText(subTerm.value().toString()); + } else if (m_facetsWidget->isRatingTerm(subTerm)) { + m_facetsWidget->setRatingTerm(subTerm); } - emit requestSearchOptions(); } - return QWidget::eventFilter(watched, event); + m_startSearchTimer->stop(); + blockSignals(false); +#endif } - -void DolphinSearchBox::emitSearchSignal() +void DolphinSearchBox::updateFacetsToggleButton() { - emit search(m_searchInput->text()); + const bool facetsIsVisible = SearchSettings::showFacetsWidget(); + m_facetsToggleButton->setChecked(facetsIsVisible ? true : false); + m_facetsToggleButton->setIcon(KIcon(facetsIsVisible ? "arrow-up-double" : "arrow-down-double")); + m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Fewer Options") : i18nc("action:button", "More Options")); } #include "dolphinsearchbox.moc"