Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

[原]Ubuntu 手机开发培训准备

在这篇文章中,我们将介绍学生如何做培训准备前的准备工作。提前准备并安装好自己的环境是做好一个培训非常重要的步骤。


                 

1)安装好自己的SDK


如果想在自己的电脑上安装Ubntu系统

学生可以按照文章“Ubuntu SDK 安装”安装好自己的Ubuntu系统及SDK。让后根据文章“创建第一个Ubuntu for phone应用”来检验自己安装的环境是否正确。这种安装通常需要在电脑上安装多系统功能,或虚拟机(模拟器在虚拟机的效果可能并不好)。

如果想做一个专为Ubuntu手机开发而做的Live USB

请参照文章“如何制作Ubuntu SDK Live USB盘”来专门制作一个可以启动的Live USB盘。这个盘可以直接插入到电脑中的USB口中,并启动Ubuntu系统。这个USB盘中已经安装好整个可以供开发的SDK,不需要安装任何额外的软件即可开发。

a) 在BIOS中启动硬件虚拟化功能,这样会使得模拟器的运行速度加快
b) 在BIOS中设置优选顺序以使得USB可以优先启动,或在启动的时候按下F12功能键,并选择由USB来启动Ubuntu

在启动Ubuntu系统后,Ubuntu SDK已经完全安装好了。开发者可以直接进行开发了。建议参阅文章“创建第一个Ubuntu for phone应用”来检验自己安装的环境是否正确。


在开发过程中如果使用手机进行安装时,如果需要密码解锁手机的话,这个密码是“0000”。

2)Ubuntu手机介绍


对不熟悉Ubuntu手机的开发者来说,可以先观看视频“如何使用Ubuntu手机”来了解Ubuntu手机。如果你想对Ubuntu SDK有更深的认识,请观看视频“如何使用Ubuntu SDK (视频)”。

你可以在地址“Ubuntu手机介绍”下载有关Ubuntu手机介绍的幻灯片,并在地址观看相应的视频


3)QML应用开发


Flickr应用开发

阅读文章“使用Ubuntu SDK开发Flickr应用教程”,并观看视频“Ubuntu手机应用QML开发 (视频)”。幻灯片“Ubuntu应用开发”。

教程的源码在: bzr branch lp:~liu-xiao-guo/debiantrial/flickr7
我们可以在Shell中输入以上的指令来下载源码。

DeveloperNews RSS阅读器

首先我们可以阅读文章“从零开始创建一个Ubuntu应用--一个小的RSS阅读器”及文章“如何在Ubuntu中使用条件布局”。视频在“在Ubuntu平台上开发Qt Quick QML应用 (视频)

教程的源码在:bzr branch lp:~liu-xiao-guo/debiantrial/developernews4

我们可以在Shell中输入以上的指令来下载源码。


4)Scope 开发


大家可以先观看视频“Ubuntu Scope简介及开发流程”来了解Ubuntu OS上的Scope开发流程。


教程的源码在: bzr branch lp:~liu-xiao-guo/debiantrial/dianpianclient8
我们可以在Shell中输入以上的指令来下载源码。

更多关于Scope开发的例程可以在链接找到。

5)更多的培训材料


我们也有更多的英文的培训材料。开发者可以在地址下载。


如果有任何问题,请在该文章处评论。我会尽力回答你们的问题。


作者:UbuntuTouch 发表于2015-1-4 15:36:54 原文链接
阅读:622 评论:2 查看评论

Read more
UbuntuTouch

在这篇文章中,我们来介绍如何判断一个QML应用被推到后台或前台。我们知道,在Ubuntu手机平台中,它是一个单应用的操作系统。当一个应用被推到后台后,应用就被挂起,不能运行。我们有时需要这个标志来判断我们的应用什么时候是在前台,什么时候是在后台。


我们用Ubuntu SDK创建一个简单的QML应用:


import QtQuick 2.0
import Ubuntu.Components 1.1

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

MainView {
    id: main
    // 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.foregrounddetect"

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

        Connections {
             target: Qt.application
             onActiveChanged: {
                 console.log("Qt.application.active: " + Qt.application.active);
             }
         }
    }
}

在这里,我们使用Qt.application这个变量的"active"属性来判断一个应用是否被推到后台或前台。我们运行应用结果如下:




当我们在手机上把应用推到后台时,就会显示false;当我们把应用推到前台时,就会显示true。

整个应用的源码在:bzr branch lp:~liu-xiao-guo/debiantrial/foregrounddetect


作者:UbuntuTouch 发表于2015-1-5 10:56:25 原文链接
阅读:229 评论:0 查看评论

Read more
UbuntuTouch

在这个视频里,我们从“0”开始来开发一个mini的RSS阅读器。通过这个练习,开发者可以对QML的编程有一个基本的了解,并了解在Ubuntu平台上的一些开发的流程。应用的图片如下:


  



作者:UbuntuTouch 发表于2015-1-13 15:08:31 原文链接
阅读:163 评论:0 查看评论

Read more
UbuntuTouch

[原]如何制作Ubuntu SDK Live USB盘

对于一些想开发Ubuntu手机应用或Scope的开发者来说,不想重新买一个电脑安装Ubuntu操作系统或在自己的硬盘上重新安装一个Ubuntu系统,那么可以考虑制作一个Ubuntu系统的Live USB盘。这个USB包括如下的部分:


  • Ubuntu Kylin 14.10操作系统
  • Ubuntu SDK (包括已经安装好的SDK,模拟器及编译环境)

使用这个Live USB盘,开发者就不用安装任何的东西,直接插入电脑的USB口中。在电脑启动的过程中,选择我们制作好的USB启动盘进行启动(在电脑启动的过程中,按下“F12”键)。在启动的过程中选择“Try Ubuntu Kylin without installing




虽然这是一个Ubuntu OS的启动盘,但是它可以保存我们在开发过程中所创建的项目(存于Home目录中)及一些设置(比如wifi设置密码等)。


当我们选择USB时,我们最好是选择USB 3.0并把USB盘放入到电脑USB 3.0的口中。一般来说,电脑上的USB 3.0口是用蓝色标示的。建议使用质量较好,速度较快一点的USB这样可以使得系统的启动和运行更快更流畅。目前我们使用SanDisk CZ80来做测试,效果还是不错的。USB需要有16G的存储。


为了使得我们的模拟器能够更加流畅及模拟器不会出现黑色的屏幕,我们需要在电脑的BIOS里启动硬件虚拟化功能。开发者需要到自己的电脑的BIOS里的设置启动VT-X/AMD-V。开发者可以参考文章“Ubuntu SDK 安装”来检查自己的电脑是否支持virtualization。




如果开发者想要在自己的电脑上安装Ubuntu系统并在上面开发的话,可以参考文章“Ubuntu SDK 安装”来一步一步地安装Ubuntu SDK。



1)如何在Ubuntu系统下制作Live USB盘


启动Ubuntu操作系统,打开浏览器并在如下的地址下载最新的image:


https://mega.co.nz/#F!S8QSRZyI!2HBWgXk4kmc_2bcCcpBR3Q


下载的文件包含:

  • kylin-live-20150133.iso (md5sum 13cd61270bf98eb462dc0497df8eee33) 
  • casper-rw-20150113.tar.bz2  (md5sum 8c69f94a03481275bf66aa883b69ae1b)
  • post-usb-creator-window.sh(在Windows下制作需要这个)
  • README.md (简单的说明文件)

我们把下载的文件存于到我们想要的一个目录中,比如在自己的Home下的“usb”目录中。


在Dash中输入“usb”,并启动“Startup Disk Creator/启动盘创建器”






我们按照如下的方法来制作我们的USB启动盘。





在设置“储存在额外保留空间”时,它的值应该为非零的值。等USB盘已经制作好以后,你将会看到如下的画面:







重新挂载USB盘,因为在前一步会自动卸载USB盘,或者在Ubuntu中的文件浏览器中点击USB所在的device。这样就可以完成重新挂载USB:





然后按下面运行自带的脚本,参数为 USB 盘挂载的路径。


解压已经下载的casper-rw-2015xxxx.tar.bz2文件


等文件都被解压完后,进入解压文件所在的目录,并在shell中执行如下的指令:


liuxg@liuxg:~/usb$ ./post-usb-creator-linux.sh /media/liuxg/BD52-7153/


这里“/liuxg/BD52-7153”为USB盘挂载的路径。根据自己USB盘所在的路径替换。


2)如何在Windows 平台下制作启动盘


http://www.ubuntu.com/download/desktop/create-a-usb-stick-on-windows

下载制作工具,与 Linux 平台的工具相似。




单我们在选择“Persistent file”时,它的大小应该是非零的一个值。在我们填入“Step 2”时,我们不应该把拷贝好的字符串拷到该输入框中,否则在“Step 3”中的输入框就会是灰色的。我们应该点击“Browse”按钮,并按照如下的方式进行输入image的路径:




在这之后把 casper-rw 文件拷贝到USB的主目录下即可。


:如果只想使用英文版的Ubuntu系统就不需要进行下面的步骤。如果想要支持中文版,请把 post-usb-creator-window.sh 也拷贝到 USB盘的根目录下。从USB 盘启动Ubuntu系统后,在shell中执行如下的指令:


$ cd /cdrom/

$ sudo ./post-usb-creator-window.sh


再次重新启动后,会进入中文版的Ubuntu系统。


3)测试已经制作好的USB启动盘


我们可以把我们的Live USB盘插入电脑,我们可以通文章“创建第一个Ubuntu for phone应用”来检验我们是否有一个完好的Ubuntu SDK。


在我们启动模拟器时,如果需要输入密码,请使用默认的密码“0000”。如果开发者需要自己修改这个密码,请到Ubuntu SDK模拟器中的“系统设置”中去修改。


对于应用开发者来说,在Qt Creator中的热键组合“Ctrl + Space”键有它独特的用处。可是,在Ubuntu系统中,“Ctrl + Space”被用来转换中英文输入法。建议开发者参考文章“怎么在Ubuntu OS上面安装搜狗输入法及对Qt Creator的支持”来重新定义键的组合。


已知问题 (known issues)

如果你在使用的过程中,发现有如下的乱码的情况(极少情况下出现),请重新启动你的机器来纠正这个问题。




在个别电脑上不能启动的问题


我们发现在联想 E455 出现不能启动的问题,目前怀疑是和 AMD 显卡驱动有关,问题仍在调查中,如果遇到些问题,请在系统上安装14.04 LTS版本并安装相应的ubuntu-sdk包来尝试学习ubuntu phone的开发知识,其中的基本概念都是一样。


注:如果想长时间致力于ubuntu phone的开发建议在电脑上安装一个ubuntu系统,最好是utopic (14.10),而不是在Live环境下进行学习,一是以防数据的丢失,二是在使用性能上有更快速的体验。



作者:UbuntuTouch 发表于2015-1-22 15:35:55 原文链接
阅读:209 评论:0 查看评论

Read more
UbuntuTouch

在这个视频里介绍了Ubuntu OS上的online account探讨。online account可以应用于Web,QML及Scope的开发。更多介绍请参阅developer.ubuntu.com


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

作者:UbuntuTouch 发表于2014-12-25 9:40:46 原文链接
阅读:336 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将介绍如何开发一个快递邮件查询的Scope。我们知道Scope很方便地把我们Web上的服务快速地集成到我们的系统中,并使之成为我们用户体验的一部分。我在先前的文章中重点介绍了如何使用点评Scope的开发,也介绍了中国天气的Scope的开。在这篇文章中,我们将从另外一个角度介绍一个新的Scope的开发。这个Scope和先前的使用的架构是不同的。它的department是从一个本地的文件中读出来的。希望对大家的开发有帮助。我们的最终Scope的显示如下:


     


1)创建一个最基本的Scope

在这个章节里,我们来创建一个最基本的Scope。大家一起来跟着我的步骤一步一步地来:



  






在这个例子里,我们选择使用的是“Empty Scope”。这样我们不必要去删除很多的文件。我们在Desktop上运行一下我们刚刚创建的Scope。没有什么是特殊的。我们使用能够热键Ctrl + R或SDK左下角的绿色的运行按钮,运行Scope:



如果你运行你的Scope,并看见如上图所示的画面,请按照如下的画面来重新设置你的configuration。




这是一个最基本的Scope,没有任何特殊的东西,因为这是个非常简单的Empty Scope。




2)加入Qt支持


由于“Empty Scope”模版对Qt没有进行支持。在这个章节里,我们教大家怎么把Qt加入到项目中。我们希望在项目中使用Qt来解析我们得到的数据。我们在项目中加入对Qt的支持。我们首先打开在“src”中的CMakeLists.txt文件,并加入如下的句子:

find_package(Qt5Core REQUIRED)     
find_package(Qt5Xml REQUIRED)      

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

....

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

qt5_use_modules(scope Core Network) 

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

)

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


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

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

qt5_use_modules(scope-unit-tests Core Network)

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

我们再重新编译我们的应用,如果我们没有错误的话,我们的Scope可以直接在desktop下直接运行。这样我们就完成了我们项目对Qt的支持。

3)快件查询API

我们可以在网址:http://www.kuaidi100.com/提供的服务来查询我们的快件。我们将使用如下的API来查询我们的快件的信息:

http://www.kuaidi100.com/query?type=shunfeng&postid=592833849048

这里type可以是任何其它的公司。postid是快件的单号。我们在这里也分别列出其它的快件公司的查询API接口:

[
    {
        "title": "顺丰",
	"pinyin": "shunfeng",
        "url": "http://www.kuaidi100.com/query?type=shunfeng&postid=%1"
    },
    {
        "title": "全峰",
	"pinyin": "quanfeng",
        "url": "http://www.kuaidi100.com/query?type=quanfengkuaidi&postid=%1"
    },
    {
        "title": "申通",
	"pinyin": "shentong",
        "url": "http://www.kuaidi100.com/query?type=shentong&postid=%1"
    },
    {
        "title": "EMS",
	"pinyin": "ems",
        "url": "http://www.kuaidi100.com/query?type=ems&postid=%1"
    },
    {
        "title": "圆通",
	"pinyin": "yuantong",
        "url": "http://www.kuaidi100.com/query?type=yuantong&postid=%1"
    },
    {
        "title": "中通",
	"pinyin": "zhongtong",
        "url": "http://www.kuaidi100.com/query?type=zhongtong&postid=%1"
    },
    {
        "title": "韵达",
	"pinyin": "yunda",
        "url": "http://www.kuaidi100.com/query?type=yunda&postid=%1"
    },
    {
        "title": "天天",
	"pinyin": "tiantian",
        "url": "http://www.kuaidi100.com/query?type=tiantian&postid=%1"
    },
    {
        "title": "汇通",
	"pinyin": "huitong",
        "url": "http://www.kuaidi100.com/query?type=huitongkuaidi&postid=%1"
    },
    {
        "title": "德邦",
	"pinyin": "debang",
        "url": "http://www.kuaidi100.com/query?type=debangwuliu&postid=%1"
    },
    {
        "title": "宅急送",
 	"pinyin": "zhaijisong",
       	"url": "http://www.kuaidi100.com/query?type=zhaijisong&postid=%1"
    }
]

这里我们使用了一个json结构的文件“departments.json”来存储这些信息,并把这个文件存于项目的“data”目录下。这里的“pinyin”项将被用作Scope中的department id。就像我们在这篇文章一开始的位置显示的那样,我们想把该Scope设计为一个department Scope。这样我们可以对所有的快递公司进行查询。我们可以对我们的一个实验性的API进行展示如下:

{"nu":"592833849048","companytype":"shunfeng","com":"shunfeng","updatetime":"2014-12-23 18:13:16","signname":"","condition":"F00","status":"200","codenumber":"592833849048","signedtime":"","data":[{"time":"2014-11-29 13:19:43","location":"","context":"已签收,感谢使用顺丰,期待再次为您服务","ftime":"2014-11-29 13:19:43"},{"time":"2014-11-29 13:19:43","location":"","context":"在官网\"运单资料&签收图\", 可查看签收人信息","ftime":"2014-11-29 13:19:43"},{"time":"2014-11-29 11:14:23","location":"","context":"正在派送途中,请您准备签收(派件人:孙连杰,电话:13810320784)","ftime":"2014-11-29 11:14:23"},{"time":"2014-11-29 10:00:30","location":"","context":"快件到达 北京北苑集散中心","ftime":"2014-11-29 10:00:30"},{"time":"2014-11-29 08:45:55","location":"","context":"快件在 北京顺义集散中心, 正转运至 北京北苑集散中心","ftime":"2014-11-29 08:45:55"},{"time":"2014-11-29 06:29:29","location":"","context":"快件在 北京集散中心, 正转运至 北京顺义集散中心","ftime":"2014-11-29 06:29:29"},{"time":"2014-11-28 20:46:26","location":"","context":"快件在 厦门总集散中心, 正转运至 北京集散中心","ftime":"2014-11-28 20:46:26"},{"time":"2014-11-28 19:32:18","location":"","context":"快件在 厦门集美集散中心, 正转运至 厦门总集散中心","ftime":"2014-11-28 19:32:18"},{"time":"2014-11-28 17:26:37","location":"厦门莲岳服务点","context":"[厦门莲岳服务点]快件在 厦门莲岳服务点, 正转运至 厦门集美集散中心","ftime":"2014-11-28 17:26:37"}],"state":"3","departure":"厦门市","addressee":"","destination":"北京市","message":"ok","ischeck":"1","pickuptime":""}

在下面的章节中,我们将介绍如何使用这个API来对内容在Scope中进行显示。

为了在下面的练习中能够更加好地完成,我们可以在如下的地址下载


下载“images”,“renderer”目录及“departments.json”文件,并放到项目中的“data”目录中。为了能够在Desktop上进行运行,我们也创建了如下的script文件“setup.sh”,并把该文件置于项目的根目录中:

#!/bin/bash

if [ $# -eq 0 ]; then
    DEST="../build-mailcheck-Desktop-Default/src/"

    mkdir -p $DEST
    cp -r data/departments.json $DEST/
    cp -r data/renderer $DEST/
    cp -r data/images $DEST/
    
    echo "Setup complete."
    exit 0;
fi

我们在我们项目的目录下打入如下的命令:



这样做的目的是把我们所需要在Desktop下运行所需要的文件拷到相应的目录中,以便我们在下面的程序中用到。通过这样的方法,我们把我们所需要的文件拷贝到如下的目录中以便我们在如下的练习中使用到。




重新运行编译我们的Scope以确保我们没有任何的错误。整个项目的源码在如下的地址:

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


4)为Scope加入department

在这个章节中,我们准备为我们的Scope加入所需要的department。



我们知道我们所需要的department信息存在于一个叫做“deparments.json”的文件中。我们需要得到Scope的路径才可以得到这个文件。我们对scope.cpp做如下的修改:

sc::SearchQueryBase::UPtr Scope::search(const sc::CannedQuery &query,
                                        const sc::SearchMetadata &metadata) {
    const QString scopePath = QString::fromStdString(scope_directory());
    // Boilerplate construction of Query
    return sc::SearchQueryBase::UPtr(new Query(query, metadata, scopePath, config_));
}

通过得到的scopePath,我们把它传入到query.cpp中以便使用。记得在scope.cpp中加入

#include <QString>

以使得项目可以得到编译。

我们也对query.cpp要做相应的修改:

   Query(const unity::scopes::CannedQuery &query,
          const unity::scopes::SearchMetadata &metadata,  QString scopePath,
          api::Config::Ptr config);


我们在query.cpp中也存储了一些相应的全局变量来保存我们的状态:

QString g_scopePath;
QString g_rootDepartmentId;
QMap<QString, std::string> g_renders;
QString g_userAgent;
QString g_imageDefault;
QString g_imageError;
QMap<QString, QString> g_depts;
QString g_curScopeId;
static QMap<QString, QString> g_deptLayouts;

我们的Query类的构造函数如下:

#define LOAD_RENDERER(which) g_renders.insert(which, getRenderer(g_scopePath, which))

Query::Query( const sc::CannedQuery &query, const sc::SearchMetadata &metadata,
             QString scopePath, Config::Ptr config ) :
    sc::SearchQueryBase( query, metadata ), client_( config ) {
    g_scopePath = scopePath;
    g_userAgent = QString("%1 (Ubuntu)").arg(SCOPE_PACKAGE);
    g_imageDefault = QString("file://%1/images/%2").arg(scopePath).arg(IMG_DEFAULT);
    g_imageError = QString("file://%1/images/%2").arg(scopePath).arg(IMG_ERROR);

    // Load all of the predefined rederers. You can comment out the renderers
    // you don't use.
    LOAD_RENDERER( "journal" );
    LOAD_RENDERER( "wide-art" );
    LOAD_RENDERER( "hgrid" );
    LOAD_RENDERER( "carousel" );
    LOAD_RENDERER( "large" );
}

std::string Query::getRenderer( QString scopePath, QString name ) {
    QString renderer = readFile( QString("%1/renderer/%2.json" )
                                .arg(scopePath).arg(name));
    return renderer.toStdString();
}

QString Query::readFile(QString path) {
    QFile file(path);
    file.open(QIODevice::ReadOnly | QIODevice::Text);
    QString data = file.readAll();
    // qDebug() << "JSON file: " << data;
    file.close();
    return data;
}

我们通过如下的方法来解析“departments.json",并把“pinyin”项作为department id以便以后进行查询。虽然deparment id可以为任何一个字符串,只要保证它们之间是互相不同的。我们把得到的url数据存于一个叫做g_depts的全局变量中。

DepartmentList Query::getDepartments(QJsonArray data) {
    qDebug() << "entering getDepartments";

    DepartmentList depts;

    // Clear the previous departments since the URL may change according to settings
    g_depts.clear();
    qDebug() << "m_depts is being cleared....!";

    int index = 0;
    FOREACH_JSON( json, data ) {
        auto feed = (*json).toObject();
        QString title = feed["title"].toString();
//        qDebug() << "title: " << title;

        QString url = feed["url"].toString();
//        qDebug() << "url: " << url;

        QString pinyin = feed["pinyin"].toString();
//        qDebug() << "pinyin: " << pinyin;

        // This is the default layout otherwise it is defined in the json file
        QString layout = SURFACING_LAYOUT;

        if ( feed.contains( "layout" ) ) {
            layout = feed[ "layout" ].toString();
        }

        g_depts.insert( pinyin, url );
        g_deptLayouts.insert( pinyin, layout );

        CannedQuery query( SCOPENAME.toStdString() );
        query.set_department_id( url.toStdString() );
        query.set_query_string( url.toStdString() );

        Department::SPtr dept( Department::create(
                               pinyin.toStdString(), query, title.toStdString() ) );

        depts.push_back(dept);

        index++;
    }

    // Dump the departments. The map has been sorted out
    QMapIterator<QString, QString> i(g_depts);
    while (i.hasNext()) {
        i.next();
        qDebug() << "scope id: " << i.key() << ": " << i.value();
    }

    qDebug() << "Going to dump tthe department layouts";

    QMapIterator<QString, QString> j( g_deptLayouts );
    while (j.hasNext()) {
        j.next();
        qDebug() << "scope id: " << j.key() << ": " << j.value();
    }

    return depts;
}

为了能够使得我们的Scope能在手机或emulator上运行,我们还得对“data”目录下的“CMakeLists.txt”做如下的修改:

# Install the scope ini file
install(
  FILES "com.ubuntu.developer.liu-xiao-guo.mailcheck_mailcheck.ini"
  DESTINATION ${SCOPE_INSTALL_DIR}
)

# Install the scope images
install(
  FILES
    "icon.png"
    "logo.png"
    "screenshot.png"
    "departments.json"
  DESTINATION
    "${SCOPE_INSTALL_DIR}"
)

INSTALL(
    DIRECTORY "images"
    DESTINATION ${SCOPE_INSTALL_DIR}
)

INSTALL(
    DIRECTORY "renderer"
    DESTINATION ${SCOPE_INSTALL_DIR}
)

这样把“departments.json”,“images”及“render”都加入到项目中。

最终我们的结果如下:

   

我们可以看到我们的deparment了。所有的源码在如下的地址:

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


5)完成surfacing及查询

上面我们已经产生了department列表,但是,我们还是不能使用它或查询到什么结果。下面我们添加如下的Search方法来得到查询的结果:

void Query::search(sc::SearchReplyProxy const& reply) {
    CategoryRenderer renderer(g_renders.value("journal", ""));
    auto search = reply->register_category(
                "search", RESULTS.toStdString(), "", renderer);

    CannedQuery cannedQuery = SearchQueryBase::query();

    QString deptId = QString::fromStdString(cannedQuery.department_id());
    qDebug() << "deptId: " << deptId;

    qDebug() << "m_rootDepartmentId: " << g_rootDepartmentId;
    QString url;

    qDebug() << "m_curScopeId: " << g_curScopeId;

    if ( !deptId.isEmpty() ) {
        g_curScopeId = deptId;
    }

    if ( deptId.isEmpty() && !g_rootDepartmentId.isEmpty()
         && g_curScopeId == g_rootDepartmentId ) {

        QMapIterator<QString, QString> i(g_depts);
        qDebug() << "m_depts count: "  << g_depts.count();

        qDebug() << "Going to set the surfacing content";

        const CannedQuery &query(sc::SearchQueryBase::query());
        // Trim the query string of whitespace
        string query_string = alg::trim_copy(query.query_string());
        QString queryString = QString::fromStdString(query_string);

        if ( queryString.isEmpty()) {
            url = QString(g_depts[g_rootDepartmentId]).arg(592833849048);
        } else {
            url = QString(g_depts[g_rootDepartmentId]).arg(queryString);
        }
    } else {
        QString queryString = QString::fromStdString(cannedQuery.query_string());
        qDebug() << "queryString: " << queryString;

        // Dump the departments. The map has been sorted out
        QMapIterator<QString, QString> i(g_depts);
        qDebug() << "m_depts count: "  << g_depts.count();

        while (i.hasNext()) {
            i.next();
            qDebug() << "scope id: " << i.key() << ": " << i.value();
        }

        url = g_depts[g_curScopeId].arg(queryString);
    }

    qDebug() << "url: "  << url;
    qDebug() << "m_curScopeId: " << g_curScopeId;

    try {
        QByteArray data = get(reply, QUrl(url));
        getMailInfo(data, reply);
    } catch (domain_error &e ) {
        cerr << e.what() << endl;
        reply->error(current_exception());
    }
}

void Query::getMailInfo(QByteArray &data, SearchReplyProxy const& reply) {
    QJsonParseError e;
    QJsonDocument document = QJsonDocument::fromJson(data, &e);
    if (e.error != QJsonParseError::NoError) {
        throw QString("Failed to parse response: %1").arg(e.errorString());
    }

    // This creates a big picture on the top
    CategoryRenderer rssCAR(CAR_GRID);
    auto catCARR = reply->register_category("A", "", "", rssCAR);
    CategorisedResult res_car(catCARR);
    res_car.set_uri("frontPage");
    QString defaultImage1 ="file://"+ g_scopePath + "/images/" + g_curScopeId + ".jpg";
    qDebug() << "defaultImage1: "  << defaultImage1;
    res_car["largepic"] = defaultImage1.toStdString();
    res_car["art2"] =  res_car["largepic"];
    reply->push(res_car);

    QJsonObject obj = document.object();

    qDebug() << "***********************\r\n";

    if ( obj.contains("data") ) {
        qDebug() << "it has data!";

        QJsonValue data1 = obj.value("data");

        QJsonArray results = data1.toArray();

        qDebug() << "g_curScopeId: " << g_curScopeId;
        QString layout = g_deptLayouts.value( g_curScopeId );
        std::string renderTemplate;

        if (g_renders.contains( layout )) {
            qDebug() << "it has layout: " << layout;
            renderTemplate = g_renders.value( layout, "" );
            // qDebug() << "renderTemplate: " << QString::fromStdString(renderTemplate);
        }
        else {
            qDebug() << "it does not have layout!";
            renderTemplate = g_renders.value( "journal" );
            // qDebug() << "renderTemplate: " << QString::fromStdString(renderTemplate);
        }

        CategoryRenderer grid(renderTemplate);
        std::string categoryId = "root";
        std::string categoryTitle = " "; // #1330899 workaround
        std::string icon = "";
        auto tracking = reply->register_category(categoryId, categoryTitle, icon, grid);

        FOREACH_JSON(result, results) {
            QJsonObject o = (*result).toObject();

            QString time = o.value("time").toString();
//            qDebug() << "time: " << time;

            QString context = o.value("context").toString();
//            qDebug() << "context: " << context;

            QString link = "http://www.kuaidi100.com/";
            QString defaultImage ="file://"+ g_scopePath + "/images/" + g_curScopeId + ".jpg";

            CategorisedResult result(tracking);

            SET_RESULT("uri", link);
            SET_RESULT("image", defaultImage);
            //            SET_RESULT("video", video);
            SET_RESULT("title", time);
            //            SET_RESULT("subtitle", context);
            SET_RESULT("summary", context);
            //            SET_RESULT("full_summary", fullSummary);
            //            result["actions"] = actions.end();

            if (!reply->push(result)) break;
        }
    }
}

QByteArray Query::get(sc::SearchReplyProxy const& reply, QUrl url) const {
    QNetworkRequest request(url);
    QByteArray data = makeRequest(reply, request);
    return data;
}

“Search”在“run”方法中被调用:

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

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

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

        // Only push the departments when the query string is null
        if ( queryString.length() == 0 ) {
            qDebug() << "it is going to push the departments...!";
            pushDepartments( reply );
        }

        search(reply);

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

我们通过如下的方式向网路发出一个请求:

QByteArray Query::makeRequest(SearchReplyProxy const& reply,QNetworkRequest &request) const {
    int argc = 1;
    char *argv = const_cast<char*>("rss-scope");
    QCoreApplication *app = new QCoreApplication( argc, &argv );

    QNetworkAccessManager manager;
    QByteArray response;
    QNetworkDiskCache *cache = new QNetworkDiskCache();
    QString cachePath = g_scopePath + "/cache";
    //qDebug() << "Cache dir: " << cachePath;
    cache->setCacheDirectory(cachePath);

    request.setRawHeader( "User-Agent", g_userAgent.toStdString().c_str() );
    request.setRawHeader( "Content-Type", "application/rss+xml, text/xml" );
    request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );

    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), app, SLOT(quit()));
    QObject::connect(&manager, &QNetworkAccessManager::finished,
                     [this, &reply, &response](QNetworkReply *msg) {
        if (msg->error() != QNetworkReply::NoError) {
            qCritical() << "Failed to get data: " << msg->error();
            pushError( reply, NO_CONNECTION );
        } else {
            response = msg->readAll();
        }

        msg->deleteLater();
    });

    manager.setCache( cache );
    manager.get( request );
    app->exec();

    delete cache;
    return response;
}

这个请求得到的数据将被“getMailInfo”使用并解析。最终被展示出来。我们希望对用户所输入的数据有所提示,所以我们对.ini文件做了如下的修改:

[ScopeConfig]
DisplayName = 快递查询
Description = This is a Mailcheck scope
Art = screenshot.png
Author = Firstname Lastname
Icon = icon.png
SearchHint = 请输入单号

[Appearance]
PageHeader.Logo = logo.png

这里的“SearchHint”显示的是提示的信息。

另外一个值得注意的是:当我们点击Scope中的每一项时,我们发现没有相应的Preview页面。这是通过修改“renderer”目录下的文件实现的,尽管在Desktop上是可以看到preview页面的。

{
    "schema-version": 1,
    "template": {
        "category-layout": "vertical-journal",
        "card-layout": "horizontal",
        "card-size": "medium",
        "collapsed-rows": 0,
	"non-interactive":"true"
    },
    "components": {
        "art": "image",
        "title": "title",
        "subtitle": "subtitle",
        "summary": "summary"
    }
}

我可以打开“data/render”目录下的“journal.json”。我们看见上面的“non-interactive”项被置为“true”。这项表明它不希望有交互。

所有项目的源码在如下的地址可以找到:


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

或在地址:git clone https://gitcafe.com/ubuntu/mailcheck.git


作者:UbuntuTouch 发表于2014-12-23 16:07:06 原文链接
阅读:209 评论:0 查看评论

Read more
UbuntuTouch

[原]如何使用Ubuntu手机

在这个视频里,我们介绍了如何使用Ubuntu手机。Ubuntu手机对很多的使用者来说还不是很熟悉。特别是他没有任何的物理键(菜单键,home键,返回键)。那么用户该如何操控手机呢?Ubuntu手机以其出色的操控性,使我个人非常喜欢。尽管UI还在不断地演化中,它的流畅的操控及快速地切换,将会深深地打动许多最终的使用者。视频的地址在:


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

作者:UbuntuTouch 发表于2014-12-23 10:38:39 原文链接
阅读:459 评论:0 查看评论

Read more
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 原文链接
阅读:224 评论: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 原文链接
阅读:222 评论: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 原文链接
阅读:355 评论: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 原文链接
阅读:403 评论: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 原文链接
阅读:400 评论: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 原文链接
阅读:243 评论: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


6)QML在Ubuntu平台中的performance



作者:UbuntuTouch 发表于2014-11-27 19:13:50 原文链接
阅读:452 评论: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 原文链接
阅读:495 评论: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 原文链接
阅读:398 评论: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 原文链接
阅读:369 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章中,我们已经创建了很多点评的Scope。在这个篇文章中,我们将使用现有的SDK来重新开发点评Scope。我们将有意识地使得我们的数据和Scope的框架进行分离,从而展示一个更好的开发框架。更多关于Scope的介绍可以在网址http://developer.ubuntu.com/scopes/或在http://cn.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

或在地址:git clone https://gitcafe.com/ubuntu/dianping.git

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

  

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


10)使Scope做好发布准备


等我们已经开发好我们自己的Scope后,我们可以通过参阅文章“使Scope做好发布准备”来完成最后的发布工作了。真心希望能够在Ubuntu商店中看到越来越多的开发者开发出更多的Scope!
作者:UbuntuTouch 发表于2014-11-14 13:15:58 原文链接
阅读:335 评论:2 查看评论

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