]> cloud.milkyroute.net Git - dolphin.git/blob - src/search/dolphinsearchbox.cpp
* Show the search options as soon as the search bar gains focus.
[dolphin.git] / src / search / dolphinsearchbox.cpp
1 /***************************************************************************
2 * Copyright (C) 2009 by Peter Penz <peter.penz@gmx.at> *
3 * Copyright (C) 2009 by Matthias Fuchs <mat69@gmx.net> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20 #include "dolphinsearchbox.h"
21
22 #include <config-nepomuk.h>
23
24 #include <KConfigGroup>
25 #include <KDesktopFile>
26 #include <kglobalsettings.h>
27 #include <klineedit.h>
28 #include <klocale.h>
29 #include <kiconloader.h>
30 #include <KStandardDirs>
31
32 #include <QEvent>
33 #include <QKeyEvent>
34 #include <QHBoxLayout>
35 #include <QStandardItemModel>
36 #include <QtGui/QCompleter>
37 #include <QtGui/QTreeView>
38 #include <QToolButton>
39
40 #ifdef HAVE_NEPOMUK
41 #include <Nepomuk/ResourceManager>
42 #include <Nepomuk/Tag>
43 #endif
44
45 DolphinSearchCompleter::DolphinSearchCompleter(KLineEdit* linedit) :
46 QObject(0),
47 q(linedit),
48 m_completer(0),
49 m_completionModel(0),
50 m_wordStart(-1),
51 m_wordEnd(-1)
52 {
53 m_completionModel = new QStandardItemModel(this);
54
55 #ifdef HAVE_NEPOMUK
56 if (!Nepomuk::ResourceManager::instance()->init()) {
57 //read all currently set tags
58 //NOTE if the user changes tags elsewhere they won't get updated here
59 QList<Nepomuk::Tag> tags = Nepomuk::Tag::allTags();
60 foreach (const Nepomuk::Tag& tag, tags) {
61 const QString tagText = tag.label();
62 addCompletionItem(tagText,
63 "tag:\"" + tagText + '\"',
64 i18nc("Tag as in Nepomuk::Tag", "Tag"),
65 QString(),
66 KIcon("mail-tagged"));
67 }
68 }
69 #endif //HAVE_NEPOMUK
70
71 // load the completions stored in the desktop file
72 KDesktopFile file(KStandardDirs::locate("data", "dolphin/dolphinsearchcommands.desktop"));
73 foreach (const QString &group, file.groupList()) {
74 KConfigGroup cg(&file, group);
75 const QString displayed = cg.readEntry("Name", QString());
76 const QString usedForCompletition = cg.readEntry("Completion", QString());
77 const QString description = cg.readEntry("Comment", QString());
78 const QString toolTip = cg.readEntry("GenericName", QString());
79 const QString icon = cg.readEntry("Icon", QString());
80
81 if (icon.isEmpty()) {
82 addCompletionItem(displayed, usedForCompletition, description, toolTip);
83 } else {
84 addCompletionItem(displayed, usedForCompletition, description, toolTip, KIcon(icon));
85 }
86 }
87
88 m_completionModel->sort(0, Qt::AscendingOrder);
89
90 m_completer = new QCompleter(m_completionModel, this);
91 m_completer->setWidget(q);
92 m_completer->setCaseSensitivity(Qt::CaseInsensitive);
93 QTreeView *view = new QTreeView;
94 m_completer->setPopup(view);
95 view->setRootIsDecorated(false);
96 view->setHeaderHidden(true);
97
98 connect(q, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString)));
99 connect(m_completer, SIGNAL(highlighted(QModelIndex)), this, SLOT(highlighted(QModelIndex)));
100 }
101
102 void DolphinSearchCompleter::addCompletionItem(const QString& displayed, const QString& usedForCompletition, const QString& description, const QString& toolTip, const KIcon& icon)
103 {
104 if (displayed.isEmpty() || usedForCompletition.isEmpty()) {
105 return;
106 }
107
108 QList<QStandardItem*> items;
109 QStandardItem *item = new QStandardItem();
110 item->setData(QVariant(displayed), Qt::DisplayRole);
111 item->setData(QVariant(usedForCompletition), Qt::UserRole);
112 item->setData(QVariant(toolTip), Qt::ToolTipRole);
113 items << item;
114
115 item = new QStandardItem(description);
116 if (!icon.isNull()) {
117 item->setIcon(icon);
118 }
119 item->setData(QVariant(toolTip), Qt::ToolTipRole);
120 items << item;
121
122 m_completionModel->insertRow(m_completionModel->rowCount(), items);
123 }
124
125 void DolphinSearchCompleter::findText(int* wordStart, int* wordEnd, QString* newWord, int cursorPos, const QString &input)
126 {
127 --cursorPos;//decrease to get a useful position (not the end of the word e.g.)
128
129 if (!wordStart || !wordEnd) {
130 return;
131 }
132
133 *wordStart = -1;
134 *wordEnd = -1;
135
136 // the word might contain "" and thus maybe spaces
137 if (input.contains('\"')) {
138 int tempStart = -1;
139 int tempEnd = -1;
140
141 do {
142 tempStart = input.indexOf('\"', tempEnd + 1);
143 tempEnd = input.indexOf('\"', tempStart + 1);
144 if ((cursorPos >= tempStart) && (cursorPos <= tempEnd)) {
145 *wordStart = tempStart;
146 *wordEnd = tempEnd;
147 break;
148 } else if ((tempEnd == -1) && (cursorPos >= tempStart)) {
149 //one " found, so probably the beginning of the new word
150 *wordStart = tempStart;
151 break;
152 }
153 } while ((tempStart != -1) && (tempEnd != -1));
154 }
155
156 if (*wordEnd > -1) {
157 *wordEnd = input.indexOf(' ', *wordEnd) - 1;
158 } else {
159 *wordEnd = input.indexOf(' ', cursorPos) - 1;
160 }
161 if (*wordEnd < 0) {
162 *wordEnd = input.length() - 1;
163 }
164
165 if (*wordStart > -1) {
166 *wordStart = input.lastIndexOf(' ', *wordStart + 1) + 1;
167 } else {
168 *wordStart = input.lastIndexOf(' ', cursorPos) + 1;
169 }
170 if (*wordStart < 0) {
171 *wordStart = 0;
172 }
173
174
175 QString word = input.mid(*wordStart, *wordEnd - *wordStart + 1);
176
177 //remove opening braces or negations ('-' = not) at the beginning
178 while (word.count() && ((word[0] == '(') || (word[0] == '-'))) {
179 word.remove(0, 1);
180 ++(*wordStart);
181 }
182
183 //remove ending braces at the end
184 while (word.count() && (word[word.count() - 1] == ')')) {
185 word.remove(word.count() - 1, 1);
186 --(*wordEnd);
187 }
188
189 if (newWord) {
190 *newWord = word;
191 }
192 }
193
194 void DolphinSearchCompleter::slotTextEdited(const QString& text)
195 {
196 findText(&m_wordStart, &m_wordEnd, &m_userText, q->cursorPosition(), text);
197
198 if (!m_userText.isEmpty()) {
199 const int role = m_completer->completionRole();
200
201 //change the role used for comparison depending on what the user entered
202 if (m_userText.contains(':') || m_userText.contains('\"')) {
203 //assume that m_userText contains searchinformation like 'tag:"..."'
204 if (role != Qt::UserRole) {
205 m_completer->setCompletionRole(Qt::UserRole);
206 }
207 } else if (role != Qt::EditRole) {
208 m_completer->setCompletionRole(Qt::EditRole);
209 }
210
211 m_completer->setCompletionPrefix(m_userText);
212 m_completer->complete();
213 }
214 }
215
216 void DolphinSearchCompleter::highlighted(const QModelIndex& index)
217 {
218 QString text = q->text();
219 int wordStart;
220 int wordEnd;
221
222 findText(&wordStart, &wordEnd, 0, q->cursorPosition(), text);
223
224 QString replace = index.sibling(index.row(), 0).data(Qt::UserRole).toString();
225 //show the originally entered text
226 if (replace.isEmpty()) {
227 replace = m_userText;
228 }
229
230 text.replace(wordStart, wordEnd - wordStart + 1, replace);
231 q->setText(text);
232 q->setCursorPosition(wordStart + replace.length());
233 }
234
235 DolphinSearchBox::DolphinSearchBox(QWidget* parent) :
236 QWidget(parent),
237 m_searchInput(0),
238 m_searchButton(0),
239 m_completer(0)
240 {
241 QHBoxLayout* hLayout = new QHBoxLayout(this);
242 hLayout->setMargin(0);
243 hLayout->setSpacing(0);
244
245 m_searchInput = new KLineEdit(this);
246 m_searchInput->setClearButtonShown(true);
247 m_searchInput->setMinimumWidth(150);
248 m_searchInput->setClickMessage(i18nc("@label:textbox", "Search..."));
249 m_searchInput->installEventFilter(this);
250 hLayout->addWidget(m_searchInput);
251 connect(m_searchInput, SIGNAL(textChanged(const QString&)),
252 this, SIGNAL(textChanged(const QString&)));
253 connect(m_searchInput, SIGNAL(returnPressed()),
254 this, SLOT(emitSearchSignal()));
255
256 m_searchButton = new QToolButton(this);
257 m_searchButton->setAutoRaise(true);
258 m_searchButton->setIcon(KIcon("edit-find"));
259 m_searchButton->setToolTip(i18nc("@info:tooltip", "Click to begin the search"));
260 hLayout->addWidget(m_searchButton);
261 connect(m_searchButton, SIGNAL(clicked()),
262 this, SLOT(emitSearchSignal()));
263 }
264
265 DolphinSearchBox::~DolphinSearchBox()
266 {
267 }
268
269 bool DolphinSearchBox::event(QEvent* event)
270 {
271 if (event->type() == QEvent::Polish) {
272 m_searchInput->setFont(KGlobalSettings::generalFont());
273 } else if (event->type() == QEvent::KeyPress) {
274 if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
275 m_searchInput->clear();
276 }
277 }
278 return QWidget::event(event);
279 }
280
281 bool DolphinSearchBox::eventFilter(QObject* watched, QEvent* event)
282 {
283 if ((watched == m_searchInput) && (event->type() == QEvent::FocusIn)) {
284 // Postpone the creation of the search completer until
285 // the search box is used. This decreases the startup time
286 // of Dolphin.
287 if (m_completer == 0) {
288 m_completer = new DolphinSearchCompleter(m_searchInput);
289 }
290 emit requestSearchOptions();
291 }
292
293 return QWidget::eventFilter(watched, event);
294 }
295
296
297 void DolphinSearchBox::emitSearchSignal()
298 {
299 emit search(KUrl("nepomuksearch:/" + m_searchInput->text()));
300 }
301
302 #include "dolphinsearchbox.moc"