~ps-jenkins/unity-scopes-shell/ubuntu-rtm-14.09-proposed

« back to all changes in this revision

Viewing changes to src/scope.cpp

  • Committer: Michal Hruby
  • Date: 2013-11-07 17:48:16 UTC
  • Revision ID: michal.mhr@gmail.com-20131107174816-w1vqq6juarrawx23
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 */
 
20
 
 
21
// Self
 
22
#include "scope.h"
 
23
 
 
24
// local
 
25
#include "categories.h"
 
26
#include "preview.h"
 
27
#include "variantutils.h"
 
28
 
 
29
// Qt
 
30
#include <QUrl>
 
31
#include <QUrlQuery>
 
32
#include <QDebug>
 
33
#include <QtGui/QDesktopServices>
 
34
#include <QQmlEngine>
 
35
 
 
36
#include <UnityCore/Variant.h>
 
37
#include <UnityCore/GLibWrapper.h>
 
38
 
 
39
#include <libintl.h>
 
40
#include <glib.h>
 
41
 
 
42
Scope::Scope(QObject *parent) : QObject(parent)
 
43
    , m_formFactor("phone")
 
44
    , m_isActive(false)
 
45
    , m_searchInProgress(false)
 
46
{
 
47
    m_categories.reset(new Categories(this));
 
48
 
 
49
    connect(this, &Scope::isActiveChanged, this, &Scope::scopeIsActiveChanged);
 
50
}
 
51
 
 
52
QString Scope::id() const
 
53
{
 
54
    return QString::fromStdString(m_unityScope->id());
 
55
}
 
56
 
 
57
QString Scope::name() const
 
58
{
 
59
    return QString::fromStdString(m_unityScope->name());
 
60
}
 
61
 
 
62
QString Scope::iconHint() const
 
63
{
 
64
    return QString::fromStdString(m_unityScope->icon_hint());
 
65
}
 
66
 
 
67
QString Scope::description() const
 
68
{
 
69
    return QString::fromStdString(m_unityScope->description());
 
70
}
 
71
 
 
72
QString Scope::searchHint() const
 
73
{
 
74
    return QString::fromStdString(m_unityScope->search_hint());
 
75
}
 
76
 
 
77
bool Scope::searchInProgress() const
 
78
{
 
79
    return m_searchInProgress;
 
80
}
 
81
 
 
82
bool Scope::visible() const
 
83
{
 
84
    return m_unityScope->visible();
 
85
}
 
86
 
 
87
QString Scope::shortcut() const
 
88
{
 
89
    return QString::fromStdString(m_unityScope->shortcut());
 
90
}
 
91
 
 
92
bool Scope::connected() const
 
93
{
 
94
    return m_unityScope->connected();
 
95
}
 
96
 
 
97
Categories* Scope::categories() const
 
98
{
 
99
    return m_categories.get();
 
100
}
 
101
 
 
102
QString Scope::searchQuery() const
 
103
{
 
104
    return m_searchQuery;
 
105
}
 
106
 
 
107
QString Scope::noResultsHint() const
 
108
{
 
109
    return m_noResultsHint;
 
110
}
 
111
 
 
112
QString Scope::formFactor() const
 
113
{
 
114
    return m_formFactor;
 
115
}
 
116
 
 
117
bool Scope::isActive() const
 
118
{
 
119
    return m_isActive;
 
120
}
 
121
 
 
122
Filters* Scope::filters() const
 
123
{
 
124
    return m_filters.get();
 
125
}
 
126
 
 
127
void Scope::setSearchQuery(const QString& search_query)
 
128
{
 
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.
 
133
    */
 
134
    using namespace std::placeholders;
 
135
 
 
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();
 
144
        }
 
145
    }
 
146
}
 
147
 
 
148
void Scope::setNoResultsHint(const QString& hint) {
 
149
    if (hint != m_noResultsHint) {
 
150
        m_noResultsHint = hint;
 
151
        Q_EMIT noResultsHintChanged();
 
152
    }
 
153
}
 
154
 
 
155
void Scope::setFormFactor(const QString& form_factor) {
 
156
    if (form_factor != m_formFactor) {
 
157
        m_formFactor = form_factor;
 
158
        if (m_unityScope) {
 
159
            m_unityScope->form_factor = m_formFactor.toStdString();
 
160
            synchronizeStates(); // will trigger a re-search
 
161
        }
 
162
        Q_EMIT formFactorChanged();
 
163
    }
 
164
}
 
165
 
 
166
void Scope::setActive(const bool active) {
 
167
    if (active != m_isActive) {
 
168
        m_isActive = active;
 
169
        Q_EMIT isActiveChanged(m_isActive);
 
170
    }
 
171
}
 
172
 
 
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)
 
178
{
 
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);
 
189
 
 
190
    return res;
 
191
}
 
192
 
 
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)
 
197
{
 
198
    auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, metadata);
 
199
    m_unityScope->Activate(res);
 
200
}
 
201
 
 
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)
 
206
{
 
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()));
 
211
    }
 
212
 
 
213
    auto res = createLocalResult(uri, icon_hint, category, result_type, mimetype, title, comment, dnd_uri, real_metadata);
 
214
    m_previewCancellable.Renew();
 
215
 
 
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);
 
219
    } else {
 
220
        m_unityScope->Preview(res, nullptr, m_previewCancellable);
 
221
    }
 
222
}
 
223
 
 
224
void Scope::cancelActivation()
 
225
{
 
226
    m_previewCancellable.Cancel();
 
227
}
 
228
 
 
229
void Scope::onActivated(unity::dash::LocalResult const& result, unity::dash::ScopeHandledType type, unity::glib::HintsMap const& hints)
 
230
{
 
231
    Q_EMIT activated();
 
232
    // note: we will not get called on SHOW_PREVIEW, instead UnityCore will signal preview_ready.
 
233
    switch (type)
 
234
    {
 
235
        case unity::dash::NOT_HANDLED:
 
236
            fallbackActivate(QString::fromStdString(result.uri));
 
237
            break;
 
238
        case unity::dash::SHOW_DASH:
 
239
            Q_EMIT showDash();
 
240
            break;
 
241
        case unity::dash::HIDE_DASH:
 
242
            Q_EMIT hideDash();
 
243
            break;
 
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)));
 
247
            } else {
 
248
                qWarning() << "Missing goto-uri hint for GOTO_DASH_URI activation reply";
 
249
            }
 
250
            break;
 
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)));
 
256
            }
 
257
            break;
 
258
        default:
 
259
            qWarning() << "Unhandled activation response:" << type;
 
260
    }
 
261
}
 
262
 
 
263
void Scope::onPreviewReady(unity::dash::LocalResult const& /* result */, unity::dash::Preview::Ptr const& preview)
 
264
{
 
265
    Q_EMIT activated();
 
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);
 
271
}
 
272
 
 
273
void Scope::fallbackActivate(const QString& uri)
 
274
{
 
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
 
277
       Qt to open the uri.
 
278
    */
 
279
    QUrl url(uri);
 
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.
 
283
 
 
284
           Ref: https://bugs.launchpad.net/upicek/+bug/689667
 
285
        */
 
286
        QDesktopServices::openUrl(url);
 
287
        return;
 
288
    }
 
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);
 
294
        } else {
 
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)) {
 
301
                path = full_path;
 
302
                Q_EMIT activateApplication(path);
 
303
            } else {
 
304
                qWarning() << "Unable to activate " << path;
 
305
            }
 
306
            g_key_file_free(key_file);
 
307
            g_free(full_path);
 
308
        }
 
309
        return;
 
310
    }
 
311
 
 
312
    qDebug() << "Trying to open" << uri;
 
313
 
 
314
    /* Try our luck */
 
315
    QDesktopServices::openUrl(uri); //url?
 
316
}
 
317
 
 
318
void Scope::setUnityScope(const unity::dash::Scope::Ptr& scope)
 
319
{
 
320
    m_unityScope = scope;
 
321
 
 
322
    m_categories->setUnityScope(m_unityScope);
 
323
    m_filters.reset(new Filters(m_unityScope->filters, this));
 
324
 
 
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));
 
336
 
 
337
    /* FIXME: signal should be forwarded instead of calling the handler directly */
 
338
    m_unityScope->activated.connect(sigc::mem_fun(this, &Scope::onActivated));
 
339
 
 
340
    m_unityScope->preview_ready.connect(sigc::mem_fun(this, &Scope::onPreviewReady));
 
341
 
 
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()));
 
346
    synchronizeStates();
 
347
}
 
348
 
 
349
unity::dash::Scope::Ptr Scope::unityScope() const
 
350
{
 
351
    return m_unityScope;
 
352
}
 
353
 
 
354
void Scope::synchronizeStates()
 
355
{
 
356
    using namespace std::placeholders;
 
357
 
 
358
    if (connected()) {
 
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();
 
366
            }
 
367
        }
 
368
    }
 
369
}
 
370
 
 
371
void Scope::scopeIsActiveChanged()
 
372
{
 
373
    if (!isActive() || !m_unityScope || !m_unityScope->results_dirty()) return;
 
374
 
 
375
    // force new search
 
376
    synchronizeStates();
 
377
}
 
378
 
 
379
void Scope::resultsDirtyToggled(bool results_dirty)
 
380
{
 
381
    if (!results_dirty || !isActive()) return;
 
382
 
 
383
    // force new search
 
384
    synchronizeStates();
 
385
}
 
386
 
 
387
void Scope::onSearchFinished(std::string const& /* query */, unity::glib::HintsMap const& hints, unity::glib::Error const& err)
 
388
{
 
389
    QString hint;
 
390
 
 
391
    GError* error = const_cast<unity::glib::Error&>(err);
 
392
 
 
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();
 
397
        }
 
398
    } else {
 
399
        // no need to check the results hint, we're still searching
 
400
        return;
 
401
    }
 
402
 
 
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());
 
407
        } else {
 
408
            hint = QString::fromUtf8(dgettext("unity", "Sorry, there is nothing that matches your search."));
 
409
        }
 
410
    }
 
411
 
 
412
    setNoResultsHint(hint);
 
413
}