Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

[原]Ubuntu OS应用Runtime Enviroment

在这篇文章中,我们将介绍Ubuntu OS的Runtime Environment。在文章“App confinement: Security policy for click packages”中,我们看见它里面有介绍一个应用的runtime环境。这里,我们通过一个例子来显示一个应用的runtime环境到底是怎样的。


在这里我们可以参阅我以前的文章“在Ubuntu上的C++及QML混合编程”,我们下载文章中的例程:


bzr branch lp:~liu-xiao-guo/debiantrial/readenv


在一个Terminal中打入上述的句子,就可以下载例程中的软件。同时,我们对我们的主程序文件“ReadEnv.qml”做如下的修改:


import QtQuick 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1 as ListItem
import ReadEnv 1.0
import "ui"

/*!
    \brief MainView with Tabs element.
           First Tab has a single Label and
           second Tab has a single ToolbarAction.
*/

MainView {
    id: root

    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.liu-xiao-guo.ReadEnv"

    anchorToKeyboard: true

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    width: units.gu(50)
    height: units.gu(75)

    property string app_pkgname

    ReadEnv {
        id: readEnv
    }

    Flickable {
        id: scrollWidget
        anchors.fill: parent
        contentHeight: contentItem.childrenRect.height
        boundsBehavior: (contentHeight > root.height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
        /* Set the direction to workaround https://bugreports.qt-project.org/browse/QTBUG-31905
           otherwise the UI might end up in a situation where scrolling doesn't work */
        flickableDirection: Flickable.VerticalFlick

        Column {
            anchors.left: parent.left
            anchors.right: parent.right

            ListItem.Base {
                height: ubuntuLabel.height + runtime.height + units.gu(6)

                Column {
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.centerIn: parent
                    spacing: units.gu(2)
                    Label {
                        id: ubuntuLabel
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: ""
                        fontSize: "x-large"
                    }
                    Label {
                        id: runtime
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: "Runtime Environment"
                    }
                }
            }

            ListItem.Subtitled {
                text: i18n.tr("UBUNTU_APPLICATION_ISOLATION")
                subText: readEnv.getenv("UBUNTU_APPLICATION_ISOLATION")
            }

            ListItem.Subtitled {
                text: i18n.tr("APP_ID")
                subText: readEnv.getenv("APP_ID")
            }

            ListItem.Subtitled {
                text: i18n.tr("XDG_CACHE_HOME")
                subText: readEnv.getenv("XDG_CACHE_HOME")
            }

            ListItem.Subtitled {
                text: i18n.tr("XDG_CONFIG_HOME")
                subText: readEnv.getenv("XDG_CONFIG_HOME")
            }

            ListItem.Subtitled {
                text: i18n.tr("XDG_DATA_HOME")
                subText: readEnv.getenv("XDG_DATA_HOME")
            }

            ListItem.Subtitled {
                text: i18n.tr("XDG_RUNTIME_DIR")
                subText: readEnv.getenv("XDG_RUNTIME_DIR")
            }

            ListItem.Subtitled {
                text: i18n.tr("TMPDIR")
                subText: readEnv.getenv("TMPDIR")
            }

            ListItem.Subtitled {
                text: i18n.tr("PWD")
                subText: readEnv.getenv("PWD")
            }

            ListItem.Subtitled {
                text: i18n.tr("APP_PKGNAME")
                subText: app_pkgname
            }

            ListItem.Subtitled {
                text: i18n.tr("PATH")
                subText: readEnv.getenv("PATH")
            }

            ListItem.Subtitled {
                text: i18n.tr("LD_LIBRARY_PATH")
                subText: readEnv.getenv("LD_LIBRARY_PATH")
            }

            ListItem.Subtitled {
                text: i18n.tr("QML2_IMPORT_PATH")
                subText: readEnv.getenv("QML2_IMPORT_PATH")
            }
        }
    }

    Component.onCompleted: {
        var APP_ID = readEnv.getenv("APP_ID");

        console.log("APP_ID: " + APP_ID );
        app_pkgname = APP_ID.split('_')[0]
        console.log("APP_PKGNAME: " + app_pkgname);
    }
}

我们可以通过我们设计的ReadEnv库来读取该应用的环境变量。运行我们的程序,显示如下:


 


在文章中,它提到如下的目录,该应具有读和写的权限:


  • XDG_CACHE_HOME/<APP_PKGNAME>
  • XDG_CONFIG_HOME/<APP_PKGNAME>
  • XDG_DATA_HOME/<APP_PKGNAME>
  • XDG_RUNTIME_DIR/<APP_PKGNAME>
  • XDG_RUNTIME_DIR/confined/<APP_PKGNAME> (for TMPDIR)
针对我们的应用来说,也即如下的目录具有读写的权限:

  • /home/phablet/.cache/com.ubuntu.developer.liu-xiao-guo.readenv
  • /home/phablet/.config/com.ubuntu.developer.liu-xiao-guo.readenv
  • /home/phablet/.local/share/com.ubuntu.developer.liu-xiao-guo.readenv
  • /run/user/32011/confined/com.ubuntu.developer.liu-xiao-guo.readenv

整个程序的源码在如下的地址可以找到:


bzr branch lp:~liu-xiao-guo/debiantrial/runtimeevn




作者:UbuntuTouch 发表于2014-11-19 14:52:03 原文链接
阅读:179 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章中,我们已经创建了很多点评的Scope。在这个篇文章中,我们将使用现有的SDK来重新开发点评Scope。我们将有意识地使得我们的数据和Scope的框架进行分离,从而展示一个更好的开发框架。更多关于Scope的介绍可以在网址http://developer.ubuntu.com/scopes/找到。我们最终的显示图片如下:

   

     


1)创建一个最基本的Scope

我们打开我们的SDK。我们选择菜单“File”==>“New File or Project”。然后我们选择“Unity Scope”。输入“dianping”作为我们项目的名字。

  

我们选择适合自己的“Qt Scope using HTTP+JSON API”模版,并输入我们的Scope名字。

  

同时要记得输入像我上面一样的“Maintainer”格式的信息。否则编译会会有一些问题。紧接着,我们选择我们需要支持的kits。这里我们选择同时支持Desktop,armhf(手机)及i386(模拟器)。这样,我们就基本上完成了我们的模版Scope。我们可以在manifest.json.in中修改我们所需要的。



我们可以修改我们Scope的描述及选择正确的Framework。在上面我们选择了“ubuntu-sdk-14.10-dev2”。这个是在手机或在emulator里被支持的Framework。我们可以通过如下的方式来得到手机或emulator所支持的Framework。



我们可以运行一下我们刚创建的Scope。运行的情况如下。如果我们有问题的话,我们可能需要参照文章“Ubuntu SDK 安装Qt SDK安装”。

\

我们可以使用热键“Ctrl + R”来在Desktop下运行我们的Scope。也可以点击SDK中做下角的绿色的按钮就可以直接运行我们的Scope了。如果在“Application Output”显示的是“scope-unit-tests”,我们必须按照如下的方法进行修改以使得它能够在我们的Desktop下面能够正常运行:



我们也可以通过如下的方式选择emulator进行运行:





等我们选择完emulator后,我们就可以按上面的方法Ctrl+R或绿色的按钮来在emulator中运行我们的Scope了。我们可以参考文章“怎么安装Ubuntu应用到Device中

这个模版的Scope是一个完整的Scope,我们可以在“Search Query”中输入“Beijing”看看有什么变化。

2)点评接口

我们可以在dianping的网站申请自己的开发者账号,并可以试一下它的接口的功能。点评的接口文档可以在点评地址找到。等你注册完好后,我们可以在地址进行测试。我已经注册好了。大家可以在自己的浏览器地址栏中输入如下的内容,我们可以看到JSON格式的输出:

http://api.dianping.com/v1/business/find_businesses?appkey=3562917596&sign=1EAF11F443AE46F87D7D4F2F55903A1D8719455F&category=美食&city=北京&limit=20&platform=2&sort=2



我们可以看到正确的JSON输出。

3)设计Client数据接口

在整个template的设计中,Client的作用是用来向Web Service申请数据,并把数据存入内存中以便在Query的类中调用,并用Scope UI toolkit进行展示。最初的设计和我们的是不一样的。为了方便我们的设计,我们把Client类的接口进行修改,如下:


#ifndef API_CLIENT_H_
#define API_CLIENT_H_

#include <api/config.h>

#include <atomic>
#include <deque>
#include <map>
#include <string>
#include <core/net/http/request.h>
#include <core/net/uri.h>

#include <QJsonDocument>

namespace api {

/**
 * Provide a nice way to access the HTTP API.
 *
 * We don't want our scope's code to be mixed together with HTTP and JSON handling.
 */
class Client {
public:

    /**
     * Data structure for the quried data
     */
    struct Data {
        std::string name;
        std::string business_url;
        std::string s_photo_url;
        std::string photo_url;
        std::string rating_s_img_url;
        std::string address;
        std::string telephone;
    };

    /**
     * A list of weather information
     */
    typedef std::deque<Data> DataList;


    Client(Config::Ptr config);

    virtual ~Client() = default;

    /**
     * Get the query data
     */
    virtual DataList getData(const std::string &query);

    /**
     * Cancel any pending queries (this method can be called from a different thread)
     */
    virtual void cancel();

    virtual Config::Ptr config();

protected:
    void get(QString uri, QJsonDocument &root);

    /**
     * Progress callback that allows the query to cancel pending HTTP requests.
     */
    core::net::http::Request::Progress::Next progress_report(
            const core::net::http::Request::Progress& progress);

private:
    QString getQueryString(QString query);
    QString removeTestInfo(QString name);

    /**
     * Hang onto the configuration information
     */
    Config::Ptr config_;

    /**
     * Thread-safe cancelled flag
     */
    std::atomic<bool> cancelled_;
};

}

#endif // API_CLIENT_H_

在这里我们简化了以前的设计。我们设计了一个Data数据结构来装我们所得到的数据,我们使用如下的方法来获取我们query所需要的数据:

    virtual DataList getData(const std::string &query);

这个函数返回一个数组。这个数组将被我们的Query类所利用,并显示所获得的数据。Client类的实现如下:

#include <api/client.h>

#include <core/net/error.h>
#include <core/net/http/client.h>
#include <core/net/http/content_type.h>
#include <core/net/http/response.h>
#include <QVariantMap>

#include <QString>
#include <QCryptographicHash>
#include <QDebug>
#include <QTextCodec>
#include <QUrl>

#include <iostream>

namespace http = core::net::http;
namespace net = core::net;

using namespace api;
using namespace std;
using namespace std;

const QString appkey = "3562917596";
const QString secret = "091bf584e9d24edbbf48971d65307be3";
const QString BASE_URI = "http://api.dianping.com/v1/business/find_businesses?";

Client::Client(Config::Ptr config) :
    config_(config), cancelled_(false) {
}

void Client::get( QString uri, QJsonDocument &root) {
    // Create a new HTTP client
    auto client = http::make_client();

    // Start building the request configuration
    http::Request::Configuration configuration;

    // Build the URI from its components
    configuration.uri = uri.toStdString();

    // Give out a user agent string
    configuration.header.add("User-Agent", config_->user_agent);

    // Build a HTTP request object from our configuration
    auto request = client->head(configuration);

    try {
        // Synchronously make the HTTP request
        // We bind the cancellable callback to #progress_report
        auto response = request->execute(
                    bind(&Client::progress_report, this, placeholders::_1));

        // Check that we got a sensible HTTP status code
        if (response.status != http::Status::ok) {
            throw domain_error(response.body);
        }
        // Parse the JSON from the response
        root = QJsonDocument::fromJson(response.body.c_str());

        // qDebug() << "response: " << response.body.c_str();
    } catch (net::Error &) {
    }
}

Client::DataList Client::getData(const string& query) {
    QJsonDocument root;

    QString temp = QString::fromStdString(query);
    QByteArray bytearray = query.c_str();
    QString query_string = QString::fromUtf8(bytearray.data(), bytearray.size());

    qDebug() << "query_string: " << query_string;

    QString uri = getQueryString(query_string);
    qDebug() << "uri: "  << uri;
    get(uri, root);

    DataList result;

    QVariantMap variant = root.toVariant().toMap();

    // Iterate through the weather data
    for (const QVariant &i : variant["businesses"].toList()) {
        QVariantMap item = i.toMap();

        QString name = removeTestInfo(item["name"].toString());
        qDebug() << "name: " << name;

        QString business_url = item["business_url"].toString();
        qDebug() << "business_url: " << business_url;

        QString s_photo_url = item["s_photo_url"].toString();
        qDebug() << "s_photo_url: " << s_photo_url;

        QString photo_url = item["photo_url"].toString();
        qDebug() << "photo_url: " << photo_url;

        QString rating_s_img_url = item["rating_s_img_url"].toString();
        qDebug() << "rating_s_img_url: " << rating_s_img_url;

        QString address = item["address"].toString();
        qDebug() << "address: " << address;

        QString telephone = item["telephone"].toString();
        qDebug() << "telephone: " << telephone;

        // Add a result to the weather list
        result.emplace_back(
            Data { name.toStdString(), business_url.toStdString(), s_photo_url.toStdString(),
                   photo_url.toStdString(), rating_s_img_url.toStdString(),
                   address.toStdString(), telephone.toStdString() });
    }

    return result;
}

http::Request::Progress::Next Client::progress_report(
        const http::Request::Progress&) {

    return cancelled_ ?
                http::Request::Progress::Next::abort_operation :
                http::Request::Progress::Next::continue_operation;
}

void Client::cancel() {
    cancelled_ = true;
}

Config::Ptr Client::config() {
    return config_;
}

QString Client::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "美食";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = "20";
    map["platform"] = "2";

    QCryptographicHash generator(QCryptographicHash::Sha1);

    QString temp;
    temp.append(appkey);
    QMapIterator<QString, QString> i(map);
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        temp.append(i.key()).append(i.value());
    }

    temp.append(secret);

    qDebug() << temp;

    qDebug() << "UTF-8: " << temp.toUtf8();

    generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper();

//    qDebug() << sign;

    QString url;
    url.append(BASE_URI);
    url.append("appkey=");
    url.append(appkey);

    url.append("&");
    url.append("sign=");
    url.append(sign);

    i.toFront();
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        url.append("&").append(i.key()).append("=").append(i.value());
    }

    qDebug() << "Final url: " << url;

    QUrl url1(url);

    qDebug() << url1.url();
    QByteArray bytearray = url1.toEncoded();
    QString string = QString::fromUtf8(bytearray.data(), bytearray.size());
    qDebug() << "url new: " << string;

    return string;
}

// The following method is used to remove the
// "这是一条测试商户数据,仅用于测试开发,开发完成后请申请正式数据..." string
const QString TEST_STRING = "(这是一条测试商户数据,仅用于测试开发,开发完成后请申请正式数据...)";
QString Client::removeTestInfo(QString name)
{
    if ( name.contains(TEST_STRING) ) {
        int index = name.indexOf(TEST_STRING);
        QString newName = name.left(index);
        // qDebug() << "newName: " << newName;
        return newName;
    } else {
        qDebug() << "it does not contain the string";
        return name;
    }
}

首先,我们可以看一下这里的helper方法“getQueryString”。这个方法的作用是用来得到我们的申请的url的。我们可以参考“点评开发入门示例”是怎么生成申请的url的。“removeTestInfo”是用来删除我们得到的数据的部分内容以更好地呈现。如果我们需要正式的数据,需要和dianping进行注册。我们通过使用能够net-cpp的API来进行HTTP请求,并使用Qt API QJsonDocument来进行JSON的解析。我们可以通过qDebug来打印并输出我们想要看到的内容。

为了能对我们修改的Client类进行测试,我们对Query.cpp中的“run”进行了如下的修改:

void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        QString queryString = QString::fromStdString(query.query_string());

        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());

        Client::DataList datalist;
        if (query_string.empty()) {
            queryString = QString("北京");
            datalist = client_.getData("北京");
        } else {
            // otherwise, get the forecast for the search string
            datalist = client_.getData(query_string);
        }
    } catch (domain_error &e) {
        // Handle exceptions being thrown by the client API
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}

这样做的目的是为了产生一个对Client类的getData方法调用以便我们调试我们的Client类。重新编译并运行我们的Scope。目前应该什么也看不到,但是我们可以看到许多的输出:


这样我们的Client类的实现基本已经完成。它可以帮我们向点评的web service发送请求,并得到我们想要的数据。这个数据被放在一个叫做Client::DataList的数组中。我们在下面的章节中具体描述怎么利用这个数据进行显示。通过Client类的实现,我们完成了数据(Client)和展示(Query)之间的分离。这也是一个比较推荐的设计。

整个部分的源代码可以在地址下载:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient1

4)代码讲解


src/scope/scope.cpp


这个文件定义了一个unity::scopes::ScopeBase的类。它提供了客户端用来和Scope交互的起始接口。
  • 这个类定义了“start", "stop"及"run"来运行scope。绝大多数开发者并不需要修改这个类的大部分实现。在我们的例程中,我们将不做任何的修改
  • 它也同时实现了另外的两个方法:search 和 preview。我们一般来说不需要修改这俩个方法的实现。但是他们所调用的函数在具体的文件中必须实现
注意:我们可以通过研究Scope API的头文件来对API有更多的认识。更多的详细描述,开发者可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/查看。
在我们本次的实现中,我们不需要做任何的修改。

src/scope/query.cpp


这个文件定义了一个unity::scopes::SearchQueryBase类。这个类用来产生由用户提供的查询字符串而生产的查询结果。这个结果可能是基于json或是xml的。这个类可以用来进行对返回的结果处理并显示。

  • 得到由用户输入的查询字符串
  • 向web services发送请求
  • 生成搜索的结果(根据每个不同而不同)
  • 创建搜索结果category(比如不同的layout-- grid/carousel)
  • 根据不同的搜寻结果来绑定不同的category以显示我们所需要的UI
  • 推送不同的category来显示给最终用户

创建并注册CategoryRenderers


在本例中,我们创建了两个JSON objects. 它们是最原始的字符串,如下所示,它有两个field:template及components。template是用来定义是用什么layout来显示我们所搜索到的结果。这里我们选择的是”grid"和carousel布局。用的是小的card-size。components项可以用来让我们选择预先定义好的field来显示我们所需要的结果。这里我们添加了"title"及“art"。同时我们可以显示一个title及一个图片(art)。更多详细的介绍可以参阅文章“Customization & branding”。

//Create a JSON string to be used tro create a category renderer - uses grid layout
std::string CR_GRID = R"(
{
        "schema-version" : 1,
        "template" : {
        "category-layout" : "grid",
        "card-size": "small"
        },
        "components" : {
        "title" : "title",
        "art" : {
        "field": "art",
        "aspect-ratio": 1.6,
        "fill-mode": "fit"
        }
        }
        }
        )";

//Create a JSON string to be used tro create a category renderer - uses carousel layout
std::string CR_CAROUSEL = R"(
{
        "schema-version" : 1,
        "template" : {
        "category-layout" : "carousel",
        "card-size": "small",
        "overlay" : true
        },
        "components" : {
        "title" : "title",
        "art" : {
        "field": "art",
        "aspect-ratio": 1.6,
        "fill-mode": "fit"
        }
        }
        }
        )";

我们把上述的定义加到我们Query.cpp文件的开始部分。同时我们也可以删除由template已经创建好的“WEATHER_TEMPLATE”及“CITY_TEMPLATE”定义。它们被定义在文件的起始部分。更多关于 CategoryRenderer 类的介绍可以在docs找到。

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册。我们修改我们的run方法来实现显示:

void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        QString queryString = QString::fromStdString(query.query_string());

        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());

        Client::DataList datalist;
        if (query_string.empty()) {
            queryString = QString("北京");
            datalist = client_.getData("北京");
        } else {
            // otherwise, get the forecast for the search string
            datalist = client_.getData(query_string);
        }

        CategoryRenderer rdrGrid(CR_GRID);
        CategoryRenderer rdrCarousel(CR_CAROUSEL);

        QString title = queryString + "美味";

        // Register two categories
        auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
        auto grid = reply->register_category("dianpinggrid", "", "", rdrGrid);

        bool isgrid = false;

        // For each of the entry in the datalist
        for (const Client::Data &data : datalist) {

            // for each result
            const std::shared_ptr<const Category> * category;

            if ( isgrid ) {
                category = &grid;
                isgrid = false;
            } else {
                isgrid = true;
                category = &carousel;
            }

            //create our categorized result using our pointer, which is either to out
            //grid or our carousel Category
            CategorisedResult catres((*category));

            // We must have a URI
            catres.set_uri(data.business_url);
            catres.set_dnd_uri(data.business_url);
            catres.set_title(data.name);
            catres.set_art(data.photo_url);

            catres["address"] = Variant(data.address);
            catres["telephone"] = Variant(data.telephone);

            // Push the result
            if (!reply->push(catres)) {
                // If we fail to push, it means the query has been cancelled.
                // So don't continue;
                return;
            }
        }
    } catch (domain_error &e) {
        // Handle exceptions being thrown by the client API
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}

为了能够使得上面的代码能够被成功编译,我们必须在Query.cpp的开始部分加入如下的句子:

using namespace unity::scopes;

我们从我们的Client API中的Client::getData来获取我们所需要的web service的数据,把数据填入到相应的CategorisedResult中。重新编译我们的Scope,我们可以看到如下的画面:


我们可以在“Search Query”中输入“上海”,我们可以看到不同的结果。我们也可以尝试点击我们的画面,在另外一个画面中可以看到一个图片。
到这里,我们基本上已经看到了Scope工作的了。我们下面来更进一步来在Preview中显示更多的内容。这样我们基本上完成了Query.cpp的代码。整个源码可以在如下的地址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient2

src/scope/preview.cpp


这个文件定义了一个unity::scopes::PreviewQueryBase类。
这个类定义了一个widget及一个layout来展示我们搜索到的结果。这是一个preview结i果,就像它的名字所描述的那样。

  • 定义在preview时所需要的widget
  • 让widget和搜索到的数据field一一对应起来
  • 定义不同数量的layout列(由屏幕的尺寸来定)
  • 把不同的widget分配到layout中的不同列中
  • 把reply实例显示到layout的widget中
更多关于这个类的介绍可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/previewwidgets/找到。


Preview


Preview需要来生成widget并连接它们的field到CategorisedResult所定义的数据项中。它同时也用来为不同的显示环境(比如屏幕尺寸)生成不同的layout。根据不同的显示环境来生成不同数量的column。

Preview Widgets


这是一组预先定义好的widgets。每个都有一个类型。更据这个类型我们可以生成它们。你可以在这里找到Preview Widget列表及它们提供的的field类型。

这个例子使用了如下的widgets

  • header:它有title及subtitle field
  • image:它有source field有来显示从哪里得到这个art
  • text:它有text field
  • action:用来展示一个有"Open"的按钮。当用户点击时,所包含的URI将被打开
如下是一个例子,它定义了一个叫做“headerId"的PreviewWidget。第二个参数是它的类型"header"。
    PreviewWidget w_header("headerId", "header");

最终的程序如下:

void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts
    // Client can display Previews differently depending on the context
    // By creates two layouts (one with one column, one with two) and then
    // adding widgets to them differently, Unity can pick the layout the
    // scope developer thinks is best for the mode
    ColumnLayout layout1col(1), layout2col(2);

    // add columns and widgets (by id) to layouts.
    // The single column layout gets one column and all widets
    layout1col.add_column({"headerId", "artId", "infoId", "telId", "actionsId"});
    // The two column layout gets two columns.
    // The first column gets the art and header widgets (by id)
    layout2col.add_column({"artId", "headerId"});
    // The second column gets the info and actions widgets
    layout2col.add_column({"infoId", "telId", "actionsId"});

    // Push the layouts into the PreviewReplyProxy intance, thus making them
    // available for use in Preview diplay
    reply->register_layout({layout1col, layout2col});

    //Create some widgets
    // header type first. note 'headerId' used in layouts
    // second field ('header) is a standard preview widget type
    PreviewWidget w_header("headerId", "header");
    // This maps the title field of the header widget (first param)  to the
    // title field in the result to be displayed in this preview, thus providing
    // the result-specific data to the preview for display
    w_header.add_attribute_mapping("title", "title");

    // Standard subtitle field here gets our 'artist' key value
    // w_header.add_attribute_mapping("subtitle", "artist");

    PreviewWidget w_art("artId", "image");
    w_art.add_attribute_mapping("source", "art");

    PreviewWidget w_info("infoId", "text");
    w_info.add_attribute_mapping("text", "address");

    PreviewWidget w_tel("telId", "text");
    w_tel.add_attribute_mapping("text", "telephone");

    Result result = PreviewQueryBase::result();
    QString urlString(result["uri"].get_string().c_str());
    // qDebug() << "[Details] GET " << urlString;
   // QUrl url = QUrl(urlString);

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

    // Bundle out widgets as required into a PreviewWidgetList
    PreviewWidgetList widgets({w_header, w_art, w_info, w_tel, w_actions});
    // And push them to the PreviewReplyProxy as needed for use in the preview
    reply->push(widgets);
}

为了能够编译我们的代码,我们必须在Preview.cpp文件的开始部分加入如下的代码:

using namespace unity::scopes;
#include <QDebug>

重新编译我们的Scope。并运行它,我们可以看到如下的画面:

 

在手机的上的运行情况。选择手机为当前的kit(armhf):



我们使用热键Ctrl + R或按下绿色的按钮:

   

这样我们基本上初步完成了我们的Scope。同样,我们可以在搜索的框中输入其它任何一个城市的名字,这样我们就可以搜到不同的结果。整个Scope的源代码可以在如下的地址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient3

我们将在以下的章节中对我们的Scope做进一步的定制。

5)定制Scope


定制Scope图标


我们虽然可以在我们的手机上或者是在emulator上运行我们的Scope,但是它的icon不是我们需要的。我们可以打开data文件夹,并修改里面的“icon.png”及“logo.png”。这样我们就可以得到我们定制的应用图标了。



定制Scope颜色

打开我们Scope在data目录下的文件“com.ubuntu.developer.liu-xiao-guo.dianping_dianping.ini”。并做如下的修改:

[ScopeConfig]
DisplayName = Dianping Scope
Description = This is a Dianping scope
Art = screenshot.png
Author = Firstname Lastname
Icon = icon.png

[Appearance]
PageHeader.Logo = logo.png
PageHeader.Background = color:///#FFFFFF
PageHeader.ForegroundColor = #F8500F
PageHeader.DividerColor = #F8500F
BackgroundColor = #FFFFFF
PreviewButtonColor = #F8500F

这里我们对PageHeader的颜色做了些改变。我们会看到在手机上的不同的颜色的变化。

   

更多关于个性化的设置,可以参阅文章“Customization & branding”。到目前位置的所有源代码在如下的地址可以下载:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient5

定制Category template


我们到目前下面显示的是一个grid的布局,我们想显示更多的信息。先前的template是:

//Create a JSON string to be used tro create a category renderer - uses grid layout
std::string CR_GRID = R"(
{
        "schema-version" : 1,
        "template" : {
        "category-layout" : "grid",
        "card-size": "small"
        },
        "components" : {
        "title" : "title",
        "art" : {
        "field": "art",
        "aspect-ratio": 1.6,
        "fill-mode": "fit"
        }
        }
        }
        )";

由于默认的是水平方向的,我们希望是垂直布局的,这样我们把template修改为:

std::string CR_GRID = R"(
{
        "schema-version" : 1,
        "template" : {
            "category-layout" : "vertical-journal",
            "card-layout": "horizontal",
            "card-size": "small",
            "collapsed-rows": 0
        },
        "components" : {
            "title" : "title",
            "subtitle":"subtitle",
            "summary":"summary",
            "art":{
                "field": "art2",
                "aspect-ratio": 1
            }
        }
})";

同时,为了显示更多的信息,我们加入了“subtitle”及“summary”的项目到“Components”中。我们可以参考文章“Customization & branding”进行对比:

  

为了得到我们所需要的“summary”数据,我们修改我们的Data数据结构为:

    struct Data {
        std::string name;
        std::string business_url;
        std::string s_photo_url;
        std::string photo_url;
        std::string rating_s_img_url;
        std::string address;
        std::string telephone;
        std::string summary; //added
    };

这里,我们新增加一个“summary”的数据,同时我们修改Client.cpp中getData的方法,并增加如下的代码:

        QVariantList deals = item["deals"].toList();

        QString summary;
        if ( deals.count() > 0 ) {
            QVariantMap temp = deals.first().toMap();
            summary = temp["description"].toString();
        }

        qDebug() << "summary: " << summary;

在Query.cpp中,我们也新加入如下的代码来显示我们的“summary”数据:

            catres["subtitle"] = data.address;
            catres["summary"] = data.summary;
            catres["fulldesc"] = data.summary;
            catres["art2"] = data.s_photo_url;

重新运行我们的Scope,我们可以看到如下的显示:

  

显然通过上面的改变,我们已经达到了修改我们的Scope的目的。虽然是同样的数据,我们可以通过修改我们的template来得到不同的显示。所有的源代码在如下的地址可以找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient6

加入设置


我们在这里想对dianping Scope做一个设置。比如我想有更多的搜寻的结果,而不是每次只有最多20个。我们可以通过文章“如何在Ubuntu Scope中定义设置变量并读取”来多我们的limit进行设置。首先,在Query类中加入函数。我们也需要在query.h文件中加入相应的定义:

query.h

private:
    void initScope();

// The followoing function is used to retrieve the settings for the scope
void Query::initScope()
{
    unity::scopes::VariantMap config = settings();  // The settings method is provided by the base class
//    if (config.empty())
//        qDebug() << "CONFIG EMPTY!";

    int limit = config["limit"].get_double();
    cerr << "limit: " << limit << endl;

    client_.setLimit(limit);
}

我们在Client.cpp中定义一个方法:

void Client::setLimit(int limit)
{
    m_limit = limit;
}

这里“m_limit”是一个成员变量。我们同时修改:

QString Client::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "美食";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = QString::number(m_limit);  // 修改这个
    map["platform"] = "2";
 ...
}


并在“run”的开始部分调用它:

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

    // Initialize the scopes
    initScope();

 ....
}

同时不要忘记在“data”目录下生产相应的.ini文件(/dianping/data/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini)。注意这里的格式必须是“包名”-settings.ini。其内容如下:

[limit]
type = number
defaultValue = 20
displayName = 搜寻条数

我们也同时需要对“data”目录下的CMakeLists.txt文件进行修改。添加如下的部分到其中:

configure_file(
  "com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
  "${CMAKE_BINARY_DIR}/src/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
)

INSTALL(
  FILES "${CMAKE_BINARY_DIR}/src/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
  DESTINATION "${SCOPE_INSTALL_DIR}"
)

这个部分的改动是为了能够把设置文件也安装到手机的文件系统中去。我们可以在项目中点击右键,并选择运行“Run CMake”,这样,我们在Project中可以看到新添加的.ini文件。重新运行我们的Scope,并在Scope的右上角的设置图标(像有锯齿的 )去尝试改变limit的值,看看效果是什么样的。

  

这样通过改变搜寻的数目,我们可以看到搜寻的结果的条数的变化。整个源码在如下的地址可以找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient7

6)利用Scope位置信息搜索附近位置的信息


目前我们的点评Scope已经能够完成绝大部分的工作了。我们可以通过它来搜索不同城市的美食佳肴。比如我们可以输入“北京",“上海”,“广州”等城市的美食。美中不足的是我们大多数情况下是希望能够搜索到我们附近的美食等。许多外地的美食可能对我们来讲并不感兴趣。为了能够多位置进行搜索,我们可以利用Scope架构中所提供的位置信息进行对位置进行搜索。我们可以重新对点评API来进行研究,我们发现点评的API中有如下的描述:



在这里我们可以看到,在点评的API中,可以对地理位置经纬度进行搜索。如果我们可以利用当前的经纬度进行搜索,我们就可以找到当前位置的有兴趣的东西了。如果这样,我们搜索的关键词又会是什么呢?我们可以发现API这里有一个关键词category。我想我们可以对category进行搜索。也即对分类进行搜索。通过我们对category的分析,目前category的分类有如下:

美食,休闲娱乐,购物,丽人, 结婚,亲子,运动健身,酒店, 汽车服务,生活服务

在我们的Scope中,我们可以通过输入上面的关键词,这样就可以找到当前位置我们喜欢的美食,酒店等信息。这个信息对于我们来说是非常有用的。
为了实现这个功能,我们在我们的Query类中加入位置信息成员变量:

class Query: public unity::scopes::SearchQueryBase {
public:
    Query(const unity::scopes::CannedQuery &query,
          const unity::scopes::SearchMetadata &metadata, api::Config::Ptr config);
    ~Query() = default;
    void cancelled() override;
    void run(const unity::scopes::SearchReplyProxy &reply) override;

private:
    void initScope();

private:
    api::Client client_;

    QString m_longitude;    // added
    QString m_latitude;  // added
};

同时在query.cpp的开始部分,我们加入两个经纬度常量。这样当我们没有获得位置时,我们将使用这个常量的位置信息来显示当前位置的信息:

const QString DEFAULT_LATITUDE = "39.9698";
const QString DEFAULT_LONGITUDE = "116.446";

在Query::run的方法中加入如下的代码:

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

        // Get the current location of the search
        auto metadata = search_metadata();
        if ( metadata.has_location() ) {
            qDebug() << "Location is supported!";
            auto location = metadata.location();

            if ( location.has_altitude()) {
                cerr << "altitude: " << location.altitude() << endl;
                cerr << "longitude: " << location.longitude() << endl;
                cerr << "latitude: " << location.latitude() << endl;
                auto latitude = std::to_string(location.latitude());
                auto longitude = std::to_string(location.longitude());
                m_longitude = QString::fromStdString(longitude);
                m_latitude = QString::fromStdString(latitude);
            }

            if ( m_longitude.isEmpty() ) {
                m_longitude = DEFAULT_LONGITUDE;
            }
            if ( m_latitude.isEmpty() ) {
                m_latitude = DEFAULT_LATITUDE;
            }

            qDebug() << "m_longitude1: " << m_longitude;
            qDebug() << "m_latitude1: " << m_latitude;
        } else {
            qDebug() << "Location is not supported!";
            m_longitude = DEFAULT_LONGITUDE;
            m_latitude = DEFAULT_LATITUDE;
        }

        client_.setCoordinate(m_longitude, m_latitude);

        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        QString queryString = QString::fromStdString(query.query_string());

        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());

        Client::DataList datalist;
        if (query_string.empty()) {
            queryString = QString("美食");  // changed
            datalist = client_.getData(queryString.toStdString());
        } else {
            // otherwise, get the forecast for the search string
            datalist = client_.getData(query_string);
        }

 ...
}

注意这里,我们加入了代码对位置信息的获取,并同时修改了对搜索的关键词的改动。我们默认的是“美食”而不是先前的“北京”城市。

为了能够进行编译,在Query.cpp的开始部分加入如下的头文件:

#include <unity/scopes/SearchMetadata.h>
#include <QDebug>

我们必须同时在Client类中加入成员变量:

    QString m_longitude;
    QString m_latitude;

同时加入如下的方法:

void Client::setCoordinate(QString longitude, QString latitude)
{
    m_longitude = longitude;
    m_latitude = latitude;
}

当然,最后,我们也不要忘记修改我们的API请求的部分,把经纬度的信息加入进去:

QString Client::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = query; //This is the new search item
    // map["city"] = query;
    map["sort"] = "2";
    map["limit"] = QString::number(m_limit);
    map["platform"] = "2";

    map["latitude"] = m_latitude;
    map["longitude"] = m_longitude;
   
    ...
}

这里我们对“category”进行搜索而不是上面的对“城市”进行搜索。经过这样的改造,我们基本上完成了我们对位置的搜索的Scope了。由于在Desktop上显示对位置不支持,所要对这个对位置的搜索进行测试,我们必须在真实的手机上进行测试。重新编译我们的Scope,并部署到一个真实的手机上。当我们重新运行我们的Scope在手机上时,在“Application Output”窗口显示的是如下的信息“



它显示我们并没有对位置进行支持虽然我们的GPS已经被打开了,这是什么原因呢?这是由于Ubuntu平台的安全性所造成的。我们必须修改我们的相关设置。我们可以参照文章“怎么在Ubuntu Scope中获取location地址信息”对我们的Scope进行配置。打开我们Scope的“com.ubuntu.developer.liu-xiao-guo.dianping_dianping.ini”,并加入如下的句子:

[ScopeConfig]
DisplayName = Dianping Scope
Description = This is a Dianping scope
Art = screenshot.png
Author = Firstname Lastname
Icon = icon.png

LocationDataNeeded=true // added

[Appearance]
PageHeader.Logo = logo.png
PageHeader.Background = color:///#FFFFFF
PageHeader.ForegroundColor = #F8500F
PageHeader.DividerColor = #F8500F
BackgroundColor = #FFFFFF
PreviewButtonColor = #F8500F

重新运行我们的Scope:

   


注意通过这样的改变,我们可以只能对如下的关键词进行搜索:

美食休闲娱乐购物丽人结婚亲子运动健身酒店汽车服务生活服务

一个更为完整的department的Scope可以在“在Ubuntu OS上创建一个department 点评Scope (Qt XML)”找到。到目前位置的所有的源码在如下的地址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient8

7)生成click包及部署到手机中


如果开发者想生成自己的click包或把Scope安装到自己的手机中,我们可以按如下所示的步骤操作:

选择手机target并成功编译





选择“Publish”生成click安装包或直接安装到手机中




如果是生产click安装包的话,文件可以在和项目名称(dianping)处于同一级的下面的一个目录中可以找到,比如对我们的情况:



最终的安装名称是一个以“click”为扩展名的文件。对于我们的情况是“com.ubuntu.developer.liu-xiao-guo.dianping_0.1_armhf.click。”如果我们得到click安装包的话,我们就可以在其它的任何一个手机上进行安装了。我们可以按照文章“怎么安装Ubuntu应用到Device”把我们的click文件安装到手机中了。

对于安装到手机的中Scope,我们可以通过如下的方式打开我们的Scope:








作者:UbuntuTouch 发表于2014-11-14 13:15:58 原文链接
阅读:117 评论:0 查看评论

Read more
UbuntuTouch

如果你还没有安装好你的环境的话,请参考"Ubuntu SDK 安装"章节来安装好自己的SDK环境。这篇文章的主要目的是为了检查我们所安装的环境是否正确以确保我们所安装的环境是正确的。

1)  创建一个简单的QML应用
  • 启动Ubuntu SDK
  • 选中菜单"File" ==> "New File or Project"
  • 选中"App with Simple UI"


  • 选中"Choose",然后选择所需要创建的项目的名字接路经,如下:

\



在创建的时候,一定要输入正确的“Maintainer”的格式,否则在以后编译的时候会有问题。
  • 然后接受默认的设置,就可以完成一个简单的QML应该。如下:


打开manifiest.json文件,检查framework的确保设置为“ubuntu-sdk-14.10”。这是目前手机及模拟器支持的framework。


2)在Desktop上面运行

我们这时可以选择在IDE左下角的绿色的三角按钮或同时按下Ctrl + R。这样我们就可以在默认的情况下在Desktop下运行该应用。如果我们能够看见如下的画面,说明我们的安装是没有问题的。



3)在模拟器上运行应用

为了能够在模拟器上运行我们的应用,我们可以按如下的操作进行:

  • 启动Ubuntu SDK
  • 选择IDE左侧的"Devices",并同时选中我们已经创建的模拟器(我先前已经创建好myinstance)。同时点击图中的绿色的按钮以启动模拟器。



  • 回到我们先前的界面,如果在创建项目时没有选择emulator Kit的话,我们可以通过如下的方式来再添加:

  • 同时我们设置选好运行时的emulator Kit

  • 使用快捷键Ctrl + R 或点击屏幕左下的三角型的运行按钮。 这样我们就可以看到如下的画面:



如果我们看见这样的画面,我们可以认为我们的模拟器环境是没有问题的。我们可以接下来让这个应用在手机中运行。

3)在手机中运行

为了在手机中运行该应用,我们首先得把自己的手机连接到自己的开发电脑。首先我们必须打开手机中的“开发者模式“。具体步骤可以参照我的另外一篇文章:怎么在Ubuntu手机中打开开发者模式

我们可以通过如下的步骤:
  • 启动Ubuntu SDK
  • 点击IDE 左侧的"Devices",并同时点击"Ubuntu Device" (这是一个默认的名字,该名字可以修改)这时我们在Qt Creator IDE中可以看到如下的界面
  • 点击"AutoCreate"按钮,安装Device Kits。这个过程可能需要一些时间,需要耐心等待
  • 保持"Ubuntu Device"为当前选定的设备



  • 回到项目页面。如果先前在创建应用时没有选择手机的Kit,我们可以通过如下的方式加入

  • 同时选择运行时的Kit

  • 直接使用快捷键Ctrl + R或按下屏幕左下方的运行按钮(绿色的三角按钮)。这样就可以在手机上看到该应用的运行情况。


  • 按下”Application Ouput“窗口中的红色正方形按钮,将会终止应用在手机上的运行:



4)创建一个"App with QML extension Library" 应用

现在我们来创建一个带有QML extension Libray的应用,并运行它:







我们选择默认的设置,直至到如下的界面:



记得选中"Ubuntu Device (GCC armhf-ubuntu-sdk-14.10-utopic)",这样是为了可以在以后在手机上面直接运行。如果在创建的时候没有选上,可以在主界面中,选中"Projects",并选中”Add Kit".



为了使得该应用在模拟器中运行:
  • 点击"Devices", 然后点击自己先前创建的模拟器(对我的情况是myinstance)
  • 点击模拟器中绿色的按钮以启动模拟器
  • 如果"Device Kits"没有被添加,点击"AutoCreate"按钮进行安装。期间如果没有安装相应的chroot,系统会提示你安装相应的chroot。如果是这样的话,安装的过程可能需要一定的时间,请耐心等待



  • 等"Device Kits"安装完后,就是如下的画面:


  • 回到"Projects"界面,点击"Add Kit"。选中刚刚创建的"myinstance (GCC i386-ubuntu-sdk-14.10-utopic)" (这个名字可能会和你自己选择的名字不同)
  • 选择IDE左下角的桌面图标,然后选择不同的架构进行运行即可。对模拟器架构来说,选择”myinstance (GCC i386-ubuntu-sdk-14.10-utopic)"。这样就可以使得应用在模拟器中运行了

5)怎么import一个项目并运行它

我们知道,目前我们的SDK支持两种的项目文件:
  • 具有.qmlproject后缀的项目文件,比如“Flickr.qmlproject”。这种情况针对的是项目没有C++代码的纯QML/Javascript项目。目前在SDK中,”App with Simple UI“及”App with tabbed UI"都是这类的项目
  • 具有“CMakeLists.txt”的项目文件。这类项目通常是有C++代码的项目
无论对哪种项目来说,我们只需要打开项目的项目文件即可import整个项目。具体操作如下:



我们也可以直接使用热键Ctrl +O




一旦项目被import进来后,我们就可以直接按我们先前将的方法在不同的架构下运行我们的应用了。如果有的架构没有被加入,我们可以使用如下的方法加入:





一旦选择好我们的架构,可以按下IDE左下角的绿色按钮或热键Ctrl + R。



6)编译并运行我们的Core Apps

如果大家对Ubuntu OS的Core Apps感兴趣的话,可以参考文章“如何编译并安装Ubuntu OS Core Apps”进行编译及运行。

总结,在这编文章中,我们介绍了怎么创建一个最基本的应用及怎么在不同的框架中运行该应用。通过这样的实践,我们可以检验我们的安装环境是否正确,同时也达到熟悉整个的运行环境的目的。在下一个章节中,我们将介绍怎么生成一个click安装包,并如何安装它到手机中。


作者:UbuntuTouch 发表于2014-8-6 9:47:52 原文链接
阅读:227 评论:0 查看评论

Read more
UbuntuTouch

[原]怎么安装Ubuntu应用到Device中

这里我们先设想你们已经把手机刷到Ubuntu Touch最新软件。下面我们来介绍怎么生成Click package,并安装到手机中。开始这前,我们必须确保我们已经在手机上打开开发者模式”。关于如何打开开发者模式,可以参考文章“怎么在Ubuntu手机中打开开发者模式”。


1) 生成Click Package

  • 启动Ubuntu SDK
  • 打开已经创建的应用


  • 选择IDE左下方的目标架构为"Ubuntu Device (GCC armhf-ubuntu-sdk-14.10-utopic)"
  • 选中IDE 左侧的"Publish",在这个框中我们可以直接点击“Install on device”把应用安装到手机中,我们也可以点击“Create and calidate Click package”来生成click文件包,并按下面的方法把生成的包直接安装到手机中

  • 点击"Click Package",这样在和项目目录"test2"平行的一个目录中"build-test2-Ubuntu_Device_GCC_armhf_ubuntu_sdk_14_10_utopic-Default"生成一个叫做"com.ubuntu.developer.liu-xiao-guo.test2_0.1_all.click"的click文件。这个即是可以安装到手机的文件。

2)安装Click文件包到手机上

启动一个Terminal。我们可以通过如下的指令来完成安装的工作

$ adb push com.ubuntu.developer.liu-xiao-guo.test2_0.1_all.click /tmp
$ adb shell "sudo -iu phablet pkcon --allow-untrusted install-local /tmp/com.ubuntu.developer.liu-xiao-guo.test2_0.1_all.click"



我们也可以直接登陆手机然后再在手机中安装:

$adb push *.click /home/phablet
$adb shell
$pkcon --allow-untrusted install-local *.click




这样在手机中的"应用”页面就可以找到我们的应用了。如果找不到的话,可以通过搜索的方式寻找它:



3)通过当前项目生成click包

我们也可以同过IDE的集成环境来完成应用的安装。具体的步骤如下:
  • 选中当前的项目(对纯QML项目,无C++代码)
  • 在项目名称上点击右键


我们可以在项目当前目录退后的一个目录找到所需要的click包。比如对我们的项目”balloon"来说,在目录build-balloon-UbuntuSDK_for_armhf_GCC_ubuntu_sdk_14_10_utopic-default里可以找到"com.ubuntu.developer.liu-xiao-guo.balloon_0.1_all.click"包。一旦生成这个包,我们可以按上述讲的方法来安装我们生成的应用。


4)查看Click安装包中的内容。

有时我们想查看一下Click安装包中到底有那些的内容,我们可以打入如下的命令:

$ click contents com.ubuntu.developer.liu-xiao-guo.test2_0.1_all.click


我们也可以通过如下的命令来得到click包里所有的文件。把我下面的click包文件名换成你自己的包的名字即可以

dpkg -x myapp.click unpacked
file unpacked/path/to/your/binary

通过”file"命令来查看文件的特性,比如:

/tmp/unpacked/lib/arm-linux-gnueabihf/bin/filemanager: ELF 32-bit LSB  executable, ARM, . . 

可以看到确实,该文件是一个ARM的可执行文件。



关于click命令还有其他的很多的功能,我们可以通过:

$ click --help

来查看它的具体的用法。

5) 登陆到手机

我们可以通过如下的命令来登陆到手机

$ adb shell



等进入手机后,我们就可以做任何我们可以做的事。如果果需要在手机上安装软件的话,我们需要在Terminal中输入如下的命令:



打入这样的命令后,手机会重启。等重新启动后,就可以在手机中直接安装软件了。千万要记得安装的密码就是我们手机启动设置的密码

6) 通过Terminal命令来生产click package


对有“CMakeLists.txt”的项目(通常是有C++代码的项目),我们也可以通过如下的命令来生产click package文件。首先我们使用Terminal进入到项目的目录(含有CMakeLists.txt)的目录,并键入如下的命令:

$click-buddy --arch armhf --framework ubuntu-sdk-14.10

一旦生产click package文件,我们就可以通过上面的方法来进行安装我们的应用了。


作者:UbuntuTouch 发表于2014-8-6 9:56:09 原文链接
阅读:209 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu SDK 安装Qt SDK安装

在这篇文章里,你将学到如何安装Ubuntu SDK到你的系统中,并生成一个简单的应用以测试你的安装是否成功。对英文好的学习者,可以参考Ubuntu 网站中的英文地址来进行安装。


操作系统选择

Ubuntu for phone的开发是基于Ubuntu 14.10 (Utopic)上的。为了能够使得Scope应用的开发编译成功,Ubuntu SDK应该安装在Utopic (14.10)的Ubuntu OS之中。如果你使用的操作系统不是这个版本的,你可以安装一个VM(比如VirtualBoxVMWare),在VM中再安装Ubuntu OS 14.10。关于如何安装VirtualBox,请参阅文章”怎么在Virtualbox下安装Ubuntu OS“。如果你想在你的电脑里从一个分区里安装Ubunut系统,你也可以参考文章“How to use manual partitioning during installation”。

对于一些开发者来说,我们提供了一个比较快捷的安装包。这个安装包把Ubuntu OS及SDK打到一个文件里,该文件可以一次性下载,并安装到VirtualBox中。详细的安装步骤在“在不同的系统中的virtualbox中安装Ubuntu SDK”找到。

添加Phablet Tools PPA

Phablet Tools PPA 提供了一些额外的工具来对device进行安装。这个工具是安装在从Ubuntu OS 12.04以后的版本中的。

你可以在Ubunt 14.04 Trusty 以后的版本中并不需要添加,因为它已经在Ubuntu通用的发布中。你可以通过如下的方式进行添加:

$ sudo add-apt-repository ppa:phablet-team/tools

添加Ubuntu SDK 发布 PPA中

按照一下方式添加Ubuntu SDK 发布 PPA (https://launchpad.net/~ubuntu-sdk-team/+archive/ppa)。
输入你的Linux管理员密码

$ sudo add-apt-repository ppa:ubuntu-sdk-team/ppa

安装 Ubuntu SDK

按一下方式安装SDK。在需要的时候输入Linux管理员密码

$ sudo apt-get update && sudo apt-get install ubuntu-sdk

提示:对一些人,特别是对那些安装Ubuntu 14.10 ( Utopic)的开发者来说,必须确保所有的安装的包更新到最新的版本。这个可以通过如下的命令实现:

$ sudo apt-get update && sudo apt-get dist-upgrade

启动Ubuntu SDK IDE

  • 在Ubuntu "Unity Dash Applications lens"中寻找 "Ubuntu SDK
  • 点击找到的”Ubuntu SDK" 图标


你也可以在shell中启动Ubuntu SDK:

$ ubuntu-sdk 

提示:对一些开发者来说,他们可能很想让Ubuntu SDK IDE的图标出现在Ubuntu Unity 的启动面板中,这样可以每次很方便地启动。只要先启动SDK,然后在Ubuntu桌面的左侧的启动面板中,找到SDK的图标,并按下右键,然后选定"Lock to Launcher"。这样,SDK 就可以固定在启动的面板中了。



当我们第一次启动Ubuntu SDK时,可以看到如下的界面:



我们可以在SDK的第一次启动过程中来安装armhf chroot (为手机架构)及i386 chroot (为emulator架构)。依赖于网络的速度,这个安装的过程比较漫长,所以请大家耐心等待!

     


如果我们在SDK启动时,选择不再显示安装wizard,并且我们选择不安装armhf及i386架构,我们也可以在下面的步骤中来安装它们。

安装Ubuntu SDK armhf chroot

这个步骤是为了交叉编译我们所开发的应用(armhf格式)并部署到手机上。我们可以通过如下的步骤进行安装:

  • 启动Ubuntu SDK
  • 选中IDE菜单中的"Tools",然后在选中"Options",然后再选中”Ubuntu"。就会看到如下的画面
  • 点击"Create Click Target",然后可以看到如图所示的对话框。选择"armhf/Framework-14.10"即可。之后你可以看到安装开始。依赖于你的网络的情况,安装需要一段时间。耐心等待。


在上图中,我们可以看到已经安装好的"utopic ubuntu-sdk ... armhf",这里我们可以点击"update"来更新我们所安装的包,同时,我们也可以看到"Maintain"这个按钮。这个是用来对我们的chroot来进行维护的。比如说我们所开发的应用中,可能需要一个库,但它不是标准的库,没有安装。这时我们想测试时,就可以点击这个按钮,并在shell中进行安装或删除某个包。当然我们必须也要记得在手机中进行安装这个库以使编译好的应用能够运行。

等安装完后,我们可以在shell中看到如下的信息:

~$ schroot -l
chroot:click-ubuntu-sdk-14.10-armhf
chroot:trusty-amd64-armhf
chroot:trusty-armhf
chroot:utopic-amd64-armhf
source:click-ubuntu-sdk-14.10-armhf
source:trusty-amd64-armhf
source:trusty-armhf
source:utopic-amd64-armhf

这里 "chroot:click-ubuntu-sdk-14.10-armhf"就是我们在这个步骤中安装的chroot。有了这个我们就可以为手机target生成目标安装文件进行部署了

安装Ubuntu SDK i386 chroot

这个安装是为了使得以后我们含有C++代码(比如说C++ plugins)的应用能够顺利编译并使得应用在模拟器中运行。我们可以一并安装,在以后需要的时候我们可以生下这个步骤。这个安装过程同样需要很长的时间。需要耐心等待。这个安装步骤和上面几乎是一样的,只是我们需要选择"i386"架构。



安装完后,我们可以在shell中通过如下的命令查看已经安装好的chroot:

~$ schroot -l
chroot:click-ubuntu-sdk-14.10-armhf
chroot:click-ubuntu-sdk-14.10-i386
chroot:trusty-amd64-armhf
chroot:trusty-armhf
chroot:utopic-amd64-armhf
source:click-ubuntu-sdk-14.10-armhf
source:click-ubuntu-sdk-14.10-i386
source:trusty-amd64-armhf
source:trusty-armhf
source:utopic-amd64-armhf

安装模拟器

这个步骤是为了安装一个在手机一个模拟器以仿真一个手机,这样开发者可以在电脑上进行开发及测试。等调试好了以后,就可以部署到我们的真手机中以进行下一步的测试。具体的安装步骤如下:

  • Ubuntu 启动SDK
  • 选择IDE左侧的"Devices",然后在所在的界面中点击图中的"+"。这样就可以看到如下的画面
  • 在所显示的对话框中,输入所需要的模拟器的名字。选择"i386",然后点击"Create"即可。整个过程可能会花很长的时间完成。请耐心等待。这个安装虽然也可以选择"armhf"来进行模拟,但目前建议的还是"i386"架构。


注意当我们安装摸拟器时,我们最好选择“devel”或“stable”  channel(stable是最新Ubuntu的官方发行版,devel是最新经过验证的daily build)。“devel-proposed”是包含最新变化的发行版。这个版本还需要被验证,并且可能有bug。



有了这个模拟器,我们就可以在模拟器中运行我们开发的应用了。我们可以选择刚才生成的模拟器(myinstance),并运行它:



注意,当我们运行模拟器时,如果被提问需要密码时,这个默认的密码是“0000实际运行的效果图如下,



开发者也可以参阅https://wiki.ubuntu.com/Touch/Emulator文章来安装自己的模拟器。

安装搜狗中文输入法


我们知道对中文应用开发者来说,中文的支持很重要。开发者可以参考我的文章“怎么在Ubuntu OS上面安装搜狗输入法及对Qt Creator的支持"来进行安装。

Qt SDK 安装

这个安装步骤是不必须的。对于有些开发者来说,想更多地学习Qt,并且在硬盘存贮允许的情况下可以在http://qt-project.org/downloads下载并安装最新的Qt SDK。Qt SDK里有丰富的例程,是我们学习Qt的一个很好的资源。


总结

至此,我们的开发安装环境基本上已经好了。在下一个章节中,我们来试着创建一个应用来检测一下我们的环境是否已经成功了。我们可以转到"开发第一个Ubuntu Touch应用"来检查我们的安装环境是否正确。

作者:UbuntuTouch 发表于2014-8-6 8:53:28 原文链接
阅读:304 评论:0 查看评论

Read more
UbuntuTouch

目前在Ubuntu提供了丰富的开发途径供开发者进行开发。它覆盖了从本地开发到Web的应用开发。下面我来介绍一下有那些开发的语言及途径可以用来开发:





1)Scope 开发

Scope 这个词对很多的开发者来说可能比较陌生,到目前为止也没有一个相应的中文词来精确地描述它到底是什么。简单地说Scope是Ubuntu OS区别于其他系统的一个独特的UI显示及应用框架。它很容易使得应用的内容得以显示及按照不同的要求进行呈现。使用Scope,可以使得运营商,OEM及应用开发着可以很方便地把一些Web Service的内容方便简洁地呈现,并使得内容很容易被搜索和发现。常见的Scope有应用商店,音乐商店,视频商店等。我们也可以包Amazon及淘宝商店也用Scope进行实现。下面我们通过一些图片看一些Scope到底长的是什么样子:










就其实质,就是利用一些现有的Web Service API来通过返回的json或xml数据,Scope利用其自身已经建立起的显示框架(有许多不同的布局供选择)来呈现数据,同时利用Scope的框架很方便地进行搜寻。

Scope的开发可以使用Qt C++, C++11及Scope 相应的C++API进行开发。

更多关于Scope的资料可以在Ubuntu的网站上http://developer.ubuntu.com/scopes/查看到。在那里可以看到相应的Tutorials

2)利用Qt/QML (QtQuick)进行开发

Ubuntu的应用基本上都是使用Qt C++/QML进行开发的。这也是Ubuntu的本地开发语言。使用Qt 语言可以开发出很炫很酷的应用。目前BlackBerry的平台也是使用Qt/QML来进行开发的。具体关于Qt/QML的更多的介绍,开发者可以在网站http://qt-project.org/找到。在Ubuntu 网站上,我们也可以看到关于Qt/QML的一些介绍,请查阅网站http://developer.ubuntu.com/apps/qml/tutorial/

3)利用HTML 5开发Web应用

Ubuntu上面也可以使用HTML 5进行应用开发。通过使用ApacheCordova & Phonegap及HTML 5,Javascript, CSS,开发者可以开发出像本地应用一样界面的应用。通过Cordova API接口,应用可以访问系统的很多的应用接口,比如Camera, battery, geolocation, media-capture等。

更多的关于Web的应用开发可以在Ubuntu网站http://developer.ubuntu.com/apps/html-5/tutorials/


作者:UbuntuTouch 发表于2014-8-6 9:58:16 原文链接
阅读:154 评论:0 查看评论

Read more
UbuntuTouch

[原]怎么在Virtualbox下安装Ubuntu OS

在这篇文章中,我们来介绍如何安装VirtualBox及在VirtualBox下面安装最新的Ubuntu操作系统。


1)下载VirtualBox文件

首先我们到网站 https://www.virtualbox.org/wiki/Downloads下载最新的virtualbox。



根据自己的操作系统,在该网站上下载两样东西:

  • VirtualBox platform packages
  • VirtualBox 4.3.16 Oracle VM VirtualBox Extension Pack 
你可以永远下载最新的版本。

2)安装VirtualBox

我们在Ubuntu OS或Windows下,双击刚刚下载的VirtualBox文件,系统会自动帮我们调用软件帮我们安装。在Ubuntu OS下的界面如下:



至此,我们已经完成了对VirtualBox的安装。我们可以在Ubuntu中打开dash,找到VirtualBox的图标。在Windows上,我们也可以通过类似的方法找到相应的图标:



点击VirtualBox图标,我们可以看到如下的画面:



3) 下载Ubuntu Desktop Image

由于在Ubuntu for phone的开发中,我们将使用Utopic (14.10)来进行开发。我们在网址http://cdimage.ubuntu.com/daily-live/current/下载适合自己平台的image。这里我们选择utopic-desktop-amd64.iso。我们把image存放于我们电脑中的一个目录中。

4) 在VirtualBox中安装Ubuntu OS

打开"Oracle VM VirtualBox Manager",并点击“New”。可以看到如下的画面:



紧接着,我们根据我们电脑的实际情况给予一定的内存大小。



对于我们首次创建Ubuntu OS来说,我们选择“Create a virtual hard drive now”。如果你曾经创建过,并存有一个virtual hard drive的文件,你可以直接选择“Using an existing virtual hard drive file”。这样创建起来更块。



接下来:



再接下来,我们选择“Dynamically allocated”。



紧接着,我们选择至少有20G的硬盘容量以装载整个OS及SDK的安装:



这样就完成了我们初步的设置。下面我们来真正地来安装我们的Ubuntu OS系统。

点击VirtualBox中的“Start




我们选择我们刚才下载好的image:



再选择“Install Ubuntu



如果网络的情况好,可以在安装的过程中同时下载更新及第三方的软件。











紧接下来,就开始安装Ubuntu OS了:



最终,我们把Ubuntu系统安装好了。




5) 安装VirtualBox Extensions

我们发现当我们把Ubuntu OS安装好后,界面的分辨率是不对的。我们没法把它的分辨率调到我们的主屏幕的分辨率。这时我们需要安装Extensions。



首先,我们关掉Ubuntu OS。打开VirtualBox,并点击菜单中的“Preferences”:



点击"Extensions", 



重新启动我们创建的VM。



然后选择:



如果弹出一个对话框,就点击“运行”(Run)即可。如果没有对话框出现的话,寻找“Terminal



打开“Terminal”应用,并输入如下的命令:



等安装完后,我们重新启动VM。我们就可以看到全屏的Ubuntu系统了。它的分辨率和Host的分辨率应该是一样的。另外,如果在任何时候我们在VM中的系统升级而造成的分辨率下降或共享文件夹不可以工作,我们都可以通过上面的方法重新编译,并使得显示正常。

6) 设置VirtualBox


点击VirtualBox的“Settings”,选择“Display”进行设置。




点击“Shared Folders”,来设置可以在host及guest OS之间进行拷贝文件。



同时,我们在VM中的Ubuntu OS中的“Terminal”键入如下的命令:

$sudo usermod -a -G vboxsf username

这里“username"是用户名。具体操作如下:

 

在VM中打开文件浏览器,即可看见Host中被分享的文件夹。文件名通常会有一个“sf”开始,表示shared folder。




选择“General”,我们可以设置可以在host及guest系统之间进行拷贝。这个对我们有时非常有用。



这样我们基本上对VirtualBox的设置已经完成。当然,你们如果有兴趣的话,可以对更多的选项进行设置。


7) 安装中文输入法

我们知道中文输入法对开发中文的应用是非常有用的。我们可以参照文章“怎么在Ubuntu OS上面安装搜狗输入法及对Qt Creator的支持”进行输入法的设置。

8) 安装Ubuntu SDK

我们可以按照“Ubuntu SDK 安装”来安装我们的SDK来进行我们的Ubuntu应用开发了。


作者:UbuntuTouch 发表于2014-9-24 13:34:18 原文链接
阅读:314 评论:0 查看评论

Read more
UbuntuTouch

当我们刷最新的Ubuntu手机软件时,我们必须打开开发者模式。否则,当我们使用"adb"命令时,会出现如下的情况:




当手机和电脑连接后,不能查看到任何的device。当我们打开开发者模式后,我们可以看到如下的画面:



这里我们可以看到,我们通过"adb"命令可以看到连接到的device。


为了打开开发者模式,我们可以通过如下的步骤来操作:

1)打开“系统设置”应用




2) 选中“关于此手机”




3)点击“开发者模式”




4) 打开开关




一旦完成开发者模式的设置,我们就可以对手机进行部署了。

我们也可以通过“adb shell"命令对手机的文件系统进行操作




作者:UbuntuTouch 发表于2014-9-23 10:06:47 原文链接
阅读:384 评论:0 查看评论

Read more
UbuntuTouch

搜狗输入法是一个非常流行的输入法。在Ubuntu系统上没有默认安装这个输入法。在网上搜索,会发现不同的网站给出不同的方法。有些是工作的,有些不工作。现在,这里我把我的体会及安装步骤写下来。希望对开发者们有帮助。中文输入法对我们在Ubuntu Phone上开发中文的应用也是非常有帮助的。安装好中文输入法,我们可以在Qt Creator及Scope的测试工具中输入汉字来开发我们的中文应用。


1)首先下载“搜狗输入法for Linux”


我们可以到如下的网站“http://pinyin.sogou.com/linux/”下载最新发布的搜狗输入法。根据自己CPU的位数,下载合适的版本。



我们把文件存于默认的“Downloads”的文件夹中。当然你们也可以找一个自己喜欢的文件夹来存储这个debian文件。

2)安装搜狗输入法的debian文件

首先我们打开存放搜狗输入法的文件夹,并双击该文件:



我们可以看到系统会自动调用“Ubuntu Software Center”来安装已经下载的sogoupinyin软件。我们等待其安装完成。

3)安装Ubuntu OS的语言支持

我们首先来打开Ubuntu OS的设置。点击屏幕右上方的设置图标。



选择“System Settings..."。然后我们可以看到如下的画面:



点击“Language Support”图标。可以看大如下的画面:



如果你的系统还没有安装中文的话,请选择“Install/Remove Languages...”来安装对中文的支持。并同时选择“fcitx”。最终的画面为:



4) 配置搜狗中文输入法


重新启动系统,并点击带有“en”字样的屏幕右上方的图标:



点击屏幕右上角带有“en”字样的图标,或带有键盘的图标。


选择最后的一个“Text entry”项,并点击“+”来添加搜狗输入法。点击“OK"。重新启动系统即可。

5)添加对Qt的中文支持

启动Qt Creator。如果你在Qt Creator中输入汉字(使用Ctrl + Space组合键),没有中文显示的话,我们必须在termnial中输入如下的指令

$ sudo apt-get install fcitx-frontend-qt5

这样,重新启动Qt Creator。我们就可以输入汉字了。



6) 对Qt Creator 定制

在上面我们虽然已经完成了对中文输入法的安装。通常我们使用Ctrl+Space组合键来启动输入法的转变,可是,在Qt Creator中Ctrl+Space有一个特别的用途。具体描述如下:



当我们点击Ctrl+Space时,在QML文件中会显示该item的properties。对于大多数已经适应中文输入法切换的开发者来说,我们可以修改Qt Creator中的设置来改变这个功能的热键。



如果你不愿意自己修改Qt Creator中的设置,你也可以尝试修改切换输入法的方式。在Terminal中键入:

$ fcitx-config-gtk3

通过修改改应用中的设置达到目的!

至此。我们对Qt Creator的支持已经完成了。

作者:UbuntuTouch 发表于2014-9-23 11:45:26 原文链接
阅读:346 评论:0 查看评论

Read more
UbuntuTouch

[原]在Ubuntu上的C++及QML混合编程

在这篇文章中,我讲述如果使用QML来调用C++来扩展我们的应用。我们知道,在许多的场景中,QML虽然很强大,在UI方面是非常突出的,但是,有的时候,我们可能会想到应用我们的C++代码。这有一下方面的原因:

1)我们已经有成熟的C++引擎设计或协议等。比如我们已经用C++设计好了我们的Fetion协议代码。我们没要再用另外一个低性能的语言来重新写一边

2)有些功能我们没有办法使用Javascript来完成,必须使用系统或专用的一些功能包来完成。这时我们可以使用C++语言来完成我们需要的功能

3)有些代码对计算的要求非常高,需要使用大量的CPU时间,这时,我们可以考虑使用C++来完成这部分的工作


事实上,在Ubuntu OS上,我们可以可以使用QML/Javascript来创建非常炫的UI,同时我们也可以使用C++来完成我们特有的一些功能(比如需要大量计算)。两者之间的结合是非常自然的和密切的。你可以想像C++是可以用来拓展QML的功能的。


在这里,我们来创建一个应用读取Linux的一些环境变量。通过这个练习,我们可以掌握怎么在QML文件中调用C++的代码。

1)创建一个基本的应用

  • 启动Ubuntu IDE,选定"App with QML extension Library"模版应用



  • 设定应用的设置


  • 选择所需要运行的架构。这里我们选择"Desktop"及在手机上能运行的“armhf"架构


至此,我们基本上已经产生了一个可以在手机和电脑上运行的应用。选择合适的架构,点击IDE左下角的运行键



如果你们在运行到这里,还有什么问题的话,请参照我先前的文章正确安装你的环境。

2)添加C++功能模块


首先我们找到项目的子目录“ReadEnv/backend/modules/ReadEnv”,在这里我们创建两个文件“readenv.cpp”及“readenv.h”。他们的内容如下:


#ifndef READ_ENV_H
#define READ_ENV_H

#include <QObject>

class ReadEnv : public QObject
{
    Q_OBJECT
public:
    explicit ReadEnv(QObject *parent = 0);
    Q_INVOKABLE QString getenv(const QString envVarName) const;

private:
};

#endif // READ_ENV_H

#include "readenv.h"
#include <QDebug>

ReadEnv::ReadEnv(QObject *parent) : QObject(parent)
{
}

QString ReadEnv::getenv(const QString envVarName) const
{
    QByteArray result = qgetenv(envVarName.toStdString().c_str());
    QString output = QString::fromLocal8Bit(result);
    qDebug() << envVarName << " value is: "  << output;
    return output;
}

这是一个非常简单的C++类。这个类必须是从QObject类继承,这样可以使得这个类可以使用Qt的signal-slot机制。如果大家对这个还不熟的话,可以参阅一些资料。对一个这样的类来说,如果它的函数或方法可以被QML调用的话,必须定义为“Q_INVOKABLE”。另外所有被定义为"slot"的函数或方法也可以被QML中的JS直接调用。我们可以通过更多的例子来说明这个问题。“getenv"方法很简单。它直接读取所需要的环境变量的值,并通过转换返回。

我们也同时打开该目录下的另外一个文件"backend.cpp"。经过修改的文件显示如下:

#include <QtQml>
#include <QtQml/QQmlContext>
#include "backend.h"
#include "mytype.h"
#include "readenv.h"


void BackendPlugin::registerTypes(const char *uri)
{
    Q_ASSERT(uri == QLatin1String("ReadEnv"));

    qmlRegisterType<MyType>(uri, 1, 0, "MyType");
    qmlRegisterType<ReadEnv>(uri, 1, 0, "ReadEnv");
}

void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
{
    QQmlExtensionPlugin::initializeEngine(engine, uri);
}

这里其实也没有做什么。加入了一句

 qmlRegisterType<ReadEnv>(uri, 1, 0, "ReadEnv");

这里的第一个“ReadEnv"对应于我们所定义的类的名称,而不是项目的名称。第二个“ReadEnv"究其实是在QML使用的来实例化一个这样的类。它的名称可以和实际的类不同名字,也可以是任何一个你喜欢的名称。不过你必须也要在QML文件中做相应的修改。通过这样的注册,该类就可以在QML中进行实例化,并引用!等我们已经把源码已经做好,我们必须把它加入到我们的项目中进行编译。我们打开位于"backend"目录下的”CMakeLists.txt"文件中,加入如下的语句:

set(
<pre style="margin-top: 0px; margin-bottom: 0px;"><!--StartFragment-->    ReadEnvbackend_SRCS<!--EndFragment-->

<span style="font-family: Arial, Helvetica, sans-serif; color: rgb(192, 192, 192);">        </span><span style="font-family: Arial, Helvetica, sans-serif;">modules/ReadEnv/backend.cpp</span>
    modules/ReadEnv/mytype.cpp
    modules/ReadEnv/readenv.cpp
)

我们可以通过关掉项目再重新打开应用的方式,使得项目中的文件目录得到更新。也可以使用点击右键的方式使新加入的文件得到正确的显示:
这样,我们的C++部分的代码基本上就已经完成了。

3)修改QML部分的代码

上面我们已经完成了基本的C++部分的代码,我们在这里继续来完成QML部分的界面。首先我们来熟悉一下QML的layout。我们知道layout可以很方便地帮我们管理我们的控件,并在适当的时候自动适配合适的屏幕尺寸及手机方向的改变。QML中有Column及Row来帮我们管理我们的控件:
        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 10

            Label {
                text: "Value:  "
                fontSize: "large"
            }

            TextArea {
                id:value
                readOnly: true
                contentWidth: units.gu(40)
                contentHeight: units.gu(80)
            }

        }


这里我们并排放上两个控件,一个是Label,另外一个是"TextArea"。通过这样的组合,这整个又可以看做是一个复合的控件,里面有两个控件。它整个又可以被放到它外面的布局管理器(Row, Grid, Flow, Column等)中。通过我的修改,我们创建了如下的界面及界面。




我们通过修改“ReadEnv.qml” 来实现界面。具体的代码如下:

import QtQuick 2.0
import Ubuntu.Components 0.1
import ReadEnv 1.0
import "ui"

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.liu-xiao-guo.ReadEnv"

    anchorToKeyboard: true

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    width: units.gu(100)
    height: units.gu(75)

    ReadEnv {
        id: readEnv
    }

    Column {
        // anchors.fill: parent
        //        anchors.verticalCenter: parent.verticalCenter
        //        anchors.horizontalCenter: parent.horizontalCenter
        anchors.centerIn: parent
        spacing: 20

        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 10
            Label {
                text: "Variable:"
                fontSize: "large"
            }

            TextField {
                id:input
                text:"PWD"
                focus: true

                placeholderText: "Please input a env variable"

                Keys.onPressed: {
                    if (event.key === Qt.Key_Return) {
                        print("Enter is pressed!")
                        value.text = readEnv.getenv(input.text)
                    }
                }
            }
        }

        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 10

            Label {
                text: "Value:  "
                fontSize: "large"
            }

            TextArea {
                id:value
                readOnly: true
                contentWidth: units.gu(40)
                contentHeight: units.gu(80)
            }

        }

        Button {
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Get"
            onClicked: {
                print("Button is clicked!")
                value.text = readEnv.getenv(input.text)
            }
        }
    }
}



特别注意的是:
  • 在调用backend的库时,我们必须使用如下的动作来完成。
	import ReadEnv 1.0
这里的“ReadEnv",实际上对应于在“backend.cpp"中的如下语句中的“ReadEnv"。如果该处发生变化,import语句也得变化

    Q_ASSERT(uri == QLatin1String("ReadEnv"));
我们也可以在手机的安装环境中可以看出。"ReadEnv"同时也是在“lib"下的一个目录名称。其实这个理解是非常重要的。所有的import中的文字其实就是表示的是路经。它标示了库所在的位置




  • 我们通过如下的语句来对"ReadEnv"类进行实例化。我们在实例化的同时,也给了一个ID,这样就可以在其他的地方引用了。在本应用中,我们在Button中调用
      ReadEnv {
          id: readEnv
      }

        Button {
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Get"
            onClicked: {
                print("Button is clicked!")
                value.text = readEnv.getenv(input.text)
            }
        }


这个应用的运行效果为:


好了今天我们已经完成了一个练习。它实现了从QML中调用C++代码。我们以后还可以添加更多的功能到这个应用中。本例程的代码可以在如下网站找到:

bzr branch lp:~liu-xiao-guo/debiantrial/readenv

作者:UbuntuTouch 发表于2014-8-14 13:28:23 原文链接
阅读:70 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章里,我们将使用Ubuntu SDK从零开始来创建一个“中国天气”的Scope应用。通过这个过程,让开发者了解Scope在Ubuntu上的开发流程,以及对Scope有更深的认识。该应用完全使用std C++来完成的。更多关于Scope的知识,可以在网址:http://developer.ubuntu.com/scopes/。我们开发应用的最终显示图为:


        


1)启动Ubuntu SDK来创建一个基本的Scope应用


首先,我们来打开我们的Ubuntu SDK来创建一个最基本的应用。我们选择菜单“New file or Project”或使用热键“Ctrl + N”。我们选择“Unity Scope”模版。



我们给我们的应用一个名字“ChinaWeather”。我们同事也选择template的类型为“Scope using HTTP+JSON API”

   

接下来,我们也同时选择不同的Kit,这样我们都可以在他们上面编译并部署我们的应用。




我们直接在电脑的Desktop上运行我们的应用。为了确保我们能够在desktop上运行我们的scope并看到界面,我们可以点击“Projects”,并在Desktop中的“Run Configuration”中进行设置。确保选中“chinaweather”。




我们可以在“Unity Scope tool”中输入北京,我们就可以看到北京的天气的情况:



如果你能运行到这里,说明你的安装环境是没有问题的。如果有问题的话,请参阅我的Ubuntu SDK安装文章。这个最基本的应用其实没有什么内容。在下面的章节中我们来向这里添加一些东西以实现我们所需要的一些东西。

如果大家有手机的话,也可以直接在手机上运行看一下运行的效果。




2)完成我们的Client API代码


我们可以看到在项目的“src”目录下有两个目录:apiscope。api目录下的代码主要是为了来访问我们的web service来得到一个json或是xml的数据。这个数据可以在我们的Scope中进行利用并得到显示。下面我们来完成我们的工作。

首先我们需要在百度的开发者网站来申请我们的开发者账号。大家可以放问网站来申请账号。我们首先来做一个测试以确保我们的账号是可以工作的。按照文中所提到的,我们可以在浏览器中输入如下的地址:http://api.map.baidu.com/telematics/v3/weather?location=%E5%8C%97%E4%BA%AC&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ。我们可以得到如下的内容:

{"error":0,"status":"success","date":"2014-09-29","results":[{"currentCity":"北京","pm25":"42","index":[{"title":"穿衣","zs":"较冷","tipt":"穿衣指数","des":"建议着大衣、呢外套加毛衣、卫衣等服装。体弱者宜着厚外套、厚毛衣。因昼夜温差较大,注意增减衣服。"},{"title":"洗车","zs":"较不宜","tipt":"洗车指数","des":"较不宜洗车,未来一天无雨,风力较大,如果执意擦洗汽车,要做好蒙上污垢的心理准备。"},{"title":"旅游","zs":"适宜","tipt":"旅游指数","des":"天气较好,温度适宜,但风稍微有点大。这样的天气适宜旅游,您可以尽情地享受大自然的无限风光。"},{"title":"感冒","zs":"易发","tipt":"感冒指数","des":"昼夜温差大,风力较强,易发生感冒,请注意适当增减衣服,加强自我防护避免感冒。"},{"title":"运动","zs":"较适宜","tipt":"运动指数","des":"天气较好,但风力较大,推荐您进行室内运动,若在户外运动请注意避风保暖。"},{"title":"紫外线强度","zs":"弱","tipt":"紫外线强度指数","des":"紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。"}],"weather_data":[{"date":"周一 09月29日 (实时:23℃)","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/qing.png","weather":"多云转晴","wind":"北风4-5级","temperature":"23 ~ 10℃"},{"date":"周二","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/yin.png","weather":"多云转阴","wind":"微风","temperature":"18 ~ 12℃"},{"date":"周三","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/zhenyu.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/zhenyu.png","weather":"阵雨","wind":"微风","temperature":"15 ~ 12℃"},{"date":"周四","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/duoyun.png","weather":"多云","wind":"微风","temperature":"23 ~ 13℃"}]}]}

首先,我们可以看到API是工作的。没有任何问题。显示的架构是json格式的。我们下面来修改架构中的“Client”类来完成对所得到的json格式的内容进行解析。首先,我们删除整个“Client::Current Client::weather(const string& query)”函数,因为这个是我们不需要的。为了能够编译,我们也删除或注释掉在query.cpp文件run函数中的部分内容,这样我们可以集中精力来完成这个Client API的设计。我们只留下最基本的部分以帮助我们来完成如下的设计。

void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());

        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());

        Client::Forecast forecast;
        if (query_string.empty()) {
            // If there is no search string, get the forecast for London
            forecast = client_.forecast_daily("北京");
        } else {
            // otherwise, get the forecast for the search string
            forecast = client_.forecast_daily(query_string);
        }

        // Register a category for the forecast
        auto forecast_cat = reply->register_category("forecast",
                                                     "7 day forecast", "", sc::CategoryRenderer(WEATHER_TEMPLATE));

        // For each of the forecast days
        for (const auto &weather : forecast.weather) {
            // Create a result
            sc::CategorisedResult res(forecast_cat);
        }

    } catch (domain_error &e) {
        // Handle exceptions being thrown by the client API
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}


为了能够正确地使用API,我们还必须在项目的设置文件中做一些设置。打开IDE项目中的api文件夹,并打开config.h文件。把它的内容修改为:

#ifndef API_CONFIG_H_
#define API_CONFIG_H_

#include <memory>
#include <string>

namespace api {

struct Config {
    typedef std::shared_ptr<Config> Ptr;

    /*
     * The root of all API request URLs
     */
    std::string apiroot { "http://api.map.baidu.com" };

    /*
     * The custom HTTP user agent string for this library
     */
    std::string user_agent { "chineweather 0.1; (foo)" };
};

}

#endif /* API_CONFIG_H_ */


为了适应我们的情况,我们把forecast_daily API修改为:

    virtual Forecast forecast_daily(const std::string &query);

这是因为我们的百度API中不需要天数。为了能够使得我们的数据结构和我们上面百度天气API接口返回的数据相匹配,我们对“client.h”做了修改:

class Client {
public:
    /**
     * Information about a City
     */
    struct City {
        unsigned int id;
        std::string name;
        std::string country;
    };

    /**
     * Weather information for a day.
     */
    struct Weather {
        std::string date;
        std::string dayPictureUrl;
        std::string nightPictureUrl;
        std::string weather;
        std::string wind;
        std::string temperature;
        std::string uri;
    };

    /**
     * A list of weather information
     */
    typedef std::deque<Weather> WeatherList;

    /**
     * Forecast information about a city
     */
    struct Forecast {
        City city;
        std::string pmIndex;
        WeatherList weather;
    };

    Client(Config::Ptr config);

    virtual ~Client() = default;

    /**
     * Get the weather forecast for the specified location and duration
     */
    virtual Forecast forecast_daily(const std::string &query);

    /**
     * Cancel any pending queries (this method can be called from a different thread)
     */
    virtual void cancel();

    virtual Config::Ptr config();

protected:
    void get(const core::net::Uri::Path &path,
             const core::net::Uri::QueryParameters ¶meters,
             Json::Value &root);

    /**
     * Progress callback that allows the query to cancel pending HTTP requests.
     */
    core::net::http::Request::Progress::Next progress_report(
            const core::net::http::Request::Progress& progress);

    /**
     * Hang onto the configuration information
     */
    Config::Ptr config_;

    /**
     * Thread-safe cancelled flag
     */
    std::atomic<bool> cancelled_;
};

}

特别值得注意的是,我们修改了weather的数据结构。这个和我们从百度API中返回的数据结构是一样的:

{"date":"周一 09月29日 (实时:23℃)","dayPictureUrl":"http://api.map.baidu.com/images/weather/day/duoyun.png","nightPictureUrl":"http://api.map.baidu.com/images/weather/night/qing.png","weather":"多云转晴","wind":"北风4-5级","temperature":"23 ~ 10℃"}

下面我们来看一看在client.cpp文件中的“get"函数。这是一个标准的函数接口。它是通过http来访问所需要访问输入参数所提供的地址,并得到相应的内容。这个内容可以是json或xml形式的。这个函数,我们不需要做任何的改变。

void Client::get(const net::Uri::Path &path, const net::Uri::QueryParameters &parameters, json::Value &root)

我们来修改forecast_daily函数,如下:

Client::Forecast Client::forecast_daily(const string& query) {
    json::Value root;

    // Build a URI and get the contents
    // The fist parameter forms the path part of the URI.
    // The second parameter forms the CGI parameters.
    get( { "telematics", "v3", "weather" },
    { { "location", query },
      { "output", "json" }, { "ak", "DdzwVcsGMoYpeg5xQlAFrXQt" } }, root);
    // e.g. http://api.map.baidu.com/telematics/v3/weather?location=%1&output=json&ak=DdzwVcsGMoYpeg5xQlAFrXQt

    Forecast result;

    //    // Iterate through the weather data
    string date = root["date"].asString();
    cerr << "date: "  << date << endl;

    int indexofYear = date.find_first_of("-", 0);
    cerr << "indexofYear: " << indexofYear << endl;

    string year = date.substr(0, indexofYear);
    cerr << "year: " << year << endl;

    int indexofMonth = date.find("-", indexofYear+1);
    cerr << "indexofMonth: " << indexofMonth << endl;

    string month = date.substr(indexofYear + 1, indexofMonth-indexofYear-1);
    cerr << "month: " << month << endl;

    string day = date.substr(indexofMonth +1, date.length()-indexofMonth);
    cerr << "day: " << day << endl;

    std::locale::global(std::locale(""));

    // current date/time based on current system
    time_t now = time(0);

    tm *localtm = localtime(&now);

    localtm->tm_year = stoi( year ) - 1900;
    localtm->tm_mon = stoi( month );
    localtm->tm_mday = stoi( day );

    json::Value results = root["results"];
    for (json::ArrayIndex index = 0; index < results.size(); ++index) {
        json::Value item = results.get(index, json::Value());

        // Extract the first weather item
        result.city.name = item["currentCity"].asString();

        cerr << "city name: " << result.city.name << endl;

        result.pmIndex = item["pm25"].asString();
        cerr << "PM index: " << result.pmIndex  << endl;

        json::Value weathers = item["weather_data"];

        for ( json::ArrayIndex i = 0; i < weathers.size(); i ++ ) {
            json::Value weather = weathers.get(i, json::Value());

            localtm->tm_mday++;

            time_t newtime = mktime(localtm);
            tm *newlocaltm = localtime(&newtime);
            char buffer[256];
            strftime(buffer, sizeof(buffer), "%a %Y年%b%d ", newlocaltm);

            string date = buffer;
            cerr << "date: " << date << endl;

            string dayPictureUrl = weather["dayPictureUrl"].asString();
            cerr << "dayPictureUrl: " << dayPictureUrl << endl;

            string nightPictureUrl = weather["nightPictureUrl"].asString();
            cerr << "nightPictureUrl: " << nightPictureUrl << endl;

            string weather1 = weather["weather"].asString();
            cerr << "weather: " << weather1 << endl;

            string temperature = weather["temperature"].asString();
            cerr << "temperature: " << temperature << endl;

            string wind = weather["wind"].asString();
            cerr << "wind: " << wind << endl;

            cerr << "====================================" << endl;

            result.weather.emplace_back(
                        Weather { date,
                                  dayPictureUrl,
                                  nightPictureUrl,
                                  weather1,
                                  wind,
                                  temperature,
                                  URI
                        }
                        );

        }
    }

    return result;
}

同时我们在client.h中定义如下的宏:

#define URI "http://www.weather.com.cn/html/weather/101010100.shtml"

重新编译项目,如果遇到任何的问题,我们必须停下来解决以使得整个项目能够被正确地编译。在Ubuntu Desktop下运行我们的应用。我们可以在Application Output窗口看见许多的输出。


3)代码讲解


src/scope/scope.cpp


这个文件定义了一个unity::scopes::ScopeBase的类。它提供了客户端用来和Scope交互的起始接口。

  • 这个类定义了“start", "stop"及"run"来运行scope。绝大多数开发者并不需要修改这个类的大部分实现。在我们的例程中,我们将不做任何的修改
  • 它也同时实现了另外的两个方法:search 和 preview。我们一般来说不需要修改这俩个方法的实现。但是他们所调用的函数在具体的文件中必须实现

注意:我们可以通过研究Scope API的头文件来对API有更多的认识。更多的详细描述,开发者可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/查看。

src/scope/query.cpp


这个文件定义了一个unity::scopes::SearchQueryBase类。

这个类用来产生由用户提供的查询字符串而生产的查询结果。这个结果可能是基于json或是xml的。这个类可以用来进行对返回的结果处理并显示。

  • 得到由用户输入的查询字符串
  • 向web services发送请求
  • 生成搜索的结果(根据每个不同而不同)
  • 创建搜索结果category(比如不同的layout-- grid/carousel)
  • 根据不同的搜寻结果来绑定不同的category以显示我们所需要的UI
  • 推送不同的category来显示给最终用户

创建并注册CategoryRenderers

在本例中,我们创建了两个JSON objects. 它们是最原始的字符串,如下所示,它有两个field:template及components。template是用来定义是用什么layout来显示我们所搜索到的结果。这里我们选择的是”grid"及小的card-size。components项可以用来让我们选择预先定义好的field来显示我们所需要的结果。这里我们添加了"title"及“art"。


std::string CR_GRID = R"(
    {
        "schema-version" : 1,
        "template" : {
            "category-layout" : "grid",
            "card-size": "medium"
        },
        "components" : {
            "title" : "title",
            "art" : {
                "field": "art",
                "aspect-ratio": 1.6,
                "fill-mode": "fit"
            }
        }
    }

这是一个grid的layout,同时我们可以显示一个title及一个图片(art)。我们在文件的开始部分加入如上的的template的定义。

更多关于 CategoryRenderer 类的介绍可以在 docs找到。

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册。我们修改我们的run方法来实现显示:


void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());

        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());

        Client::Forecast forecast;

        cerr << "query_string: " << query_string;

        if (query_string.empty()) {
            // If there is no search string, get the forecast for London
            forecast = client_.forecast_daily("北京");
        } else {
            // otherwise, get the forecast for the search string
            forecast = client_.forecast_daily(query_string);
        }

        // Register a category for the forecast
        auto forecast_cat = reply->register_category("Chineweather",
                                                     forecast.city.name,
                                                     "", sc::CategoryRenderer(CR_GRID));

        // For each of the forecast days
        for (const auto &weather : forecast.weather) {

            // Create a result
            sc::CategorisedResult res(forecast_cat);

            // Set the rest of the attributes
            res.set_art(weather.dayPictureUrl);
            stringstream ss(stringstream::in | stringstream::out);
            ss << "白天: " << weather.date;


            res.set_title(ss.str());

            // We must have a URI
            res.set_uri(weather.uri);
            res.set_dnd_uri(weather.uri);

            // Add some extra data, and they will be shown in the preview
            res["weather"] = sc::Variant(weather.weather);
            res["temperature"] = sc::Variant(weather.temperature);
            res["wind"] = sc::Variant(weather.wind);

            // Push the result
            if (!reply->push(res)) {
                // If we fail to push, it means the query has been cancelled.
                // So don't continue;
                return;
            }

            res.set_art(weather.nightPictureUrl);
            ss.str(std::string());
            ss << "夜晚: " << weather.date;
            res.set_title(ss.str());

            // We must have a URI
            res.set_uri(weather.uri);
            res.set_dnd_uri(weather.uri);

            // Push the result
            if (!reply->push(res)) {
                // If we fail to push, it means the query has been cancelled.
                // So don't continue;
                return;
            }

        }

    } catch (domain_error &e) {
        // Handle exceptions being thrown by the client API
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}

我们从我们的Client API中的Client::Forecast来获取我们所需要的web service的数据,把数据填入到相应的CategorisedResult中。

我们运行我们的程序,我们可以在屏幕上看到如下的画面:

    

我们也可以尝试点击我们的画面,在另外一个画面中可以看到一个图片。到这里,我们基本上已经看到了Scope工作的了。我们下面来更进一步来在Preview中显示更多的内容。

src/scope/preview.cpp

这个文件定义了一个unity::scopes::PreviewQueryBase类。

这个类定义了一个widget及一个layout来展示我们搜索到的结果。这是一个preview结i果,就像它的名字所描述的那样。

  • 定义在preview时所需要的widget
  • 让widget和搜索到的数据field一一对应起来
  • 定义不同数量的layout列(由屏幕的尺寸来定)
  • 把不同的widget分配到layout中的不同列中
  • 把reply实例显示到layout的widget中

大多数的代码在“run&quot;中实现。跟多关于这个类的介绍可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/previewwidgets/找到。

Preview

Preview需要来生成widget并连接它们的field到CategorisedResult所定义的数据项中。它同时也用来为不同的显示环境(比如屏幕尺寸)生成不同的layout。根据不同的显示环境来生成不同数量的column。

Preview Widgets

这是一组预先定义好的widgets。每个都有一个类型。更据这个类型我们可以生成它们。你可以在这里找到Preview Widget列表及它们提供的的field类型。

这个例子使用了如下的widgets

  • header:它有title及subtitle field
  • image:它有source field有来显示从哪里得到这个art
  • text:它有text field
  • action:用来展示一个有"Open"的按钮。当用户点击时,所包含的URI将被打开

如下是一个例子,它定义了一个叫做“headerId"的PreviewWidget。第二个参数是它的类型"header"。

  1. PreviewWidget w_header("headerId""header");  

最终的程序如下:

#include <scope/preview.h>

#include <unity/scopes/ColumnLayout.h>
#include <unity/scopes/PreviewWidget.h>
#include <unity/scopes/PreviewReply.h>
#include <unity/scopes/Result.h>
#include <unity/scopes/VariantBuilder.h>

#include <iostream>

namespace sc = unity::scopes;

using namespace std;
using namespace scope;
using namespace unity::scopes;

Preview::Preview(const sc::Result &result, const sc::ActionMetadata &metadata) :
    sc::PreviewQueryBase(result, metadata) {
}

void Preview::cancelled() {
}

void Preview::run(sc::PreviewReplyProxy const& reply) {
    //
    // This preview handler just reuses values of the original result via
    // add_attribute_mapping() calls, but it could also do another network
    // request for more details if needed.
    //

    // Client can display Previews differently depending on the context
    // By creates two layouts (one with one column, one with two) and then
    // adding widgets to them differently, Unity can pick the layout the
    // scope developer thinks is best for the mode
    sc::ColumnLayout layout1col(1), layout2col(2);

    // add columns and widgets (by id) to layouts.
    // The single column layout gets one column and all widets
    layout1col.add_column({"headerId", "artId", "tempId", "windId", "actionsId"});

    // The two column layout gets two columns.
    // The first column gets the art and header widgets (by id)
    layout2col.add_column({"artId", "headerId"});
    // The second column gets the info and actions widgets
    layout2col.add_column({"infoId", "windId", "actionsId"});

    // Push the layouts into the PreviewReplyProxy intance, thus making them
    // available for use in Preview diplay
    reply->register_layout({layout1col, layout2col});

    //Create some widgets
    // header type first. note 'headerId' used in layouts
    // second field ('header) is a standard preview widget type
    PreviewWidget w_header("headerId", "header");
    // This maps the title field of the header widget (first param)  to the
    // title field in the result to be displayed in this preview, thus providing
    // the result-specific data to the preview for display
    w_header.add_attribute_mapping("title", "title"); // attribute, result field name
    // Standard subtitle field here gets our 'artist' key value
    w_header.add_attribute_mapping("subtitle", "weather");

    PreviewWidget w_art("artId", "image");
    w_art.add_attribute_mapping("source", "art"); // // key, result field name

    PreviewWidget w_info("tempId", "text");
    w_info.add_attribute_mapping("text", "temperature");

    PreviewWidget w_wind("windId", "text");
    w_wind.add_attribute_mapping("text", "wind");

    Result result = PreviewQueryBase::result();
//    QString urlString(result["uri"].get_string().c_str());
    cerr << "[Details] GET " << result["uri"].get_string() << endl;

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

    // Bundle out widgets as required into a PreviewWidgetList
    PreviewWidgetList widgets({w_header, w_art, w_info, w_wind, w_actions});
    // And push them to the PreviewReplyProxy as needed for use in the preview
    reply->push(widgets);
}


我们再重新运行程序,我们可以看到如下的画面:

      


在手机上的运行情况如下:

   

最后,我们可以找到data文件夹,并换上我们喜欢的应用图标。这样,我们就基本完成了我们所要完成的应用了。

所有的程序代码可以在如下的网址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/chinaweathernew

该应用的另外一个用Qt进行开发的范例可以在如下的网址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/chinaweatherfinal

4)调试应用

当我们在开发应用时,我们可以通过上面的“cerr”在“Application Output”输出结果来查看结果。当在手机运行时,我们也可以通过查看如下的文件来看Scope的运行情况:



我们可以通过查看在手机中的文件“~/.cache/upstart/scope-registry.log”来看最新的Scope的运行情况。




作者:UbuntuTouch 发表于2014-9-29 11:20:38 原文链接
阅读:333 评论:0 查看评论

Read more
UbuntuTouch

[原]在Ubuntu上面创建一个dianping Scope

前面我们已经学习了如何在Ubuntu Touch上面制作一个Scope应用。Scope也是Ubuntu上面一个非常重要的,又和其他平台区分的一种应用。它能很好地把web services整合到手机平台中,就像是系统的一部分。Scope也对手机制造商来说也是非常重要的。它可以让他们深度定制自己的服务到系统中。


值得指出的是:由于一些原因,目前所有的Scope的开发必须是在Ubuntu OS Utopic (14.10)版本之上的。在Ubuntu OS 14.04上是不可以的。

1)创建一个最基本的Scope应用


首先打开我们的Ubuntu SDK。选择“Unity Scope"模版。



然后选择好项目的路径,并同时选好自己的项目名称"dianping"。我们选择“Empty scope” template。






接下来,我们就完成剩下的步骤来完成一个最基本的Scope应用。我们可以直接在电脑上运行。当然我们也可以把它运行到手机中。




如果你能运行到这里,说明你的安装环境是没有问题的。如果有问题的话,请参阅我的Ubuntu SDK安装文章。这个最基本的应用其实没有什么内容。在下面的章节中我们来向这里添加一些东西以实现我们所需要的一些东西。


2)加入对Qt的支持


我们可以看到在项目的“src”目录下有两个目录:api及scope。api目录下的代码主要是为了来访问我们的web service来得到一个json或是xml的数据。在这个项目中,我们并不准备采用这个目录中的client类。有兴趣的开发者可以尝试把自己的client和scope的代码分开。


我们首先打开在“src”中的CMakeLists.txt文件,并加入如下的句子:

find_package(Qt5Network REQUIRED) 
find_package(Qt5Core REQUIRED)      

include_directories(${Qt5Core_INCLUDE_DIRS})   
include_directories(${Qt5Network_INCLUDE_DIRS})

....

# Build a shared library containing our scope code.
# This will be the actual plugin that is loaded.
add_library(
  scope SHARED
  $<TARGET_OBJECTS:scope-static>
)

qt5_use_modules(scope Core Network)

# Link against the object library and our external library dependencies
target_link_libraries(
  scope
  ${SCOPE_LDFLAGS}
  ${Boost_LIBRARIES}
)


我们可以看到,我们加入了对Qt Core,XML及Network库的调用。同时,我们也打开"tests/unit/CMakeLists.txt"文件,并加入“qt5_use_modules(scope-unit-tests Core Xml Network)":


# Our test executable.
# It includes the object code from the scope
add_executable(
  scope-unit-tests
  scope/test-scope.cpp
  $<TARGET_OBJECTS:scope-static>
)

# Link against the scope, and all of our test lib dependencies
target_link_libraries(
  scope-unit-tests
  ${GTEST_BOTH_LIBRARIES}
  ${GMOCK_LIBRARIES}
  ${SCOPE_LDFLAGS}
  ${TEST_LDFLAGS}
  ${Boost_LIBRARIES}
)

qt5_use_modules(scope-unit-tests Core Network)

# Register the test with CTest
add_test(
  scope-unit-tests
  scope-unit-tests
)

我们第一步的代码可以在如下的网址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianping1

3)代码讲解


src/dianping-scope.cpp

这个文件定义了一个unity::scopes::ScopeBase的类。它提供了客户端用来和Scope交互的起始接口。

  • 这个类定义了“start", "stop"及"run"来运行scope。绝大多数开发者并不需要修改这个类的大部分实现。在我们的例程中,由于需要,我们将做简单的修改
  • 它也同时实现了另外的两个方法:search 和 preview。我们一般来说不需要修改这俩个方法的实现。但是他们所调用的函数在具体的文件中必须实现

注意:我们可以通过研究Scope API的头文件来对API有更多的认识。更多的详细描述,开发者可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/查看。


我们再重新编译我们的应用,如果我们没有错误的话,我们的Scope可以直接在desktop下直接运行。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制及生成一个Qt应用。我们来修改scope.h文件,并加QoreApplication的变量app及forward申明。我们也必须同时加入一个方法"run"。


class QCoreApplication; // added

namespace scope {
class Scope: public unity::scopes::ScopeBase {
public:
    void start(std::string const&) override;
    void stop() override;
    void run(); // added
    unity::scopes::PreviewQueryBase::UPtr preview(const unity::scopes::Result&,
                                                  const unity::scopes::ActionMetadata&) override;
    unity::scopes::SearchQueryBase::UPtr search(
            unity::scopes::CannedQuery const& q,
            unity::scopes::SearchMetadata const&) override;

protected:
    api::Config::Ptr config_;
    QCoreApplication *app; // added
};

我们同时打开scope.cpp,并做如下的修改:

#include <QCoreApplication> // added

...

void Scope::stop() {
    /* The stop method should release any resources, such as network connections where applicable */
    delete app;
}

void Scope::run()
{
    int zero = 0;
    app = new QCoreApplication(zero, nullptr);
}

src/dianping-query.cpp


这个文件定义了一个unity::scopes::SearchQueryBase类。

这个类用来产生由用户提供的查询字符串而生产的查询结果。这个结果可能是基于json或是xml的。这个类可以用来进行对返回的结果处理并显示。

  • 得到由用户输入的查询字符串
  • 向web services发送请求
  • 生成搜索的结果(根据每个scope不同而不同)
  • 创建搜索结果category(比如不同的layout-- grid/carousel)
  • 根据不同的搜寻结果来绑定不同的category以显示我们所需要的UI
  • 推送不同的category来显示给最终用户

基本上所有的代码集中在"run"方法中。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制。


当我们使用dianping发生请求时,我们需要对我们的请求发生请求进行签名。具体的细节可以参照链接点评网上的“API请求签名生成文档”。为此,我们设计了如下的helper方法getQueryString。这个类的输入参数是一个搜索的关键词,最终的输出是一个可以访问的URL。这里的appkey及secret可以在下面的代码中找到。通过这个方法,我们可以得到一个URL并在浏览器中打开链接看到我们需要解析的json数据。

QString Query::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "美食";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = "20";
    map["platform"] = "2";

    QCryptographicHash generator(QCryptographicHash::Sha1);

    QString temp;
    temp.append(appkey);
    QMapIterator<QString, QString> i(map);
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        temp.append(i.key()).append(i.value());
    }

    temp.append(secret);

    qDebug() << temp;

    qDebug() << "UTF-8: " << temp.toUtf8();

    generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper();

//    qDebug() << sign;

    QString url;
    url.append(BASE_URI);
    url.append("appkey=");
    url.append(appkey);

    url.append("&");
    url.append("sign=");
    url.append(sign);

    i.toFront();
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        url.append("&").append(i.key()).append("=").append(i.value());
    }

    qDebug() << "Final url: " << url;
    return url;
}

整个Query类的设计如下:


#include <boost/algorithm/string/trim.hpp>

#include <scope/query.h>

#include <unity/scopes/Annotation.h>
#include <unity/scopes/CategorisedResult.h>
#include <unity/scopes/CategoryRenderer.h>
#include <unity/scopes/QueryBase.h>
#include <unity/scopes/SearchReply.h>

#include <iomanip>
#include <sstream>

// The following headers are added
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QCoreApplication>

namespace sc = unity::scopes;
namespace alg = boost::algorithm;

using namespace std;
using namespace api;
using namespace scope;
using namespace unity::scopes; // added

const QString appkey = "3562917596";
const QString secret = "091bf584e9d24edbbf48971d65307be3";
const QString BASE_URI = "http://api.dianping.com/v1/business/find_businesses?";

//Create a JSON string to be used tro create a category renderer - uses grid layout
std::string CR_GRID = R"(
    {
        "schema-version" : 1,
        "template" : {
            "category-layout" : "grid",
            "card-size": "small"
        },
        "components" : {
            "title" : "title",
            "art" : {
                "field": "art",
                "aspect-ratio": 1.6,
                "fill-mode": "fit"
            }
        }
    }
)";

//Create a JSON string to be used tro create a category renderer - uses carousel layout
std::string CR_CAROUSEL = R"(
    {
        "schema-version" : 1,
        "template" : {
            "category-layout" : "carousel",
            "card-size": "small",
            "overlay" : true
        },
        "components" : {
            "title" : "title",
            "art" : {
                "field": "art",
                "aspect-ratio": 1.6,
                "fill-mode": "fit"
            }
        }
    }
)";


Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata,
             Config::Ptr config) :
    sc::SearchQueryBase(query, metadata), client_(config) {
}

void Query::cancelled() {
    client_.cancel();
}

void Query::run(sc::SearchReplyProxy const& reply) {
    // Firstly, we get the query string here.
    CannedQuery query = SearchQueryBase::query();
    QString queryString = QString::fromStdString(query.query_string());

    QString uri;
    if ( queryString.isEmpty() ) {
        queryString = QString("北京");
        uri = getQueryString(queryString);
    } else  {
        uri = getQueryString(queryString);
    }

    qDebug() << "queryString: " << queryString;

    CategoryRenderer rdrGrid(CR_GRID);
    CategoryRenderer rdrCarousel(CR_CAROUSEL);

    QString title = queryString + "美味";

    auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
    auto grid = reply->register_category("dianpinggrid", "", "", rdrGrid);

    QEventLoop loop;

    QNetworkAccessManager manager;

    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));

    QObject::connect(&manager, &QNetworkAccessManager::finished,
            [reply, carousel, grid, this](QNetworkReply *msg){
                QByteArray data = msg->readAll();
                QString json = data;
                // qDebug() << "Data:" << json;
                QJsonParseError err;
                QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &err);

                if (err.error != QJsonParseError::NoError) {
                    qCritical() << "Failed to parse server data: " << err.errorString();
                } else {
                    // Find the "payload" array of results
                    QJsonObject obj = doc.object();
                    QJsonArray results = obj["businesses"].toArray();

                    // for each result
                    const std::shared_ptr<const Category> * top;

                    bool isgrid = false;
                    //loop through results of our web query with each result called 'result'
                    for(const auto &result : results) {

                        if ( isgrid ) {
                          top = &grid;
                          isgrid = false;
                        } else {
                          isgrid = true;
                          top = &carousel;
                        }

                        //create our categorized result using our pointer, which is either to out
                        //grid or our carousel Category
                        CategorisedResult catres((*top));

                        //treat result as Q JSON
                        QJsonObject resJ = result.toObject();

                        // load up vars with from result
                        auto name = resJ["name"].toString();
//                        qDebug() << "name: " << name;
                        name = removeTestInfo( name );

                        auto business_uri = resJ["business_url"].toString();
//                        qDebug() << "business_uri: " << business_uri;

                        auto s_photo_uri = resJ["s_photo_url"].toString();
//                        qDebug() << "s_photo_url: " << s_photo_uri;

                        auto photo_uri = resJ["photo_url"].toString();
//                        qDebug() << "photo_url: " << photo_uri;

                        auto rating_s_img_uri = resJ["rating_s_img_url"].toString();
//                        qDebug() << "rating_s_img_uri: " << rating_s_img_uri;

                        auto address = resJ["address"].toString();
                        auto telephone = resJ["telephone"].toString();

                        //set our CateogroisedResult object with out searchresults values
                        catres.set_uri(business_uri.toStdString());
                        catres.set_dnd_uri(business_uri.toStdString());
                        catres.set_title(name.toStdString());
                        catres.set_art(photo_uri.toStdString());

                        catres["address"] = Variant(address.toStdString());
                        catres["telephone"] = Variant(telephone.toStdString());

                        //push the categorized result to the client
                        if (!reply->push(catres)) {
                            break; // false from push() means search waas cancelled
                        }
                    }
                }
            }
            );

    qDebug() << "Uri:" << uri ;
    manager.get(QNetworkRequest(QUrl(uri)));
    loop.exec();
}

QString Query::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "美食";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = "20";
    map["platform"] = "2";

    QCryptographicHash generator(QCryptographicHash::Sha1);

    QString temp;
    temp.append(appkey);
    QMapIterator<QString, QString> i(map);
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        temp.append(i.key()).append(i.value());
    }

    temp.append(secret);

    qDebug() << temp;

    qDebug() << "UTF-8: " << temp.toUtf8();

    generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper();

//    qDebug() << sign;

    QString url;
    url.append(BASE_URI);
    url.append("appkey=");
    url.append(appkey);

    url.append("&");
    url.append("sign=");
    url.append(sign);

    i.toFront();
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        url.append("&").append(i.key()).append("=").append(i.value());
    }

    qDebug() << "Final url: " << url;
    return url;
}

// The following method is used to remove the
// "这是一条测试商户数据,仅用于测试开发,开发完成后请申请正式数据..." string
const QString TEST_STRING = "(这是一条测试商户数据,仅用于测试开发,开发完成后请申请正式数据...)";
QString Query::removeTestInfo(QString name)
{
    if ( name.contains(TEST_STRING) ) {
        int index = name.indexOf(TEST_STRING);
        QString newName = name.left(index);
        // qDebug() << "newName: " << newName;
        return newName;
    } else {
        qDebug() << "it does not contain the string";
        return name;
    }
}

我们可以参阅http://developer.dianping.com/来注册成为dianping网站的开发者。在网址http://developer.dianping.com/app/tutorial可以找到开发指南。可以通过getQueryString()方法来得到所需要请求的uri。

创建并注册CategoryRenderers

在本例中,我们创建了两个JSON objects. 它们是最原始的字符串,如下所示,它有两个field:template及components。template是用来定义是用什么layout来显示我们所搜索到的结果。这里我们选择的是”grid"及小的card-size。components项可以用来让我们选择预先定义好的field来显示我们所需要的结果。这里我们添加了"title"及“art"。


std::string CR_GRID = R"(
    {
        "schema-version" : 1,
        "template" : {
            "category-layout" : "grid",
            "card-size": "small"
        },
        "components" : {
            "title" : "title",
            "art" : {
                "field": "art",
                "aspect-ratio": 1.6,
                "fill-mode": "fit"
            }
        }
    }
)";

更多关于 CategoryRenderer 类的介绍可以在 docs找到。

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册:

    CategoryRenderer rdrGrid(CR_GRID);
    CategoryRenderer rdrCarousel(CR_CAROUSEL);

    QString title = queryString + "美味";

    auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
    auto grid = reply->register_category("dianpinggrid", "", "", rdrGrid);


我们可以运行我们所得到的程序,看看我们的结果。我们可以在“Search query”中输入其它城市,比如“上海”或“广州”等,我们就会看到不同的显示结果。



src/dianping-preview.cpp

这个文件定义了一个unity::scopes::PreviewQueryBase类。

这个类定义了一个widget及一个layout来展示我们搜索到的结果。这是一个preview结i果,就像它的名字所描述的那样。

  • 定义在preview时所需要的widget
  • 让widget和搜索到的数据field一一对应起来
  • 定义不同数量的layout列(由屏幕的尺寸来定)
  • 把不同的widget分配到layout中的不同列中
  • 把reply实例显示到layout的widget中

大多数的代码在“run&quot;中实现。跟多关于这个类的介绍可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/previewwidgets/找到。

Preview

Preview需要来生成widget并连接它们的field到CategorisedResult所定义的数据项中。它同时也用来为不同的显示环境(比如屏幕尺寸)生成不同的layout。根据不同的显示环境来生成不同数量的column。

Preview Widgets

这是一组预先定义好的widgets。每个都有一个类型。更据这个类型我们可以生成它们。你可以在这里找到Preview Widget列表及它们提供的的field类型。

这个例子使用了如下的widgets

  • header:它有title及subtitle field
  • image:它有source field有来显示从哪里得到这个art
  • text:它有text field
  • action:用来展示一个有"Open"的按钮。当用户点击时,所包含的URI将被打开

如下是一个例子,它定义了一个叫做“headerId"的PreviewWidget。第二个参数是它的类型"header"。

 PreviewWidget w_header("headerId", "header");

最终的程序如下:

#include <scope/preview.h>

#include <unity/scopes/ColumnLayout.h>
#include <unity/scopes/PreviewWidget.h>
#include <unity/scopes/PreviewReply.h>
#include <unity/scopes/Result.h>
#include <unity/scopes/VariantBuilder.h>

#include <iostream>

#include <QString>

namespace sc = unity::scopes;

using namespace std;
using namespace scope;
using namespace unity::scopes;

Preview::Preview(const sc::Result &result, const sc::ActionMetadata &metadata) :
    sc::PreviewQueryBase(result, metadata) {
}

void Preview::cancelled() {
}

void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts
    // Client can display Previews differently depending on the context
    // By creates two layouts (one with one column, one with two) and then
    // adding widgets to them differently, Unity can pick the layout the
    // scope developer thinks is best for the mode
    ColumnLayout layout1col(1), layout2col(2);

    // add columns and widgets (by id) to layouts.
    // The single column layout gets one column and all widets
    layout1col.add_column({"headerId", "artId", "infoId", "telId", "actionsId"});
    // The two column layout gets two columns.
    // The first column gets the art and header widgets (by id)
    layout2col.add_column({"artId", "headerId"});
    // The second column gets the info and actions widgets
    layout2col.add_column({"infoId", "telId", "actionsId"});

    // Push the layouts into the PreviewReplyProxy intance, thus making them
    // available for use in Preview diplay
    reply->register_layout({layout1col, layout2col});

    //Create some widgets
    // header type first. note 'headerId' used in layouts
    // second field ('header) is a standard preview widget type
    PreviewWidget w_header("headerId", "header");
    // This maps the title field of the header widget (first param)  to the
    // title field in the result to be displayed in this preview, thus providing
    // the result-specific data to the preview for display
    w_header.add_attribute_mapping("title", "title");

    // Standard subtitle field here gets our 'artist' key value
    // w_header.add_attribute_mapping("subtitle", "artist");

    PreviewWidget w_art("artId", "image");
    w_art.add_attribute_mapping("source", "art");

    PreviewWidget w_info("infoId", "text");
    w_info.add_attribute_mapping("text", "address");

    PreviewWidget w_tel("telId", "text");
    w_tel.add_attribute_mapping("text", "telephone");

    Result result = PreviewQueryBase::result();
    QString urlString(result["uri"].get_string().c_str());
    // qDebug() << "[Details] GET " << urlString;
   // QUrl url = QUrl(urlString);

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

    // Bundle out widgets as required into a PreviewWidgetList
    PreviewWidgetList widgets({w_header, w_art, w_info, w_tel, w_actions});
    // And push them to the PreviewReplyProxy as needed for use in the preview
    reply->push(widgets);
}


运行的效果图如下:

  




在手机上的运行情况如下:


     


整个完整的代码在如下的网址可以看到:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpingqtjson


一个比这个更加完整的department scope可以在网址“在Ubuntu OS上创建一个department 点评Scope (Qt XML)”找到。

作者:UbuntuTouch 发表于2014-8-20 16:35:02 原文链接
阅读:282 评论:0 查看评论

Read more
UbuntuTouch

在Ubuntu平台上面,我们可以使用History API来读取电话本及短信的内容,并使用listview来显示出来。下面我们来展示该怎么做。


1)创建一个基本的QML应用

我们使用Qt Creator来创建一个最基本的QML应用。我们选择使用“App with Simple UI"模版来创建我们的应用。





为了使用History API, 我们必须引入

import Ubuntu.History 0.1

为了能够使我们读取不同的history,我们先来做一个ComboButton。它的设计如下:

            ComboButton {
                id: type
                expanded: false

                text: "Voice"
                ListView {
                    model: typemodel
                    delegate: ListItem.Standard {
                        text: modelData

                        onClicked: {
                            console.log("item is clicked!" + index + " " + name);
                            type.expanded = false;
                            type.text = text;

                            console.log("type is: " + type.text);

                            if ( name === "Voice") {
                                historyEventModel.type = HistoryThreadModel.EventTypeVoice;
                                listView.model = historyEventModel
                            } else if ( name === "Text" ) {
                                historyEventModel.type = HistoryThreadModel.EventTypeText;
                                listView.model = historyEventModel
                            } else if ( name === "Thread") {
                                listView.model = historyThreadModel
                            }

                        }
                    }
                }
            }
这里我们可以参考ComboButton来更加多了解如何使用这个控件。这里,我们提供了三个选项"Voice", "Text" 及“Thread"。同时我们也创建了俩个不同的history model。

    HistoryEventModel {
        id: historyEventModel
        filter: HistoryFilter {}
        type: HistoryThreadModel.EventTypeVoice
        sort: HistorySort {
            sortField: "timestamp"
            sortOrder: HistorySort.DescendingOrder
        }
    }

    SortProxyModel {
        id: sortProxy
        sortRole: HistoryEventModel.TimestampRole
        sourceModel: historyEventModel
        ascending: false
    }

    HistoryThreadModel {
        id: historyThreadModel        
	filter: HistoryFilter {}
 sort: HistorySort { sortField: "lastEventTimestamp" sortOrder: HistorySort.DescendingOrder 
        } 


由于一些性能方面的原因的考虑,目前我们必须把filter设置为空,即:
filter: HistoryFilter {}

我们同时也把上面定义的model和我们的ListView一起连接起来:

            ListView {
                id: listView

                width: parent.width
                height: parent.height - type.height

                Component {
                    id: sectionDelegate

                    Text {
                        text: section
                    }
                }

                model: historyEventModel

               delegate:
               ...
            }

目前似乎一切都已经好了。接下来我们来运行我们的应用到手机中。我们会发现在手机中没有任何的history的东西显示。问题出现在哪里呢?



我们开启一个terminal,然后打入如下的命令:

$adb shell

然后再在手机中的命令行输入:

root@ubuntu-phablet:~# grep "DENIED" /var/log/syslog 

我们可以看到如下的图片:



显然,我们遇到了一个安全的问题。为了接的这个问题,我们必须在应用中加入它所需要的policy。



我们再重新运行程序。在手机上,我们可以看到如下的画面:




我们再也没有security的问题了。整个工程的源码在如下的地址可以下载:

bzr branch lp:~liu-xiao-guo/debiantrial/history





作者:UbuntuTouch 发表于2014-9-4 12:05:55 原文链接
阅读:148 评论:0 查看评论

Read more
UbuntuTouch

QML中的Loader是用来动态地载入一个QML的Component。它可以用来载入一个QML文件(使用它的source属性)。它也可以载入一个Component(使用它的sourceComponent属性)。它适合在需要载入一个Component时才载入它,这样避免资源的浪费。它可以动态地载入按需求在需要的时候创建我们需要的Component。更多阅读,可以参照:http://qt-project.org/doc/qt-4.8/qml-loader.html


1)动态载入一个Component


我们首先来创建我们一个基本的应用。这里我们使用一个"App with Simple UI"的模版。我们首先创建一个称作为"filearray.js"的javascript文件,如下:

var filearray = [
                    ["images/fall1.jpg", "First image"],
                    ["images/fall2.jpg", "Second image"],
                    ["images/fall3.jpg", "Three image"],
                    ["images/fall4.jpg", "Fourth image"]
                ];

这里创建了一个二维的数组。为了我们能够在应用中使用。同时,我们在"main.qml"中也创建一个Component。这样它可以在我们的应用中动态地产生。代码如下:

        Component {
            id: imageText

            Rectangle {
                id:top
                anchors.fill: parent
                Image {
                    id:innerImage
                    anchors.top: parent.top
                    anchors.topMargin:30
                    anchors.horizontalCenter: parent.horizontalCenter
                    width:parent.width*0.8;
                    height: parent.height*0.8
                    source:root.currentImage

                }
                Text{
                    id:answer
                    anchors.top:innerImage.bottom
                    anchors.topMargin:30
                    horizontalAlignment: Text.AlignHCenter
                    width:parent.width;
                    text:root.currentText
                }
            }

       
这个Component非常简单。上面使用了一个Image,下面是一个Text。这两个item的内容是从root控件中的两个property中获得。我们希望这root中的这两个property改变时他们的内容也可以改变。

   Page {
        id: root;
        title: i18n.tr("QML Loader")
        property int index: 0
        property string currentImage
        property string currentText:" "

同时,为了说明问题,我们也设计了一个timer。当这个timer每次timeout时,我们希望我们的loader:

        Loader {
            id: loader
            anchors.fill: parent
            anchors.centerIn:parent
        }

的"sourceComponent"每次都能发生改变,以使得UI得到变化。这里有趣的是,我们也可以为Loader定义它的大小:

  • 如果没有定义Loader的大小的话,Loader将会自动地适配到Component的大小尺寸(当component完成装载以后)
  • 如果Loader的大小已经被定义的话,当component完成装载后,component的尺寸将自动被适配到Loader的尺寸
    function timeout() {
        console.log("root.index" + root.index);
        console.log(FileExt.filearray[root.index][0]);
        root.currentImage = FileExt.filearray[root.index][0];
        root.currentText = FileExt.filearray[root.index][1];
        loader.sourceComponent = imageText;

        root.index++;
        if ( root.index === FileExt.filearray.length) {
            root.index = 0;
        }
    }

        Timer {
            id: timer
            interval: 3000
            onTriggered: {
                timeout();
            }
            repeat: true
            triggeredOnStart: true
        }

运行的效果图如下:



我们可以看到画面中的每三次改变一次。每一次都是一个新的Component,而不是同一个Component不变,只是其中的属性发生改变。Loader很适用于在不同的场景中装载不同的component,比如在不同的菜单中,装载不同的component,以显示不同的UI。Qt SDK很多的例程就是这样写的!

整个例程的代码在如下地址找到:

bzr branch lp:~liu-xiao-guo/debiantrial/loaderqml

2)使用Loader载入qml文件

在下面的例程中,我们来完成一个使用Loader来载入qml文件。我们来做一个类似wizard的东西。当第一个页面显示完后,紧接着按下一个按钮,进入下一个页面。我们首先来创建一个简单的"App with Simple UI"的模版应用,然后,修改main.qml文件:

import QtQuick 2.0
import Ubuntu.Components 1.1
import "components"

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.liu-xiao-guo.wizard"


    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(100)
    height: units.gu(75)

    Page {
        id: root
        title: i18n.tr("Wizard")

        Loader {
            z: 1
            id: main
            anchors.fill: parent
        }

        Image {
            source: "images/fall1.jpg"
            anchors.fill: parent
        }

        Button {
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 20
            anchors.rightMargin: 20


            text: "Go to Page 1"
            onClicked: {
                main.source = "Page1.qml"
                console.log("Clicked in main")
            }
        }

        Connections {
            target:main.item
            onHandlerLoader:{
                console.log("Something happened!")
            }
        }
    }
}


这里我们定义了一个叫做main的Loader。当我们点击按钮”Go to Page 1"时,我们使用它来装载另外一个页面“Page1.qml"。注意我们在这里设置它的"z" order值为"1”。同时,我们也可以通过如下的Connections来接受来自main Loader的一些signal来做我们所需要的一些处理。
        Connections {
            target:main.item
            onHandlerLoader:{
                console.log("Something happened!")
            }
        }

在我们的Page1.qml文件中,我们设计如下:

import QtQuick 2.0
import Ubuntu.Components 1.1

Rectangle {
    id:page1
    anchors.fill: parent

    signal handlerLoader;

    Loader{
        z: 2
        id:loader
        anchors.fill: parent
    }
    
    Image {
        source: "images/fall2.jpg"
        anchors.fill: parent
    }

    Button {
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 20
        anchors.rightMargin: 20

        text: "Go to Page 2"
        onClicked: {
            loader.source = "Page2.qml"
            handlerLoader();
        }
    }
}

在这里,我们定义了另外一个Loader, 并且设置它的“z” order值为2,使一个画面得它可以覆盖以前的页面。我们也尝试定义了一个signal "handlerLoader"。这样我们可以使得前一个面可以的得到一个响应。我们可以把我们想要的信号通过这样的方式发送出去,让需要对它感兴趣的代码利用它。

运行我们的程序,结果如下:

       

代码在如下的地址可以找到:

bzr branch lp:~liu-xiao-guo/debiantrial/wizard

作者:UbuntuTouch 发表于2014-9-9 14:07:55 原文链接
阅读:171 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章里,我来介绍如何在Ubuntu  OS上上面读取电话本的信息。


1)首先我们来创建一个最基本的应用

打开我们的Qt Creator, 我们来创建一个称作为“contact1"的项目。在本项目中,我们使用"App with Simple UI"的模版。我们修改我们的“main.qml"代码如下:


import QtQuick 2.0
import Ubuntu.Components 0.1
import "components"
import QtContacts 5.0
import Ubuntu.Components.ListItems 0.1 as ListItem

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.liu-xiao-guo.contact1"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(100)
    height: units.gu(75)

    Page {
        title: i18n.tr("Contact1")

        ContactModel {
            id: contactModel

            manager: "galera"
        }

        ListView {
            id: contactView

            anchors {
                left: parent.left
                right: parent.right
                top: parent.top
                bottom: buttons.bottom
            }

            model: contactModel

            delegate: ListItem.Subtitled {
                text: contact.name.firstName
                subText: contact.phoneNumber.number
            }
        }
    }
}

这里我们首先输入我们的QtContact 5.0库。在代码中我们定义了如下的ContactModel:

        ContactModel {
            id: contactModel
            manager: "galera"
        }

一定要记住使用"galera" manager。最后,我们使用一个ListView来展示我们的内容。代码显示如上所示。但是我们现在马上在手机上运行时,可能看到如下的信息:

Fail to connect with service: QDBusError("org.freedesktop.DBus.Error.AccessDenied", "An AppArmor policy prevents this sender from sending this message to this recipient, 0 matched rules; type="method_call", sender=":1.119" (uid=32011 pid=20604 comm="/usr/lib/arm-linux-gnueabihf/qt5/bin/qmlscene $@ m") interface="org.freedesktop.DBus.Introspectable" member="Introspect" error name="(unset)" destination="com.canonical.pim" (uid=32011 pid=3057 comm="/usr/lib/arm-linux-gnueabihf/address-book-service/")")

这说明,我们的程序遇到了安全的问题。我们必须在我们应用的apparmor文件中加入相应的policy以来使得我们的读取是可行的。

==ou

加入"Contacts"policy后,我们再在手机中运行,可以看到如下的画面:



我们可以在如下的地址下载我们的代码:

bzr branch lp:~liu-xiao-guo/debiantrial/contact1

2)读取favorite contact信息


我们在我们的应用中定义如下的以个favorite model

        ContactModel {
            id: favouritesContactsModel

            manager: "galera"
            sortOrders: [
                SortOrder {
                    id: sortOrder

                    detail: ContactDetail.Name
                    field: Name.FirstName
                    direction: Qt.AscendingOrder
                }
            ]

            fetchHint: FetchHint {
                detailTypesHint: [ContactDetail.Avatar,
                    ContactDetail.Name,
                    ContactDetail.PhoneNumber]
            }

            filter: DetailFilter {
                id: favouritesFilter

                detail: ContactDetail.Favorite
                field: Favorite.Favorite
                value: true
                matchFlags: DetailFilter.MatchExactly
            }

            onErrorChanged: {
                if (error) {
                    busyIndicator.busy = false
                    contactListView.error(error)
                }
            }
        }

我们在手机上使一个contact成为favorite。这时在我们的ListView中使用我们的favorite Model。代码如下:

        ListView {
            id: contactView
            anchors.fill:parent

            model: root.showFavourites ? favouritesContactsModel : contactModel

            delegate: ListItem.Subtitled {
                text: contact.name.firstName
                subText: contact.phoneNumber.number
            }
        }
我们可以看到我们的一个被设为favorite的contact被列举出来了。



代码在如下的地址可以下载:

bzr branch lp:~liu-xiao-guo/debiantrial/contact2





作者:UbuntuTouch 发表于2014-9-9 9:01:03 原文链接
阅读:136 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章里,我们将学习如何使用QML的Qt.createComponent来动态生成我们所需要的Component。这是一个有趣的练习。我希望大家能跟随我一步一步地完成这个练习。最终使得大家对QML应用有更多的认识。这篇文章中我们也将使用我们的sensor来完成我们其中的一部分功能。在练习之前请大家先去按照安装Ubuntu SDK来安装好我们的环境。


1)使用Qt Creator创建一个最基本的应用


我们首先选择一个“App with Simple UI"的模版来创建我们的最基本的应用。在创建应用的时候,由于应用的package名字中不能出现大写的字母,所以我们选择使用小写的“balloon"来作为我们的工程的名字:



紧接着,我们填入所需要的信息来完成我们的应用。



把应用的大小设为如下的值:

    width: units.gu(50)
    height: units.gu(75)


运行我们的应用:



我们看到,这个应用没有什么太多的内容。我们可以尝试点击按钮,然后看见方框中的文字发生改变。

2)添加Balloon Component

我们按照如下的图,用右键点击项目"balloon",并选择“Add New"。



我们选择创建一个叫做“Balloon.qml”的文件。记住第一个字母为大写的字母。



至此我们已经创建了一个名字叫做"Balloon.qml"的component,虽然现在它做不了什么。为了测试我们刚刚已经创建好的Balloon Component,我们把我们的Balloon在main.qml中创建出来。现在我们来修改main.qml文件:

import QtQuick 2.0
import Ubuntu.Components 1.1
import "components"

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.liu-xiao-guo.balloon"

    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(50)
    height: units.gu(75)

    Page {
        title: i18n.tr("Balloon")

        Balloon {
            width: parent.width/3
            height: parent.height/3
            x: (parent.width - width) / 2
            y: (parent.height - height) /2
        }
    }
}

这里我们把Balloon放到了我们主界面的正中央的位置。重新运行我们的程序,我们可以看到:



显然,我们可以看到,我们的Balloon component里没有任何的东西。只是一个白色方框在那里。但是,至少,我们可以看到确实Balloon已经能被正确地调用。在下面的章节中,我们来一步一步地完成我们的Balloon的功能。

3)完成Balloon模块

请到地址https://github.com/liu-xiao-guo/balloon下载应用,并拷贝应用的"images"目录到我们已经创建好的项目中(别的文件不要拷贝)。这样使得我们的应用有一个叫做"images"的目录,里面有我们想要的图片。



打开我们的Balloon.qml文件。我们知道Balloon component其实是一个Image的item。我们首先把它设计为如下:

import QtQuick 2.0

Image {
    id: balloon
    width: 100
    height: 250

    source: "images/red.svg"
}

我们运行程序,我们发现它的结果如下:



显然,我们已经看到了我们所需要看到的气球了。只是它的颜色是固定的红色。我们想显示不同的气球。这时我们需要加入一个颜色的属性。我们的Balloon.qml的代码如下:

Image {
    id: balloon
    width: 100
    height: 250
    property string color: "red"

    source: "images/" + color + ".svg"
}

默认的颜色我红色(在没有定义的情况下)。我们可以尝试改变我们的main.qml文件。加入color属性:

        Balloon {
            color: "green"
            width: parent.width/3
            height: parent.height/3
            x: (parent.width - width) / 2
            y: (parent.height - height) /2
        }

再重新运行应用:



我们看见了一个绿色的气球。显然它的颜色属性是起作用的。为了能够使我们拖动鼠标移动气球,我们可以在Balloon.qml的Image中加入如下的代码:

    MouseArea {
        x: 0; y: parent.height/2
        width: parent.width
        height: parent.height/2

        drag.target: parent
        drag.axis: "XandYAxis"
    }

再重新运行应用。我们尝试用鼠标点击气球的下半部,并移动气球。我们可以看到气球随着鼠标的移动而移动。下面,我们想在点击气球的上半部分时,气球发生爆炸。为了这样做,我们必须定义另外一个MouseArea来扑捉这个事件。当我们点击气球的上半部时会发出一个爆破声。

    MouseArea {
        x: 0; y: 0
        width: parent.width
        height: parent.height/2

        onClicked: {
            balloon.state = "exploded";
            player.play();
        }
    }

为了我们能够听到一个声音,我们也同时定义了一个MediaPlayer。同时记得把刚下载好的程序中的"sounds"目录考到我们的项目中,并处于和“images”相同的目录中。

import QtMultimedia 5.0

Image {
    ...

    MediaPlayer {
        id: player
        source: "sounds/blast.wav"
    }

   ...
}

为了能够使得我们的应用能够在手机上播放出声音,我们必须为它加入我们所需要的security policy。为此,我们必须修改项目的“balloon.apparmor”文件:



有了“Audio”的policy,我们就可以在我们的手机上听到一声“砰”的声音(在气球爆炸的时候)。

这里我们把Image的id定义为"balloon"。当点击气球的上半部时,我们同时设置balloon的状态为“exploded”状态。我们知道,QML设计中可以设置component为不同的状态。在不同的状态中,可以定义component中各个item的不同属性的值。默认的状态为"",即空串。我们定义的状态如下:

    states: [
        State {
            name: "exploded"

            StateChangeScript {
                script: {
 //                   particle.running = true;
                }
            }

            PropertyChanges {
                target: balloon
                visible: false
                source: "images/" + color + "_exploded.png"
            }

            PropertyChanges {
                target: balloon
                opacity: 0
            }

            PropertyChanges {
                target: balloon
                scale: 0
            }

 //           StateChangeScript { script: balloon.destroy(1000); }
        }
    ]

这时我们重新运行我们的应用,我们会发现我们可以点击球的上半部,并听到一声爆破声。随后球就消失了。我们的目的达到了,但是,还不是我们最终想要的。这是因为从气球点击到消失,速度太快了。我们机会没有看到任何的中间过程。为了我们能够看到气球是怎么爆炸的,我们必须使用一个叫做 transition的。

    transitions: [
        Transition {
            to: "exploded"
            SequentialAnimation {

                // Disappear
                NumberAnimation { target: balloon; property: "opacity"
                    to: 0; duration: 800 }
                NumberAnimation { target: balloon; property: "scale"
                    to: 0; duration: 800 }

                PropertyAction { target: balloon; property: "source"
                    value:  {
                        if ( !surprise )
                            "images/" + color + "_exploded.png"
                        else
                            "images/flower.png";
                    }
                }

                NumberAnimation { target: balloon; property: "opacity"
                    to: 1; duration: 300 }
                NumberAnimation { target: balloon; property: "scale"
                    to: 1; duration: 300 }

                PauseAnimation {
                    duration: {
                        if (surprise)
                            2000
                        else
                            800
                    }
                }

                PropertyAction { target: balloon; property: "visible"
                    value: "false"}
            }
        }
    ]

有了这个我们可以看到气球是逐渐消失的。我们在Image中也加入了如下的属性。当这个气球是一个surprise时,我们会显示一朵花:

Image {
    id: balloon
    width: 100
    height: 250
    property string color: "red"
    property bool surprise: true
    ....
}

为了达到更加逼真的效果,我也为我们的Balloon加入了一个烟花的效果:

import QtQuick.Particles 2.0
...
Image {
    ...
ParticleSystem {
        id: particle
        anchors.fill: parent
        running: false

        Emitter {
            group: "stars"
            emitRate: 800
            lifeSpan: 2400
            size: 24
            sizeVariation: 8
            anchors.fill: parent
        }

        ImageParticle {
            anchors.fill: parent
            source: "qrc:///particleresources/star.png"
            alpha: 0
            alphaVariation: 0.2
            colorVariation: 1.0
        }

        Emitter {
            anchors.centerIn: parent
            emitRate: 400
            lifeSpan: 2400
            size: 20 // 48
            sizeVariation: 8
            velocity: AngleDirection {angleVariation: 180; magnitude: 60}
        }

        Turbulence {
            anchors.fill: parent
            strength: 2
        }
    }

...
}

并在state变化时让它运行:

            StateChangeScript {
                script: {
                    particle.running = true;
                }
            }

运行应用,效果图如下:

    

至此所有的源码可以在如下的网址下载:


bzr branch lp:~liu-xiao-guo/debiantrial/balloon1

由于篇幅的原因。我们将在下一篇文章中详细介绍怎么动态创建很多个Balloon的。大家敬请期待!











            
作者:UbuntuTouch 发表于2014-9-10 11:41:52 原文链接
阅读:237 评论:0 查看评论

Read more
UbuntuTouch

上一篇文章中,我们已经生成了我们Balloon component了。现在我们来让大家怎么来动态生成很多的气球。


4)更进一步完成我们的Balloon component


为了使得我们的Balloon更加像现实生活中的气球,我们来给Balloon给予更多的属性:

    property int x1
    property int y1
    property int speed
    property bool created: false

这里x1, y1是我们让气球飞到一个目的地时的终点位置。我们可以使用如下的语句使得它具有动画的效果:

   NumberAnimation on y {
        easing.type: Easing.InOutQuad; to: y1; duration: speed
        running: created
    }

    NumberAnimation on x {
        easing.type: Easing.InOutQuad; to: x1; duration: speed
        running: created
    }

无论x, 或是y变化时,我们都做一个动画,用speed定义的时间来完成。这个动画只有在“created"为真时才起作用。这是为了能够保证我们的动画只有在Balloon被动态生成完成后才可以产生动作。“created"在我们的程序设计中,只有被动态生成时才设计为true。为了能够destroy我们动态生成的Ballloon,我们也在Transition中的部分做了如下的修改:

    transitions: [
        Transition {
            to: "exploded"
            SequentialAnimation {
                NumberAnimation { target: balloon; property: "opacity"
                    to: 0; duration: 800 }

                NumberAnimation { target: balloon; property: "scale"
                    to: 0; duration: 800 }

                PropertyAction { target: balloon; property: "source"
                    value:  {
                        if ( surprise )
                            "images/flower.png";
                        else
                            ""
                    }
                }

                NumberAnimation { target: balloon; property: "opacity"
                    to: 1; duration: 300 }
                NumberAnimation { target: balloon; property: "scale"
                    to: 1; duration: 300
                }

                PauseAnimation {
                    duration: {
                        if (surprise)
                            400
                        else
                            200
                    }
                }

                PropertyAction { target: balloon; property: "visible"
                   value: "false"}
            }
        }
    ]

5)动态生成QML Component

为了动态生成Balloon, 我们在主界面里加入一个Button。这个按钮可以帮我们生成所需要的Balloon。我们同时也为我们的主界面加上一个天空的背景:

    Page {
        id:main
        title: i18n.tr("Balloon")
        property int time: 2000
        property int rotateVal: 0

        Image {
            anchors.fill:parent
            source: "images/sky.jpg"
        }

        Balloon {
            id: red
            x: main.width/2
            y: main.height - 60
            rotation: main.rotateVal
            color: "red"
            y1: main.height/6
            x1: 0
            speed: main.time/2
            created: true
            surprise: true
        }

        Balloon {
            id: blue
            x: main.width / 3 - 60
            y: main.height - 60
            color: "blue"
            rotation: main.rotateVal
            y1: main.height/4
            x1: main.width/2 + 20
            speed: main.time/2
            created: true
        }

        Balloon {
            id: green
            x: main.width*2/3
            y: main.height - 10
            rotation: main.rotateVal
            color: "green"
            y1: main.height/5
            x1: main.width/3 + 20
            speed: main.time/3
            created: true
            surprise: true
        }

        Button {
            z: 2
            id: restartButton

            anchors.bottom: parent.bottom
            anchors.right: parent.right
            anchors.bottomMargin: 10
            anchors.rightMargin: 10

            width: 100
            height: 40
            text: "Add"
            onClicked: {
                var x = Math.random() * main.width
                var y = main.height - 60
                var colors = new Array("red","blue","green");

                var date = new Date()
                // Use miliseconds avoids the same random secuece
                // generation among calls
                var mils = date.getMilliseconds()
                var index = Math.floor((Math.random()*mils)%3)

                var obj = Logic.createBalloon( Math.floor(x), Math.floor(y),
                                              colors[index] )
            }
        }
    }

在这里,我们使用了一个天空的Image背景。同时也创建了三个不同颜色的气球。同时,我们也加入了一个按钮“Add”用来添加动态生成的气球。为了能够使用上面的“Logic”,我们也创建了一个叫做“logic.js”的文件,处于和"main.qml"同一个目录中。它的内容如下:

var component;

function addBinding (from, toObj, toProp)
{
    var bindObj = Qt.createQmlObject("import QtQuick 2.0; Binding {value:"+from+"}", main)
    if (bindObj) {
        bindObj.target = toObj
        bindObj.property = toProp
    }
    else {
        console.log("error creating binding object")
        console.log(bindObj.errorString())
        return false
    }
    return true
}


function createBalloon(x, y, color) {
    // console.log( "Color:" + color)
    if (component == null)
        component = Qt.createComponent("Balloon.qml");

    // Note that if Block.qml was not a local file, component.status would be
    // Loading and we should wait for the component's statusChanged() signal to
    // know when the file is downloaded and ready before calling createObject().
    if (component.status === Component.Ready) {
        var dynamicObject = component.createObject(main);
        if (dynamicObject === null) {
            console.log("error creating block");
            console.log(component.errorString());
            return false;
        }

        var xx = main.width*Math.random();
        var xx1 = Math.floor(Math.min(xx, main.width-dynamicObject.width));

        dynamicObject.x = x;
        dynamicObject.y = y;
        dynamicObject.x1 = xx1;
        dynamicObject.y1 = 100 * Math.random();
        dynamicObject.speed = 2000
        dynamicObject.color = color;

        with(Math) {
            var tmp = floor(random() * 10 + 1)
            dynamicObject.surprise = (tmp===10);
        }

        var bindObj = Qt.createQmlObject("import QtQuick 2.0; Binding {value: main.rotateVal}", main);

        if (bindObj) {
            bindObj.target = dynamicObject
            bindObj.property = "rotation"
        }
        else {
            console.log("error creating binding component") ;
            console.log(bindObj.errorString());
            return false;
        }

        // This should be set last
        dynamicObject.created = true;

        // addBinding( "scaleVal", dynamicObject, scale );

    } else {
        console.log("error loading block component");
        console.log(component.errorString());
        dynamicObject = null;
        return null;
    }

    return dynamicObject;
}

function playsound(surprise) {
    if ( surprise ) {
        clapPlayer.play();
    }
    else {
        player.play();
    }
}

这里,我们使用了createBalloon(x, y, color)方法在(x, y)处创建我们所需要颜色的气球。最后为了引用"logic.js",我们必须在main.qml文件的顶头部分使用如下的语句:

import "logic.js" as Logic

这样我们就可以使用这个js文件中的方法了。


    

至此,我们已经基本完成了如何自动动态地生成QML component。全部的代码在如下的地址可以下载:

bzr branch lp:~liu-xiao-guo/debiantrial/balloon2

6)加入sensor 到应用中去

接下来,我们想把sensor加入到我们的应用中去。我们想当我们倾斜我们的手机的时候,气球也要随着转动,同时当我们摇动手机的时候,我们希望气球能跟着我们的摇动不断地变大,这样能使得我们的气球直至破裂。为了我们能够使用sensor,我们必须在main.qml中加入如下的库:

import QtSensors 5.0

同时,我们也加入如下的代码:

        Accelerometer {
            id: accel
            active: true
            dataRate: 20

            onReadingChanged: {
                var x = Math.abs(accel.reading.x);
                var y = Math.abs(accel.reading.y);
                var z = Math.abs(accel.reading.z);

                if ( x > main.maxX ) {
                    main.maxX = x;
                }

                if ( y > main.maxY ) {
                    main.maxY = y;
                }

                if ( z > main.maxZ ) {
                    main.maxZ = z;
                }

                if ( x > main.threshold || y > main.threshold || z > main.threshold ) {
                    console.log("x: " + main.maxX + " y: " + main.maxY + " z: " + main.maxZ);
                    var count = main.children.length

                    for(var i=0; i < count; i++) {
                        if ( main.children[i].type !== "balloon" )
                            continue;
                        if ( main.children[i].type === "balloon") {
                            if ( main.children[i].color === "red")
                                main.children[i].scale += 0.1
                            else if ( main.children[i].color === "green" )
                                main.children[i].scale += 0.02
                            else
                                main.children[i].scale += 0.05
                        }

                        if( main.children[i].scale > 2.0 &&
                                main.children[i].state !== "exploded" &&
                                main.children[i].color === "red" &&
                                main.children[i].type === "balloon" ) {
                            main.children[i].state = "exploded"
                        }

                        if( main.children[i].scale > 3.0 &&
                                main.children[i].state !== "exploded" &&
                                main.children[i].color === "green" &&
                                main.children[i].type === "balloon" ) {
                            main.children[i].state = "exploded"
                        }

                        if( main.children[i].scale > 4.0 &&
                                main.children[i].state !== "exploded" &&
                                main.children[i].color === "blue" &&
                                main.children[i].type === "balloon" ) {
                            main.children[i].state = "exploded"
                        }
                    }
                }
            }
        }

        RotationSensor {
            id: rotation
            dataRate: 50
            onReadingChanged: {
                var count = main.children.length

                for ( var i = 0; i < count; i ++ ) {
                    if (main.children[i].type !== "balloon")
                        continue;

                    main.children[i].rotation = -rotation.reading.x;
                    main.children[i].rotation = -rotation.reading.y;
                }

            }
        }

我们使用了加速传感器和旋转传感器。这样在我们摇动的时候,我们就可以使得气球不断地变大。当我们倾斜的时候,气球也可以随着我们的倾斜而倾斜。

最终的代码可以在如下的地址下载:

bzr branch lp:~liu-xiao-guo/debiantrial/balloonfinal





作者:UbuntuTouch 发表于2014-9-10 20:39:32 原文链接
阅读:202 评论:0 查看评论

Read more
UbuntuTouch

[原]如何编译并安装Ubuntu OS Core Apps

在这篇文章中,我们将介绍如何下载及安装Ubuntu OS中的Core Apps。这些Core Apps是由社区的开发者开发的应用,并由他们维护。这些应用包括时钟,计算器,日历,音乐,文件浏览器等。


1)首先我们打开我们的SDK

我们打开我们的SDK,我们可以看到如下的画面:



点击屏幕左边的“Core Apps”,我们就可以看到:



在上面的屏幕中,我们可以看到一个地址https://launchpad.net/ubuntu-phone-coreapps/。我们也可以直接在浏览器中输入这个地址。也可以在上面的屏幕中点击“Projects”中你所感兴趣的项目。在这里我们点击“Ubuntu Clock App”。我们点击屏幕上方的“Code”链接,我们就可以看到代码的branch在:

bzr branch lp:ubuntu-clock-app

如果你还没有安装bzr的话,请打开你的Terminal,并输入如下的命令:

$sudo apt-get install bzr




安装完bzr后,我们可以创建一个我们喜欢的目录,再打入如下的命令:

$bzr branch lp:ubuntu-clock-app



这样,我们就下载完我们的Clock应用的代码了。

2)编译并部署Clock应用

首先我们进入应用的更目录,并打入如下的命令:

$click-buddy --arch armhf --framework ubuntu-sdk-14.10 

  

等编译完后,我们可以发现在该目录中有生产一个以click为扩展名的文件。它就是我们所需要的安装文件。我们就可以开始部署它到我们的手机中了。我们首先打入如下的命令:

$adb push com.ubuntu.clock_3.2.163_armhf.click  /home/phablet/

这样就把click文件推送到手机中了(用户名为phablet)。我们可以通过如下的方式来进入手机进行安装:

$adb shell

等进入到手机后,再打入如下的命令:

$pkcon --allow-untrusted install-local com.ubuntu.clock_3.2.163_armhf.click 

整个的命令显示如下:



当命令完成后,我们会发现我们的手机已经安装上最新的Clock应用了。当我们的手机在没有连到电脑的情况下,我们在SDK中启动emulator。用同样的方法,我们也可以把armhf的click包部署到一个armhf的emulator中去。






作者:UbuntuTouch 发表于2014-11-5 15:05:10 原文链接
阅读:83 评论:0 查看评论

Read more
UbuntuTouch

[原]利用ContentHub API来import图片

由于Ubuntu OS的安全机制,任何第三方的应用无法访问另外一个应用的存储。这样就带来了问题,比如我们想访问Gallery中的图片怎么办?我们可以利用Ubuntu OS提供的ContentHub API来访问另外一个应用所提供的可以访问的内容。前提是另外一个应用必须有相应的功能实现exporter的功能。在这篇文章中,我们来介绍如何使用ContentHub来获取由Gallery所提供的图片。更多关于ContentHub的API介绍,可以参照链接


1)创建一个最基本的QML应用

我们打开SDK来创建一个最基本的QML应用。我们使用"App with Simple UI" 模版。并取一个名字为“contenthub-importer”。在这里,我们必须注意如下项:

applicationName: "com.ubuntu.developer.liu-xiao-guo.contenthub-importer"

这个应用的名称必须和package的名称是一致的。否则在以后我们导入图片到本应用时,由于目录的路径不同从而会导致所创建的目录不能访问。我们将生成的package名称为:

com.ubuntu.developer.liu-xiao-guo.contenthub-importer_0.1_all.click

在手机上导入的路径将为:



当然这是我们在最后运行应用时,在导入图片后才可以创建的路径。

2)设计我们的界面

我们的设计界面如下:



在屏幕的做上方,是一个“ComboButton”。我们的设计如下:

        ListModel {
            id: typemodel
            ListElement { name: "Import single item" }
            ListElement { name: "Import multiple items" }
        }

        ListItem.Empty {
            id: options

            ComboButton {
                id: type
                anchors {
                    left: parent.left
                    margins: units.gu(2)
                }

                dropdownColor: "red"
                width: root.width/2

                expanded: false

                text: "Import single item"
                ListView {
                    anchors.fill: parent
                    model: typemodel
                    delegate: ListItem.Standard {
                        text: modelData

                        onClicked: {
                            console.log("item is clicked!" + index + " " + name);
                            type.expanded = false;
                            type.text = text;

                            console.log("type is: " + type.text);

                            if ( name === "Import single item") {
                                activeTransfer = picSourceSingle.request(appStore);
                            } else if ( name === "Import multiple items" ) {
                                activeTransfer = picSourceMulti.request(appStore);
                            }
                        }
                    }
                }
            }

            Button {
                anchors {
                    right: parent.right
                    margins: units.gu(2)
                }
                text: "Finalize import"
                enabled: activeTransfer.state === ContentTransfer.Collected
                onClicked: activeTransfer.finalize()
            }
        }

我们可以选择import一个或多个图片。我们同时也定义两个ContentPeer。一个ContentPeer是一个已经向ContentHub注册作为一个数据源或一个有ContentType定义的目的地。

        ContentPeer {
            id: picSourceSingle
            contentType: ContentType.Pictures
            handler: ContentHandler.Source
            selectionType: ContentTransfer.Single
        }

        ContentPeer {
            id: picSourceMulti
            contentType: ContentType.Pictures
            handler: ContentHandler.Source
            selectionType: ContentTransfer.Multiple
        }

我们也可以定义一个“ContentPeerModel”来显示所有可以提供图片的应用。

        // Provides a list<ContentPeer> suitable for use as a model
        ContentPeerModel {
            id: picSources
            // Type of handler: Source, Destination, or Share
            handler: ContentHandler.Source
            // well know content type
            contentType: ContentType.Pictures
        }

我们可以用如下的ListView来显示这些应用:

        ListView {
            id: peerList
            anchors {
                left: parent.left
                right: parent.right
                top: options.bottom
            }
            height: childrenRect.height
            model: picSources.peers

            delegate: ListItem.Standard {
                text: modelData.name
                control: Button {
                    text: "Import"
                    onClicked: {
                        // Request the transfer, it needs to be created and dispatched from the hub
                        activeTransfer = modelData.request();
                    }
                }
            }
        }

我们可以用如下的方法来检测在transfer过程中的状态:

        Connections {
            target: activeTransfer
            onStateChanged: {
                //            console.log("StateChanged: " + activeTransfer.state);
                switch (activeTransfer.state) {
                case ContentTransfer.Created:
                    console.log("Created");
                    break
                case ContentTransfer.Initiated:
                    console.log("Initiated");
                    break;
                case ContentTransfer.InProgress:
                    console.log("InProgress");
                    break;
                case ContentTransfer.Downloading:
                    console.log("Downloading");
                    break;
                case ContentTransfer.Downloaded:
                    console.log("Downloaded");
                    break;
                case ContentTransfer.Charged:
                    console.log("Charged");
                    break;
                case ContentTransfer.Collected:
                    console.log("Collected");
                    break;
                case ContentTransfer.Aborted:
                    console.log("Aborted");
                    break;
                case ContentTransfer.Finalized:
                    console.log("Finalized");
                    break;
                default:
                    console.log("not recognized state!")
                    break;
                }

                if (activeTransfer.state === ContentTransfer.Charged) {
                    importItems = activeTransfer.items;

                    for ( var i = 0; i < importItems.length; i ++ ) {
                        console.log(importItems[i].url);
                    }

                    var item;
                    for ( item in importItems ) {
                        console.log( "imported url: " + importItems[item].url);
                    }
                }
            }

在上面的代码中,我们显示了存入本应用中的文件的路径。这里activeTransfer及importItems被定义如下:

    property list<ContentItem> importItems
    property var activeTransfer

我们用另外一个ListView来显示我们所导入的照片:

        ListView {
            id: peerList
            anchors {
                left: parent.left
                right: parent.right
                top: options.bottom
            }
            height: childrenRect.height
            model: picSources.peers

            delegate: ListItem.Standard {
                text: modelData.name
                control: Button {
                    text: "Import"
                    onClicked: {
                        // Request the transfer, it needs to be created and dispatched from the hub
                        activeTransfer = modelData.request();
                    }
                }
            }
        }

最应用显示的效果如下:



整个的代码在如下的地址可以找到:

bzr branch lp:~liu-xiao-guo/debiantrial/contenthub-importer



作者:UbuntuTouch 发表于2014-11-6 18:07:22 原文链接
阅读:82 评论:0 查看评论

Read more
UbuntuTouch

Ubuntu SDK对我们大多数开发者来说非常有效。它甚至可以帮我们来进行在线调试。在这篇文章中,我们来介绍如何使用command line来编译并运行我们的scope。


1)创建一个最基本的Scope

我们可以使用Ubuntu SDK来创建一个最基本的Scope。在创建时,我们可以选择任何一个template。我们可以在SDK中试着在Desktop上进行编译运行以确保没有任何问题。



我们创建一个叫做“scopetest”的scope。

2)运用Command Line进行编译及运行

我们首先打开一个Terminal。我们也可以打开已经创建好的template Scope中的readme.txt文件:

This project contains a simple Unity Scope and all files needed to build
and deploy it. Building it is simple. First you need to install the scopes
development package and CMake. Then you go into the source root and type
the following commands.

mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=debug ..
make

我们首先进入Scope所在的目录,并创建一个“build”子目录。

  


  


我们打入如上所述的命令。等编译好没有错误后,我们再进入当前目录下的“src”子目录,并打入如下的命令:

unity-scope-tool com.ubuntu.developer.liu-xiao-guo.scopetest_scopetest.ini 

这样,我们就可以在desktop上运行我们的scope了。


3)安装到手机中

我们可以创建两个如下的scripts文件:

build.sh


build.sh:
#!/bin/bash
click-buddy --arch armhf --framework ubuntu-sdk-14.10 --dir ..

provision.sh

#!/bin/bash
rm -f ../*click
click-buddy --arch armhf --framework ubuntu-sdk-14.10 --dir .. --provision

并同时把这两个文件存于scope项目的上一个目录。对我来说,我存放于“release”目录中。这样当我们处于“build”目录时,




我们也可以同时使用provision.sh来部署scope到手机中



在我们的手机上就可以看到我们已经部署好的scope。我们如果看不到的话,我们可以通过搜索的方式找到我们的scope。


作者:UbuntuTouch 发表于2014-10-31 10:54:57 原文链接
阅读:173 评论:0 查看评论

Read more