]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphinsearchbox.cpp
Start of refactoring so that the metadata widget can be moved outside of Dolphin...
[dolphin.git] / src / 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 //HAVE_NEPOMUK
44
45
46 DolphinSearchCompleter::DolphinSearchCompleter(KLineEdit* linedit) :
47 QObject(0),
48 q(linedit),
49 m_completer(0),
50 m_completionModel(0),
51 m_wordStart(-1),
52 m_wordEnd(-1)
53 {
54 }
55
56 void DolphinSearchCompleter::init()
57 {
58 m_completionModel = new QStandardItemModel(this);
59
60 #ifdef HAVE_NEPOMUK
61 if (!Nepomuk::ResourceManager::instance()->init()) {
62 //read all currently set tags
63 //NOTE if the user changes tags elsewhere they won't get updated here
64 QList<Nepomuk::Tag> tags = Nepomuk::Tag::allTags();
65 foreach (const Nepomuk::Tag& tag, tags) {
66 const QString tagText = tag.label();
67 addCompletionItem(tagText,
68 "tag:\"" + tagText + '\"',
69 i18nc("Tag as in Nepomuk::Tag", "Tag"),
70 QString(),
71 KIcon("mail-tagged"));
72 }
73 }
74 #endif //HAVE_NEPOMUK
75
76 //load the completions stored in the desktop file
77 KDesktopFile file(KStandardDirs::locate("data", "dolphin/dolphinsearchcommands.desktop"));
78 foreach (const QString &group, file.groupList()) {
79 KConfigGroup cg(&file, group);
80 const QString displayed = cg.readEntry("Name", QString());
81 const QString usedForCompletition = cg.readEntry("Completion", QString());
82 const QString description = cg.readEntry("Comment", QString());
83 const QString toolTip = cg.readEntry("GenericName", QString());
84 const QString icon = cg.readEntry("Icon", QString());
85
86 if (icon.isEmpty()) {
87 addCompletionItem(displayed, usedForCompletition, description, toolTip);
88 } else {
89 addCompletionItem(displayed, usedForCompletition, description, toolTip, KIcon(icon));
90 }
91 }
92
93 m_completionModel->sort(0, Qt::AscendingOrder);
94
95 m_completer = new QCompleter(m_completionModel, this);
96 m_completer->setWidget(q);
97 m_completer->setCaseSensitivity(Qt::CaseInsensitive);
98 QTreeView *view = new QTreeView;
99 m_completer->setPopup(view);
100 view->setRootIsDecorated(false);
101 view->setHeaderHidden(true);
102
103 connect(q, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString)));
104 connect(m_completer, SIGNAL(activated(QModelIndex)), this, SLOT(completionActivated(QModelIndex)));
105 connect(m_completer, SIGNAL(highlighted(QModelIndex)), this, SLOT(highlighted(QModelIndex)));
106 }
107
108 void DolphinSearchCompleter::addCompletionItem(const QString& displayed, const QString& usedForCompletition, const QString& description, const QString& toolTip, const KIcon& icon)
109 {
110 if (displayed.isEmpty() || usedForCompletition.isEmpty()) {
111 return;
112 }
113
114 QList<QStandardItem*> items;
115 QStandardItem *item = new QStandardItem();
116 item->setData(QVariant(displayed), Qt::DisplayRole);
117 item->setData(QVariant(usedForCompletition), Qt::UserRole);
118 item->setData(QVariant(toolTip), Qt::ToolTipRole);
119 items << item;
120
121 item = new QStandardItem(description);
122 if (!icon.isNull()) {
123 item->setIcon(icon);
124 }
125 item->setData(QVariant(toolTip), Qt::ToolTipRole);
126 items << item;
127
128 m_completionModel->insertRow(m_completionModel->rowCount(), items);
129 }
130
131 void DolphinSearchCompleter::findText(int* wordStart, int* wordEnd, QString* newWord, int cursorPos, const QString &input)
132 {
133 --cursorPos;//decrease to get a useful position (not the end of the word e.g.)
134
135 if (!wordStart || !wordEnd) {
136 return;
137 }
138
139 *wordStart = -1;
140 *wordEnd = -1;
141
142 //the word might contain "" and thus maybe spaces
143 if (input.contains('\"')) {
144 int tempStart = -1;
145 int tempEnd = -1;
146
147 do {
148 tempStart = input.indexOf('\"', tempEnd + 1);
149 tempEnd = input.indexOf('\"', tempStart + 1);
150 if ((cursorPos >= tempStart) && (cursorPos <= tempEnd)) {
151 *wordStart = tempStart;
152 *wordEnd = tempEnd;
153 break;
154 } else if ((tempEnd == -1) && (cursorPos >= tempStart)) {
155 //one " found, so probably the beginning of the new word
156 *wordStart = tempStart;
157 break;
158 }
159 } while ((tempStart != -1) && (tempEnd != -1));
160 }
161
162 if (*wordEnd > -1) {
163 *wordEnd = input.indexOf(' ', *wordEnd) - 1;
164 } else {
165 *wordEnd = input.indexOf(' ', cursorPos) - 1;
166 }
167 if (*wordEnd < 0) {
168 *wordEnd = input.length() - 1;
169 }
170
171 if (*wordStart > -1) {
172 *wordStart = input.lastIndexOf(' ', *wordStart + 1) + 1;
173 } else {
174 *wordStart = input.lastIndexOf(' ', cursorPos) + 1;
175 }
176 if (*wordStart < 0) {
177 *wordStart = 0;
178 }
179
180
181 QString word = input.mid(*wordStart, *wordEnd - *wordStart + 1);
182
183 //remove opening braces or negations ('-' = not) at the beginning
184 while (word.count() && ((word[0] == '(') || (word[0] == '-'))) {
185 word.remove(0, 1);
186 ++(*wordStart);
187 }
188
189 //remove ending braces at the end
190 while (word.count() && (word[word.count() - 1] == ')')) {
191 word.remove(word.count() - 1, 1);
192 --(*wordEnd);
193 }
194
195 if (newWord) {
196 *newWord = word;
197 }
198 }
199
200 void DolphinSearchCompleter::slotTextEdited(const QString& text)
201 {
202 findText(&m_wordStart, &m_wordEnd, &m_userText, q->cursorPosition(), text);
203
204 if (!m_userText.isEmpty()) {
205 const int role = m_completer->completionRole();
206
207 //change the role used for comparison depending on what the user entered
208 if (m_userText.contains(':') || m_userText.contains('\"')) {
209 //assume that m_userText contains searchinformation like 'tag:"..."'
210 if (role != Qt::UserRole) {
211 m_completer->setCompletionRole(Qt::UserRole);
212 }
213 } else if (role != Qt::EditRole) {
214 m_completer->setCompletionRole(Qt::EditRole);
215 }
216
217 m_completer->setCompletionPrefix(m_userText);
218 m_completer->complete();
219 }
220 }
221
222 void DolphinSearchCompleter::highlighted(const QModelIndex& index)
223 {
224 QString text = q->text();
225 int wordStart;
226 int wordEnd;
227
228 findText(&wordStart, &wordEnd, 0, q->cursorPosition(), text);
229
230 QString replace = index.sibling(index.row(), 0).data(Qt::UserRole).toString();
231 //show the originally entered text
232 if (replace.isEmpty()) {
233 replace = m_userText;
234 }
235
236 text.replace(wordStart, wordEnd - wordStart + 1, replace);
237 q->setText(text);
238 q->setCursorPosition(wordStart + replace.length());
239 }
240
241 void DolphinSearchCompleter::activated(const QModelIndex& index)
242 {
243 if ((m_wordStart == -1) || (m_wordStart == -1)) {
244 return;
245 }
246
247 const QString replace = index.sibling(index.row(), 0).data(Qt::UserRole).toString();
248 QString newText = q->text();
249 newText.replace(m_wordStart, m_wordEnd - m_wordStart + 1, replace);
250 q->setText(newText);
251 }
252
253 DolphinSearchBox::DolphinSearchBox(QWidget* parent) :
254 QWidget(parent),
255 m_searchInput(0),
256 m_searchButton(0),
257 m_completer(0)
258 {
259 QHBoxLayout* hLayout = new QHBoxLayout(this);
260 hLayout->setMargin(0);
261 hLayout->setSpacing(0);
262
263 m_searchInput = new KLineEdit(this);
264 m_searchInput->setClearButtonShown(true);
265 m_searchInput->setMinimumWidth(150);
266 m_searchInput->setClickMessage(i18nc("@label:textbox", "Search..."));
267 hLayout->addWidget(m_searchInput);
268 connect(m_searchInput, SIGNAL(returnPressed()),
269 this, SLOT(emitSearchSignal()));
270
271 m_completer = new DolphinSearchCompleter(m_searchInput);
272 m_completer->init();
273
274 m_searchButton = new QToolButton(this);
275 m_searchButton->setAutoRaise(true);
276 m_searchButton->setIcon(KIcon("edit-find"));
277 m_searchButton->setToolTip(i18nc("@info:tooltip", "Click to begin the search"));
278 hLayout->addWidget(m_searchButton);
279 connect(m_searchButton, SIGNAL(clicked()),
280 this, SLOT(emitSearchSignal()));
281 }
282
283 DolphinSearchBox::~DolphinSearchBox()
284 {
285 delete m_completer;
286 }
287
288 bool DolphinSearchBox::event(QEvent* event)
289 {
290 if (event->type() == QEvent::Polish) {
291 m_searchInput->setFont(KGlobalSettings::generalFont());
292 } else if (event->type() == QEvent::KeyPress) {
293 if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
294 m_searchInput->clear();
295 }
296 }
297 return QWidget::event(event);
298 }
299
300 void DolphinSearchBox::emitSearchSignal()
301 {
302 emit search(KUrl("nepomuksearch:/" + m_searchInput->text()));
303 }
304
305 #include "dolphinsearchbox.moc"