Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

[原]如何在Ubuntu上本地化一个Scope

在这篇文章中,我们将介绍如何本地化一个Scope。本地化对有些Scope是非常重要的。正确地使用本地化,可以使得我们的Scop很方便地支持多个国家的语言。


1)创建一个基本的Scope

打开我们的Ubuntu SDK,并按如下的步骤生成:




     


    

等我们已经创建好我们的一个最基本的Scope后,我们可以可以在如下的目录中发现:




这里有一个叫做“localizedscope.pot”文件,我们可以尝试打开该文件。它是一个空文件。我们选择desktop先运行一次我们的Scope,然后再重新打开该文件。




我们可以看到在该文件中有一些字符串,在它们的下面有一些空的串。这些字符串都是从我们的项目中源文件中提取出来的。比如在文件“com.ubuntu.developer.liu-xiao-guo.localizedscope_localizedscope.ini.in”中我们可以找到“Localizedscope Scope”。它实际上是我们的Scope的标题。在query.cpp文件中,我们可以找到如下的定义:

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

通过这个例子,我们可以看出,如果我们想要本地化自己的字符串,我们使用“_("string")”的形式来让我们的工具来提取相应的需要本地化的字符串。这里的“string”应该写成英文。

在localizedscope.pot文件中,我们可以看到:

#: ../data/com.ubuntu.developer.liu-xiao-guo.localizedscope_localizedscope.ini.in.h:2
msgid "This is a Localizedscope scope"
msgstr ""

上面的地一行字显示了字符串出自哪个文件及在该文件中的行数。

2)本地化字符串


我们为了达到本地化该Scope,我们首先把.pot文件拷贝到该目录下的另外一个文件“zh_CN.po”,并做相应的翻译的工作。同时要记得把里面的

charset=CHARSET

修改为

charset=UTF-8

整个zh_CN.po文件的内容如下:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-12-22 12:13+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../src/scope/query.cpp:144
msgid "7 day forecast"
msgstr "7天天气预测"

#: ../data/com.ubuntu.developer.liu-xiao-guo.localizedscope_localizedscope.ini.in.h:1
msgid "Localizedscope Scope"
msgstr "本地化Scope"

#: ../data/com.ubuntu.developer.liu-xiao-guo.localizedscope_localizedscope.ini.in.h:2
msgid "This is a Localizedscope scope"
msgstr "这是一个本地化Scope"

我们可以选择在手机上安装并运行我们的Scope。最终显示如下:

         

我们也可以查看具体的操作步骤在项目中的"readme.txt"文件中。通过这样的方法,我们完成了对中文简体语言的本地化。整个项目的源码在如下地址找到:

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

作者:UbuntuTouch 发表于2014-12-22 11:37:52 原文链接
阅读:49 评论:0 查看评论

Read more
UbuntuTouch

——“和你圆梦”百万青年创业就业计划最新赛事公布

中国移动联合产业链合作伙伴Canonical,合作举办“Ubuntu开发者创新大赛”。旨为让青年开发者率先接触新兴的移动生态系统,从而获得崭新的创业机遇。“Ubuntu开发者创新大赛”是中国移动“和你圆梦”百万青年创业就业计划最新开启的子赛事。

赛事面向学生群体、职业开发者和开源社区,共设置6个奖项,奖励包括7万人民币现金和手机等奖品,并为学生组的获奖参赛者提供Canonical实习机会。开发者可访问“和你圆梦”百万青年创业就业计划官网报名参赛,报名截止时间为2015年4月30日,2015年6月进行决赛评选。

本次比赛作品提交可为Scopes和应用(包括HTML5和QML原生两种形式),参赛者将分为学生组和职业组进行评审。相关Ubuntu开发工具、网上教程和资料信息请登陆cn.developer.ubuntu.com

本次赛事活动将使开发者接触到Ubuntu所带来的新一代移动体验。这将打破自第一款iphone以来所形成的对于应用为王的固定模式,也将突破屏幕上惟有应用图标排列组成的单一视觉效果和使用上的局限。Ubuntu系统实现了内容和服务的前置,可直接呈现于屏幕上,从而有效的为用户创造一个丰富、快速且不碎片化的体验。而开发者只需花费相对于传统应用开发和维护而言很小的成本,便可在系统级别上创造出与应用同样的用户体验。这些独特体验是通过使用Ubuntu Scopes来完成的,它是Ubuntu独有的全新UI工具,可通过Ubuntu SDK获取。想了解更多Ubuntu Scopes和Ubuntu手机的信息,请访问cn.ubuntu.com/phone

“和你圆梦”百万青年创业就业计划由共青团中央和中国移动于2010年联合启动,旨在为青年学生提供全流程的创业就业扶持。其中丰富的赛事活动是中国移动“和你圆梦”百万青年创业就业计划的亮点,除此次Ubuntu开发者创新大赛之外,第四届的系列赛事活动共包含原创内容类、应用开发类、专项能力类三个类别,13个子赛事,设置了660个奖项,共计提供550万元现金奖金及培训、创业、就业等奖励扶持。>>更多赛事活动和扶持政策请访问“和你圆梦”百万青年创业就业计划官网(dream.10086.cn

该文章的出自:http://dev.10086.cn/news/mmnews/12145.html

作者:UbuntuTouch 发表于2014-12-18 22:01:54 原文链接
阅读:58 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu Scope简介及开发流程

在这个视频里,我们介绍了在Ubuntu平台上的Scope,并讲解了如何开发Scope。


视频地址:http://v.youku.com/v_show/id_XODQ3MDY5NTQ0.html

视频中的源码: bzr branch lp:~liu-xiao-guo/debiantrial/openmap

作者:UbuntuTouch 发表于2014-12-12 15:13:38 原文链接
阅读:217 评论:0 查看评论

Read more
UbuntuTouch

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

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


1) 生成Click Package

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


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

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

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

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

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



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

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




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



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

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


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


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

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

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


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

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

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

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

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



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

$ click --help

来查看它的具体的用法。

5) 登陆到手机

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

$ adb shell



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



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

如果你已经有超过一个以上的装置连接到你的电脑上,你可以通过如下的命令来登陆到你想要的装置中:

如果你同时有两个装置(或模拟器)连接在你的电脑上,你可以通过如下的命令来进入到你的手机中:


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


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

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

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

7)如何卸载已经安装好的Scope

我们可以通过如下的方法来卸载已经在手机上安装好的Scope

  

注意在这个步骤中,如果需要密码的话,它就是我们设置的手机启动密码

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

Read more
UbuntuTouch

[原]Ubuntu SDK 安装

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


操作系统选择

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

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

添加Phablet Tools PPA

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

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

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

添加Ubuntu SDK 发布 PPA中

按照一下方式添加Ubuntu SDK 发布 PPA (https://launchpad.net/~ubuntu-sdk-team/+archive/ppa)。注意使用Utopic(14.10)也要加入此PPA
键入如下命令,并输入你的Linux管理员密码来完成。

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

安装 Ubuntu SDK

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

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

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

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

启动Ubuntu SDK IDE

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


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

$ ubuntu-sdk 

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



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



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

     


如果我们在SDK启动时,选择不再显示安装wizard,并且我们选择不安装armhf及i386架构,我们也可以在下面的步骤中来安装它们。具体的步骤也可以参阅“如何使用Ubuntu SDK”。

安装Ubuntu SDK armhf chroot

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

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


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

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

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

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

安装Ubuntu SDK i386 chroot

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



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

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

安装模拟器

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

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


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

 

在安装模拟器过程中,如果出现问题,可以按照上面右图所示的方式我们的安装的log信息。
有了这个模拟器,我们就可以在模拟器中运行我们开发的应用了。我们可以选择刚才生成的模拟器(myinstance),并运行它:



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



开发者也可以参阅https://wiki.ubuntu.com/Touch/Emulator文章来安装自己的模拟器。开发者可以在shell中使用如下的命令来运行模拟器:

$ubuntu-emulator run myinstance --scale=0.8

这里myinstance是我们已经创建好的Ubuntu emulator的名称。

如果由于一些原因,开发者看见模拟器是一个黑色的屏幕(看不见任何的内容)或者运行的速度比较慢。这有可能是我们在我们的电脑的BIOS里没有启动硬件虚拟化功能而造成的。开发者需要到自己的电脑的BIOS里的设置启动VT-X/AMD-V。开发者可以在Shell中通过如下的命令来检查自己的电脑是否支持virtualization:

 # check if the hardware support virtualzation
 $ grep -e svm -e vmx /proc/cpuinfo
 
# check if it's enabled from BIOS
 $ sudo apt-get install cpu-checker && kvm-ok

安装搜狗中文输入法


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

Qt SDK 安装

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


总结

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

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

Read more
UbuntuTouch

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


        


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


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



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

   

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




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




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



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

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




2)完成我们的Client API代码


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

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

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

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

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

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

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

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

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

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


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

#ifndef API_CONFIG_H_
#define API_CONFIG_H_

#include <memory>
#include <string>

namespace api {

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

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

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

}

#endif /* API_CONFIG_H_ */


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

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

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

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

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

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

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

    Client(Config::Ptr config);

    virtual ~Client() = default;

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

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

    virtual Config::Ptr config();

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

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

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

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

}

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

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

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

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

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

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

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

    Forecast result;

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

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

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

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

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

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

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

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

    tm *localtm = localtime(&now);

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

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

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

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

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

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

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

            localtm->tm_mday++;

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

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

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

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

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

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

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

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

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

        }
    }

    return result;
}

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

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

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


3)代码讲解


src/scope/scope.cpp


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

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

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

src/scope/query.cpp


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

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

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

创建并注册CategoryRenderers

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


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

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

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

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


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

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

        Client::Forecast forecast;

        cerr << "query_string: " << query_string;

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

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

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

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

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


            res.set_title(ss.str());

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

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

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

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

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

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

        }

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

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

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

    

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

src/scope/preview.cpp

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

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

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

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

Preview

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

Preview Widgets

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

这个例子使用了如下的widgets

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

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

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

最终的程序如下:

#include <scope/preview.h>

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

#include <iostream>

namespace sc = unity::scopes;

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

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

void Preview::cancelled() {
}

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

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

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

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

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

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

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

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

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

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

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

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


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

      


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

   

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

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

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

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

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

4)调试Scope


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



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




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

Read more
UbuntuTouch

[原]如何在Ubuntu OS上开发Scope (视频)

在这个俩个视频里,我来介绍如何利用Ubuntu SDK从零开始在Ubuntu OS上开发一个点评Scope。如果大家想看文章的话,请参阅文章“在Ubuntu OS上创建一个dianping Scope (Qt JSON)”。更多关于Ubuntu Scope的介绍可以在网站http://developer.ubuntu.com/scopes/找到。


Scope开发视频第一部分

视频的地址在:http://v.youku.com/v_show/id_XODM5ODIxMDgw.html

视频的源码在:bzr branch lp:~liu-xiao-guo/debiantrial/dianpingvideo

Scope开发视频第二部分

视频的地址在:http://v.youku.com/v_show/id_XODQxOTA1OTk2.html

视频源码在:bzr branch lp:~liu-xiao-guo/debiantrial/dianpingvideo2


如果大家有什么意见或不清楚的,欢迎大家进行评论!

作者:UbuntuTouch 发表于2014-12-2 15:26:31 原文链接
阅读:331 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu手机应用QML开发 (视频)

在这个视频里,我们从零开始来展示如何使用Ubuntu SDK来开发一个Flickr应用,并部署到手机和模拟器中。本视频可以在地址:http://v.youku.com/v_show/id_XODQxMjUwMzMy.html看到。


教程的博客地址:使用Ubuntu SDK开发Flickr应用教程


视频的源码在地址:bzr branch lp:~liu-xiao-guo/debiantrial/flickrvideo

作者:UbuntuTouch 发表于2014-12-4 13:57:13 原文链接
阅读:302 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们来介绍怎么在Ubuntu OS上本地化一个应用。本地化对很多的应用很重要。我们重点介绍怎么把应用本地化为中文。


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


首先打开我们的Ubuntu SDK,并选择“App with Simple UI and localization”的模版。




然后接下来:

   



我们完成后我们的项目后,我们发现在项目的“LocalizeQml/po”目录中有生产一个文件“CMakeLists.txt”。当我们第一次运行我们的应用(无论在任何的architecture下),SDK会帮我们生产另外一个文件“com.ubuntu.developer.liu-xiao-guo.localizeqml.pot”。这个文件就是我们需要本地化的文件。我们在当前的文件目录下,复制这个文件,并重新命名为“zh_CN.po”。注意这里“zh_CN”是为中国地区简体中文的国际语言代码。我们打开该文件,并同时翻译该文件:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-20 12:05+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../app/ui/HelloTab.qml:6
msgid "Hello.."
msgstr "您好"

#: ../app/ui/HelloTab.qml:18
msgid "Hello World"
msgstr "您好,世界"

#: ../app/ui/HelloTab.qml:26
msgid "You can change the Tab from Page title above."
msgstr "您可以从页面的标题上改变标签"

#: ../app/ui/WorldTab.qml:6
msgid "..World!"
msgstr "..世界!"

#: ../app/ui/WorldTab.qml:18
msgid "WorldTab"
msgstr "世界标签"

#: /home/liuxg/release/build-localizedqml-Desktop-Default/po/localizedqml.desktop.in.h:1
msgid "LocalizeQml"
msgstr "本地化QML"

这里必须注意的是,必须设置charset为UTF-8才可以正常显示中文。我们可以在手机或模拟器中输入一下的命令来查看当前的手机的语言:



我们再重新运行我们的应用。在手机上看到的界面如下:



我们看见我们的UI现在都是显示的是中文。我们现在来回顾一下我的QML文件的内容:

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

    page: Page {
        Column {
            spacing: units.gu(2)
            anchors.centerIn: parent

            HelloComponent {
                objectName: "helloTab_HelloComponent"

                anchors.horizontalCenter: parent.horizontalCenter

                text: i18n.tr("Hello World")
            }

            Label {
                objectName: "helloTab_label"

                anchors.horizontalCenter: parent.horizontalCenter

                text: i18n.tr("You can change the Tab from Page title above.")
            }
        }
    }
}

我们可以看到所有的字符串是以“i18n.tr”来输出的。它可以帮我们进行本地化。当然我们可以把我们的应用本地化到任何一个其它的语言。

为了说明问题,我们把我们的应用改为:

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

    page: Page {
        Column {
            spacing: units.gu(2)
            anchors.centerIn: parent

            HelloComponent {
                objectName: "helloTab_HelloComponent"

                anchors.horizontalCenter: parent.horizontalCenter

                text: i18n.tr("Hello World")
            }

            Label {
                objectName: "helloTab_label"

                anchors.horizontalCenter: parent.horizontalCenter

                text: i18n.tr("You can change the Tab from Page title above.")
            }

            Button {
                text: i18n.tr("Press me")
            }
        }
    }
}

重新运行我们的应用,我们看到的是英文的界面。我们也同时查看一下我们在“po”目录下的“com.ubuntu.developer.liu-xiao-guo.localizeqml.pot”,我们看见我们新添加的“Press me”在这个文件中。我们需要重新把它添加到我们的“zh_CN.po”文件中去。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-11-20 12:05+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../app/ui/HelloTab.qml:6
msgid "Hello.."
msgstr "您好"

#: ../app/ui/HelloTab.qml:18
msgid "Hello World"
msgstr "您好,世界"

#: ../app/ui/HelloTab.qml:26
msgid "You can change the Tab from Page title above."
msgstr "您可以从页面的标题上改变标签"

#: ../app/ui/WorldTab.qml:6
msgid "..World!"
msgstr "..世界!"

#: ../app/ui/WorldTab.qml:18
msgid "WorldTab"
msgstr "世界标签"

#: /home/liuxg/release/build-localizedqml-Desktop-Default/po/localizedqml.desktop.in.h:1
msgid "LocalizeQml"
msgstr "本地化QML"

#: ../app/ui/HelloTab.qml:30
msgid "Press me"
msgstr "按一下我"

重新运行我们的应用:



我们看到新的字符串已经被翻译了。我们可以在我们的shell中输入如下的命名:



我们可以看见一个.mo的本地化文件被打入包中,并存于“zh_CN”下的文件目录中。整个应用的源码在如下的地址找到:

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


作者:UbuntuTouch 发表于2014-11-28 15:05:01 原文链接
阅读:184 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu OS Scope开发视频(英文)

在这篇文章中,我们来介绍一下我们录制的Scope开发的视频。这些视频是我们公司的社区team帮录制的。它们的内容是英文的。希望对大家的开发有帮助。


1)Scope开发介绍


2)为Scope添加Location支持


3)设置Scope开发环境


4)Scope开发How-tos



5)如何在装置中运行应用

http://v.youku.com/v_show/id_XODM5MjM4Njg0.html

作者:UbuntuTouch 发表于2014-11-27 19:13:50 原文链接
阅读:378 评论:0 查看评论

Read more
UbuntuTouch

[原]如何使用Ubuntu SDK (视频)

在这个视频里,我们介绍了如何使用我们的Ubuntu SDK来开发我们的应用。视频的地址可以在youku.com的如下的地址可以找到:http://v.youku.com/v_show/id_XODM0NDg0Njk2.html




作者:UbuntuTouch 发表于2014-11-25 12:13:10 原文链接
阅读:396 评论:0 查看评论

Read more
UbuntuTouch

由于一些平台安全性的原因,Ubuntu手机目前暂时没有提供供第三方开发者发送短信及拨打电话的接口,但是在实际的应用中,我们也许会需要用到发送短信息或拨打电话。这个时候我们怎么办呢?我们在前面的文章“使用URL dispatcher的范例”中已经介绍了如何使用url dispatcher来调用第三方应用的方法。这里我们用该方法来展示如何在我们的应用中发送短信息及拨打电话。


首先,我们创建一个最简单的“App with Simple UI”模版应用,并修改我们的“main.qml”文件如下:


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 0.1 as ListItem

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

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

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

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

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

        function call(phonenumber) {
            Qt.openUrlExternally("tel:///" + encodeURIComponent(phonenumber))
        }

        function sendMessage(phonenumber, text) {
            Qt.openUrlExternally("message:///" + encodeURIComponent(phonenumber))
        }


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

            Row {
                anchors.horizontalCenter: parent.horizontalCenter

                Label {
                    anchors.verticalCenter: parent.verticalCenter
                    text: i18n.tr("Number: ")
                }

                TextField {
                    id: inputnumber
                    placeholderText: "please type your phone number"
                    text: "1111111111"
                }
            }

            Button {
                width: parent.width
                text: i18n.tr("Call")

                onClicked: {
                    call(inputnumber.text)
                }
            }

            ListItem.Divider {}

            Row {
                anchors.horizontalCenter: parent.horizontalCenter

                Label {
                    anchors.verticalCenter: parent.verticalCenter
                    text: i18n.tr("Number: ")
                }

                TextField {
                    id: inputnumber1
                    placeholderText: "please type your phone number"
                    text: "22222222222"
                }
            }

            TextEdit {
                id: messageText
            }

            Button {
                width: parent.width
                text: i18n.tr("Send Message")

                onClicked: {
                    sendMessage(inputnumber1.text)
                }
            }
        }
    }
}

这个应用的设计非常简单。我们的UI如下:




我们在上面的号码输入框中输入自己想要拨打或发送短信的号码,按下“Call”或“Send Message”按钮,就可以拨打电话或发送短信了。只不过短信或电话的应用被调用起来来完成这个动作。从安全的角度来说,这个需要用户的交互才可以完成。对手机是非常安全的。我们使用了如下的代码来完成url dispatcher的工作:

      function call(phonenumber) {
            Qt.openUrlExternally("tel:///" + encodeURIComponent(phonenumber))
        }

        function sendMessage(phonenumber, text) {
            Qt.openUrlExternally("message:///" + encodeURIComponent(phonenumber))
        }


整个应用的完整代码在如下的地址可以找到:

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



作者:UbuntuTouch 发表于2014-11-25 11:51:26 原文链接
阅读:345 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu OS应用Runtime Enviroment

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


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


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


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


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

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

MainView {
    id: root

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

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

    anchorToKeyboard: true

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

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

    property string app_pkgname

    ReadEnv {
        id: readEnv
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


 


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


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

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

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


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




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

Read more
UbuntuTouch

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

   

     


这个Scope的开发视频可以在地址“如何在Ubuntu OS上开发Scope (视频)”找到。

1)创建一个最基本的Scope

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

  

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

  

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



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



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

\

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



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





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

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

2)点评接口

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

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



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

3)设计Client数据接口

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


#ifndef API_CLIENT_H_
#define API_CLIENT_H_

#include <api/config.h>

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

#include <QJsonDocument>

namespace api {

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

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

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


    Client(Config::Ptr config);

    virtual ~Client() = default;

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

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

    virtual Config::Ptr config();

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

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

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

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

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

}

#endif // API_CLIENT_H_

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

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

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

#include <api/client.h>

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

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

#include <iostream>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    DataList result;

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

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

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

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

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

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

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

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

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

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

    return result;
}

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

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

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

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

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

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

    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

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

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

//    qDebug() << sign;

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

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

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

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

    QUrl url1(url);

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

    return string;
}

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

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

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

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

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

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

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


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

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

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

4)代码讲解


src/scope/scope.cpp


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

src/scope/query.cpp


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

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

创建并注册CategoryRenderers


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

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

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

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

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

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

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

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

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

        QString title = queryString + "美味";

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

        bool isgrid = false;

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

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

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

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

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

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

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

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

using namespace unity::scopes;

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


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

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

src/scope/preview.cpp


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

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


Preview


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

Preview Widgets


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

这个例子使用了如下的widgets

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

最终的程序如下:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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



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

   

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

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

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

5)定制Scope


定制Scope图标


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



定制Scope颜色

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

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

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

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

   

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

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

定制Category template


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

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

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

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

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

  

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

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

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

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

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

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

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

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

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

  

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

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

加入设置


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

query.h

private:
    void initScope();

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

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

    client_.setLimit(limit);
}

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

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

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

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

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


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

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

    // Initialize the scopes
    initScope();

 ....
}

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

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

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

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

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

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

  

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

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

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


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



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

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

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

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

private:
    void initScope();

private:
    api::Client client_;

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

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

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

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

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

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

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

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

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

        client_.setCoordinate(m_longitude, m_latitude);

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

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

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

 ...
}

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

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

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

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

    QString m_longitude;
    QString m_latitude;

同时加入如下的方法:

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

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

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

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

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

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



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

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

LocationDataNeeded=true // added

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

重新运行我们的Scope:

   


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

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

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

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

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


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

选择手机target并成功编译





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




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



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

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




8)调试Scope


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



如果你同时有两个装置(或模拟器)连接在你的电脑上,你可以通过如下的命令来进入到你的手机中:



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

9)如何卸载已经安装好的Scope

我们可以通过如下的方法来卸载已经在手机上安装好的Scope

  

注意在这个步骤中,如果需要密码的话,它就是我们设置的手机或模拟器的启动密码



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

Read more
UbuntuTouch

如果你还没有安装好你的环境的话,请参考"Ubuntu SDK 安装"章节来安装好自己的SDK环境。这篇文章的主要目的是为了检查我们所安装的环境是否正确以确保我们所安装的环境是正确的。建议大家观看视频“如何使用Ubuntu SDK”来对本章节有更进一步的认识。

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


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

\



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


打开manifiest.json文件,检查framework的确保设置为“ubuntu-sdk-14.10”。这是目前手机及模拟器支持的framework。如果手机或者自己的emulator不支持我们所选择的framework,我们的应用将无法进行安装。我们可以通过如下的方式来检查我们的手机或模拟器(在模拟器运行起来的情况下)所支持的framework:




2)在Desktop上面运行

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



3)在模拟器上运行应用

为了能够在模拟器上运行我们的应用,我们可以按如下的操作进行:
  • 启动Ubuntu SDK
  • 选择IDE左侧的"Devices",并同时选中我们已经创建的模拟器(我先前已经创建好myinstance)。同时点击图中的绿色的按钮以启动模拟器。


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

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

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



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

3)在手机中运行

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

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



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

  • 同时选择运行时的Kit

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


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



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

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







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



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



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



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


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

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

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



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




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





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



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

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

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


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

Read more
UbuntuTouch

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





1)Scope 开发

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










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

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

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

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

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

3)利用HTML 5开发Web应用

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

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


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

Read more
UbuntuTouch

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

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


1)下载VirtualBox文件

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



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

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

2)安装VirtualBox

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



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



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



3) 下载Ubuntu Desktop Image

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

4) 在VirtualBox中安装Ubuntu OS

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



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



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



接下来:



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



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



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

点击VirtualBox中的“Start




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



再选择“Install Ubuntu



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











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



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




5) 安装VirtualBox Extensions

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



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



点击"Extensions", 



重新启动我们创建的VM。



然后选择:



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



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



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

6) 设置VirtualBox


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




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



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

$sudo usermod -a -G vboxsf username

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

 

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




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



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


7) 安装中文输入法

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

8) 安装Ubuntu SDK

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


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

Read more
UbuntuTouch

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




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



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


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

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




2) 选中“关于此手机”




3)点击“开发者模式”




4) 打开开关




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

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




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

Read more
UbuntuTouch

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


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


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



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

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

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



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

3)安装Ubuntu OS的语言支持

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



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



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



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



4) 配置搜狗中文输入法


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



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


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

5)添加对Qt的中文支持

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

$ sudo apt-get install fcitx-frontend-qt5

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



6) 对Qt Creator 定制

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



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



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

$ fcitx-config-gtk3

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

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

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

Read more
UbuntuTouch

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

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

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

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

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


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


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

1)创建一个基本的应用

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



  • 设定应用的设置


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


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



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

2)添加C++功能模块


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


#ifndef READ_ENV_H
#define READ_ENV_H

#include <QObject>

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

private:
};

#endif // READ_ENV_H

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

3)修改QML部分的代码

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

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

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

        }


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




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

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

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

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

    anchorToKeyboard: true

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

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

    ReadEnv {
        id: readEnv
    }

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

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

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

                placeholderText: "Please input a env variable"

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

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

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

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

        }

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



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

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




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

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


这个应用的运行效果为:


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

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

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

Read more