2
* Copyright (C) 2011 Canonical, Ltd.
5
* Florian Boucault <florian.boucault@canonical.com>
6
* Michal Hruby <michal.hruby@canonical.com>
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.
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.
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/>.
25
#include "categories.h"
27
#include "variantutils.h"
33
#include <QtGui/QDesktopServices>
36
#include <UnityCore/Variant.h>
37
#include <UnityCore/GLibWrapper.h>
42
Scope::Scope(QObject *parent) : QObject(parent)
43
, m_formFactor("phone")
45
, m_searchInProgress(false)
47
m_categories.reset(new Categories(this));
49
connect(this, &Scope::isActiveChanged, this, &Scope::scopeIsActiveChanged);
52
QString Scope::id() const
54
return QString::fromStdString(m_unityScope->id());
57
QString Scope::name() const
59
return QString::fromStdString(m_unityScope->name());
62
QString Scope::iconHint() const
64
return QString::fromStdString(m_unityScope->icon_hint());
67
QString Scope::description() const
69
return QString::fromStdString(m_unityScope->description());
72
QString Scope::searchHint() const
74
return QString::fromStdString(m_unityScope->search_hint());
77
bool Scope::searchInProgress() const
79
return m_searchInProgress;
82
bool Scope::visible() const
84
return m_unityScope->visible();
87
QString Scope::shortcut() const
89
return QString::fromStdString(m_unityScope->shortcut());
92
bool Scope::connected() const
94
return m_unityScope->connected();
97
Categories* Scope::categories() const
99
return m_categories.get();
102
QString Scope::searchQuery() const
104
return m_searchQuery;
107
QString Scope::noResultsHint() const
109
return m_noResultsHint;
112
QString Scope::formFactor() const
117
bool Scope::isActive() const
122
Filters* Scope::filters() const
124
return m_filters.get();
127
void Scope::setSearchQuery(const QString& search_query)
129
/* Checking for m_searchQuery.isNull() which returns true only when the string
130
has never been set is necessary because when search_query is the empty
131
string ("") and m_searchQuery is the null string,
132
search_query != m_searchQuery is still true.
134
using namespace std::placeholders;
136
if (m_searchQuery.isNull() || search_query != m_searchQuery) {
137
m_searchQuery = search_query;
138
m_cancellable.Renew();
139
m_unityScope->Search(search_query.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
140
Q_EMIT searchQueryChanged();
141
if (!m_searchInProgress) {
142
m_searchInProgress = true;
143
Q_EMIT searchInProgressChanged();
148
void Scope::setNoResultsHint(const QString& hint) {
149
if (hint != m_noResultsHint) {
150
m_noResultsHint = hint;
151
Q_EMIT noResultsHintChanged();
155
void Scope::setFormFactor(const QString& form_factor) {
156
if (form_factor != m_formFactor) {
157
m_formFactor = form_factor;
159
m_unityScope->form_factor = m_formFactor.toStdString();
160
synchronizeStates(); // will trigger a re-search
162
Q_EMIT formFactorChanged();
166
void Scope::setActive(const bool active) {
167
if (active != m_isActive) {
169
Q_EMIT isActiveChanged(m_isActive);
173
unity::dash::LocalResult Scope::createLocalResult(const QVariant &uri, const QVariant &icon_hint,
174
const QVariant &category, const QVariant &result_type,
175
const QVariant &mimetype, const QVariant &title,
176
const QVariant &comment, const QVariant &dnd_uri,
177
const QVariant &metadata)
179
unity::dash::LocalResult res;
180
res.uri = uri.toString().toStdString();
181
res.icon_hint = icon_hint.toString().toStdString();
182
res.category_index = category.toUInt();
183
res.result_type = result_type.toUInt();
184
res.mimetype = mimetype.toString().toStdString();
185
res.name = title.toString().toStdString();
186
res.comment = comment.toString().toStdString();
187
res.dnd_uri = dnd_uri.toString().toStdString();
188
res.hints = convertToHintsMap(metadata);
193
// FIXME: Change to use row index.
194
void Scope::activate(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
195
const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
196
const QVariant &comment, const QVariant &dnd_uri, const QVariant &metadata)
198
auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, metadata);
199
m_unityScope->Activate(res);
202
// FIXME: Change to use row index.
203
void Scope::preview(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
204
const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
205
const QVariant &comment, const QVariant &dnd_uri, const QVariant &metadata)
207
QVariant real_metadata(metadata);
208
// handle overridden results, since QML doesn't support defining maps
209
if (metadata.type() == QVariant::String) {
210
real_metadata = QVariant::fromValue(subscopeUriToMetadataHash(metadata.toString()));
213
auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, real_metadata);
214
m_previewCancellable.Renew();
216
// canned queries don't have previews, must be activated
217
if (uri.toString().startsWith("x-unity-no-preview-scopes-query://")) {
218
m_unityScope->Activate(res);
220
m_unityScope->Preview(res, nullptr, m_previewCancellable);
224
void Scope::cancelActivation()
226
m_previewCancellable.Cancel();
229
void Scope::onActivated(unity::dash::LocalResult const& result, unity::dash::ScopeHandledType type, unity::glib::HintsMap const& hints)
232
// note: we will not get called on SHOW_PREVIEW, instead UnityCore will signal preview_ready.
235
case unity::dash::NOT_HANDLED:
236
fallbackActivate(QString::fromStdString(result.uri));
238
case unity::dash::SHOW_DASH:
241
case unity::dash::HIDE_DASH:
244
case unity::dash::GOTO_DASH_URI:
245
if (hints.find("goto-uri") != hints.end()) {
246
Q_EMIT gotoUri(QString::fromStdString(g_variant_get_string(hints.at("goto-uri"), nullptr)));
248
qWarning() << "Missing goto-uri hint for GOTO_DASH_URI activation reply";
251
case unity::dash::PERFORM_SEARCH:
252
if (hints.find("query") != hints.end()) {
253
// note: this will emit searchQueryChanged, and shell will call setSearchQuery back with a new query,
254
// but it will get ignored.
255
setSearchQuery(QString::fromStdString(g_variant_get_string(hints.at("query"), nullptr)));
259
qWarning() << "Unhandled activation response:" << type;
263
void Scope::onPreviewReady(unity::dash::LocalResult const& /* result */, unity::dash::Preview::Ptr const& preview)
266
auto prv = Preview::newFromUnityPreview(preview);
267
// is this the best solution? QML may need to keep more than one preview instance around, so we can't own it.
268
// passing it by value is not possible.
269
QQmlEngine::setObjectOwnership(prv, QQmlEngine::JavaScriptOwnership);
270
Q_EMIT previewReady(prv);
273
void Scope::fallbackActivate(const QString& uri)
275
/* Tries various methods to trigger a sensible action for the given 'uri'.
276
If it has no understanding of the given scheme it falls back on asking
280
if (url.scheme() == "file") {
281
/* Override the files place's default URI handler: we want the file
282
manager to handle opening folders, not the dash.
284
Ref: https://bugs.launchpad.net/upicek/+bug/689667
286
QDesktopServices::openUrl(url);
289
if (url.scheme() == "application") {
290
// get the full path to the desktop file
291
QString path(url.path().isEmpty() ? url.authority() : url.path());
292
if (path.startsWith("/")) {
293
Q_EMIT activateApplication(path);
295
// TODO: use the new desktop file finder/parser when it's ready
296
gchar* full_path = nullptr;
297
GKeyFile* key_file = g_key_file_new();
298
QString apps_path = "applications/" + path;
299
if (g_key_file_load_from_data_dirs(key_file, apps_path.toLocal8Bit().constData(), &full_path,
300
G_KEY_FILE_NONE, nullptr)) {
302
Q_EMIT activateApplication(path);
304
qWarning() << "Unable to activate " << path;
306
g_key_file_free(key_file);
312
qDebug() << "Trying to open" << uri;
315
QDesktopServices::openUrl(uri); //url?
318
void Scope::setUnityScope(const unity::dash::Scope::Ptr& scope)
320
m_unityScope = scope;
322
m_categories->setUnityScope(m_unityScope);
323
m_filters.reset(new Filters(m_unityScope->filters, this));
325
m_unityScope->form_factor = m_formFactor.toStdString();
326
/* Property change signals */
327
m_unityScope->id.changed.connect(sigc::mem_fun(this, &Scope::idChanged));
328
m_unityScope->name.changed.connect(sigc::mem_fun(this, &Scope::nameChanged));
329
m_unityScope->icon_hint.changed.connect(sigc::mem_fun(this, &Scope::iconHintChanged));
330
m_unityScope->description.changed.connect(sigc::mem_fun(this, &Scope::descriptionChanged));
331
m_unityScope->search_hint.changed.connect(sigc::mem_fun(this, &Scope::searchHintChanged));
332
m_unityScope->visible.changed.connect(sigc::mem_fun(this, &Scope::visibleChanged));
333
m_unityScope->shortcut.changed.connect(sigc::mem_fun(this, &Scope::shortcutChanged));
334
m_unityScope->connected.changed.connect(sigc::mem_fun(this, &Scope::connectedChanged));
335
m_unityScope->results_dirty.changed.connect(sigc::mem_fun(this, &Scope::resultsDirtyToggled));
337
/* FIXME: signal should be forwarded instead of calling the handler directly */
338
m_unityScope->activated.connect(sigc::mem_fun(this, &Scope::onActivated));
340
m_unityScope->preview_ready.connect(sigc::mem_fun(this, &Scope::onPreviewReady));
342
/* Synchronize local states with m_unityScope right now and whenever
343
m_unityScope becomes connected */
344
/* FIXME: should emit change notification signals for all properties */
345
connect(this, SIGNAL(connectedChanged(bool)), SLOT(synchronizeStates()));
349
unity::dash::Scope::Ptr Scope::unityScope() const
354
void Scope::synchronizeStates()
356
using namespace std::placeholders;
359
/* Forward local states to m_unityScope */
360
if (!m_searchQuery.isNull()) {
361
m_cancellable.Renew();
362
m_unityScope->Search(m_searchQuery.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
363
if (!m_searchInProgress) {
364
m_searchInProgress = true;
365
Q_EMIT searchInProgressChanged();
371
void Scope::scopeIsActiveChanged()
373
if (!isActive() || !m_unityScope || !m_unityScope->results_dirty()) return;
379
void Scope::resultsDirtyToggled(bool results_dirty)
381
if (!results_dirty || !isActive()) return;
387
void Scope::onSearchFinished(std::string const& /* query */, unity::glib::HintsMap const& hints, unity::glib::Error const& err)
391
GError* error = const_cast<unity::glib::Error&>(err);
393
if (!err || !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
394
if (m_searchInProgress) {
395
m_searchInProgress = false;
396
Q_EMIT searchInProgressChanged();
399
// no need to check the results hint, we're still searching
403
if (!m_unityScope->results()->count()) {
404
unity::glib::HintsMap::const_iterator it = hints.find("no-results-hint");
405
if (it != hints.end()) {
406
hint = QString::fromStdString(it->second.GetString());
408
hint = QString::fromUtf8(dgettext("unity", "Sorry, there is nothing that matches your search."));
412
setNoResultsHint(hint);