Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

在先前的例程中,我们探讨了如何利用audio PreviewWidget在Scope中播放音乐。在这篇文章中,我们将介绍如何使用video PreviewWidget来播放一个video。


我们首先来下载我在上一篇文章中的例程:


git clone https://gitcafe.com/ubuntu/scopetemplates_audio.git


为了加入video PreviewWidget,我在我们的例程中加入了如下的句子:


query.cpp


    r["videoSource"] = "http://techslides.com/demos/sample-videos/small.mp4";
    r["screenshot"] = icons_[2].toStdString();

在这里,我们加入了video PreviewWidget中必须的两项“source"及“screenshot”。在实践中,我发现如果没有定义“screenshot”,该视频将不会被正确显示和播放。这一项的设定非常重要。在这里我们利用了一个我们本地的图片来显示该视频的一个截图(尽管不太精确)。


preview.cpp


在Preview中,我们修改了我们的代码如下:

    Result result = PreviewQueryBase::result();
    PreviewWidget listen("tracks", "audio");
    {
        VariantBuilder builder;
        builder.add_tuple({
            {"title", Variant("This is the song title")},
            {"source", Variant(result["musicSource"].get_string().c_str())}
        });
        listen.add_attribute_value("tracks", builder.end());
    }

    PreviewWidget video("videos", "video");
    video.add_attribute_value("source", Variant(result["videoSource"].get_string().c_str()));
    video.add_attribute_value("screenshot", Variant(result["screenshot"].get_string().c_str()));

    PreviewWidgetList widgets({ image, header, description });

    if ( result["musicSource"].get_string().length() != 0 ) {
        widgets.emplace_back(listen);

    }

    if( result["videoSource"].get_string().length() != 0 ) {
        widgets.emplace_back(video);
    }

    reply->push( widgets );

运行我们的Scope,我们可以看到如下的图片:


   

当我们点击播放按钮时,它会自动调用浏览器来播放我们的视频。

整个项目的源码在:git clone https://gitcafe.com/ubuntu/scopetemplates_video.git



作者:UbuntuTouch 发表于2015/6/23 14:07:54 原文链接
阅读:236 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将介绍如何使用gallery PreviewWidget在Scope Preview中显示多幅图片。更多关于PreviewWidget类型可以参阅API


首先,我们来下载我们上一节课里讲到的scopetemplate例程:


git clone https://gitcafe.com/ubuntu/scopetemplates_video.git


为了能够显示多幅图片,我们对我们的程序做了如下的修改:


query.cpp


  // add an array to show the gallary of it
    sc::VariantArray arr;

    for(const auto &datum : icons_) {
        arr.push_back(Variant(datum.toStdString()));
    }

    r["array"] = sc::Variant(arr);

在这里,我们把我们本地的图片压入到一个VariantArray中,并写入到“array”字段里。


preview.cpp


    Result result = PreviewQueryBase::result();
    PreviewWidget listen("tracks", "audio");
    {
        VariantBuilder builder;
        builder.add_tuple({
            {"title", Variant("This is the song title")},
            {"source", Variant(result["musicSource"].get_string().c_str())}
        });
        listen.add_attribute_value("tracks", builder.end());
    }

    PreviewWidget video("videos", "video");
    video.add_attribute_value("source", Variant(result["videoSource"].get_string().c_str()));
    video.add_attribute_value("screenshot", Variant(result["screenshot"].get_string().c_str()));

    sc::PreviewWidget header_gal("gallery_header", "header");
    header_gal.add_attribute_value("title", Variant("Gallery files are:"));

    PreviewWidget gallery("gallerys", "gallery");
    gallery.add_attribute_value("sources", Variant(result["array"]));

    PreviewWidgetList widgets({ image, header, description });

    if ( result["musicSource"].get_string().length() != 0 ) {
        widgets.emplace_back(listen);
    }

    if( result["videoSource"].get_string().length() != 0 ) {
        widgets.emplace_back(video);
    }

    if( result["array"].get_array().size() != 0 ) {
        widgets.emplace_back(header_gal);
        widgets.emplace_back(gallery);
    }

    reply->push( widgets );

在这里,我们创建了一个新的header叫做header_gal。它用来向我们提示一个header,同时,我们直接使用:

    PreviewWidget gallery("gallerys", "gallery");
    gallery.add_attribute_value("sources", Variant(result["array"]));


    PreviewWidget gallery("gallerys", "gallery");
    gallery.add_attribute_mapping("sources", "array");

把array字段的内容映射到sources里,这样就可以让gallery得到正确的显示:

运行我们的Scope:

    


整个项目的源码在:git clone https://gitcafe.com/ubuntu/scopetemplates_gallery.git

作者:UbuntuTouch 发表于2015/6/23 16:17:41 原文链接
阅读:239 评论:0 查看评论

Read more
UbuntuTouch

我们可以利用reviews PreviewWidget来对我们的结果进行显示评价。比如对点评来说,如果是5星的评价,我们可以显示5个星表示客户对商品最高认可。


参阅我们的API的连接,我们可以看到如下的代码:


{
    PreviewWidget w1("summary", "reviews");
    w1.add_attribute_value("rating-icon-empty", Variant("file:///tmp/star-empty.svg"));
    w1.add_attribute_value("rating-icon-full", Variant("file:///tmp/star-full.svg"));
    VariantBuilder builder;
    builder.add_tuple({
        {"author", Variant("John Doe")},
        {"rating", Variant(3)}
    });
    builder.add_tuple({
        {"author", Variant("Mr. Smith")},
        {"rating", Variant(5)}
    });
    w1.add_attribute_value("reviews", builder.end());
    ...
}

这里虽然使用了自己定义的图片“rating-icon-empty”及“rating-icon-full”,但是我觉得一般不建议使用虽然可以做到定制化。原因是我们必须严格按照Ubuntu标准的icon来设计自己的图片,这样才可以使得显示正确无误!


结合上面的代码,我们在我们自己的代码中使用如下的代码:


    PreviewWidget review("reviews", "reviews");
    VariantBuilder builder;
    builder.add_tuple({
                          {"author", Variant("John Doe")},
                          {"review", Variant("very good")},
                          {"rating", Variant(3.5)}
                      });
    builder.add_tuple({
                          {"author", Variant("Mr. Smith")},
                          {"review", Variant("very poor")},
                          {"rating", Variant(5)}
                      });
    review.add_attribute_value("reviews", builder.end());
    widgets.emplace_back(review);

在这里我们添加了一个项“review”用来表示对商品的文字评价。这里我们没有使用任何自定义的图片,它默认的情况下使用的是系统自身的。


运行我们的Scope,显示如下:


  


整个项目的源码在: git clone https://gitcafe.com/ubuntu/scopetemplates_review.git


作者:UbuntuTouch 发表于2015/6/24 10:04:12 原文链接
阅读:245 评论:0 查看评论

Read more
UbuntuTouch

在先前的文章“利用reviews PreviewWidget在Ubuntu Scope中显示评价等级”中,我们展示了如何使用review PreviewWidget来显示评价的数据。在这篇文章中,我们将介绍如何使用rating-input PreviewWidget来对事物进行评价及打分。


参考我们的API介绍,rating-input PreviewWidget分为两种:

  • A star-based rating (rating)
  • an input field for the user to enter his/her review (review)
也就是,我们可以对事物进行打分,用星级来标示。另外我们也可以输入文字来发表我们的意见。

根据这两种情况,我分别做了如下的代码:

review


    // The following shows a review rating-input
    PreviewWidget w_review("review_input", "rating-input");
    w_review.add_attribute_value("submit-label", Variant("Send"));
    w_review.add_attribute_value("visible", Variant("review"));
    w_review.add_attribute_value("required", Variant("review"));
    std::string reply_label = "Reply";
    std::string max_chars_label = "140 characters max";
    w_review.add_attribute_value("review-label", Variant(reply_label + ": " + max_chars_label));
    widgets.emplace_back(w_review);

rating


    // The follwing shows a rating rating-input
    PreviewWidget w_rating("rating_input", "rating-input");
    w_rating.add_attribute_value("visible", Variant("rating"));
    w_rating.add_attribute_value("required", Variant("rating"));
    w_rating.add_attribute_value("rating-label", Variant("Please rate this"));
    widgets.emplace_back(w_rating);


运行我们的Scope,显示如下:

  


我们可以从上面看到一个review及rating的rating-input。

在上面我们可以看到当我们点击“Send”及输入我们的rating时,我们怎么获取他们的值,并使用它们呢?为了这个目的,我们创建了一个新的类:

action.h


#ifndef SCOPE_ACTION_H_
#define SCOPE_ACTION_H_

#include <unity/scopes/ActionMetadata.h>
#include <unity/scopes/ActivationQueryBase.h>
#include <unity/scopes/ActivationResponse.h>
#include <unity/scopes/Result.h>

class Action : public unity::scopes::ActivationQueryBase
{
public:
    Action(unity::scopes::Result const& result,
           unity::scopes::ActionMetadata const& metadata,
           std::string const& action_id);
    ~Action() = default;

     virtual unity::scopes::ActivationResponse activate() override;

private:
    std::string const action_id_;
};

#endif // SCOPE_ACTION_H_


action.cpp


#include <scope/action.h>
#include <unity/scopes/ActivationResponse.h>
#include <unity/UnityExceptions.h>

#include <QString>
#include <QDebug>

#include <iostream>

namespace sc = unity::scopes;
using namespace std;

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

Action::Action(const unity::scopes::Result &result,
               const unity::scopes::ActionMetadata &metadata,
               std::string const& action_id)
    : sc::ActivationQueryBase(result, metadata),
      action_id_(action_id)
{
    qDebug() << "action id: " << qstr_(action_id_);
}

sc::ActivationResponse Action::activate()
{
    QString review = QString("%1").arg(qstr_(action_metadata().scope_data().
                                              get_dict()["review"].get_string()));

    double rating = action_metadata().scope_data().
                          get_dict()["rating"].get_double();

    qDebug() << "review: " << review;
    qDebug() << "rating: " << rating;

    sc::ActivationResponse done(sc::ActivationResponse::ShowDash);
    cerr << "activate called" << endl;
    return done;
}


scope.cpp


sc::ActivationQueryBase::UPtr Scope::perform_action(
                                             sc::Result const& result,
                                             sc::ActionMetadata const& metadata,
                                             string const& widget_id,
                                             string const& action_id)
{
    cerr << "perform_action called" << endl;
    return sc::ActivationQueryBase::UPtr(new Action(result, metadata, action_id));
}


使用action类,我们可以在按下按钮“Send”及改变星级rating输入后,我们可以看到如下的输出:



从上面的输出,我们可以看到所有的输出都可以被截获。我们可以利用它们并使用相应的API来更新我们网上的数据,比如点评中餐馆的评价。具体的使用实例请参考我的例程“在Ubuntu Scope中如何截获按钮事件并做我们想做的事情”。

整个项目的源码在:git clone https://gitcafe.com/ubuntu/scopetemplates_rating_input.git


作者:UbuntuTouch 发表于2015/6/24 12:53:43 原文链接
阅读:282 评论:0 查看评论

Read more
UbuntuTouch

我们知道golang越来越被很多的开发者来开发应用。go语言也可以用于开发Ubuntu Scope。在今天的教程中,我们将详细介绍如何使用go语言来开发我们的Scope。这对于很多的不太熟悉C/C++的开发者来说,无疑是一个福音。对我来说,这个语言也是比较新的。如果大家想学习golang的话,建议大家阅读“Go by Example”。


对于更多的关于Go Scope的开发,可以参阅文章“Go scopes development”。


IDE选择

由于一些原因,目前我们的Ubuntu SDK并没有支持go语言的Scope的开发。可喜的是,我们可以使用Command Line来完成我们的开发。俗话说,好镰不误砍柴功。一个好的工具无疑能帮我们更快更好地开发。我目前使用的编辑器是LiteIDE。这个是转为go语言而设计的IDE,非常简洁而使用,建议大家试用。




安装开发环境


我们首先必须安装golang包及相关包:

$sudo apt-get install unity-scope-tool golang git bzr python3-scope-harness mercurial


为了说明问题的方便,大家可以先下载我的模版Scope:

$git clone https://gitcafe.com/ubuntu/goscope.git

在自己的Terminal下键入上述的命令,你就可以在你所在的目录下发现一个叫做“goscope”的目录:

liuxg@liuxg:~/release/goscope$ ls -l
total 68
-rwxrwxr-x 1 liuxg liuxg 2886  7月  2 12:37 build-click-package.sh
-rwxrwxr-x 1 liuxg liuxg  752  7月  2 12:37 build.sh
-rwxrwxr-x 1 liuxg liuxg 1144  7月  2 12:37 clean.sh
-rw-rw-r-- 1 liuxg liuxg 9835  7月  2 12:37 goscope.go
-rw-rw-r-- 1 liuxg liuxg  329  7月  2 12:37 goscope.ini
-rw-rw-r-- 1 liuxg liuxg  102  7月  2 12:37 goscope-security.json
-rw-rw-r-- 1 liuxg liuxg 8403  7月  2 12:37 icon.jpg
-rw-rw-r-- 1 liuxg liuxg 4851  7月  2 12:37 logo.jpg
-rw-rw-r-- 1 liuxg liuxg  435  7月  2 12:37 manifest.json
-rwxrwxr-x 1 liuxg liuxg 1293  7月  2 12:37 run.sh
-rwxrwxr-x 1 liuxg liuxg 1666  7月  2 12:37 setup-chroot-go.sh
drwxrwxr-x 3 liuxg liuxg 4096  7月  2 12:37 src

从上面的显示中,我们可以看到除了我们期望的goscope.go文件外,我们多了几个脚本来帮我们完成我们的工作。我们可以按照如下的命令把下载后的script变为可以执行的脚本

$chmod +x *.sh


首先,我们来看一下我们的build.sh。

build.sh


#!/bin/bash

#
#  usage: ./build.sh 
#         it builds project and produces the armhf click package
#         ./build.sh -d
#		  it builds the project and deploy it to the phone	
#  A developer needs to change the armhf names in the following script according tuo your project	
#

export GOPATH=`pwd`
go get launchpad.net/go-unityscopes/v2
./setup-chroot-go.sh ubuntu-sdk-15.04 vivid 
./build-click-package.sh goscope liu-xiao-guo ubuntu-sdk-15.04 vivid

if [ $# -eq 1 ]
then
	if [ $1 = "-d" ] 
	then
		echo "Start to deploy to the phone ..."
		adb push ./goscope.liu-xiao-guo/goscope.liu-xiao-guo_1.0.0_armhf.click /tmp
		adb shell "sudo -iu phablet pkcon --allow-untrusted install-local /tmp/goscope.liu-xiao-guo_1.0.0_armhf.click"
		exit 0
	fi
fi

从这个脚本中,我们可以看到当脚本执行的时候,它执行:

go get launchpad.net/go-unityscopes/v2

这个语句的作用是从上面的地址处下载最新的“go-unityscope”版本2。当你们下载完我的模版后,其实里面已经有一个叫做“src”的目录。里面已经包含了所有的所需要的文件。

当我们加入“-d”选项后,这个script将会自动帮我们把脚本部署到手机中去。记得根据自己的scope名字不同,需要修改部署部分的click包的文件名及目录名字

setup-chroot-go.sh


#!/bin/bash

# Function that executes a given command and compares its return command with a given one.
# In case the expected and the actual return codes are different it exits
# the script.
# Parameters:
#               $1: Command to be executed (string)
#               $2: Expected return code (number), Can be not defined.
function executeCommand()
{
    # gets the command
    CMD=$1
    # sets the return code expected
    # if it's not definedset it to 0
    OK_CODE=$2
    if [ -n $2 ]
    then
        OK_CODE=0
    fi
    # executes the command
    ${CMD}

    # checks if the command was executed successfully
    RET_CODE=$?
    if [ $RET_CODE -ne $OK_CODE ]
    then
        echo "ERROR executing command: \"$CMD\""
        echo "Exiting..."
        exit 1
    fi
}


# ******************************************************************************
# *                                   MAIN                                     *
# ******************************************************************************

if [ $# -ne 2 ]
then
    echo "usage: $0 FRAMEWORK_CHROOT SERIES_CHROOT"
    exit 1
fi

CHROOT=$1
SERIES=$2

sudo click chroot -aarmhf -f$CHROOT -s $SERIES create
sudo click chroot -aarmhf -f$CHROOT -s $SERIES maint apt-get install golang-go golang-go-linux-arm golang-go-dbus-dev golang-go-xdg-dev golang-gocheck-dev golang-gosqlite-dev golang-uuid-dev libgcrypt20-dev:armhf libglib2.0-dev:armhf libwhoopsie-dev:armhf libdbus-1-dev:armhf libnih-dbus-dev:armhf libsqlite3-dev:armhf crossbuild-essential-armhf

echo "Executing go get launchpad.net/go-unityscopes/v2 ...."
GOPATH=`pwd` go get launchpad.net/go-unityscopes/v2
echo "Done."



这个脚本是为了帮我们下载我们所需要的chroots,这样可以进行交叉编译armhf架构从而部署到我们的手机上。如果大家已经安装好自己的Ubuntu SDK,并且已经安装好所需要的armhf chroot,这个script将不会下载任何的东西。

build-click-package.sh


#/bin/bash

# Function that executes a given command and compares its return command with a given one.
# In case the expected and the actual return codes are different it exits
# the script.
# Parameters:
#               $1: Command to be executed (string)
#               $2: Expected return code (number), may be undefined.
function executeCommand()
{
    # gets the command
    CMD=$1
    # sets the return code expected
    # if it's not definedset it to 0
    OK_CODE=$2
    if [ -n $2 ]
    then
        OK_CODE=0
    fi
    # executes the command
    eval ${CMD}

    # checks if the command was executed successfully
    RET_CODE=$?
    if [ $RET_CODE -ne $OK_CODE ]
    then
	echo ""
        echo "ERROR executing command: \"$CMD\""
        echo "Exiting..."
        exit 1
    fi
}

# ******************************************************************************
# *                                   MAIN                                     *
# ******************************************************************************

if [ $# -ne 4 ]
then
    echo "usage: $0 SCOPE_NAME DEVELOPER_NAME FRAMEWORK_CHROOT SERIES_CHROOT"
    exit 1
fi

SCOPE_NAME=$1
DEVELOPER_NAME=$2
CHROOT=$3
SERIES=$4

CURRENT_DIR=`pwd`

FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}"
MANIFEST_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}"

echo -n "Removing ${FILE_NAME} directory... "
executeCommand "rm -rf ./${FILE_NAME}"
echo "Done"

echo -n "Creating clean ${FILE_NAME} directory... "
executeCommand "mkdir -p ${FILE_NAME}/${FILE_NAME}"
echo "Done"

echo -n "Copying scope ini file... "
executeCommand "cp $SCOPE_NAME.ini ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}_${SCOPE_NAME}.ini"
echo "Done"

echo -n "Copying the logo file ... "
executeCommand "cp logo.jpg ${FILE_NAME}/${FILE_NAME}/logo.jpg"
echo "Done"

echo -n "Copying the icon file ... "
executeCommand "cp icon.jpg ${FILE_NAME}/${FILE_NAME}/icon.jpg"
echo "Done"

echo -n "Setting scope name in ini file..."
executeCommand 'sed -i "s/%SCOPE_NAME%/${FILE_NAME}/g" ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}_${SCOPE_NAME}.ini'
echo "Done"

echo -n "Copying scope json files... "
executeCommand "cp *.json ${FILE_NAME}/"
echo "Done"

echo -n "Setting scope name in manifest file..."
executeCommand 'sed -i "s/%SCOPE_NAME%/${MANIFEST_NAME}/g" ${FILE_NAME}/manifest.json'
echo "Done"

echo -n "Cross compiling ${FILE_NAME}..."
executeCommand "click chroot -aarmhf -f$CHROOT -s $SERIES run CGO_ENABLED=1 GOARCH=arm GOARM=7 PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig GOPATH=/usr/share/gocode/:$GOPATH CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ go build -ldflags '-extld=arm-linux-gnueabihf-g++' -o ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}"
echo "Done"

executeCommand "cd ./${FILE_NAME}"

echo -n "Building click package ... "
executeCommand "click build ./"
echo "Done"

executeCommand "cd .."

我们可以通过这个脚本来交叉编译我们的goscope armhf click包。使用时,就像上面的build.sh中展示的那样:

$./build-click-package.sh goscope liu-xiao-guo ubuntu-sdk-15.04 vivid

这里,我们把项目的Scope名,开发者的名字,framework 及Ubuntud的系列号传入即可。


run.sh


#!/bin/bash

#
# This is the script to build and run the scope on desktop environment. 
# A developer needs to change the following variable according to your projectt
# 		SCOPE_NAME=goscope
# 		DEVELOPER_NAME=liu-xiao-guo
#
#

function executeCommand()
{
    # gets the command
    CMD=$1
    # sets the return code expected
    # if it's not definedset it to 0
    OK_CODE=$2
    if [ -n $2 ]
    then
        OK_CODE=0
    fi
    # executes the command
    eval ${CMD}

    # checks if the command was executed successfully
    RET_CODE=$?
    if [ $RET_CODE -ne $OK_CODE ]
    then
	echo ""
        echo "ERROR executing command: \"$CMD\""
        echo "Exiting..."
        exit 1
    fi
}

export GOPATH=`pwd`

SCOPE_NAME=goscope
DEVELOPER_NAME=liu-xiao-guo

FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}"

echo -n "Removing ${FILE_NAME} directory... "
executeCommand "rm -rf ./${FILE_NAME}"
echo "Done"

echo -n "Copying scope ini file... "
executeCommand "cp $SCOPE_NAME.ini ${FILE_NAME}_${SCOPE_NAME}.ini"
echo "Done"

echo -n "Setting scope name in ini file..."
executeCommand 'sed -i "s/%SCOPE_NAME%/${FILE_NAME}/g" ${FILE_NAME}_${SCOPE_NAME}.ini'
echo "Done"

echo -n "Building the scope"
executeCommand "go build -o ${FILE_NAME}"

unity-scope-tool ${FILE_NAME}_${SCOPE_NAME}.ini


这个脚本是为了把我们的Scope在Desktop的环境中进行编译,并使用unity-scope-tool来启动我们的Scope:





clean.sh


#!/bin/bash

#
# This file cleans all of the intermediate files produced during the compilation. For each project
# a developer needs to customize the variables
#     SCOPE_NAME
# 	  DEVELOPER_NAME
#
#

function executeCommand()
{
    # gets the command
    CMD=$1
    # sets the return code expected
    # if it's not definedset it to 0
    OK_CODE=$2
    if [ -n $2 ]
    then
        OK_CODE=0
    fi
    # executes the command
    eval ${CMD}

    # checks if the command was executed successfully
    RET_CODE=$?
    if [ $RET_CODE -ne $OK_CODE ]
    then
	echo ""
        echo "ERROR executing command: \"$CMD\""
        echo "Exiting..."
        exit 1
    fi
}

SCOPE_NAME=goscope
DEVELOPER_NAME=liu-xiao-guo

FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}"

echo -n "Removing ${FILE_NAME} directory... "
executeCommand "rm -rf ./${FILE_NAME}"
echo "Done"

echo -n "Removing ${FILE_NAME}_${SCOPE_NAME}.ini ..."
executeCommand "rm -f ${FILE_NAME}_${SCOPE_NAME}.ini"
echo "Done"

echo -n "Removing ${FILE_NAME} ..."
executeCommand "rm -f ${FILE_NAME}"
echo "Done"

echo -n "Removing pkg directory ..."
executeCommand "rm -rf pkg"
echo "Done"


这个脚本是为了我们能够删除我们在编译过程中所产生任何的中间文件。

goscope.go


package main

import (
	"launchpad.net/go-unityscopes/v2"
	"log"
	"encoding/json"
	"net/url"
	"net/http"
)


const searchCategoryYellow = `{
    "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": "art",
       		"aspect-ratio": 1
        }
    }
}`

const searchCategoryTemplate = `{
    "schema-version" : 1,  
    "template" : {  
        "category-layout" : "carousel",  
        "card-size": "large",  
        "overlay" : true  
    },  
    "components" : {  
        "title" : "title",  
        "art" : {  
            "field": "art",  
            "aspect-ratio": 1.6,  
            "fill-mode": "fit"  
        }  
    }  
}`

// SCOPE ***********************************************************************

var scope_interface scopes.Scope

type MyScope struct {
	BaseURI string
	Key     string
	URI		string
	Dir     string	
	base *scopes.ScopeBase
}

type WathereResponse struct {
	WeatherList []Weather `json:"results"`
	Date string `json:"date"`
}

type Weather struct {
	CurrentCity string `json:"currentCity"`
	Pm25 string `json:"pm25"`
	IndexList []Index `json:"index"`
	Weather_datalist []Weather_data `json:"weather_data"`
}

type Index struct {
	Title string `json:"title"`
	Zs string `json:"zs"`
	Tipt string `json:"tipt"`
	Des string `json:"des"`
}

type Weather_data struct {
	Date string `json:"date"`
	DayPictureUrl string `json:"dayPictureUrl"`
	NightPictureUrl string `json:"nightPictureUrl"`
	Weather string `json:"weather"`
	Wind string `json:"wind"`
	Temperature string `json:"temperature"`
}

func (s *MyScope) buildUrl(url2 string, params map[string]string) string {
	query := make(url.Values)
	for key, value := range params {
		query.Set(key, value)
	}
	log.Println(url2 + query.Encode())
	return url2 + query.Encode()
}

// This is used to get results from a webservice
func (s *MyScope) get(url string, params map[string]string, result interface{}) error {
	resp, err := http.Get(s.buildUrl(url, params))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	return decoder.Decode(result)
}

func (s *MyScope) Search(q *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error {
	root_department := s.CreateDepartments(q, metadata, reply)
	reply.RegisterDepartments(root_department)

	query := q.QueryString()
	log.Println(query)	
	
	// Try to get the city name
	loc := metadata.Location()
	city := loc.City;
	log.Println("city: ", city)
	
	if query == "" {
		if q.DepartmentID() == "" {
			query = city
		} else {
			query = q.DepartmentID()
		}			
	} 
	
	log.Println("query: ", query)
	
	var response WathereResponse
	
	if err := s.get(s.BaseURI, map[string]string{"location": query, "ak": s.Key, "output": "json"}, &response); err != nil {
		return err
	} else {
		log.Println("there is no error!")
	}
	
	// log.Println(response)
	date := response.Date;
	log.Println("date: ", date)
	
	var cat *scopes.Category;
	
	if len(q.QueryString()) == 0 && q.DepartmentID() == "" {
		cat = reply.RegisterCategory("weather", query, "", searchCategoryTemplate)
	} else {
		cat = reply.RegisterCategory("weather", "", "", searchCategoryTemplate)
	}
		
	
	for _, data := range response.WeatherList {
		result := scopes.NewCategorisedResult(cat)
		result.SetURI(s.URI)

//		log.Println("Current city:", data.CurrentCity)
//		log.Println("PM25: ", data.Pm25)
		
		var yellocalendar string = ""
		for _, index := range data.IndexList {
//			log.Println("title: ", index.Title)
//			log.Println("zs: ", index.Zs)
//			log.Println("tipt: ", index.Tipt)
//			log.Println("Des: ", index.Des)
			
			yellocalendar += index.Title + " "
			yellocalendar += index.Zs + " "
			yellocalendar += index.Tipt + " "
			yellocalendar += index.Des
		}
		
		for i, weather := range data.Weather_datalist {
//			log.Println("date: ", weather.Date)
//			log.Println("dayPictureUrl: ", weather.DayPictureUrl)
//			log.Println("nightPictureUrl: ", weather.NightPictureUrl)
//			log.Println("weather: ",weather.Weather)
//			log.Println("wind: ", weather.Wind)
//			log.Println("temperature: ", weather.Temperature)
			
			result.SetTitle(weather.Date)
			result.SetArt(weather.DayPictureUrl)
			result.Set("wind", weather.Wind)
			result.Set("weather", weather.Weather)
			result.Set("temperature", weather.Temperature)
			
			if err := reply.Push(result); err != nil {
				return err
			}
			
			result.SetArt(weather.NightPictureUrl)
			if err := reply.Push(result); err != nil {
				return err
			}			
			
			// Push the yellow calender now
			if i == 0  {
				cat1 := reply.RegisterCategory("weather1", "今天天气", "", searchCategoryYellow)
				result1 := scopes.NewCategorisedResult(cat1)								
				
				result1.SetURI(s.URI)
				result1.SetTitle(date)
				result1.SetArt(weather.DayPictureUrl)
				result1.Set("subtitle", weather.Weather + " " + weather.Wind + " " +  weather.Temperature + "  PMI: " +  data.Pm25)
				result1.Set("summary", yellocalendar)
				
				if err := reply.Push(result1); err != nil {
					return err
				}					
			}		
		}		
	}
	
	return nil
}

func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
	layout1col := scopes.NewColumnLayout(1)
	layout2col := scopes.NewColumnLayout(2)
	layout3col := scopes.NewColumnLayout(3)

	// Single cyolumn layout
	layout1col.AddColumn("header", "image",  "wind", "weather", "temperature", "summary", "actions")

	// Two column layout
	layout2col.AddColumn("header")
	layout2col.AddColumn("image", "wind", "weather", "temperature", "summary", "actions")

	// Three cokumn layout
	layout3col.AddColumn("header")
	layout3col.AddColumn("image", "wind", "weather", "temperature","summary", "actions")
	layout3col.AddColumn()

	// Register the layouts we just created
	reply.RegisterLayout(layout1col, layout2col, layout3col)

	header := scopes.NewPreviewWidget("header", "header")

	// It has title and a subtitle properties
	header.AddAttributeMapping("title", "title")
	header.AddAttributeMapping("subtitle", "subtitle")

	// Define the image section
	image := scopes.NewPreviewWidget("image", "image")
	// It has a single source property, mapped to the result's art property
	image.AddAttributeMapping("source", "art")

	// Define the summary section
	description := scopes.NewPreviewWidget("summary", "text")
	description.AddAttributeMapping("text", "summary")

	wind := scopes.NewPreviewWidget("wind", "text")
	wind.AddAttributeMapping("text", "wind")

	weather := scopes.NewPreviewWidget("weather", "text")
	weather.AddAttributeMapping("text", "weather")

	temperature := scopes.NewPreviewWidget("temperature", "text")
	temperature.AddAttributeMapping("text", "temperature")

	// build variant map.
	var uri string

	if err := result.Get("uri", &uri); err != nil {
		log.Println(err)
	}

	tuple1 := make(map[string]interface{})
	tuple1["id"] = "open"
	tuple1["label"] = "Open"
	tuple1["uri"] = uri

	actions := scopes.NewPreviewWidget("actions", "actions")
	actions.AddAttributeValue("actions", []interface{}{tuple1})

	var summary string
	if err := result.Get("summary", &summary); err != nil {
		log.Println(err)
	}

	if len(summary) > 0 {
		reply.PushWidgets(header, image, description, actions)
	} else {
		reply.PushWidgets(header, image, wind, weather, temperature, actions)
	}

	return nil
}

func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) {
	s.base = base
}


func (s *MyScope) GetSubdepartments1(query *scopes.CannedQuery,
	metadata *scopes.SearchMetadata,
	reply *scopes.SearchReply) *scopes.Department {
	active_dep, err := scopes.NewDepartment("wuhan", query, "湖北")
	
	if err == nil {
//		active_dep.SetAlternateLabel("Rock Music Alt")
		department, _ := scopes.NewDepartment("武汉", query, "武汉")
		active_dep.AddSubdepartment(department)

		department2, _ := scopes.NewDepartment("宜昌", query, "宜昌")
		active_dep.AddSubdepartment(department2)
				
		department3, _ := scopes.NewDepartment("随州", query, "随州")
		active_dep.AddSubdepartment(department3)
	}

	return active_dep
}

func (s *MyScope) GetSubdepartments2(query *scopes.CannedQuery,
	metadata *scopes.SearchMetadata,
	reply *scopes.SearchReply) *scopes.Department {
	active_dep, err := scopes.NewDepartment("changsha", query, "湖南")
	
	if err == nil {
		department, _ := scopes.NewDepartment("长沙", query, "长沙")
		active_dep.AddSubdepartment(department)

		department2, _ := scopes.NewDepartment("株洲", query, "株洲")
		active_dep.AddSubdepartment(department2)
	}

	return active_dep
}

func (s *MyScope) CreateDepartments(query *scopes.CannedQuery,
	metadata *scopes.SearchMetadata,
	reply *scopes.SearchReply) *scopes.Department {
		
	department, _ := scopes.NewDepartment("", query, "选择地点")

	dept1 := s.GetSubdepartments1(query, metadata, reply)
	if dept1 != nil {
		department.AddSubdepartment(dept1)
	}

	dept2 := s.GetSubdepartments2(query, metadata, reply)
	if dept2 != nil {
		department.AddSubdepartment(dept2)
	}

	return department
}

// MAIN ************************************************************************

func main() {
	scope := &MyScope {
		BaseURI: "http://api.map.baidu.com/telematics/v3/weather?",
		Key:     "DdzwVcsGMoYpeg5xQlAFrXQt",
		URI: 	 "http://www.weather.com.cn/html/weather/101010100.shtml",
		Dir:     "",
	}
	
	scope_interface = scope
	
	if err := scopes.Run(scope); err != nil {
		log.Fatalln(err)
	}
}

这是我们的goscope的go语言程序设计。在这个goscope里,我们使用了百度的天气API来显示天气的数据。整个scope的设计其实和C++的Scope设计比较相似,但是显得更加简洁明了。

我们可以通过如下的命令来把我们的Scope打包并部署到我们的手机上:

$./build.sh -d

如果我们只想得到armhf的click包,我们可以不需要“-d”参数。在手机上的运行情况:



   


如何运行Scope


我们可以在Terminal中:

[plain] view plaincopy
  1. $chmod +x *.sh  
通过上述命令来使得.sh文件变为可以执行的文件

[plain] view plaincopy
  1. $./run.sh  
通过上面的脚本执行,在Desktop上运行我们的Scope
[plain] view plaincopy
  1. $./build.sh -d  

通过我们上面的脚本执行,可以编译goscope,并部署到手机中去

[plain] view plaincopy
  1. $./clean.sh  
通过上面的脚本执行,清除所有的编译的中间文件
作者:UbuntuTouch 发表于2015/7/2 13:36:57 原文链接
阅读:400 评论:0 查看评论

Read more
UbuntuTouch

在我们之前的C++文章“利用rating-input PreviewWidget来对事物进行评价及打分”,我们已经展示了如何使用C++来在Scope中的Preview中对事物进行评价或打分。在今天的这篇文章中,我们将介绍如何在Go Scope中来做同样的事。我们可以通过这个例子来展示如何捕获在Go Preview中的按钮并得到它们的action id以进行分别的处理。


在Go文件中的Preview方法中:


unc (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
	layout1col := scopes.NewColumnLayout(1)
	layout2col := scopes.NewColumnLayout(2)
	layout3col := scopes.NewColumnLayout(3)

	// Single column layout
	layout1col.AddColumn("image", "header", "summary", "rating", "actions")

	// Two column layout
	layout2col.AddColumn("image")
	layout2col.AddColumn("header", "summary", "rating", "actions")

	// Three cokumn layout
	layout3col.AddColumn("image")
	layout3col.AddColumn("header", "summary", "rating", "actions")
	layout3col.AddColumn()

	// Register the layouts we just created
	reply.RegisterLayout(layout1col, layout2col, layout3col)

	header := scopes.NewPreviewWidget("header", "header")

	// It has title and a subtitle properties
	header.AddAttributeMapping("title", "title")
	header.AddAttributeMapping("subtitle", "subtitle")

	// Define the image section
	image := scopes.NewPreviewWidget("image", "image")
	// It has a single source property, mapped to the result's art property
	image.AddAttributeMapping("source", "art")

	// Define the summary section
	description := scopes.NewPreviewWidget("summary", "text")
	// It has a text property, mapped to the result's description property
	description.AddAttributeMapping("text", "description")

	actions := scopes.NewPreviewWidget("actions", "actions")
	actions.AddAttributeValue("actions", []actionInfo{
		actionInfo{Id: "my_action", Label: "Close"},
		actionInfo{Id: "my_action2", Label: "Refresh"},
	})

	rating := scopes.NewPreviewWidget("rating", "rating-input")
	rating.AddAttributeValue("visible", "both")
	rating.AddAttributeValue("required", "rating")

	var scope_data string
	metadata.ScopeData(scope_data)
	if len(scope_data) > 0 {
		extra := scopes.NewPreviewWidget("extra", "text")
		extra.AddAttributeValue("text", "test Text")
		reply.PushWidgets(header, image, description, actions, rating, extra)
	} else {
		reply.PushWidgets(header, image, description, rating, actions)
	}

	return nil
}


我们可以在上面的文字中,看到:


	rating := scopes.NewPreviewWidget("rating", "rating-input")
	rating.AddAttributeValue("visible", "both")
	rating.AddAttributeValue("required", "rating")


它用来产生一个rating的PreviewWidget:即有rating,也有review,所以在设置“visible”中为both。


func (sc *MyScope) PerformAction(result *scopes.Result, metadata *scopes.ActionMetadata, widgetId, actionId string) (*scopes.ActivationResponse, error) {
	log.Printf("Perform action for widget=%s, action=%s\n", widgetId, actionId)
	
	// var scope_data interface{}
	var scope_data map[string]interface{}
	metadata.ScopeData(&scope_data)
	
	log.Println("rating: ", scope_data["rating"])
	log.Println("review: ", scope_data["review"])

	for key, value := range scope_data {
		log.Println("key: ", key)
		log.Println("value: ", value)		
	}
	
	if widgetId == "actions" && actionId == "my_action" {
		resp := scopes.NewActivationResponse(scopes.ActivationHideDash)
		resp.SetScopeData([]string{"hello", "world"})
		return resp, nil
	} 

	return scopes.NewActivationResponse(scopes.ActivationShowPreview), nil
}


在Go 文件中实现上面的PerformAction就可以捕获在Preview中的按钮事件。我们在上面的代码中打印出rating及review的值。


运行我们的代码:


  


在运行时的输出:




整个项目的源码在:git clone https://gitcafe.com/ubuntu/goscope_rating.git


我们可以在Terminal中:


$chmod +x *.sh


通过上述命令来使得.sh文件变为可以执行的文件


$./run.sh


通过上面的脚本执行,在Desktop上运行我们的Scope


$./build.sh -d


通过我们上面的脚本执行,可以编译goscope,并部署到手机中去


$./clean.sh


通过上面的脚本执行,清除所有的编译的中间文件



作者:UbuntuTouch 发表于2015/7/3 10:40:30 原文链接
阅读:301 评论:0 查看评论

Read more
UbuntuTouch

有兴趣的开发者可以参阅连接http://doc.qt.io/qt-5/qtgraphicaleffects-qmlmodule.html来深度学习Qt对Graphics方面的处理。在今天的这篇文章中,我们来使用OpacityMask做一个小小的例子来抛砖引玉看看Qt对我们的图像的处理。具体的例子在Qt的官方网站上可以看到。由于一些原因,在官方网站上下载的例程中的文件并不能被使用,需要做一些的处理才可以。


下面,我们直接来把我们的例子展示给大家:


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtGraphicalEffects 1.0

/*!
    \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: "mask.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    Page {
        title: i18n.tr("mask")

        Flickable {
            anchors.fill: parent
            contentHeight: mypics.childrenRect.height

            Item {
                id: mypics
                width: parent.width
                height: units.gu(80)

                Image {
                    id: bug
                    height: parent.height/2
                    width: height
                    source: "images/bug.png"
                    sourceSize: Qt.size(parent.width, parent.height)
                    smooth: true
                    visible: false
                }

                Image {
                    id: bug1
                    height: parent.height/2
                    width: height
                    source: "images/bug.png"
                    anchors.top: bug.bottom
                    anchors.topMargin: units.gu(1)
                    sourceSize: Qt.size(parent.width, parent.height)
                    smooth: true
                    visible: false
                }

                Rectangle {
                    id: mask
                    anchors.margins: 10
                    width: 65
                    height: 65
                    color: "black"
                    radius: width/2
                    clip: true
                    visible: false
                }

                Image {
                    id: mask1
                    height: units.gu(40)
                    width: height
                    source: "images/bufferfly.png"
                    sourceSize: Qt.size(parent.width, parent.height)
                    smooth: true
                    visible: false
                }

                OpacityMask {
                    anchors.fill: bug
                    source: bug
                    maskSource: mask
                }

                OpacityMask {
                    anchors.fill: bug1
                    source: bug
                    maskSource: mask1
                }
            }
        }
    }
}

在这里,我们使用了Image来展示图片,但是,他们并不是可见的(visible = false)。我们可以通过一个掩膜mask,在mask上值为非透明的地方可以显示出图像。可以通过OpacityMask来使得它们可以显示。

我们的mask图片bufferfly.png如下:




bug.png的原图为:



运行我们的例程,效果图如下:


  


源码在: git clone https://gitcafe.com/ubuntu/mask.git

作者:UbuntuTouch 发表于2015/7/6 11:21:09 原文链接
阅读:263 评论:0 查看评论

Read more
UbuntuTouch

在Ubuntu的Scope,目前正在研发一个新的Filter的功能。我们可以在我们的开发者网站找到有关filter的更多的信息。在那里你可以看到一些关于filter的介绍,但是真正地入手去利用它还是有一定的难度的。今天在我们的例程中,我们来具体展示如何利用filter实现更好的搜索。在实际的应用中,比如我们可以通过filter来实现如下的ctrip的Scope:


   


在上面的中间的图中,我们可以看到“重庆”被选中了,在第二个列中,我们可以使用我们的department来进行从一个城市到另外一个城市的搜索。


首先,我们还是利用我们熟悉的点评Scope来作为练习。我们可以在我的先前的文章“在Ubuntu OS上创建一个department 点评Scope (Qt XML)”中下载我们的代码:


git clone https://gitcafe.com/ubuntu/dianpingdept.git


首先,我们在我们的SDK中,确保它是可以真确地运行的。在今天的练习中,我们想实现如下的Scope:


 `  


我们在我们的设计中加入了一个filter。它可以用来选择我们显示搜寻结果的先后次序。这样在我们的department中就可以按照我们的选中来展示搜寻的结果。


首先,我们可以看看我们的点评API接口:


我们可以看见一个“sort”的项。在我们的API中,我们可以用来选中展示的结果的方式。


为了能够设计我们的filter,我们需要做如下的修改:

query.h


class Query: public unity::scopes::SearchQueryBase {
public:
    Query(const unity::scopes::CannedQuery &query,
          const unity::scopes::SearchMetadata &metadata, QString const& scopeDir,
          QString const& cacheDir, api::Config::Ptr config);

    ~Query() = default;

    ...
private:
    ...
    
    QMap<QString, QString> m_sortPref;
    QString m_pref;
};


query.cpp


// This function is used to add filter support for my scope
void Query::initFilerData() {
    m_sortPref["1"] = "默认";
    m_sortPref["3"] = "产品评价高优先";
    m_sortPref["2"] = "星级高优先";
    m_sortPref["4"] = "环境评价高优先";
    m_sortPref["5"] = "服务评价高优先";
    m_sortPref["6"] = "点评数量多优先";
    m_sortPref["7"] = "离传入经纬度坐标距离近优先";
    m_sortPref["8"] = "人均价格低优先";
    m_sortPref["9"] = "人均价格高优先";
}

这个是一个helper方法用来初始化我们的filter所需要的数据。


void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Initialize the scopes
    initScope();
    
    ...
    
    initFilerData();
	
    // set up the filter
    sc::Filters filters;
    const sc::CannedQuery &myquery(sc::SearchQueryBase::query());

    sc::OptionSelectorFilter::SPtr prefFilter = sc::OptionSelectorFilter::create("category",
                                             m_sortPref[DEFAULT_PREF].toStdString());

    prefFilter->set_display_hints(1);

    QMapIterator<QString, QString> i(m_sortPref);
    while (i.hasNext()) {
        i.next();
        qDebug() << i.key() << ": " << i.value();
        prefFilter->add_option(i.key().toStdString(), i.value().toStdString());
        prefFilter->active_options(myquery.filter_state());
        filters.push_back(prefFilter);
    }

    reply->push(filters, myquery.filter_state());

    if (prefFilter->has_active_option(myquery.filter_state())){
        auto o = *(prefFilter->active_options(myquery.filter_state()).begin());
        m_pref = QString::fromStdString(o->id());
        cerr << "pref id: " << o->id()  << endl;
        cerr << "label: " << o->label() << endl;
    } else {
        cerr << "no active option!" << endl;
        m_pref = DEFAULT_PREF;
    }

    qDebug() << "m_pref: " << m_pref;
    
    ...
}


我们通过上面的代码建立一个filter,同时,我们可以得到用户选择的filter的值,并存于成员变量m_pref中。

QString Query::getDeptUrl(QString dept)
{
    QMap<QString, QString> map;

    map["category"] = dept;
    map["sort"] = m_pref;
    map["limit"] = QString::number(m_limit);
    map["platform"] = "2";
    map["format"] = "xml";

    qDebug() << "m_latitude: " << m_latitude;
    qDebug() << "m_longitude: " << m_longitude;

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

    return getUrl( BASE_URI, map );
}


在上面的代码中,我们利用m_pref,并使之成为API请求的一部分。

这样我们就完成了我们的filter的设计工作。展示的图片在上面显示了。

所有的源码可以在地址下载:git clone https://gitcafe.com/ubuntu/dianping_filter.git


作者:UbuntuTouch 发表于2015/7/7 14:16:34 原文链接
阅读:303 评论:0 查看评论

Read more
UbuntuTouch

[原]如何在QML中使用multitouch

在Qt QML中,它可以利用multitouch来做一些我们想做的事情。在今天的文章中,我们将介绍如何使用multitouch来做一些我们想做的事。


其实,在QML中利用多点触控是非常容易的一件事。我们下面直接来展示我们的例程来给大家讲解一下:


import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \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: "multitouchtest.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    Page {
        title: i18n.tr("multitouchtest")

        MultiPointTouchArea {
            anchors.fill: parent
            touchPoints: [
                TouchPoint { id: point1 },
                TouchPoint { id: point2 },
                TouchPoint { id: point3 },
                TouchPoint { id: point4 }
            ]
        }

        Image {
            width: parent.width/5
            height: parent.height/5
            source: "images/image1.jpg"
            x: point1.x
            y: point1.y
        }

        Image {
            width: parent.width/5
            height: parent.height/5
            source: "images/image2.jpg"
            x: point2.x
            y: point2.y
        }

        Image {
            width: parent.width/5
            height: parent.height/5
            source: "images/image3.jpg"
            x: point3.x
            y: point3.y
        }

        Image {
            width: parent.width/5
            height: parent.height/5
            source: "images/image4.jpg"
            x: point4.x
            y: point4.y
        }

    }
}


如上面的介绍的那样,我们定义了一个“MultiPointTouchArea”。我们在Image中,绑定它们的坐标和TouchPoint在一起。这样,TouchPoint的坐标变化时,Image可以随时移动。 在它的里面,我们同时定义了4个TouchPoint。当我们只有一个手指触屏时,我们可以看见只有image1.jpg可以随着我们的手指移动:


  


当我们同时两个手指触屏时,我们可以看到同时可以有两个图片image1.jpg及image2.jpg来同时移动我们的画面:



同时3个手指触屏时,我们可以看到3个照片同时出现在屏幕上,并随我们的手指移动:




整个项目的源码在:git clone https://gitcafe.com/ubuntu/multitouchtest.git


作者:UbuntuTouch 发表于2015/7/8 11:09:13 原文链接
阅读:220 评论:0 查看评论

Read more
UbuntuTouch

在游戏中动画的设计非常中要。在QML中,它提供了丰富的animation,但是有时我们需要对图像进行变化,就像放电影一样。在今天的这篇文章中,我们将设计一个可以变化图像的动画。我们可以通过Qt所提供的Sprite功能来实现。


为了设计的方便,我们先设计一个我们自己的bear动画,这个动画的图像大小为: 2048x256。它刚好是8副图256x256




在我们的Sprite设计中,我们想按照上述图像显示的顺序依次显示每个图像,这样就可以形成一个可以连续变化的动画效果。


直接把我们的动画设计文件BearSprite贴出来:


BearSprite.qml

import QtQuick 2.0

Item  {
    width: 256
    height: 256

    SpriteSequence {
        id: fishSprite
        anchors.fill: parent
        interpolate: false
        goalSprite: ""

        Sprite {
            name: "first"
            source: "./gfx/Bear2.png"
            frameWidth: 256
            frameHeight: 256
            frameCount: 1
            frameDuration: 800
            frameDurationVariation: 400
            to: { "second" : 1 }
        }

        Sprite {
            name: "second"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "third" : 1 }
        }

        Sprite {
            name: "third"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*2
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "fourth" : 1 }
        }

        Sprite {
            name: "fourth"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*3
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "fifth" : 1 }
        }

        Sprite {
            name: "fifth"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*4
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "sixth" : 1 }
        }

        Sprite {
            name: "sixth"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*5
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "seventh" : 1 }
        }

        Sprite {
            name: "seventh"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*6
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "eighth" : 1 }
        }

        Sprite {
            name: "eighth"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*7
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "first" : 1 }
        }

        Sprite { //WORKAROUND: This prevents the triggering of a rendering error which is currently under investigation.
            name: "dummy"
            source: "./gfx/Bear2.png"
            frameCount: 8
            frameWidth: 256
            frameHeight: 256
            frameX: 0
            frameDuration: 200
        }
    }

}



在上面的设计中,我们使用了一个SpriteSequence,里面放了一些我们所需要的Sprite。


        Sprite {
            name: "sixth"
            source: "./gfx/Bear2.png"
            frameCount: 1
            frameX: 256*5
            frameWidth: 256
            frameHeight: 256
            frameDuration: 800
            frameDurationVariation: 400
            to: { "seventh" : 1 }
        }

这里的每个Sprite的设计都几乎都差不多。每个Sprite都有一个自己的名字。这里注意frameX。它其实是在我们上面显示的图里的x坐标位置。比如256x5,表示的是滴5副图。另外,我们的frameHeight和frameWidth也是和原图的大小是一样的,虽然在实际的显示中这个大小可以在Main.qml中可以设置。


使用同样的方法,我们可以做一个FishSprite。


FishSprite.qml




import QtQuick 2.0
import QtMultimedia 5.0

Item {
    width: 64
    height: 64
    property real hp: 3

    SoundEffect {
        id: spawnSound
        source: "./audio/catch.wav"
        loops:SoundEffect.Infinite
    }

    SoundEffect {
        id: killedSound
        source: "./audio/catch-action.wav"
    }

    SpriteSequence {
        id: fishSprite
        anchors.fill: parent
        interpolate: false
        goalSprite: ""

        Sprite {
            name: "left"
            source: "./gfx/mob-idle.png"
            frameWidth: 64
            frameHeight: 64
            frameCount: 1
            frameDuration: 800
            frameDurationVariation: 400
            to: { "front" : 1 }
        }

        Sprite {
            name: "front"
            source: "./gfx/mob-idle.png"
            frameCount: 1
            frameX: 64
            frameWidth: 64
            frameHeight: 64
            frameDuration: 800
            frameDurationVariation: 400
            to: { "left" : 1, "right" : 1 }
        }

        Sprite {
            name: "right"
            source: "./gfx/mob-idle.png"
            frameCount: 1
            frameX: 128
            frameWidth: 64
            frameHeight: 64
            frameDuration: 800
            frameDurationVariation: 400
            to: { "front" : 1 }
        }


        Sprite { //WORKAROUND: This prevents the triggering of a rendering error which is currently under investigation.
            name: "dummy"
            source: "./gfx/melee-idle.png"
            frameCount: 8
            frameWidth: 64
            frameHeight: 64
            frameX: 0
            frameDuration: 200
        }

        NumberAnimation on x {
            id: fishSwim
            running: false
            property bool goingLeft: fishSprite.currentSprite == "right"
            to: goingLeft ? -360 : 360
            duration: 300
        }

        Component.onCompleted: {
            spawnSound.play()
        }
    }

    SpriteSequence {
        id: bubble
        width: 64
        height: 64
        scale: 0.4 + (0.2  * hp)
        interpolate: false
        goalSprite: ""

        Behavior on scale {
            NumberAnimation { duration: 150; easing.type: Easing.OutBack }
        }

        Sprite {
            name: "big"
            source: "./gfx/catch.png"
            frameCount: 1
            to: { "burst" : 0 }
        }

        Sprite {
            name: "burst"
            source: "./gfx/catch-action.png"
            frameCount: 3
            frameX: 64
            frameDuration: 200
        }

        Sprite { //WORKAROUND: This prevents the triggering of a rendering error which is currently under investigation.
            name: "dummy"
            source: "./gfx/melee-idle.png"
            frameCount: 8
            frameWidth: 64
            frameHeight: 64
            frameX: 0
            frameDuration: 200
        }
        SequentialAnimation on width {
            loops: Animation.Infinite
            NumberAnimation { from: width * 1; to: width * 1.1; duration: 800; easing.type: Easing.InOutQuad }
            NumberAnimation { from: width * 1.1; to: width * 1; duration: 1000; easing.type: Easing.InOutQuad }
        }
        SequentialAnimation on height {
            loops: Animation.Infinite
            NumberAnimation { from: height * 1; to: height * 1.15; duration: 1200; easing.type: Easing.InOutQuad }
            NumberAnimation { from: height * 1.15; to: height * 1; duration: 1000; easing.type: Easing.InOutQuad }
        }
    }
}


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \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: "sprite.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    Page {
        id: page
        title: i18n.tr("sprite")

        Column {
            anchors.fill: parent

            FishSprite {
                height: units.gu(30)
                width: units.gu(30)
            }

            BearSprite {
                id: bear
                height: units.gu(30)
                width: units.gu(30)

                NumberAnimation on x {
                    to: page.width
                    duration: 8*800

                    onRunningChanged: {
                        if ( running == false) {
                            bear.x = 0
                            start()
                        }
                    }
                }
            }
        }
    }
}


运行我们的QML应用:

     


项目的源码在: git clone https://gitcafe.com/ubuntu/sprite.git


作者:UbuntuTouch 发表于2015/7/8 13:38:39 原文链接
阅读:229 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章“使用golang来设计我们的Ubuntu Scope”中,我们已经介绍了如何利用golang来开发Ubuntu Scope。在今天的文章中,我们来简单介绍一下如何使用golang来开发QML应用。这对于一些熟悉golang语言的,但是不是很熟悉C++的开发这来说,无疑是一个好的选择。虽然我们大多数的QML应用只需要QML加上一些Javascript的脚本即可,但是我们可以使用Qt C++或Go语言来拓展它的功能,来做一些需要计算或特殊功能的部分。


首先,我们来查看我们中国开发者dawndiy所做的一个repository:


https://github.com/dawndiy/ubuntu-go-qml-template


这个repository是基于另外一个repository: https://github.com/go-qml/qml


首先就像dawndiy在它的github里描述的那样:


安装Ubuntu SDK


我们按照连接“http://developer.ubuntu.com/start/ubuntu-sdk/installing-the-sdk/”来安装我们自己的SDK。我们也可以参照我的博客文章“Ubuntu SDK 安装”。


安装额外的包

$sudo apt-get install golang g++ qtdeclarative5-dev qtbase5-private-dev qtdeclarative5-private-dev libqt5opengl5-dev qtdeclarative5-qtquick2-plugin

这些包是为了我们能够成功编译我们的Go+QML应用所必须的。


设置chroots


如果你已经在上面参照“Ubuntu SDK 安装”来安装自己的SDK的话,这部分的很多部分已经做了。我们执行如下的指令:

$git clone https://github.com/nikwen/ubuntu-go-qml-template.git
$cd ubuntu-go-qml-template
$chroot-scripts/setup-chroot.sh

我们可以在我们自己喜欢的目录中做上面的事情。在实际的操作中,我发现如果在没有VPN的情况下,安装可能不能成功,原因是它需要访问“storage.googleapis.com”网址来下载一些东西。不过这没关系,你们可以到dawndiy的github里下载。那里已经有你所需要的所有的东西。


我们来看一下setup-chroot.sh:

#!/bin/bash

DIR=$(dirname $(readlink -f "$0"))

echo "====================================="
echo "========== Creating chroot =========="
echo "====================================="
echo

sudo click chroot -a armhf -f ubuntu-sdk-14.10 -s utopic create
sudo click chroot -a armhf -f ubuntu-sdk-14.10 -s utopic upgrade

echo
echo "====================================="
echo "=== Installing packages in chroot ==="
echo "====================================="
echo

sudo click chroot -a armhf -f ubuntu-sdk-14.10 -s utopic maint apt-get install git qtdeclarative5-dev:armhf qtbase5-private-dev:armhf qtdeclarative5-private-dev:armhf libqt5opengl5-dev:armhf qtdeclarative5-qtquick2-plugin:armhf

GO_DIR=$DIR/../go-installation

mkdir -p $GO_DIR
cd $GO_DIR

$DIR/install-go-1-3-3.sh


在这里,我们可以看到它去下载ubuntu-sdk-14.10 的armhf。这个是为了来交叉汇编我们的应用,并编译ARM版本的可执行文件。当然我们可以设置为ubuntu-sdk-15.04。我们需要做一些改变。这个步骤一旦设置好了,就可以不用再做第二次了。


在Desktop下运行应用


$./run.sh

我们在Terminal中键入上面的命令,我们就可以在Desktop中看见如下的运行的应用:




应用中的qml文件可以在./share/ubuntu-go-qml-template/main.qml中找到:

main.qml

import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \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: "ubuntu-go-qml-template.nikwen"

    /*
     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("Simple")

        Column {
            spacing: units.gu(1)
            anchors {
                margins: units.gu(2)
                fill: parent
            }

            Label {
                id: label
                objectName: "label"

                text: ctrl.message
            }

            Button {
                objectName: "button"
                width: parent.width

                text: i18n.tr("Tap me!")

                onClicked: ctrl.hello()
            }
        }
    }
}

在./src/ubuntu-go-qml-template/main.go,我们可以看到如下的代码:


package main

import (
        "gopkg.in/qml.v1"
        "log"
)

func main() {
        err := qml.Run(run)
        if (err != nil) {
                log.Fatal(err)
        }
}

func run() error {
        engine := qml.NewEngine()
        component, err := engine.LoadFile("share/ubuntu-go-qml-template/main.qml")
        if err != nil {
                return err
        }

        ctrl := Control{Message: "Hello from Go!"}
        context := engine.Context()
        context.SetVar("ctrl", &ctrl)

        win := component.CreateWindow(nil)
        ctrl.Root = win.Root()

        win.Show()
        win.Wait()
        return nil
}

type Control struct {
	Root    qml.Object
	Message string
}

func (ctrl *Control) Hello() {
        go func() {
                if (ctrl.Message == "Hello from Go!") {
                        ctrl.Message = "Hello from Go Again!"
                } else {
                        ctrl.Message = "Hello from Go!"
                }
                qml.Changed(ctrl, &ctrl.Message)
        }()
}

具体关于这些golang的介绍,大家可以参考http://godoc.org/gopkg.in/qml.v1


部署到手机中


$./install-on-device.sh

我们使用上面的命令来部署我们的Go应用到手机中:



我们可以看到它的运行和在Desktop上面的是完全一样的。我们可以在如下的目录中找到我们所需要的click安装文件:

bin/ubuntu-go-qml-template.nikwen_0.1_armhf.click

所有的源码包括下载的库等: git clone https://gitcafe.com/ubuntu/ubuntu-go-qml-template.git


作者:UbuntuTouch 发表于2015/7/10 13:36:25 原文链接
阅读:349 评论:0 查看评论

Read more
UbuntuTouch

[原]Qt跨平台的一个例程

我的同事penk在最近北京的Hackathon展示了一个在多平台的例程。很多开发者对这个挺感兴趣的。今天我就把这个资源介绍给大家。这是同一个用Qt写的应用,可以同时在Ubuntu Destkop,android, iOS接Ubuntu phone上可以同时运行的一个例程。


大家可以在地址:http://www.terrariumapp.com/。该项目的所有的源码也可以在地址:https://github.com/penk/terrarium-app。在github里有详细介绍如何编译并部署到不同的平台的指令。


今天,我也有幸在我的Ubuntu Desktop上实现了Qt在android手机上的运行。我从Qt SDK中移植过来一个应用samegame。它的源码在:


git clone https://gitcafe.com/ubuntu/samegame.git


在Ubuntu桌面上运行:





在Samsung Note3上运行:


    


在Android模拟器上运行:





在MX4上运行:




我们可以看出来。同样一个Qt/QML应用在不同的装置上不需要做任何的修改就可以在不同的平台上运行!这就是Qt/QML的魅力。由于硬件资源限制,我们不能尝试在iOS上的运行情况。有条件的开发者可以自己在自己的苹果装置中试一下!



作者:UbuntuTouch 发表于2015/7/13 14:23:13 原文链接
阅读:254 评论:0 查看评论

Read more
UbuntuTouch

在我们设计我们的QML应用时,我们想通过一个方法在一个地方来改变我们的设置文件,从而来修改整个应用的外观或使得所有的使用同一个设置的变量值得到修改。比如我们可以设置BaseUrl="http://api.map.baidu.com/telematics/v3/weather?” 属性,我们可能有几个QML文件都需要使用这个属性,那么我们怎么没做呢?一种办法就是在每个模块中都定义同样的属性。另外一种办法就是利用Singleton来集中定义在一个文件中,从而被所有的模块所使用。这样的方法同样适合我们style我们的应用。我们在一个地方修改设置,但是在所有的模块中都使用。这类似于C/C++中定义一些常量,在不同的.cpp文件中使用一样。


为了能够实现我们上面所需要的功能,我们设计了如下的Settings.qml文件:


Settings.qml


pragma Singleton
import QtQuick 2.0

QtObject {
    property int screenHeight: 960
    property int screenWidth: 640

    property string textSize: "x-large"
    property string textColor: "red"
}

首先,我们可以看到我们在文件的开始部分使用了pragam Singleton,表明这个文件在实例化时只能有一个实例。在它里面,我们做了一些小的设置。具体开发者需要什么样的设置,可以自己定义。

另外,我们必须在我们应用的根目录下添加如下的qmldir文件:


qmldir


singleton Settings 1.0 Settings.qml

这是一个声明文件。

为了展示我们如何使用,我们可以使用我先前创建的如下的项目:

git clone https://gitcafe.com/ubuntu/TabApp1.git

并对Tab1.qml和Tab2.qml做如下的修改:


Tab1.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
// Needed for singletons QTBUG-34418
import "."

Tab {
    title: i18n.tr("Tab 1")

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: {
            console.log("reload is clicked")
        }
    }

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page one")
            color: Settings.textColor
            fontSize: Settings.textSize
        }

        tools: ToolbarItems {
            ToolbarButton {
                action: reloadAction
            }
        }
    }
}


Tab2.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
// Needed for singletons QTBUG-34418
import "."

Tab {
    title: i18n.tr("Tab 2")

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page two")
            color: Settings.textColor
            fontSize: Settings.textSize
        }
    }
}


在这两个文件中,我们同时使用了同一个设置文件Settings.qml中所定义的textSize及textColor属性。当我们修个统一处的值时,所有使用它们的地方将自动被修改,而不需要在每一个文件中分别做修改,下面是我把文字设为“x-large”及“red”时的截图:

  


当我们在Settings.qml中设为“large”及“green”时的截图:

   


从上面可以看出来,我们在同一处修个可以改变整个应用的设置及外观。

整个项目的源码在:git clone https://gitcafe.com/ubuntu/singleton.git


嵌套QtObjects

如果你需要嵌套QtObjects以访问更多的属性,可以采用如下的模版:

// Settings.qml
.pragma Singleton
 
QtObject {
 
property QtObject window: QtObject{
 property color background: "white";
 }
 
property QtObject border: QtObject{
 property QtObject width: QtObject{
 property int normal: 1;
 property int big: 3;
 }
 
property QtObject color: QtObject{
 property color normal: "gray";
 property color focus: "blue";
 property color disabled: "red";
 }
 }
}




作者:UbuntuTouch 发表于2015/7/21 9:49:12 原文链接
阅读:207 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将介绍如何使用Loader来加载不同的QML文件来实现动态的UI。在之前的文章“如何使用Loader来动态载入一个基于item的Component”中,我们已经介绍了一些关于它的用法。Loader的好处是只有在我们需要的时候才装载我们所需要的QML文件,这样可以节省应用所需要的内存,也同时可以提高应用的启动时间(如果利用好的话)。下面我们以一个简单的例子来做一个介绍。更多关于动态生产QML UI的例子,请参阅“如何使用QML动态产生Component来完成我们的气球游戏(2)”。


MainScreen.qml


import QtQuick 2.0

Rectangle {
    id: root
    
    width: 600
    height: 400
    
    property int speed: 0
    
    SequentialAnimation {
        running: true
        loops: Animation.Infinite
        
        NumberAnimation { target: root; property: "speed"; to: 145; easing.type: Easing.InOutQuad; duration: 4000; }
        NumberAnimation { target: root; property: "speed"; to: 10; easing.type: Easing.InOutQuad; duration: 2000; }
    }
    // M1>>
    Loader {
        id: dialLoader
        
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: analogButton.top

        onLoaded: {
            binder.target = dialLoader.item;
        }
    }
    Binding {
        id: binder
        
        property: "speed"
        value: speed
    }
    // <<M1
    Rectangle {
        id: analogButton
        
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        
        color: "gray"
        
        width: parent.width/2
        height: 100
        
        Text {
            anchors.centerIn: parent
            text: "Analog"
        }
        
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "analog";
        }
    }
    
    Rectangle {
        id: digitalButton
        
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        
        color: "gray"
        
        width: parent.width/2
        height: 100
        
        Text {
            anchors.centerIn: parent
            text: "Digital"
        }
        
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "digital";
        }
    }
    
    state: "analog"
    
    // M3>>
    states: [
        State {
            name: "analog"
            PropertyChanges { target: analogButton; color: "green"; }
            PropertyChanges { target: dialLoader; source: "Analog.qml"; }
        },
        State {
            name: "digital"
            PropertyChanges { target: digitalButton; color: "green"; }
            PropertyChanges { target: dialLoader; source: "Digital.qml"; }
        }
    ]
    // <<M3
}

从上面的代码中可以看出来,在程序中,我们使用了一个dialLoader:

   Loader {
        id: dialLoader
        
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: analogButton.top

        onLoaded: {
            binder.target = dialLoader.item;
        }
    }

它的source没有被指定。在程序中,它是可以被动态设置的,从而达到改变UI的目的。另外我们要注意到“dialLoader.item”,它实际上是在QML被装载完后最顶层的那个Item。对我们来说,当Analog.qml被装载后,这个Item就是Ananlog.qml所代表的Item。每当Loader的source发生改变时,它先前创建的Item将会被自动地销毁。

在程序中,也设置了两个Rectangle,被用作按钮的用途。点击它时,可以改变当前Component的state,从而装载不同的qml,以达到改变UI的目的。在应用中,默认的状态是“analog”,而不是我们通常的“”状态。

在我们的手机上运行:

   

所有项目的源码在:git clone https://github.com/liu-xiao-guo/loaderclock.git



作者:UbuntuTouch 发表于2015/7/22 14:05:19 原文链接
阅读:198 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中介绍如何使用Javascript来动态生产画面。 我们在先前的例子中“如何使用QML动态产生Component来完成我们的气球游戏 (2)”已经对动态生产QML做了一些描述。也许那个项目比较复制,现在我来用一些简单的例子来说明一下,这样更加直观。更多的说明可以参阅文章“Dynamic QML Object Creation from JavaScript”。


1)创建我们的动态QML文件


这个文件将被用来被Javascript来动态生产。这是一个模版尽管每次生成的object的属性可能会不一样。

dynamic-image.qml


import QtQuick 2.0

Image {
    width: 400
    height: 225

    source: "images/image1.jpg"

    Image {
        id: overlay

        anchors.fill: parent

        source: "images/image2.jpg"

        opacity: 0;
        Behavior on opacity { NumberAnimation { duration: 300 } }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            if (overlay.opacity === 0)
                overlay.opacity = 1;
            else
                overlay.opacity = 0;
        }
    }
}


这是一个很简单的QML文件。它显示了一个画面,当我们点击画面时,overlay中的画面将会交叉显示或隐藏。



2)创建动态生产QML Object的Javascript


create-component.js


var component;

function createImageObject(x, y) {
    component = Qt.createComponent("dynamic-image.qml");
    if (component.status === Component.Ready || component.status === Component.Error)
        finishCreation(x, y);
    else
        component.statusChanged.connect(finishCreation);
}

function finishCreation(x, y) {
    if (component.status === Component.Ready)
    {
        var image = component.createObject(container, {"x": x, "y": y, width: 300, height:200});
        if (image == null)
            console.log("Error creating image");
    }
    else if (component.status === Component.Error)
        console.log("Error loading component:", component.errorString());
}


这个文件被用来动态生产我们所需要的QML Object。它采用的模版就是我们在上一节中所使用的“dynamic-image.qml”。我们可以设置它的位置参数。当然我们也可以设置它的其它的属性。这里的“container”是我们希望被生产的QML Object所希望放置的位置,也就是它的“父亲”。


3)在QML代码中调用Javascript来生产QML object


import QtQuick 2.0
import Ubuntu.Components 1.1
import "create-component.js" as ImageCreator

/*!
    \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: "dynamicqml.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    Page {
        id: root
        title: i18n.tr("dynamicqml")
        property int position: 0

        Flickable {
            width: parent.width
            height: parent.height
            clip:true
            contentHeight: container.childrenRect.height

            Column {
                id: container
                anchors.centerIn: parent
            }
        }

        Row {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: units.gu(1)
            spacing: units.gu(1)

            Button {
                text: "Create New"

                onClicked: {
                    ImageCreator.createImageObject(0, root.position);
                    root.position += 200;
                }
            }

            Button {
                text: "Create from string"

                onClicked: {
                   var newObject = Qt.createQmlObject('import QtQuick 2.0;
                        Image {source: "images/image3.jpg"; width: 300; height: 200}',
                        container, "dynamicSnippet1");
                    newObject.x = 0;
                    newObject.y = root.position;
                }

            }

        }

        Component.onCompleted: {
            ImageCreator.createImageObject(0, 0);
            root.position += 200
        }
    }
}


在上面的代码中:

   Button {
                text: "Create New"

                onClicked: {
                    ImageCreator.createImageObject(0, root.position);
                    root.position += 200;
                }
            }

这个代码被用来生产我们所需要的QML Object, 并放置于我们所需要的位置。就像我们上一节中所介绍的那样,我们定义了一个“container”。这里实际上是一个Column的布局管理器。

另外,我们也可以使用代码:

           Button {
                text: "Create from string"

                onClicked: {
                   var newObject = Qt.createQmlObject('import QtQuick 2.0;
                        Image {source: "images/image3.jpg"; width: 300; height: 200}',
                        container, "dynamicSnippet1");
                    newObject.x = 0;
                    newObject.y = root.position;
                }

            }

使用字符串的方式 ,使用Qt.createQmlObject来创建我们的QML object。这也是一种简洁的方式。

运行我们的应用:





我们可以利用屏幕下面的按钮来动态生产我们的QML object。


4)动态生产QML Object的管理


我们可以通过使用destroy来销毁已经生产的QML object。为了管理我们的object,我们来创建一个ListModel:

        ListModel {
            id: objectsModel
        }

我们可以通过如下的方式来添加我们生成的object:

 function itemAdded(obj, source) {
        objectsModel.append({"obj": obj, "source": source})
    }

这样,每当我们创建一个新的object时,我们也把它添加进来:

       Row {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: units.gu(1)
            spacing: units.gu(1)

            Button {
                text: "Create New"

                onClicked: {
                    var image = ImageCreator.createImageObject(0, root.position);
                    itemAdded(image, "dynamic-image.qml");
                    root.position += 200;
                }
            }

最后,我们可以通过如下的方式来销毁我们所创建的所有的dynamic-image.qml的object。

            Button {
                text: "Clear objects"

                onClicked: {
                    while(objectsModel.count > 0) {
                        objectsModel.get(0).obj.destroy();
                        objectsModel.remove(0);
                    }
                }
            }

   


上面右图是按下“Clear objects”后的显示。


整个项目的源码在: git clone https://gitcafe.com/ubuntu/dynamicqml.git

作者:UbuntuTouch 发表于2015/7/23 11:40:55 原文链接
阅读:220 评论:0 查看评论

Read more
UbuntuTouch

我们知道Cordova HTML5应用具有夸平台的特性,同时也具有访问本地一些资源的能力。在今天的这篇文章中,我们将介绍一下如何创建并运行一个Cordova HTML5的应用到我们的Ubuntu手机中。本文的英文原文在“http://developer.ubuntu.com/en/apps/html-5/guides/cordova-guide/”。


1)安装好我们的armhf chroot


如果开发者已经看过我以前的文章“Ubuntu SDK 安装”的话,你可能已经安装好自己的armhf chroot了。除了在SDK中可以帮我们安装我们所需要的chroot外,我们也可以通过如下的命令来简单地安装我们自己所需要的chroot。下面以15.04 framework为例:

$sudo click chroot -aarmhf -f ubuntu-sdk-15.04 create  

我们可以在命令行键入如上的命令就可以创建我们的15.04的armhf chroot。等安装完以后,我们就可以进行下一步的动作。开发者如果想为14.10的目标进行编译,也可以使用同样的方法来安装14.10的armhf chroot。


2)安装Cordova


在这一步,我们来安装Cordova环境。如果你以前已经安装过的,建议你使用如下的方法删除以前的安装(由于以前的安装有bug)。如果你从来没有安装过的话,请忽略这一步

$rm -rf ~/.cordova
$rm -rf ~/.cache

这是为了彻底删除以前已经在你的电脑中的安装。

然后,我们按照如下的步骤来安装Cordova:

$ sudo apt-add-repository ppa:cordova-ubuntu/ppa; sudo apt-get update
$ sudo apt-get install cordova-cli

到目前的这一步,我们基本上已经创建好我们的Cordova环境了。


3)创建一个简单的Cordova例程


目前14.10的架构是默认的开发架尽管将来会有变化。在如下的命令中,如果没有指定具体的架构,14.10架构将会被采用。

使用如下的命令来创建一个简单的Cordova应用:

$cordova create myapp  myapp.mycompany "My App"
$cd myapp
$cordova platform add ubuntu
$vi config.xml

注意:请在你的config.xml中加入如下的句子,以保证你的应用有一个icon图标:

  <icon src="www/img/logo.png" />

另外,请你在config.xml中加入自己的有效的邮件地址:

 <author email="myid@ubuntu.com" />

这样整个config.xml的文件如下:

config.xml



<?xml version='1.0' encoding='utf-8'?>
<widget id="myapp.mycompany" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>My App</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="myname@mycompany.com" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <plugin name="cordova-plugin-whitelist" version="1" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
    <icon src="www/img/logo.png" />
</widget>


这样我们整个的Cordova模版已经被建立好了。下面我们来具体描述怎么进行编译。


4)编译我们的模版Cordova应用


我们可以使用如下的命令为我们的手机进行编译:

$ cordova build --device

就如我们上面所说的一样,它选择默认的一个版本的armhf chroot进行编译。目前它指的是14.10。在第一次编译时,可能需要我们去安装一些额外的库才可以进行编译。它会提示如下所示的错误信息:




List of devices attached 
750ABLLH4897	device

Target Device: 750ABLLH4897
Building Phone Application...

Error: missing dependency inside armhf chroot
run:
sudo click chroot -a armhf -f ubuntu-sdk-14.10 install cmake libicu-dev:armhf pkg-config qtbase5-dev:armhf qtchooser qtdeclarative5-dev:armhf qtfeedback5-dev:armhf qtlocation5-dev:armhf qtmultimedia5-dev:armhf qtpim5-dev:armhf libqt5sensors5-dev:armhf qtsystems5-dev:armhf

就像上面显示的错误信息一样,我们必须在命令行中打入如下的命令来安装我们所需要的库:

$sudo click chroot -a armhf -f ubuntu-sdk-14.10 install cmake libicu-dev:armhf pkg-config qtbase5-dev:armhf qtchooser qtdeclarative5-dev:armhf qtfeedback5-dev:armhf qtlocation5-dev:armhf qtmultimedia5-dev:armhf qtpim5-dev:armhf libqt5sensors5-dev:armhf qtsystems5-dev:armhf

当然我们也可以使用如下的方法来安装:

$click chroot -aarmhf -fubuntu-sdk-14.10 maint

然后,再打入如下的命令:



等安装完后,我们打入exit命令,退出即可。

重新进入到我们的应用的根目录,再次打入如下的命令:

$ cordova build --device


我们可以在项目目录下找到我们所需要的click包文件:

liuxg@liuxg:~/web/myapp$ find ./ -name *.click
./platforms/ubuntu/ubuntu-sdk-14.10/armhf/prefix/myapp.mycompany_0.0.1_armhf.click

为了能够在手机上直接运行,我们可以直接运行一下的命令:

$ cordova run --device --debug

在手机上的运行结果:



对于使用基于ubuntu-sdk-15.04 chroot来说,我们必须使用如下的命令来完成我们的build:

$ cordova build --device -- --framework ubuntu-sdk-15.04 --verbose

当我们运行时,我们也必须使用如下的命令来完成:

$ cordova run --device --debug -- --framework ubuntu-sdk-15.04

整个项目的源码在:git clone https://gitcafe.com/ubuntu/cordovasample.git


5)如何在手机中调试自己的应用


为了能够输出一些有用的调试信息,我们可以在我们的index.js文件中加入如下的console.log语句:

var app = {
	
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    }
};

console.log("This shows how to output something to debug");

app.initialize();


当我们运行我们的HTML5应用到手机时,我们可以看到如下的输出:

$ cordova run --device --debug -- --framework ubuntu-sdk-15.04





就像上图中显示的那样,我们可以在电脑中打开chromium浏览器,并输入以上的http://127.0.0.1:9222地址:





从上面我们可以看出来我们添加的的调试信息:

This shows how to output something to debug

在Desktop上的调试也是一样的。我们只需要用如下的方法进行运行:

$ cordova run --debug

在chromium浏览器中输入地址http://127.0.0.1:9222即可。

更多想知道如何在手机上利用Cordova实现camera功能,请参阅文章“Cordova camera app tutorial”。开发者可以参考文章“在Ubuntu平台上创建Cordova Camera HTML5应用”来开发一个Cordova Camera HTML5应用。


作者:UbuntuTouch 发表于2015/7/24 13:18:10 原文链接
阅读:312 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将详细介绍如何使用Cordova Camera HTML5 应用。更多关于Cordova的开发指南,开发者可以参考文章“the Cordova Guide”。通过这个例程,我们可以学习在Ubuntu平台上如何利用Cordova API来完成一个我们所需要的照相机功能。关于如何创建一个Cordova架构的简单的应用,开发者可以参阅文章“如何在Ubuntu手机平台中开发Cordova HTML5应用”。在那篇文章中,它介绍了如何设置自己的环境。建议开发者先阅读该文章。


在我们做练习之前,我们可以在我们想要创建应用目录的下面,打入如下的命令:

$ bzr branch lp:ubuntu-sdk-tutorials

在上面的代码中,有一个已经完成好的Camera代码。


1)创建一个最基本的Cordova框架应用


由于一些原因,Cordova的开发环境没有集成到Qt Creator中去,所以我们只有通过命令行来完成我们的操作。我们可以参阅文章“如何在Ubuntu手机平台中开发Cordova HTML5应用”来创建一个叫做Camera的简单应用:

$ cordova create cordovacam cordovacam.mycompany "CordovaCam"
$ cd cordovacam


2)定义应用自己的图标



我们可以自己设计一个图标为我们想要创建的应用。为了方便,我们可以直接从我们已经下载好的应用ubuntu-sdk-tutorials/html5/html5-tutorial-cordova-camera目录中直接拷贝过来我们所需要的图标:

$ cp ../ubuntu-sdk-tutorials/html5/html5-tutorial-cordova-camera/www/icon.png ./www/img/logo.png

然后,我们需要修改我们的config.xml文件,添加如下的项到它里面去:

<icon src="www/img/logo.png" />

当然,我们也有必要修改作者自己的邮件地址。修改完后,config.xml的显示如下:

<?xml version='1.0' encoding='utf-8'?>
<widget id="cordovacam.mycompany" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>CordovaCam</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="myname@mycompany.com" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <plugin name="cordova-plugin-whitelist" version="1" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
    <icon src="www/img/logo.png" />
</widget>

注意: 这一步必不可少,否则我们的应用通过不了package的验证。


3)添加Ubuntu平台支持代码到项目中


我们可以利用如下的命令来添加我们的Ubuntu支持代码:

$ cordova platform add ubuntu

现在你的项目中将有如下的目录:

  • platforms/ubuntu/
由于我们需要使用到Camera的功能,所有我们需要在如下的文件中添加camera的security policy:

cordovacam/platforms/ubuntu/apparmor.json

{
 "policy_groups": ["networking", "camera”, "audio"],
  "policy_version":1
}

否则我们的照相机功能在手机中将不能工作。

4)添加Camera API支持


通过如下的命令:

$ cordova plugin add org.apache.cordova.camera

来添加Cordova runtime到你的项目中去。


5)运行我们的应用


$ cordova run --device --debug

就像在我们之前文章“如何在Ubuntu手机平台中开发Cordova HTML5应用”中介绍的那样,这个命令是在利用默认的平台进行运行的(目前是14.10)。在其它的平台上,你需要在它的后面加上平台的参数:

$ cordova run --device -- --framework ubuntu-sdk-15.04

如果没有任何问题的话,你将看到如下的画面:





到目前位置,我们已经完创建了我们的最基本的Cordova Camera框架应用,但是我们还是没有做任何的界面及Camera功能的调用。


6)定义HTML 5用户界面



在这节里,我们将设计我们的HTML 用户界面。我们修改index.html(cordovacam/www/index.html)文件如下:


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>An Ubuntu HTML5 application</title>
    <meta name="description" content="An Ubuntu HTML5 application">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

    <!-- Ubuntu UI Style imports - Ambiance theme -->
    <link href="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />

    <!-- Ubuntu UI javascript imports - Ambiance theme -->
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/dialogs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>

    <!-- Cordova platform API access - Uncomment this to have access to the Javascript APIs -->
    <script src="cordova.js"></script>

    <!-- Application script and css -->
    <script src="js/app.js"></script>
    <link href="css/app.css" rel="stylesheet" type="text/css" />
  </head>

  <body>
    <div data-role="mainview">
      <header data-role="header">
        <ul data-role="tabs">
          <li data-role="tabitem" data-page="camera">Camera</li>
        </ul>
      </header>

      <div data-role="content">
        <div data-role="tab" id="camera">
            <div id="loading">
                <header>Loading...</header>
                <progress class="bigger">Loading...</progress>
            </div>
            <div id="loaded">
                <button data-role="button" class="ubuntu" id="click">Take Picture</button>
                <img id="image" src="" />
            </div>
        </div> <!-- tab: camera -->
      </div> <!-- content -->
    </div> <!-- mainview -->
  </body>
</html>


这里的界面非常简单,一个progress及一个可以按下照相的按钮。这里注意一下下面的代码:

  <!-- Cordova platform API access - Uncomment this to have access to the Javascript APIs -->
    <script src="cordova.js"></script>

    <!-- Application script and css -->
    <script src="js/app.js"></script>
    <link href="css/app.css" rel="stylesheet" type="text/css" />

这里有一个app.css文件,一个app.js文件。我们需要把原来的index.css及index.js文件换成它们。

app.css


#loading {
  position: absolute;
  left:45%;
}
#loaded {
  display: none;
}

app.js

/**
 * Wait before the DOM has been loaded before initializing the Ubuntu UI layer
 */

window.onload = function () {
    var UI = new UbuntuUI(); // This must be called after window is loaded
    UI.init();

    document.addEventListener("deviceready", function() {
        if (console && console.log)
            console.log('Platform layer API ready');

        //hide the loading div and display the loaded div
        document.getElementById("loading").style.display = "none";
        document.getElementById("loaded").style.display = "block";

        // event listener to take picture
        UI.button("click").click( function() {
            navigator.camera.getPicture(onSuccess, onFail, {
                quality: 100,
                targetWidth: 400,
                targetHeight: 400,
                destinationType: Camera.DestinationType.DATA_URL,
                correctOrientation: true
             });
           console.log("Take Picture button clicked");
        }); // "click" button event handler

      }, false); // deviceready event handler

}; // window.onload event handler

// show new picture in html and log
function onSuccess(imageData) {
    var image = document.getElementById('image');
    image.src = "data:image/jpeg;base64," + imageData;
    image.style.margin = "10px";
    image.style.display = "block";
}

// log failure message
function onFail(message) {
    console.log("Picture failure: " + message);
}


在这里,我们必须注意的是

var UI = new UbuntuUI(); // This must be called after window is loaded

必须是在window.onload里做,不可以放在它的前面去做。否则我们的UI就会有问题。


这里的设计非常简单。我不在累述。我们选择试着来运行我们的应用到手机上去。运行的显示如下:




显然,它没有我们所希望看到的结果。为什么呢?


我们回头来看看我们的UI设计:

 <body>
    <div data-role="mainview">
      <header data-role="header">
        <ul data-role="tabs">
          <li data-role="tabitem" data-page="camera">Camera</li>
        </ul>
      </header>

      <div data-role="content">
        <div data-role="tab" id="camera">
            <div id="loading">
                <header>Loading...</header>
                <progress class="bigger">Loading...</progress>
            </div>
            <div id="loaded">
                <button data-role="button" class="ubuntu" id="click">Take Picture</button>
                <img id="image" src="" />
            </div>
        </div> <!-- tab: camera -->
      </div> <!-- content -->
    </div> <!-- mainview -->
  </body>

这里采用了Ubuntu平台上的HTML5 UI Toolkit。而在我们的index.html head部分:

    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/dialogs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>

它依赖于手机系统提供的ambiance。这有可能造成我们的UI和目前的最新的ambiance不能够完全匹配。为了解决这个问题,我们参考连接https://code.launchpad.net/~dbarth/ubuntu-html5-theme/cmdline-tool/+merge/253498

来解决这个问题。在我的博客文章中“为HTML5应用创建独立于平台的Theme”我也做了详细的介绍。

我们做如下的步骤:

$ ubuntu-html5-theme install 14.10

然后

$ ubuntu-html5-theme convert

运行完后,我们再重新看一下我们的index.html文件:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>An Ubuntu HTML5 application</title>
    <meta name="description" content="An Ubuntu HTML5 application">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

    <!-- Ubuntu UI Style imports - Ambiance theme -->
    <link href="ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />

    <!-- Ubuntu UI javascript imports - Ambiance theme -->
    <script src="ambiance/js/fast-buttons.js"></script>
    <script src="ambiance/js/core.js"></script>
    <script src="ambiance/js/buttons.js"></script>
    <script src="ambiance/js/dialogs.js"></script>
    <script src="ambiance/js/page.js"></script>
    <script src="ambiance/js/pagestacks.js"></script>
    <script src="ambiance/js/tabs.js"></script>

    <!-- Cordova platform API access - Uncomment this to have access to the Javascript APIs -->
    <script src="cordova.js"></script>

    <!-- Application script and css -->
    <script src="js/app.js"></script>
    <link href="app.css" rel="stylesheet" type="text/css" />
  </head>

  <body>
    <div data-role="mainview">
      <header data-role="header">
        <ul data-role="tabs">
          <li data-role="tabitem" data-page="camera">Camera</li>
        </ul>
      </header>

      <div data-role="content">
        <div data-role="tab" id="camera">
            <div id="loading">
                <header>Loading...</header>
                <progress class="bigger">Loading...</progress>
            </div>
            <div id="loaded">
                <button data-role="button" class="ubuntu" id="click">Take Picture</button>
                <img id="image" src="" />
            </div>
        </div> <!-- tab: camera -->
      </div> <!-- content -->
    </div> <!-- mainview -->
  </body>
</html>

我们可以看出如下的部分已经发生改变:

 <!-- Ubuntu UI javascript imports - Ambiance theme -->
    <script src="ambiance/js/fast-buttons.js"></script>
    <script src="ambiance/js/core.js"></script>
    <script src="ambiance/js/buttons.js"></script>
    <script src="ambiance/js/dialogs.js"></script>
    <script src="ambiance/js/page.js"></script>
    <script src="ambiance/js/pagestacks.js"></script>
    <script src="ambiance/js/tabs.js"></script>

并且在当前的cordovacam/www目录下多了一个叫做“ambiance”的目录。这样应用再也不依赖于系统所提供的ambiance了。

特别需要支持的是:如果你的界面不需要使用HTML5 UI Toolkit,你可以不做上面的步骤。

重新运行我们的应用:


$ cordova run --device --debug


    


所有的源码在:git clone https://gitcafe.com/ubuntu/cordovacam.git

本文英文阅读: Cordova camera app tutorial

作者:UbuntuTouch 发表于2015/7/27 12:13:19 原文链接
阅读:222 评论:0 查看评论

Read more
UbuntuTouch

QQuickImageProvider提供了一个可以供我们对QPixmap及多线程的Image请求。这个请求的文件甚至可以在网络上。它的好处是:

  • 在Image中装载一个QPixmap或QImage而不是一个具体的图像文件
  • 在另外一个thread异步装载图片
通过访问一个图片可以通过如下的方式:

Column {
    Image { source: "image://colors/yellow" }
    Image { source: "image://colors/red" }
}

显然,这里的yellow和red不是文件名。它的提供依赖于在QQuickImageProvider中的requestImage的具体实现。

下面我们来通过一个具体的例程来介绍如何使用QQuickImageProvider来从网路上请求一个我们需要的图像。


myimageprovider.h


#ifndef MYIMAGEPROVIDER_H
#define MYIMAGEPROVIDER_H

#include <QQuickImageProvider>
class QNetworkAccessManager;

class MyImageProvider : public QQuickImageProvider
{
public:
    MyImageProvider(ImageType type, Flags flags = 0);
    ~MyImageProvider();
    QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize);

protected:
    QNetworkAccessManager *manager;
};

#endif // MYIMAGEPROVIDER_H


myimageprovider.cpp


#include "myimageprovider.h"

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>

MyImageProvider::MyImageProvider(ImageType type, Flags flags) :
    QQuickImageProvider(type,flags)
{
    manager = new QNetworkAccessManager;
}

MyImageProvider::~MyImageProvider()
{
    delete manager;
}

QImage MyImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    qDebug() << "id: " << id;
    qDebug() << "reequestedSize: " << requestedSize.width() + " " + requestedSize.height();
    QUrl url("http://lorempixel.com/" + id);
    QNetworkReply* reply = manager->get(QNetworkRequest(url));
    QEventLoop eventLoop;
    QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
    eventLoop.exec();
    if (reply->error() != QNetworkReply::NoError)
        return QImage();
    QImage image = QImage::fromData(reply->readAll());
    size->setWidth(image.width());
    size->setHeight(image.height());
    return image;
}

上面我们从网路地址“http://lorempixel.com/”取得文件,并转化为一个QImage。QQuickImageProvider要求我们必须实现如下的一个virtual方法。

QQuickImageProvider(ImageType type, Flags flags = 0)
virtual	~QQuickImageProvider()
Flags	flags() const
ImageType	imageType() const
virtual QImage	requestImage(const QString & id, QSize * size, const QSize & requestedSize)
virtual QPixmap	requestPixmap(const QString & id, QSize * size, const QSize & requestedSize)
virtual QQuickTextureFactory *	requestTexture(const QString & id, QSize * size, const QSize & requestedSize)

我们可以在QML中通过如下的方式来访问一个图片:

 Image { source: "image://myprovider/500/500/" }

显然我们看到的source不是一个具体的文件。并且,它的source是以“image://”开始的。

我们在我们的main.cpp中做如下的实现:

#include "myimageprovider.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQuickView view;
    QQmlEngine *engine = view.engine();
    MyImageProvider *imageProvider = new MyImageProvider(QQmlImageProviderBase::Image);
    engine->addImageProvider("myprovider", imageProvider );
    view.setSource(QUrl(QStringLiteral("qrc:///Main.qml")));
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.show();
    return app.exec();
}

注意这里的“myprovider”和我们上面的Image中访问的对应起来。

我们的main.qml文件如下:

main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \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: "imageprovider.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    Page {
        title: i18n.tr("imageprovider")

        Image {
            id: img
            anchors.centerIn: parent
            source: "image://myprovider/500/500/"
            anchors.fill: parent
            onStatusChanged: {
                if(status == Image.Ready)
                    indicator.running = false;
            }

            ActivityIndicator {
                id: indicator
                anchors.centerIn: parent
                running: false
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    indicator.running = true;
                    img.source = "image://myprovider/500/500/?seed=" + Math.random(1000)
                }
            }
        }
    }
}


我们在点击图片时,它会自动地随机地从网站取得下一个图片,并显示出来:

  

整个项目的源码在:git clone https://gitcafe.com/ubuntu/imageprovider.git

作者:UbuntuTouch 发表于2015/7/29 13:22:17 原文链接
阅读:141 评论:0 查看评论

Read more
UbuntuTouch

在先前的例程中“如何使用Ubuntu手机平台中的照相机API来存储照片”,我们已经展示了如何使用Item的属性来存储我们的照片。在这篇文章中,我们将使用Camera API来完成同样的功能。


我们来直接贴自己的代码:


main.qml

import QtQuick 2.0
import Ubuntu.Components 1.1
import QtMultimedia 5.0

/*!
    \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: "cameraapp.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    property var resolution

    // This function is used to get the writable private directory of this app
    function getPriateDirectory() {
        var sharepath = "/home/phablet/.local/share/";
        var path = sharepath + applicationName;
        console.log("path: " + path);
        return path;
    }

    Page {
        id: page
        title: i18n.tr("cameraapp")

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

            exposure {
                exposureCompensation: -1.0
                exposureMode: Camera.ExposurePortrait
            }

            flash.mode: Camera.FlashRedEyeReduction

            imageCapture {
                onImageCaptured: {
                    console.log("image captured! reqId: " + requestId)
                    image.source = preview  // Show the preview in an Image
                }

                onImageSaved: {
                    console.log("image has been saved: " + requestId);
                    console.log("saved path: " + path);
                    image.source = path;
                }
            }

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
            }
        }

        Row {
            id: container
            VideoOutput {
                id: video
                source: camera
                width: page.width
                height: page.height
                focus : visible // to receive focus and capture key events when visible
                orientation: -90

                //                Image {
                //                    id: photoPreview
                //                    anchors.fill: parent
                //                    rotation: -90
                //                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "left":
                            page.state = "image";
                            break
                        }
                    }
                }
            }

            Item {
                id: view
                width: page.width
                height: page.height

                Image {
//                    anchors.fill: parent
                    anchors.horizontalCenter: parent.horizontalCenter
                    anchors.verticalCenter: parent.verticalCenter
                    width: parent.height
                    height: parent.width
                    id: image
                    rotation: 90
                    fillMode: Image.PreserveAspectFit
                }

                Text {
                    text: image.source
                    color:"red"
                    font.pixelSize: units.gu(2.5)
                    width: page.width
                    wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "right":
                            page.state = "";
                            break
                        }
                    }
                }
            }
        }

        states: [
            State {
                name: "image"
                PropertyChanges {
                    target: container
                    x:-page.width
                }
                PropertyChanges {
                    target: capture
                    opacity:0
                }
            }
        ]

        transitions: [
            Transition {
                NumberAnimation { target: container; property: "x"; duration: 500
                    easing.type:Easing.OutSine}
                //                NumberAnimation { target: inputcontainer; property: "opacity"; duration: 200}
                NumberAnimation { target: capture; property: "opacity"; duration: 200}
            }
        ]

        Button {
            id: capture
            anchors.bottom: parent.bottom
            anchors.bottomMargin: units.gu(1)
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Capture"

            onClicked: {
                console.log("capture path: " + getPriateDirectory());
                camera.imageCapture.captureToLocation(getPriateDirectory());
                page.state = "image"
            }
        }
    }
}



在这里我们必须指出的是我们可以使用:

camera.imageCapture.captureToLocation(getPriateDirectory());

来把照片存储到我们指定的位置。这个位置可以是手机应用自己的私有的目录或它下面的子目录。

我们可以在如下的代码中得到照相机存储的文件信息:

           imageCapture {
                onImageCaptured: {
                    console.log("image captured! reqId: " + requestId)
                    image.source = preview  // Show the preview in an Image
                }

                onImageSaved: {
                    console.log("image has been saved: " + requestId);
                    console.log("saved path: " + path);
                    image.source = path;
                }
            }

通过得到的path信息,我们可以得到照片的位置,并显示它。

在我们的设计中,我们可以使用swipe向左或向右来切换viewFinder及拍照的相片。

运行我们的应用,我们可以看到如下的图片:

  

整个项目的源码在:git clone https://gitcafe.com/ubuntu/cameraapp.git

作者:UbuntuTouch 发表于2015/7/30 11:23:15 原文链接
阅读:179 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章“如何在QML中使用camera API来拍照”中,我们介绍了如何使用Camera API来进行拍照。今天我们在这篇文章中来介绍如何使用Camera API来进行录像。


首先,还是和以前一样,我直接把自己的代码贴出来:


main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtMultimedia 5.0

/*!
    \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: "videoapp.liu-xiao-guo"

    /*
     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(60)
    height: units.gu(85)

    property var resolution

    // This function is used to get the writable private directory of this app
    function getPriateDirectory() {
        var sharepath = "/home/phablet/.local/share/";
        var path = sharepath + applicationName;
        console.log("path: " + path);
        return path;
    }

    Page {
        id: page
        title: i18n.tr("videoapp")

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

            exposure {
                exposureCompensation: -1.0
                exposureMode: Camera.ExposurePortrait
            }

            flash.mode: Camera.FlashRedEyeReduction

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

            videoRecorder.audioEncodingMode: videoRecorder.ConstantBitrateEncoding;
            videoRecorder.audioBitRate: 128000
            videoRecorder.mediaContainer: "mp4"
            videoRecorder.outputLocation: getPriateDirectory()

            captureMode: Camera.CaptureVideo

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
                console.log("deviceId: " + camera.deviceId)
            }
        }

        Row {
            id: container

            Item {
                width: page.width
                height: page.height

                VideoOutput {
                    id: video
                    anchors.fill: parent
                    source: camera
                    focus : visible // to receive focus and capture key events when visible
                    orientation: -90
                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "left":
                            page.state = "image";
                            break
                        }
                    }
                }
            }

            Item {
                id: view
                width: page.width
                height: page.height

                Video {
                    id: myvideo
                    anchors.fill: parent
                    autoPlay: true
                    focus: true

                    Component.onCompleted: {
                        myvideo.play();
                    }
                }

                Text {
                    text: myvideo.source
                    color:"red"
                    font.pixelSize: units.gu(2.5)
                    width: page.width
                    wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "right":
                            page.state = "";
                            break
                        }
                    }
                }
            }
        }

        states: [
            State {
                name: "playvideo"
                PropertyChanges {
                    target: container
                    x:-page.width
                }
                PropertyChanges {
                    target: capture
                    opacity:0
                }
                PropertyChanges {
                    target: stop
                    opacity:0
                }
            }
        ]

        transitions: [
            Transition {
                NumberAnimation { target: container; property: "x"; duration: 500
                    easing.type:Easing.OutSine}
                //                NumberAnimation { target: inputcontainer; property: "opacity"; duration: 200}
                NumberAnimation { target: capture; property: "opacity"; duration: 200}
                NumberAnimation { target: stop; property: "opacity"; duration: 200}
            }
        ]

        Row {
            anchors.bottom: parent.bottom
            anchors.bottomMargin: units.gu(1)
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: units.gu(1)

            Button {
                id: capture
                text: "Record"

                onClicked: {
                    console.log("capture path: " + getPriateDirectory());
                    camera.videoRecorder.record();
                }
            }

            Button {
                id: stop
                text: "Stop"

                onClicked: {
                    console.log("stop is clicked!");
                    camera.videoRecorder.stop();
                    page.state = "playvideo"
                }
            }

            Button {
                id: play
                text: "Play video"

                onClicked: {
                    console.log("filepath: " + myvideo.source);
                    console.log( "actual: " +  camera.videoRecorder.actualLocation);
                    myvideo.play();
                }
            }
        }

    }
}


在这里,QML Camera是被如下的方式使用的:

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

            exposure {
                exposureCompensation: -1.0
                exposureMode: Camera.ExposurePortrait
            }

            flash.mode: Camera.FlashRedEyeReduction

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

            videoRecorder.audioEncodingMode: videoRecorder.ConstantBitrateEncoding;
            videoRecorder.audioBitRate: 128000
            videoRecorder.mediaContainer: "mp4"
            videoRecorder.outputLocation: getPriateDirectory()

            captureMode: Camera.CaptureVideo

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
                console.log("deviceId: " + camera.deviceId)
            }
        }

我们定义了capureMode为Camera.CaptureVideo。同时我们也指定了录像文件的路径:

videoRecorder.outputLocation: getPriateDirectory()

当我们使用CameraRecorder来进行如下的方式录像时:

 camera.videoRecorder.record();

我们可以通过:

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

来获取所录像的文件的实际路径。

我们可以通过:

               Video {
                    id: myvideo
                    anchors.fill: parent
                    autoPlay: true
                    focus: true

                    Component.onCompleted: {
                        myvideo.play();
                    }
                }

来播放已经录下的文件。

运行为我们的应用:

 

整个项目的源码在:git clone https://gitcafe.com/ubuntu/videoapp.git



作者:UbuntuTouch 发表于2015/7/31 15:54:58 原文链接
阅读:63 评论:0 查看评论

Read more