on 3b29ce7 help sign in
unity8/plugins/Unity/scope.cpp
368 lines 12 kB history raw
1/*
2 * Copyright (C) 2011 Canonical, Ltd.
3 *
4 * Authors:
5 * Florian Boucault <florian.boucault@canonical.com>
6 * Michal Hruby <michal.hruby@canonical.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
21// Self
22#include "scope.h"
24// local
25#include "categories.h"
26#include "preview.h"
27#include "variantutils.h"
29// Qt
30#include <QUrl>
31#include <QDebug>
32#include <QtGui/QDesktopServices>
33#include <QQmlEngine>
35#include <UnityCore/Variant.h>
36#include <UnityCore/GLibWrapper.h>
38#include <libintl.h>
39#include <glib.h>
41Scope::Scope(QObject *parent) : QObject(parent)
42 , m_formFactor("phone")
43 , m_searchInProgress(false)
45 m_categories.reset(new Categories(this));
48QString Scope::id() const
50 return QString::fromStdString(m_unityScope->id());
53QString Scope::name() const
55 return QString::fromStdString(m_unityScope->name());
58QString Scope::iconHint() const
60 return QString::fromStdString(m_unityScope->icon_hint());
63QString Scope::description() const
65 return QString::fromStdString(m_unityScope->description());
68QString Scope::searchHint() const
70 return QString::fromStdString(m_unityScope->search_hint());
73bool Scope::searchInProgress() const
75 return m_searchInProgress;
78bool Scope::visible() const
80 return m_unityScope->visible();
83QString Scope::shortcut() const
85 return QString::fromStdString(m_unityScope->shortcut());
88bool Scope::connected() const
90 return m_unityScope->connected();
93Categories* Scope::categories() const
95 return m_categories.get();
98QString Scope::searchQuery() const
100 return m_searchQuery;
103QString Scope::noResultsHint() const
105 return m_noResultsHint;
108QString Scope::formFactor() const
110 return m_formFactor;
113Filters* Scope::filters() const
115 return m_filters.get();
118void Scope::setSearchQuery(const QString& search_query)
120 /* Checking for m_searchQuery.isNull() which returns true only when the string
121 has never been set is necessary because when search_query is the empty
122 string ("") and m_searchQuery is the null string,
123 search_query != m_searchQuery is still true.
124 */
125 using namespace std::placeholders;
127 if (m_searchQuery.isNull() || search_query != m_searchQuery) {
128 m_searchQuery = search_query;
129 m_cancellable.Renew();
130 m_searchInProgress = true;
131 m_unityScope->Search(search_query.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
132 Q_EMIT searchQueryChanged();
133 Q_EMIT searchInProgressChanged();
134 }
137void Scope::setNoResultsHint(const QString& hint) {
138 if (hint != m_noResultsHint) {
139 m_noResultsHint = hint;
140 Q_EMIT noResultsHintChanged();
141 }
144void Scope::setFormFactor(const QString& form_factor) {
145 if (form_factor != m_formFactor) {
146 m_formFactor = form_factor;
147 if (m_unityScope) {
148 m_unityScope->form_factor = m_formFactor.toStdString();
149 synchronizeStates(); // will trigger a re-search
150 }
151 Q_EMIT formFactorChanged();
152 }
155unity::dash::LocalResult Scope::createLocalResult(const QVariant &uri, const QVariant &icon_hint,
156 const QVariant &category, const QVariant &result_type,
157 const QVariant &mimetype, const QVariant &title,
158 const QVariant &comment, const QVariant &dnd_uri,
159 const QVariant &metadata)
161 unity::dash::LocalResult res;
162 res.uri = uri.toString().toStdString();
163 res.icon_hint = icon_hint.toString().toStdString();
164 res.category_index = category.toUInt();
165 res.result_type = result_type.toUInt();
166 res.mimetype = mimetype.toString().toStdString();
167 res.name = title.toString().toStdString();
168 res.comment = comment.toString().toStdString();
169 res.dnd_uri = dnd_uri.toString().toStdString();
170 res.hints = convertToHintsMap(metadata);
172 return res;
175// FIXME: Change to use row index.
176void Scope::activate(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
177 const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
178 const QVariant &comment, const QVariant &dnd_uri, const QVariant &metadata)
180 auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, metadata);
181 m_unityScope->Activate(res);
184// FIXME: Change to use row index.
185void Scope::preview(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
186 const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
187 const QVariant &comment, const QVariant &dnd_uri, const QVariant &metadata)
189 auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, metadata);
190 m_previewCancellable.Renew();
192 // canned queries don't have previews, must be activated
193 if (uri.toString().startsWith("x-unity-no-preview-scopes-query://")) {
194 m_unityScope->Activate(res);
195 } else {
196 m_unityScope->Preview(res, nullptr, m_previewCancellable);
197 }
200void Scope::cancelActivation()
202 m_previewCancellable.Cancel();
205void Scope::onActivated(unity::dash::LocalResult const& result, unity::dash::ScopeHandledType type, unity::glib::HintsMap const& hints)
207 Q_EMIT activated();
208 // note: we will not get called on SHOW_PREVIEW, instead UnityCore will signal preview_ready.
209 switch (type)
210 {
211 case unity::dash::NOT_HANDLED:
212 fallbackActivate(QString::fromStdString(result.uri));
213 break;
214 case unity::dash::SHOW_DASH:
215 Q_EMIT showDash();
216 break;
217 case unity::dash::HIDE_DASH:
218 Q_EMIT hideDash();
219 break;
220 case unity::dash::GOTO_DASH_URI:
221 if (hints.find("goto-uri") != hints.end()) {
222 Q_EMIT gotoUri(QString::fromStdString(g_variant_get_string(hints.at("goto-uri"), nullptr)));
223 } else {
224 qWarning() << "Missing goto-uri hint for GOTO_DASH_URI activation reply";
225 }
226 break;
227 case unity::dash::PERFORM_SEARCH:
228 if (hints.find("query") != hints.end()) {
229 // note: this will emit searchQueryChanged, and shell will call setSearchQuery back with a new query,
230 // but it will get ignored.
231 setSearchQuery(QString::fromStdString(g_variant_get_string(hints.at("query"), nullptr)));
232 }
233 break;
234 default:
235 qWarning() << "Unhandled activation response:" << type;
236 }
239void Scope::onPreviewReady(unity::dash::LocalResult const& /* result */, unity::dash::Preview::Ptr const& preview)
241 Q_EMIT activated();
242 auto prv = Preview::newFromUnityPreview(preview);
243 // is this the best solution? QML may need to keep more than one preview instance around, so we can't own it.
244 // passing it by value is not possible.
245 QQmlEngine::setObjectOwnership(prv, QQmlEngine::JavaScriptOwnership);
246 Q_EMIT previewReady(prv);
249void Scope::fallbackActivate(const QString& uri)
251 /* Tries various methods to trigger a sensible action for the given 'uri'.
252 If it has no understanding of the given scheme it falls back on asking
253 Qt to open the uri.
254 */
255 QUrl url(uri);
256 if (url.scheme() == "file") {
257 /* Override the files place's default URI handler: we want the file
258 manager to handle opening folders, not the dash.
260 Ref: https://bugs.launchpad.net/upicek/+bug/689667
261 */
262 QDesktopServices::openUrl(url);
263 return;
264 }
265 if (url.scheme() == "application") {
266 // get the full path to the desktop file
267 QString path(url.path().isEmpty() ? url.authority() : url.path());
268 if (path.startsWith("/")) {
269 Q_EMIT activateApplication(path);
270 } else {
271 // TODO: use the new desktop file finder/parser when it's ready
272 gchar* full_path = nullptr;
273 GKeyFile* key_file = g_key_file_new();
274 QString apps_path = "applications/" + path;
275 if (g_key_file_load_from_data_dirs(key_file, apps_path.toLocal8Bit().constData(), &full_path,
276 G_KEY_FILE_NONE, nullptr)) {
277 path = full_path;
278 Q_EMIT activateApplication(path);
279 } else {
280 qWarning() << "Unable to activate " << path;
281 }
282 g_key_file_free(key_file);
283 g_free(full_path);
284 }
285 return;
286 }
288 qDebug() << "Trying to open" << uri;
290 /* Try our luck */
291 QDesktopServices::openUrl(uri); //url?
294void Scope::setUnityScope(const unity::dash::Scope::Ptr& scope)
296 m_unityScope = scope;
298 m_categories->setUnityScope(m_unityScope);
299 m_filters.reset(new Filters(m_unityScope->filters, this));
301 m_unityScope->form_factor = m_formFactor.toStdString();
302 /* Property change signals */
303 m_unityScope->id.changed.connect(sigc::mem_fun(this, &Scope::idChanged));
304 m_unityScope->name.changed.connect(sigc::mem_fun(this, &Scope::nameChanged));
305 m_unityScope->icon_hint.changed.connect(sigc::mem_fun(this, &Scope::iconHintChanged));
306 m_unityScope->description.changed.connect(sigc::mem_fun(this, &Scope::descriptionChanged));
307 m_unityScope->search_hint.changed.connect(sigc::mem_fun(this, &Scope::searchHintChanged));
308 m_unityScope->visible.changed.connect(sigc::mem_fun(this, &Scope::visibleChanged));
309 m_unityScope->shortcut.changed.connect(sigc::mem_fun(this, &Scope::shortcutChanged));
310 m_unityScope->connected.changed.connect(sigc::mem_fun(this, &Scope::connectedChanged));
312 /* FIXME: signal should be forwarded instead of calling the handler directly */
313 m_unityScope->activated.connect(sigc::mem_fun(this, &Scope::onActivated));
315 m_unityScope->preview_ready.connect(sigc::mem_fun(this, &Scope::onPreviewReady));
317 /* Synchronize local states with m_unityScope right now and whenever
318 m_unityScope becomes connected */
319 /* FIXME: should emit change notification signals for all properties */
320 connect(this, SIGNAL(connectedChanged(bool)), SLOT(synchronizeStates()));
321 synchronizeStates();
324unity::dash::Scope::Ptr Scope::unityScope() const
326 return m_unityScope;
329void Scope::synchronizeStates()
331 using namespace std::placeholders;
333 if (connected()) {
334 /* Forward local states to m_unityScope */
335 if (!m_searchQuery.isNull()) {
336 m_cancellable.Renew();
337 m_searchInProgress = true;
338 m_unityScope->Search(m_searchQuery.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
339 Q_EMIT searchInProgressChanged();
340 }
341 }
344void Scope::onSearchFinished(std::string const& /* query */, unity::glib::HintsMap const& hints, unity::glib::Error const& err)
346 QString hint;
348 GError* error = const_cast<unity::glib::Error&>(err);
350 if (!err || !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
351 m_searchInProgress = false;
352 Q_EMIT searchInProgressChanged();
353 } else {
354 // no need to check the results hint, we're still searching
355 return;
356 }
358 if (!m_unityScope->results()->count()) {
359 unity::glib::HintsMap::const_iterator it = hints.find("no-results-hint");
360 if (it != hints.end()) {
361 hint = QString::fromStdString(it->second.GetString());
362 } else {
363 hint = QString::fromUtf8(dgettext("unity", "Sorry, there is nothing that matches your search."));
364 }
365 }
367 setNoResultsHint(hint);