在Ubuntu Scope中如何截获按钮事件并做我们想做的事情

我们可以在Ubuntu的官方网站中得到关于Scope的更多的开发信息。在今天的这篇文章中,我们来主要介绍如何在Scope中截获在Preview中的按钮事件。在事件处理中,我们来做我们所需要的事情。在这篇文章中,我们将使用我们以前开发的weibo例子为例。在微博中,我们来发送一个我们需要的信息到微博中。最后我们的画面如下:


  




为了能够完成我们所需要的功能,我们首先下载我先前的代码: https://github.com/liu-xiao-guo/weibo


我们首先在query.cpp中的run方法中加入如下的代码:


query.cpp


void Query::run(sc::SearchReplyProxy const& reply) {

    // We will not show anything if the user does not login
    if( !login(reply) )
        return;

    sc::CategoryRenderer rdr_new_blog(NEW_BLOG);
    sc::Category::SCPtr cat_new_blog;
    cat_new_blog = reply->lookup_category("new_blog");
    if (!cat_new_blog)
        cat_new_blog = reply->register_category("new_blog", "", "", rdr_new_blog);
    sc::CategorisedResult res_new_blog(cat_new_blog);
    res_new_blog.set_title(_("Create new blog post"));
    res_new_blog.set_uri("http://weibo.com");
    res_new_blog["getusertext"] = "key_existence_only_has_meaning_not_this_value";//whether to get text from user in preview
    res_new_blog["newblog"] = "key_existence_only_has_meaning_not_this_value";//new blog, not reply
    res_new_blog["noart"] = "key_existence_only_has_meaning_not_this_value";//whether preview has art
    reply->push(res_new_blog);

....
}

这里,我们创建了一个类似按钮的叫做“Create new blog post”的显示:



同时,我们在“CategorisedResult”加入了一些我们可以区别其它的显示的项比如:

    res_new_blog["getusertext"] = "key_existence_only_has_meaning_not_this_value";//whether to get text from user in preview
    res_new_blog["newblog"] = "key_existence_only_has_meaning_not_this_value";//new blog, not reply
    res_new_blog["noart"] = "key_existence_only_has_meaning_not_this_value";//whether preview has art
    reply->push(res_new_blog);

这些项没有任何的实际的意义,只是在Preview中用来区分和其它的项有所不同。这样我们可以动态创建一些像,以使得对该项的处理和别的项不同。

preview.cpp


void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts
    sc::ColumnLayout layout1col(1), layout2col(2), layout3col(3);

    // We define 3 different layouts, that will be used depending on the
    // device. The shell (view) will decide which layout fits best.
    // If, for instance, we are executing in a tablet probably the view will use
    // 2 or more columns.
    // Column layout definitions are optional.
    // However, we recommend that scopes define layouts for the best visual appearance.

    // Single column layout
    layout1col.add_column( { "image", "header", "reviewId", "actions" });

    // Two column layout
    layout2col.add_column( { "image" });
    layout2col.add_column( { "header", "reviewId", "actions" });

    // Three cokumn layout
    layout3col.add_column( { "image" });
    layout3col.add_column( { "header", "reviewId", "actions" });
    layout3col.add_column( { });

    // Register the layouts we just created
    reply->register_layout( { layout1col, layout2col, layout3col });

    // Define the header section
    sc::PreviewWidget w_header("header", "header");
    // It has title and a subtitle properties
    w_header.add_attribute_mapping("title", "title");
    w_header.add_attribute_mapping("subtitle", "subtitle");

    // Define the image section
    sc::PreviewWidget w_image("image", "image");
    // It has a single source property, mapped to the result's art property
    w_image.add_attribute_mapping("source", "art");

    Result result = PreviewQueryBase::result();
    QString urlString(result["uri"].get_string().c_str());

    // Create an Open button and provide the URI to open for this preview result
    sc::PreviewWidget w_actions("actions", "actions");
    sc::VariantBuilder builder;
    builder.add_tuple({
            {"id", Variant("open")},
            {"label", Variant("Open")},
            {"uri", sc::Variant(urlString.toStdString())} // uri set, this action will be handled by the Dash
        });
    w_actions.add_attribute_value("actions", builder.end());

     PreviewWidgetList widgets({ w_header });

    //used for blogging
    PreviewWidget w_review("reviewId", "rating-input");
    w_review.add_attribute_value("submit-label", Variant("Publish"));
    w_review.add_attribute_value("visible", Variant("review"));
    w_review.add_attribute_value("required", Variant("review"));
    std::string reply_label = "Reply";
    std::string max_chars_label = "140 characters max";
    if (result.contains("newblog"))
        w_review.add_attribute_value("review-label", Variant(max_chars_label));
    if (result.contains("replyblog"))
        w_review.add_attribute_value("review-label", Variant(reply_label + ": " + max_chars_label));
    if (result.contains("getusertext"))
        widgets.emplace_back(w_review);

    if (!result.contains("noart"))
    {
        widgets.emplace_back(w_image);
        widgets.emplace_back(w_actions);
    }

    // Push each of the sections
    reply->push( widgets );
}

在Preview的run中,我们加入了reviewId,它在result中包含有“getusertext”才显示。当其它项不包含“noart”时,才显示image及action。

  


添加对action的支持

scope.cpp


sc::ActivationQueryBase::UPtr Scope::perform_action(us::Result const& result,
                                                     us::ActionMetadata const& metadata,
                                                     std::string const& widget_id,
                                                     std::string const& action_id)
{
    return sc::ActivationQueryBase::UPtr(new Activation(result, metadata, widget_id, action_id, accessToken_));
}

为例对任何一个action (比如上面图中的publish及open)在按钮被按下时,我们可以截获这个时间,我们需要创建一个独立的类:

activation.cpp


/*
 * Copyright (C) 2015 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: Kyle Nitzsche <kyle.nitzsche@canonical.com>
 *
 */

#include <scope/activation.h>

#include <unity/UnityExceptions.h>

#include <QDebug>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>


namespace us = unity::scopes;

Activation::Activation(us::Result const& result, us::ActionMetadata const& metadata, std::string const& widget_id, std::string const& action_id, std::string accessToken):
        ActivationQueryBase(result, metadata),
        action_id_(action_id),
        accessToken_(accessToken)
{
}

Activation::~Activation()
{
}

void Activation::cancelled()
{
}

QString qstr_(std::string str)
{
    return QString::fromStdString(str);
}

us::ActivationResponse Activation::activate()
{
    qDebug() << "==== in activate(). action_id: "<< QString::fromStdString(action_id_);

    bool dash_handles = false;
    if (result().contains("dash_activation"))
        dash_handles = true;
    else if (action_id_ == "profile_url")
        dash_handles = true;

    if (dash_handles)
    {
        qDebug() << "==== dash_activation";
        return us::ActivationResponse(us::ActivationResponse::Status::NotHandled);
    }

    QString uri;
    QString accessToken = QString::fromStdString(accessToken_);
    QString params_qs;
    QString message = QString("%1").arg(qstr_(action_metadata().scope_data().get_dict()["review"].get_string()));
    if (result().contains("newblog"))
    {
        qDebug() << "==== NEW BLOG";
        uri = QString("https://api.weibo.com/2/statuses/update.json?access_token=%1").arg(accessToken);
        params_qs = QString("status=%1").arg(message);
    }
    else if (result().contains("replyblog"))
    {
        qDebug() << "==== REPLY BLOG";
        //qDebug() << "==== token: " << accessToken;
        uri = QString("https://api.weibo.com/2/statuses/repost.json?access_token=%1").arg(accessToken);
        QString id_ = QString("id=%1").arg(qstr_(result()["idstring"].get_string()));
        QString msg_ = QString("status=%1").arg(message);
        params_qs = QString("%1&%2").arg(msg_,id_);
        //qDebug() << "==== params_qs: " << params_qs;
    }

    us::ActionMetadata am = action_metadata();
    //TODO: ensure text is 140 chars or less or warn user somehow
    qDebug() << "====  microblog url:\n" << uri;
    QByteArray params;
    params.append(params_qs);
    QEventLoop loop;
    QNetworkAccessManager manager;
    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&manager, &QNetworkAccessManager::finished, [](QNetworkReply *netReply) {
        netReply->deleteLater();
        QByteArray data_ba = netReply->readAll();
        QJsonParseError err;
        QJsonDocument doc = QJsonDocument::fromJson(data_ba, &err);
        if (err.error != QJsonParseError::NoError) {
            qCritical() << "Failed to parse server data: " << err.errorString();
        }
    });
    QUrl url = QUrl(uri);
    QNetworkRequest request(url);
    QString mUserAgent = QString("%1 (Ubuntu)").arg("weibo-scope");
    request.setRawHeader("User-Agent", mUserAgent.toStdString().c_str());
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
    manager.post(request, params);
    loop.exec();

    return us::ActivationResponse(us::ActivationResponse::Status::ShowDash);
}

当任何一个按钮被按下后,“activate()”方法将被调用。我们可以通过截获这个事件来做任何我们想要做的事情。