Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

[原]Ubuntu OS系统融合(英文视频)

在这个视频里,我们可以看见Ubuntu系统在不断地演进。在未来手机,平板,电视及桌面将使用一个操作系统。Ubuntu手机操作系统正在为这一切做准备。


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

作者:UbuntuTouch 发表于2015/3/11 13:26:59 原文链接
阅读:473 评论:0 查看评论

Read more
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手机介绍”下载有关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开发流程。

阅读文章“在Ubuntu OS上创建一个dianping Scope (Qt JSON)”,并观看视频“如何在Ubuntu OS上开发Scope (视频)”。教程的另外一个视频在地址观看。

幻灯片“Scope技术开发”。幻灯片讲演的视频在地址观看。

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

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


5)HTML 5开发


我们可以参阅文章“在Ubuntu手机平台上创建一个HTML 5的应用”来学习如何在Ubuntu平台上开发HTML 5的应用。源码在地址下载:

git clone https://gitcafe.com/ubuntu/html-rssreader6.git

Ubuntu上的HTML5开发幻灯片:Ubuntu上的HTML5开发。幻灯片视频

大家可以利用在线Webapp生成器来生产我们喜欢的网页的click安装包。具体教程“如何使用在线Webapp生成器生成安装包

更多例程:
  • 百度翻译: bzr branch lp:~liu-xiao-guo/debiantrial/baidutranslator
  • 字典: bzr branch lp:~liu-xiao-guo/debiantrial/meanings

6)更多的培训材料


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


如果有任何问题,请在该文章处评论。我会尽力回答你们的问题。大家也可以到Ubuntu手机专有讨论区来讨论问题



在教学中的过程中如果需要联网,请使用如下的用户名及密码

CM: Huawei-E5375-E16E 密码:ji69ea97   

手机的解锁密码为:0000


作者:UbuntuTouch 发表于2015/1/4 15:36:54 原文链接
阅读:2447 评论:6 查看评论

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 原文链接
阅读:739 评论:0 查看评论

Read more
UbuntuTouch

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


  



作者:UbuntuTouch 发表于2015/1/13 15:08:31 原文链接
阅读:667 评论: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:


http://pan.baidu.com/s/1kTpDnX9


下载的文件包含:

  • kylin-live-20150330-0.iso (md5sum bfa2ed462f39d517a85d3bdafb9b07ff) 
  • casper-rw-20150330-0.tar.bz2  (md5sum 6a8d9f3e686a9a9fa7a7d6c02cef38c8 )
  • 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-2015xxxx.tar.bz2文件,并解压后的casper-rw 文件拷贝到USB的主目录下:





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






并在Terminal中执行如下的命令:


$ 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 原文链接
阅读:867 评论:0 查看评论

Read more
UbuntuTouch

[原]Windows7下安装Ubuntu双系统

Windows7和Ubuntu双系统的安装是非常容易的。在Ubuntu的安装的时候,已经提供了图形化的界面来帮助用户安装。你可以选择单单安装Ubuntu操作系统抹去原来的Windows操作系统,也可以选择与Windows操作系统共存,用Windows引导Ubuntu。但是这就造成了一个问题——一旦你想要删除Ubuntu操作系统,想要抹掉原先分给Ubuntu的磁盘空间,就会破坏Windows的MBR分区。因为在你按照原先系统安装盘的指令安装Ubuntu操作系统的时候,MBR分区的数据会被重写。

小编之前也有过这样的情况。后果就是在我删除整个Ubuntu分区后,windows也已经打不开了。在这个时候,其实你可以做一个工作来找回你“丢失”的Windows操作系统。修复MBR分区。比较常见的,比较“古朴”的方法就是进DOS使用fdisk /mbr命令。但是当时小编用的时候貌似还是不行。于是找到了以下的方法:进入windows系统,调出cmd,用bootrec /fixmbr命令,就能修复MBR分区了。

那么每次这样是不是很麻烦?能不能从源头上来解决呢?可以的。在安装Ubuntu操作系统的时候就可以解决这个问题。下面就跟着小编的这个步骤来安装操作系统,不破坏windows的正常使用。


1)下载Ubuntu desktop操作系统


可以在以下网址:http://www.ubuntu.com/download/desktop

下载到Ubuntu desktop的发行版。根据你自己的使用情况及CPU的情况来下载适合自己电脑的Ubuntu Desktop操作系统。




建议大家下载Ubuntu 14.10,因为在做Ubuntu手机、平板开发的时候是需要14.10的系统的。小编在这里选择64位的14.10操作系统



2)磁盘分区


在这一步中,你将要对你的磁盘进行分区。如果普通使用的话20G的磁盘空间就可以了,但是做开发的话是不够的。小编在这里给Ubuntu分了100G的空间。

在我的“计算机”上右键,选择“管理”,打开之后依次选择“存储——磁盘管理”,之后你就将看到你的硬盘的虚拟化的图像。其实还有其他的外接存储设备都会有显示。




在一个硬盘分区上右键选择“压缩卷”,跟着步骤走,输入你想要磁盘大小。


3)制作USB启动盘


找一个U盘,4G的吧,应该够了,插入电脑的USB接口。小编在这里使用的是UltraISO(http://cn.ultraiso.net/xiazai.html)这个软件。




“打开文件”,定位到你刚刚下载的Ubuntu的安装文件,“打开”




选择“启动”——“写入硬盘映像”




在这里一定要注意哦,这里“硬盘驱动器”这里是不是你的那个U盘。如果你插了其他的存储设备一定要看清,不然会造成不必要的损失。

然后点“写入”,静候直至出现“刻录成功”的字样。




至此,你的U盘安装盘就已经做好了。



4)安装Ubuntu操作系统


令人激动人心的时刻到了安装Ubuntu操作系统




开机的时候进入Boot Manager选择USB启动。各个电脑的进入Boot选项的方法都不同,但是无外乎F12,F10,F2等等的function键。




在这里你可以选择

  • 使用Ubuntu操作系统但并不安装

  • 安装Ubuntu

  • OEM安装

  • 检查磁盘

我们在这里选择“Install Ubuntu”(“安装Ubuntu”)


注:如果你看到以下画面,请:

  • 检查电脑USB口

  • 更换U盘重新制作USB启动盘

  • 插拔U盘查看是否是接触问题

最有可能的还是U盘的问题,所以请使用U盘芯片比较好的U盘,推荐用Sandisk





接着就是选择语言,wifi,等等的安装前的设置。

但是我们要注意一下,在安装类型的这里仔细看一下。








在这里需要注意下


安装类型选择其他选项




选择你刚刚在Windows下分出来的那个盘,选择“Ext4 日志文件系统” 挂载点“/”




这步做完,你的那个盘的格式会变为Ext4




然后在“安装启动引导器的设备”一栏里选择你的那个盘,小编这里是“sda6”(每个人的电脑可能显示的是不同的)




之后选择“格式化”——“现在安装”

在选择时区、设置用户名密码之后就会进入到安装过程




这一步之后就已经把Ubuntu安装到那个分区了。

电脑就会自动重启


5)设置引导


在如上步骤安装完之后,重启进入windows引导没有Ubuntu的。




在这一步就是要设置Ubuntu的引导。

进入windows之后,我们所使用的软件是EasyBCD(http://easybcd.softonic.cn/)





选择“Add New Entry”选择“Linux/BSD”——选择“GRUB2”并命名为Ubuntu,点击“Add Entry”





6)重新启动计算机,安装完成


重新启动计算机后,你就会看到Windows的引导里有你刚刚建立的Ubuntu的选项。选择Ubuntu就会进入Grub。然后就会进入Ubuntu操作系统。






这样的安装方式,就不怕以后删除Ubuntu之后不能启动windows了,但是小编还是希望大家不要删除Ubuntu的啦~


Ubuntu小编原创,欢迎转载!



作者:UbuntuTouch 发表于2015/4/8 8:53:44 原文链接
阅读:545 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将介绍如何使用Ubuntu系统提供的online account API来实现我们的微博。通过这篇文章,开发者可以使用同样的方法来实现对其它Web Service的访问,比如淘宝,优酷等网站的访问。我们实现的新浪微博的Scope的截图如下。本篇文章的重点是介绍online account API的使用,所以我不会像以前的那些教程一样从最基本的Scope开发介绍。更多关于online account的介绍在网址找到。


   


1)创建一个最基本的weibo Scope

我们通过使用Ubuntu SDK来创建一个最基本的Scope。在这里我们使用如下的Scope template:


     


    

按照如上的步骤,我们可以创建一个我们自己的一个最基本的weibo Scope,虽然里面显示的还是天气。


2)为online account添加必要的文件


我们可以参考文章,也可以参照视频,来对online account API有一个更深的了解。为了方便我们的设计,我们在项目的根目录下创建如下的"click"目录:



我们同时也把在根目录下的“manifest.json.in”及“weibo.apparmor”移到“click”目录下,当然,我们也需要对根目录下的“CMakeLists.txt”做必要的修改。修改后的CMakeLists.txt文件如下:

project(weibo CXX)
cmake_minimum_required(VERSION 2.8.10)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")

# We require g++ 4.9, to avoid ABI breakage with earlier version.
set(cxx_version_required 4.9)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    if (NOT CMAKE_CXX_COMPILER_VERSION MATCHES "^${cxx_version_required}")
        message(FATAL_ERROR "g++ version must be ${cxx_version_required}!")
    endif()
endif()

# Set strict and naggy C++ compiler flags, and enable C++11
add_definitions(
  -fno-permissive
  -std=c++11
  -pedantic
  -Wall
  -Wextra
  -fPIC
  -DQT_NO_KEYWORDS
)

include(GNUInstallDirs)
find_package(PkgConfig)
find_package(Intltool)

# We depend on Boost for string trimming
find_package(
  Boost
  REQUIRED
)

# Search for our dependencies
pkg_check_modules(
  SCOPE
  libunity-scopes>=0.6.0
  net-cpp>=1.1.0
  REQUIRED
)

find_package(Qt5Core REQUIRED)
include_directories(${Qt5Core_INCLUDE_DIRS})

# Add our dependencies to the include paths
include_directories(
  "${CMAKE_SOURCE_DIR}/include"
  ${Boost_INCLUDE_DIRS}
  ${SCOPE_INCLUDE_DIRS}
)

# Do not remove this line, its required for the correct functionality of the Ubuntu-SDK
set(UBUNTU_MANIFEST_PATH "click/manifest.json.in" CACHE INTERNAL "Tells QtCreator location and name of the manifest file")
set(UBUNTU_PROJECT_TYPE "Scope" CACHE INTERNAL "Tells QtCreator this is a Scope project")

# Important project paths
set(CMAKE_INSTALL_PREFIX /)
set(SCOPE_INSTALL_DIR "/weibo")
set(GETTEXT_PACKAGE "weibo")
set(PACKAGE_NAME "weibo.ubuntu")
set(SCOPE_NAME "${PACKAGE_NAME}_weibo")

set(SCOPE_INSTALL_NAME "${PACKAGE_NAME}_weibo")
set(SCOPE_ACCOUNTS_NAME "${PACKAGE_NAME}_accounts")
set(SYMBOL_MAP "${CMAKE_SOURCE_DIR}/data/${PACKAGE_NAME}.map")

add_definitions(-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}")
add_definitions(-DSCOPE_NAME="${SCOPE_NAME}")
add_definitions(-DSCOPE_INSTALL_NAME="${SCOPE_INSTALL_NAME}")
add_definitions(-DSCOPE_ACCOUNTS_NAME="${SCOPE_ACCOUNTS_NAME}")
add_definitions(-DPACKAGE_NAME="${PACKAGE_NAME}")

# If we need to refer to the scope's name or package in code, these definitions will help

#This command figures out the target architecture and puts it into the manifest file
execute_process(
  COMMAND dpkg-architecture -qDEB_HOST_ARCH
  OUTPUT_VARIABLE CLICK_ARCH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

#configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)

# Install the click manifest
#install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION "/")
#install(FILES "weibo.apparmor" DESTINATION "/")

# Make these files show up in QtCreator
file(GLOB_RECURSE
  _PO_FILES
  "po/*.po" 
)
add_custom_target(hidden_files
  ALL
  SOURCES
#    manifest.json.in
#    weibo.apparmor
    data/${SCOPE_NAME}.ini.in
    po/POTFILES.in
    po/${GETTEXT_PACKAGE}.pot
    ${_PO_FILES}
)

add_subdirectory(click)

# Add our main directories
add_subdirectory(src)
add_subdirectory(data)
add_subdirectory(po)

# Set up the tests
enable_testing()
add_subdirectory(tests)
add_custom_target(
  check
  ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --output-on-failure
)

这里主要的修改是影藏显示“manifest.json.in”及“weibo.apparmor”文件的显示,应为这两个文件已经移到“click”目录中了。同时也注释掉如下的语句,应为这项工作在“click”目录中的“CMakeLists.txt”中已经做了。

#configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)

# Install the click manifest
#install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION "/")
#install(FILES "weibo.apparmor" DESTINATION "/")

另外,我们为了能够正确地显示项目,我们也对如下的句子做了修改:

set(UBUNTU_MANIFEST_PATH "click/manifest.json.in" CACHE INTERNAL "Tells QtCreator location and name of the manifest file")

这里我们加上了“click”路径。同时我们也加入了如下的定义:

set(SCOPE_INSTALL_NAME "${PACKAGE_NAME}_weibo")
set(SCOPE_ACCOUNTS_NAME "${PACKAGE_NAME}_accounts")
set(SYMBOL_MAP "${CMAKE_SOURCE_DIR}/data/${PACKAGE_NAME}.map")

add_definitions(-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}")
add_definitions(-DSCOPE_NAME="${SCOPE_NAME}")
add_definitions(-DSCOPE_INSTALL_NAME="${SCOPE_INSTALL_NAME}")
add_definitions(-DSCOPE_ACCOUNTS_NAME="${SCOPE_ACCOUNTS_NAME}")
add_definitions(-DPACKAGE_NAME="${PACKAGE_NAME}")

这样这些变量及定义可以在项目的其它文件中被正确地引用了。

下面,我们来看看“click”目录下的文件:

apparmor.json


{
    "template": "ubuntu-scope-network",
    "policy_groups": [
        "accounts"
    ],
    "policy_version": 1.2
}


注意这里在“policy_groups”里我们多加入了一个称作为“accounts”的policy。这个是必须的,否则不可以正常工作。


manifest.json.in

这个文件的显示如下:

{
    "description": "This is a tutorial for weibo scope",
    "framework": "ubuntu-sdk-14.10",
    "architecture": "@CLICK_ARCH@",
    "hooks": {
        "weibo": {
            "scope": "weibo",
            "apparmor": "apparmor.json",
            "account-application": "weibo.application",
            "account-service": "weibo.service"
        },
        "accounts": {
            "account-provider": "weibo.provider",
            "account-qml-plugin": "qml-plugin"
        }
    },
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "name": "@PACKAGE_NAME@",
    "title": "weibo scope",
    "version": "0.1"
}

这个文件和我们template所产生的文件略有不同。这里的“hooks”中加入了account的一些东西。在每个用户登陆时都会生产相应的配置文件。

weibo.application.in


<?xml version="1.0" encoding="UTF-8"?>
<application>
  <description>Weibo scope</description>
  <desktop-entry>@SCOPE_INSTALL_NAME@.desktop</desktop-entry>
  <services>
    <service id="@SCOPE_INSTALL_NAME@">
      <description>Watch your favorite Weibo messages</description>
    </service>
  </services>
</application>


这个是微博Scope的描述文件。它定义了Scope的service id及描述

weibo.service.in


<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>sharing</type>
  <name>Weibo scope</name>
  <icon>weibo/icon.png</icon>
  <provider>@SCOPE_ACCOUNTS_NAME@</provider>
  <translations>unity-scope-weibo</translations>
</service>

这个service文件描述了微博Scope的service信息。

weibo.provider.in

这是一个微博的account provider信息。这是一个重要的文件。如果我们不能正确地设置这个文件,可能我们不能正确地访问微博account。

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Weibo</name>
  <icon>weibo/icon.png</icon>
  <translations>unity-scope-weibo</translations>
  <plugin>generic-oauth</plugin>
  <domains>.*weibo\.com</domains>
  <single-account>true</single-account>

  <template>
    <group name="auth">
      <setting name="method">oauth2</setting>
      <setting name="mechanism">web_server</setting>
      <group name="oauth2">
        <group name="web_server">
          <setting name="Host">api.weibo.com</setting>
          <setting name="AuthPath">oauth2/authorize</setting>
          <setting name="TokenPath">oauth2/access_token</setting>
          <setting name="RedirectUri">https://api.weibo.com/oauth2/default.html</setting>
          <setting name="ResponseType">code</setting>
          <setting name="ClientId">your developer key</setting>
          <setting type="as" name="AllowedSchemes">['https','http']</setting>
          <setting name="ClientSecret">your developer secret</setting>
          <setting name="ForceClientAuthViaRequestBody" type="b">true</setting>  
      </group>
      </group>
    </group>
  </template>
</provider>

我们需要在“微博开放平台”去申请开发者key及secret。同时,我们一定要记得在微博的设置中做如下的设置,否则我们不能得到正确的数据。



“click”目录下的CMakeLists.txt文件内容如下:

function(configure_scope_files)
  foreach(_file ${ARGV})
    configure_file(
      "${_file}.in"
      "${CMAKE_CURRENT_BINARY_DIR}/${_file}"
      @ONLY
    )
  endforeach()
endfunction()

configure_scope_files(
  manifest.json
  weibo.provider
  weibo.service
  weibo.application
)

add_custom_target(hidden_files1
  ALL
  SOURCES
    apparmor.json
)

install(
  FILES
    "${CMAKE_CURRENT_BINARY_DIR}/manifest.json"
    "apparmor.json"
    "${CMAKE_CURRENT_BINARY_DIR}/weibo.provider"
    "${CMAKE_CURRENT_BINARY_DIR}/weibo.service"
    "${CMAKE_CURRENT_BINARY_DIR}/weibo.application"
  DESTINATION ${CMAKE_INSTALL_PREFIX}
)

install(
  DIRECTORY
    "qml-plugin"
  DESTINATION ${CMAKE_INSTALL_PREFIX}
)

这里它对该目录下的文件进行了安装。

为了能够对Scope的设置文件“weibo.ubuntu_weibo.ini.in”进行本地化的支持,我们对“data\CMakeListx.txt”加入如下的句子:

intltool_merge_translations(
  "${CMAKE_CURRENT_SOURCE_DIR}/${SCOPE_NAME}.ini.in"
  "${CMAKE_CURRENT_BINARY_DIR}/${SCOPE_INSTALL_NAME}.ini"
  ALL
  UTF8
)

这样它可以提取里面相应的英文,并在“po”目录中的.pot文件中进行翻译。

经过我们这样的修改后,我们的最基本的具有online account的设置文件已经基本已经做好了。我们可以在我们的手机或模拟器中运行我们的Scope了。在运行时,



我们选择“yes”继续运行。我们可以看到如下的画面:




者和我们所创建的最基本的Scope没有任何的差别,我们可以在手机的如下地址查看该目录的内容:



这个是我们所创建weibo provider的地方。


3)对代码进行修改

我们在上面已经对online account的配置文件做了修改。下面我们来对我们的cpp代码进行修改,为了能够使得我们使用online account,我们在query.cpp文件中加入如下的代码:

const static string SEARCH_CATEGORY_LOGIN_NAG = R"(
{
        "schema-version": 1,
        "template": {
        "category-layout": "grid",
        "card-size": "large",
        "card-background": "color:///#1ab7ea"
        },
        "components": {
        "title": "title",
        "background": "background",
        "art" : {
        "aspect-ratio": 100.0
        }
        }
        }
        )";


void Query::run(sc::SearchReplyProxy const& reply) {
    add_login_nag(reply);
 ...
}

void Query::add_login_nag(const sc::SearchReplyProxy &reply) {
    //    if (getenv("VIMEO_SCOPE_IGNORE_ACCOUNTS")) {
    //        return;
    //    }
    qDebug() << "SCOPE_INSTALL_NAME: " << SCOPE_INSTALL_NAME;
    qDebug() << "SCOPE_ACCOUNTS_NAME: " << SCOPE_ACCOUNTS_NAME;

    sc::OnlineAccountClient oa_client(SCOPE_INSTALL_NAME, "sharing", SCOPE_ACCOUNTS_NAME);

    // Check if our service is authenticated
    bool service_authenticated = false;

    int count = oa_client.get_service_statuses().size();
    qDebug() << "count: " << count;

    for ( sc::OnlineAccountClient::ServiceStatus const& status :
          oa_client.get_service_statuses())
    {
        if (status.service_authenticated)
        {
            service_authenticated = true;
            qDebug() << "Sevice is authenticated!";
            qDebug() << "account id: "  << status.account_id;
            qDebug() << "client id: " << QString::fromStdString(status.client_id);
            qDebug() << "service enabled: " << status.service_enabled;
            qDebug() << "secret: " << QString::fromStdString(status.client_secret);
            qDebug() << "access token: " << QString::fromStdString(status.access_token);
            accessToken_ = QString::fromStdString(status.access_token);

            // pass the access token to the client so that http request can be made
            client_.setAccessToken(accessToken_);
            break;
        }
    }

    if (!service_authenticated)
    {
        qDebug() << "Service is not authenicated!";

        sc::CategoryRenderer rdr(SEARCH_CATEGORY_LOGIN_NAG);
        auto cat = reply->register_category("weibo_login_nag", "", "", rdr);

        sc::CategorisedResult res(cat);
        res.set_title(_("Log-in to Weibo"));

        oa_client.register_account_login_item(res,
                                              query(),
                                              sc::OnlineAccountClient::InvalidateResults,
                                              sc::OnlineAccountClient::DoNothing);

        reply->push(res);
    }
}

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


我们点击“Log-in to Weibo”按钮:



我们使用我们自己的微博的账号进行登陆,在代码中,我们可以看到:



我们打开手机中的“系统设置”,再打开“账号”,当我们完成我们自己的账号登陆微博后,可以看到:



这说明我们的online account设置等已经是成功的。我们更进一步对我们的client代码进行修改,我们可以看到如下的画面:




整个项目的source code可以在如下的地址下载:

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

在使用代码时,一定要在“click/weibo.provider.in”中填入自己的开发者key及secret。
作者:UbuntuTouch 发表于2015/2/3 11:51:37 原文链接
阅读:935 评论:7 查看评论

Read more
UbuntuTouch

[原]Ubuntu OS上的QML应用框架

在我们编写QML应用的时候,我们有时事先需要来考虑我们怎么使用一个好的框架来完成我们的应用。我们的应用有多少个页面,页面之间的导航到底是怎么样子的。这个对于我们一开始来设计我们的应用来说非常中要。在这篇文章中,我们来介绍如何在上层来设计我们的应用框架。


1)使用tab来创建一个平面的导航应用

我们可以使用我们的Ubuntu SDK来创建一个最基本的叫做TabApp的应用:

   

 

这样我们就生成了我们的一个最基本的应用。我们把应用的宽度和高度设为如下的值:

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

同时,我们也修改我们的Main.qml如下:

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     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)


    Tabs {
        id: tabs

        Tab1 {
            objectName: "Tab1"
        }

        Tab2 {
            objectName: "Tab2"
        }
    }
}

在这里我们定义了两个Tab页面,分别为Tab1及Tab2。它们的内容分别为:

import QtQuick 2.0
import Ubuntu.Components 1.1

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

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

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page one")
        }

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

import QtQuick 2.0
import Ubuntu.Components 1.1

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

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page two")
        }
    }
}

这是一个最简单的Tab导航应用。我们在手机上运行:

    


所有的源码可以在地址下载:

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


我们也可以把我们的Main.qml修改如下:

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     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)

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

    Tabs {
        id: tabs
        Tab {
            title: i18n.tr("Simple page")
            page: Page {
                Label {
                    id: label
                    anchors.centerIn: parent
                    text: "A centered label"
                }
                tools: ToolbarItems {
                    ToolbarButton {
                        action: reloadAction
                    }
                }
            }
        }

        Tab {
            id: externalTab
            title: i18n.tr("External")

            page: Loader {
                id: loader
                anchors.fill: parent
                source: (tabs.selectedTab === externalTab) ? Qt.resolvedUrl("ExternalPage.qml") : ""

                onLoaded: {
                    console.log( loader.source + " is loaded")
                }
            }
        }

        Tab {
            title: i18n.tr("List view")
            page: Page {
                ListView {
                    clip: true
                    anchors.fill: parent
                    model: 20
                    delegate: ListItem.Standard {
                        iconName: "compose"
                        text: "Item "+modelData
                    }
                }
            }
        }
    }
}

运行我们的应用:


   

所有的源码在:

https://gitcafe.com/ubuntu/TabApp4.git

我们如果想在Tab架构中使用pagestack的话,我们对我们的应用必须做一些修改。我们只能把Tabs作为第一个页面推到PageStack的栈中。Main.qml具体实现如下:

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     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)

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

    PageStack {
        id: pageStack
        Component.onCompleted: push(tabs)

        Tabs {
            id: tabs
            Tab {
                title: "Tab 1"
                page: Page {
                    Button {
                        anchors.centerIn: parent
                        onClicked: pageStack.push(page3)
                        text: "Press"
                    }
                }
            }
            Tab {
                title: "Tab 2"
                page: Page {
                    Label {
                        anchors.centerIn: parent
                        text: "Use header to navigate between tabs"
                    }
                }
            }
        }
        Page {
            id: page3
            visible: false
            title: "Page on stack"
            Label {
                anchors.centerIn: parent
                text: "Press back to return to the tabs"
            }
        }
    }
}


运行我们的应用,我们可以看到:

   

我们可以看见在上面显示的那样,有一个叫做“Page on stack”。可以通过按下换回箭头回到上一个页面。

具体的代码:

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

2)使用PageStack来导航


在这一节中,我们将介绍如何使用PageStack来管理我们的页面。当用户进入下一个页面完成自己的工作后,可以通过按下标题栏中的返回箭头回到上一个页面。按照上面同样的步骤,我们可以创建一个叫做PageStack的项目。Main.qml的设计如下:

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0 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: "pagestack.ubuntu"

    /*
     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)

    PageStack {
        id: pageStack
        Component.onCompleted: {
            push(page0)
        }

        Page {
            id: page0
            title: i18n.tr("Root page")
            visible: false

            Column {
                anchors.fill: parent

                ListItem.Standard {
                    text: i18n.tr("Page one")
                    onClicked: pageStack.push(page1, {color: UbuntuColors.orange})
                    progression: true
                }
                ListItem.Standard {
                    text: i18n.tr("Page two")
                    onClicked: pageStack.push(Qt.resolvedUrl("Page2.qml"))
                    progression: true
                }
            }
        }

        Page {
            title: "Rectangle"
            id: page1
            visible: false
            property alias color: rectangle.color
            Rectangle {
                id: rectangle
                anchors {
                    fill: parent
                    margins: units.gu(5)
                }
            }
        }
    }
}


这里我们在应用启动时创建一个PageStack,并同时把“page0”压入栈中。使它成为第一个页面。在“page0”中,我们有有两个列表项,分别可以进入到下一个页面中。

运行我们的应用:

   

我们可以在每个页面看见有个返回的箭头。

整个项目的源码在:

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




作者:UbuntuTouch 发表于2015/3/25 10:20:09 原文链接
阅读:432 评论:0 查看评论

Read more
UbuntuTouch

在这个视频里,介绍如何在开发Ubuntu平台上开发Webapps及HTML 5的应用。


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

作者:UbuntuTouch 发表于2015/2/10 19:26:33 原文链接
阅读:623 评论:0 查看评论

Read more
UbuntuTouch

无论你是互联网世界的一个高手或是一个从来没有接触过互联网的新手,这篇文章将给你带来完整的在Ubuntu平台上开发HTML 5的应用。我们将慢慢地通过这个练习让你很自然地进入并熟悉整个的HTML 5应用的开发流程。在这个练习中,我们将设计一个最简单的RSS阅读器。当我们的应用设计完整后,应用的显示如下:


       

如果你是一个固执的HTM 5黑客,你可以选择任何你所喜欢的工具及工具包来开发你的HTML 5应用。它们将会很好地工作于Ubuntu手机上。我们将只专注于Ubuntu SDK提供的工具及工具包。更多关于HTML 5开发的信息可以在Ubuntu的官方网站中文网站得到。


如果你还没有安装好自己的Ubuntu SDK,请参照文章“Ubuntu SDK 安装”来安装好自己的SDK。


1)创建一个新的项目


在这一步,我们将使用Ubuntu SDK来生产一个项目:




     

      


请按照上面的步骤来创建一个新的项目“html5-rssreader”。我们首先在Desktop上运行该应用(使用热键Ctrl +R):




可以看出是以非常简单的HTML 5应用。我们可以点击应用的按钮在浏览器中打开上面显示的链接。


2)如何调试我们的应用


我们知道调试应用是非常重要的。我们怎么调试我们的HTML 5应用呢?我们开发的步骤是一般现在自己的电脑上开发好我们直接的应用,然后再打包并部署到自己的手机上或模拟器中。当我们在SDK中运行我们自己的HTML 5应用时,我们可以看到:




在Application Output输出窗口,我们可以看到一个地址:

http://192.168.1.106:9221


这个就是我们用来调试的地址。我们把这个地址拷到我们的Chrome或Chromium地址栏中,并进入:



在我们调试时,我们只能运行一个应用的实例,否则,我们将看不到任何的输出。


3)删除默认项目中不需要的代码



在这一节中,我们将删除项目中不需要的部分代码。

在项目的www/目录中,你可以看到如下的内容:
  • HTML 文件
  • images (在www/img目录下)
  • javascript文件 (在www/js目录下)
  • CSS文件 (在www/css目录下)
首先我们来对index.html进行修改:

1)首先,我们来查看如下的代码:

<div data-role="content">

2)就像你看到的那样,有一个<div>在那里。它定义了一个叫做hello-page的tab。<div>包含了最直接的HTML组成部分。我们删除所有包含在

 <div data-role="content">

里面的内容。这样整个body的内容为:

<body>

    <div data-role="mainview">

        <header data-role="header">
            <ul data-role="tabs">
                <li data-role="tabitem" data-page="hello-page">Hello World</li>
            </ul>
        </header>

        <div data-role="content">
        </div>
    </div>

    <script src="js/app.js"></script>
</body>

重新运行我们的应用,我们可以看到:



我们可以看到应用中没有显示任何的东西。这也并不奇怪,因为我们本来就没有放任何的东西在里面。我们接下来也需要修改应用的标题。


4)删除不必要的代码


在上面的一节中,我们主要对index.html进行了修改。在这个章节里,我们将对css及javascript文件进行修改。

1)我们把css/app.css中的所有内容删除掉。这里的内容是针对helloworld模版的。我们不需要它们

2)在js/app.js文件中,我们删除如下的代码:

 function addClass(elem, className) {
       elem.className += ' ' + className;
   };

   function removeClass(elem, className) {
       elem.className = elem.className.replace(className, '');
   };

同时,我们删除从如下的行:

   // Detect if Cordova script is uncommented or not and show the appropriate status.

到最后的一行的前面:

};

等我们完成所有的操作之后,最后js/app.js文件的内容如下:

/**
* Wait before the DOM has been loaded before initializing the
 Ubuntu UI layer
*/
window.onload = function () {
   var UI = new UbuntuUI();
   UI.init();
};

UbuntuUI是Ubuntu HTML 5应用的一个关键的架构类。我们需要构造它并初始化它以生产我们需要的Ubuntu HTML 5应用。我们可以通过这个对象来方位Ubuntu HTML 5的对象(对应于Ubuntu DOM元)及方法。到目前位置,这是我们最基本的一个最小的一个HTML 5应用虽然没有任何的东西。运行我们的应用,我们可以看到:




所有的源码在地址找到 git clone https://gitcafe.com/ubuntu/html-rssreader1.git


5)设计我们自己的应用


在这个章节中,我们将专注于设计我们自己的应用,所以我们我们将删除所有和helloworld相关的东西。


1)把如下的行

   <title>An Ubuntu HTML5 application</title>
   <meta name="description" content="An Ubuntu HTML5 application">

改为:

   <title>RSS Mobile Reader</title>
   <meta name="description" content="RSS Mobile Reader">

2)删除如下的代码:

   <ul data-role="tabs">
               <li data-role="tabitem" data-page="hello-page">Hello World</li>
   </ul>

取而代之的是,我们将使用pagestack。关于应用的两种布局,我们可以参考网址以了解更多的信息。

我们在如下行

 <div data-role="content">

的下面,加入如下的代码:

    <div data-role="pagestack">
         <div data-role="page" id="main" data-title="RSS Mobile Reader">
         </div>
   </div>


3)在js/app.js文件中,我们加入如下的一行代码在UbuntuUI初始化之后:

 UI.pagestack.push("main");

这里,"pagestack"以一种stack的形式管理所有的pages。最初始,没有任何的page,push()方法通常是在应用启动时,把第一个page推进stack并显示出来。

如果我们现在再运行我们的应用,我们可以看到如下的内容。我们可以看到我们的应用的page标题已经发生变化。我们同时也可以注意到在页面的最下面,我们可以看到“back”字样。这是和上面的显示是不同的地方。


6)把我们的内容放到应用中去


在这个一节中,我们将把我们想要显示的内容放到我们的应用中。我们回顾一下我们的在该文章中一开始显示设计,在第一页中,实际上是一个列表的显示。它显示了我们需要的展示的RSS feed的列表。在接下来的第二页中,我们显示的是所选中的RSS feed所包含的所有每个entry的列表。最后一个页面显示的是每个entry的具体的内容。

现在我们开始在index.html中加入我们先前已经定义好的“main”的页面:

1)在index.html中加入如下的行到javascript的插入列表中:

<script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/list.js"></script>

这个对于我们使用列表来说是必须的。在“main”页面中,我们也加入称作为“yourfeeds”的list

 <section data-role="list" id="yourfeeds"></section>

2)在完成好我们上面的代码后,我们接下来主要的代码将在js/app.js文件中。我们可以是使用外部的库来帮我们完成我们的工作。在js/app.js文件中,我们在推入“main”页面过后,即如下的行:

 UI.pagestack.push("main");

加入如下的代码:

 if (typeof localStorage["feeds"] == "undefined") {
       restoreDefault();
   }
   //load local storage feeds
   var feeds = eval(localStorage["feeds"]);
   if (feeds !== null) {
      var feeds_list = UI.list('#yourfeeds');
      feeds_list.removeAllItems();
      feeds_list.setHeader('My feeds');

       for (var i = 0; i < feeds.length; i++) {
           feeds_list.append(feeds[i],
                            null,
                            null,
                            function (target, thisfeed) { loadFeed(thisfeed); },
                            feeds[i]);
       }
   }

我们使用localStorage来存储我们所需要的feeds。如果最早没有被定义,就是“underdefined”,我们将调用restoreDefault()(在下面的部分来实现)来初始化我们所需要的feeds。如果在localStorage中已经有我们所需要的feeds,我们先清除“yourfeeds”列表中的内容,并把list的header设为“My feeds”。每当列表中的某一项被点击,我们就调用loadFeed()(在下面的代码中来实现)来下载这个feed。


3)在js/app.js文件的最后部分加入如下的代码:

function restoreDefault() {
   localStorage.clear();
   var feeds = [];
   feeds.push("http://daker.me/feed.xml");
   feeds.push("http://www.omgubuntu.co.uk/feed");
   feeds.push("http://hespress.com/feed/index.rss");
   feeds.push("http://rss.slashdot.org/Slashdot/slashdot");
   feeds.push("http://www.reddit.com/.rss");
   feeds.push("http://www.guokr.com/rss/");

   try {
       localStorage.setItem("feeds", JSON.stringify(feeds));
       window.location.reload();
   } catch (e) {
       if (e == QUOTA_EXCEEDED_ERR) {
           console.log("Error: Local Storage limit exceeds.");
       } else {
           console.log("Error: Saving to local storage.");
       }
   }
}

在这里,我们定义了restoreDefault()函数。它首先清除localStorage的数据,并加入一个JSON格式的RSS feed到localStorage中。代码中也同时加入了一些exception处理。

我再次运行我们的应用,当我们点击我们的列表中的每一项时,没有任何的反应。这是因为我们根本就没有实现loadFeed函数。




到目前为止的素有代码在如下的地址可以下载:

git clone https://gitcafe.com/ubuntu/html-rssreader2.git


7)从Internet下载内容


到目前为止,我们的代码并不复杂。互联网上有很多东西是值得我们借鉴和利用的。事实上,你可以从互联网上借鉴使用任何你所想要的库来完成你所需要的工作。在今天的练习中,我们借鉴jquery来完成我们的一部分工作。

1)打开一个Teminal, 并进入到js/app.js所在的目录中,在它里面打入如下的命令:

cp /usr/share/javascript/jquery/jquery.min.js .

这里把Desktop中的jquery考入到我们的应用中,并成为应用的一部分。


我们需要一个RSS的parser。虽然google也有一个自己的API,但是由于一些原因这个API在中国并不能被使用。我们在Terminal中打入如下的命令:

git clone https://github.com/jfhovinne/jFeed.git

等我们下载完所有的源码后,我们进入目录:jFeed/build/dist,并把文件“jquery.jfeed.pack.js”拷入到我们的js/app.js目录中。


3)在index.html文件中,在<head>加入如下的语句:

    <!-- External javascript imports -->
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/jquery.jfeed.pack.js"></script>

在“main”页面下,加入另外一个叫做“results”的页面:

        <div data-role="content">
            <div data-role="pagestack">
                 <div data-role="page" id="main" data-title="RSS Mobile Reader">
                     <section data-role="list" id="yourfeeds"></section>
                 </div>
                 <div data-role="page" id="results" data-title="Articles >">
                    <section data-role="list" id="resultscontent"></section>
                 </div>
            </div>
            <div data-role="dialog" id="loading"><section><progress></progress></section></div>
        </div>

这里,我们也加入了一个等待的“loading”对话框。在下载数据时,显示“loading”字样。

4)现在,我们更进一步在js/app.js文件中做一些修改,我们首先把UbuntuUI变成一个全球的变量,把如下的代码:

window.onload = function () {
var UI = new UbuntuUI();

改为:

var UI = new UbuntuUI();

$(document).ready(function () {

我们同时也需要在函数的最后把};修改为});。这样整个代码为:

$(document).ready(function () {
    UI.init();
    UI.pagestack.push("main");

    if (typeof localStorage["feeds"] == "undefined") {
        restoreDefault();
    }

    //load local storage feeds
    var feeds = eval(localStorage["feeds"]);
    if (feeds !== null) {
        var feeds_list = UI.list('#yourfeeds');
        feeds_list.removeAllItems();
        feeds_list.setHeader('My feeds');

        for (var i = 0; i < feeds.length; i++) {
            feeds_list.append(feeds[i],
                              null,
                              null,
                              function (target, thisfeed) { loadFeed(thisfeed); },
                              feeds[i]);
        }
    }
});


5)现在我们来完成loadFeed方法的实现。在js/app.js的末尾,我们加入如下的代码:

function loadFeed(url) {
   UI.pagestack.push("results");
   UI.dialog("loading").show()

    console.log("url is: " + url );

    $.getFeed( {
                  url: url,
                  success: function(feed) {
                      UI.dialog("loading").hide();

                      var results_list = UI.list('#resultscontent');
                      results_list.removeAllItems();
                      results_list.setHeader(feed.title);

                      console.log("title: " + feed.title);

                      // walk through the feeds
                      for( var i = 0; i < feed.items.length; i++ ) {
                          var item = feed.items[ i ];

//                          console.log("title: " + item.title);
//                          console.log("link: " +  item.link);
//                          console.log("content: " + item.description);

                            results_list.append(
                                item.title.replace(/"/g, "'"),
                                null,
                                null,
                                function (target, result_infos) {
                                  showArticle.apply(null, result_infos); },
                                [ escape(item.title),
                                  escape(item.link),
                                  escape(item.description) ] );
                      }
                    }

    });
}

重新运行我们的应用,我们可以点击我们的每个rss feed,并看到每个rss feed的标题列表:

  

所有的源码在如下的地址找到:

git clone https://gitcafe.com/ubuntu/html-rssreader3.git


8)显示实际的文章


如果细心的开发者仔细看了我们上一节的代码后,你可能会发现,我们已经提到了“showArticle”,但是我们还是没有实现它。当我们点击RSS feed的标题后,没有任何的东西显示。它的实现如下:

1)在index.html中,加入“article”页面:

        <div data-role="content">
            <div data-role="pagestack">
                 <div data-role="page" id="main" data-title="RSS Mobile Reader">
                     <section data-role="list" id="yourfeeds"></section>
                 </div>
                 <div data-role="page" id="results" data-title="Articles >">
                    <section data-role="list" id="resultscontent"></section>
                 </div>
                 <div data-role="page" id="article" data-title="Article >">
                     <section id="articleinfo"></section>
                 </div>
            </div>
            <div data-role="dialog" id="loading"><section><progress></progress></section></div>
        </div>

2)在js/app.js文件的最后面,加入如下的方法:

function showArticle(title, url, desc) {
   UI.pagestack.push("article");

   if (typeof desc == "undefined")
       desc = "(No description provided)";
   $("#articleinfo").html("<p>" + unescape(title) + "</p><p>" + unescape(desc) + "</p><p><a target=\"_blank\" href=\"" + unescape(url) + "\">" + unescape(url) + "</a></p>");

}


这样我们就可以得到文章的标题及其内容。为了展示文章的内容,我们显示文章的title,内容及一个url。为了展示文章的内容及title,我们使用“unescaped”。

重新运行我们的应用,我们可以看到如下的画面:



整个项目的源码在:

git clone https://gitcafe.com/ubuntu/html-rssreader4.git


9)添加RSS feed


现在我们的RSS feed都是固定,预先设定好的。如果一个用户需要添加一个新的RSS feed该怎么办呢?

1)在index.html文件中,在javascript的import区域,加入如下的代码:

   <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/toolbars.js"></script>

2)在main页面中加入如下的footer:

                 <div data-role="page" id="main" data-title="RSS Mobile Reader">
                     <section data-role="list" id="yourfeeds"></section>
                     <footer id="footer1" data-role="footer" class="revealed">
                         <nav>
                             <ul>
                                 <li>
                                     <a href="#" id="addfeed">
                                         <img src="/usr/share/ubuntu-html5-ui-toolkit/0.1//ambiance/img/actions/add.svg" alt="Add feed" />
                                         <span>Add feed</span>
                                     </a>
                                 </li>
                             </ul>
                         </nav>
                     </footer>                     
                 </div>

我们在footer中加入了一个toolbar以添加一个RSS feed。

3)我们添加一个对话框来输入所需要的RSS feed的详细信息:

在“loading”对话框的前面或后面,添加对话框:

            <div data-role="dialog" id="loading"><section><progress></progress></section></div>
            <div data-role="dialog" id="addfeeddialog">
             <section>
                 <h1>Add a new feed</h1>
                 <p>Type the url feed you want to add</p>
                 <input type="url" id="rssFeed" placeholder="http://">
                 <menu>
                     <button data-role="button" id="no">Cancel</button>
                     <button data-role="button" class="success" id="yes">Add</button>
                 </menu>
             </section>

这里我们定义了一个叫做“addfeeddialog”的对话框。它让用户来输入一个RSS feed的url。


4)在如下函数:

$(document).ready(function () {

结束的前一行,添加如下的代码:

   UI.button('yes').click(function (e) {
       var url = $("#rssFeed").val();
       if (url !== "") {
           var feeds = eval(localStorage["feeds"]);
           feeds.push(url);
           localStorage.setItem("feeds", JSON.stringify(feeds));
           window.location.reload();
       }
   });

   UI.button('addfeed').click(function () {
       $('#addfeeddialog').show();
   });

重新运行我们的应用,并点击“Add feed”按钮

  

添加一个RSS feed是不是在弹指之间呢?所有的源码在:

git clone https://gitcafe.com/ubuntu/html-rssreader5.git


10)你的挑战


我们现在对话框的“Cancel”还不能工作,你可以帮我完成它的代码吗?


11)美化我们的应用


在这个章节里,我们将通过使用css来达到美化我们的应用的目的。


在css/app.css文件中,我们加入如下的代码:

#articleinfo {
   padding: 10px;
   -webkit-box-sizing: border-box;
   box-sizing: border-box;
}

#articleinfo iframe {
   max-width: 100%;
}

#articleinfo p {
   margin: 7px 0;
}

#articleinfo a{
   text-decoration: none;
   color: #787878;
   font-weight: bold;
}

重新运行我们的应用,我们可以看到Article显示得更加漂亮。到目前为止,所有的源码在如下的地址可以找到:

git clone https://gitcafe.com/ubuntu/html-rssreader6.git

我们也可以参阅文章“为HTML5应用创建独立于平台的Theme”来创建一个独立于平台的theme。

如果大家有任何的问题,欢迎在文章的评论中提出来。







作者:UbuntuTouch 发表于2015/3/3 14:39:17 原文链接
阅读:646 评论:9 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将使用Ubuntu SDK所提供的online account API来访问微博的API并显示所需要的内容。这篇文章的重点是展示如何使用online account API的使用。我们将创建一个简单的QML应用。我们最终显示的画面如下:


      


更多关于QML应用开发的资料可以在地址:https://developer.ubuntu.com/zh-cn/apps/qml/


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


我们还是像以前一样使用我们的Ubuntu SDK来创建一个最基本的weibo QML应用。


   


  

这样我们就创建了一个最基本的weibo QML应用。你可以使用热键Ctrl + R来运行它虽然它并不能做什么事。


2)加入online account所需要的文件



我们可以参考文章来对online account API有更深的了解。为了能够访问,需要创建如下的文件:

1)weibo.application

它的具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<application>
  <description>Weibo QML</description>
  <desktop-entry>weibo.ubuntu_weibo.desktop</desktop-entry>
  <services>
    <service id="qml-weibo.ubuntu_qmlweibo">
      <description>Watch your favorite Weibo messages</description>
    </service>
  </services>
</application>

2)weibo.service

它的具体的内容为:

<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>sharing</type>
  <name>Weibo QML</name>
  <icon>qml-weibo.png</icon>
  <provider>qml-weibo.ubuntu_plugin</provider>
  <translations>unity-scope-weibo</translations>
</service>

3)创建一个plugin文件目录并在它的下面创建

 a)Main.qml, 它的内容为:

import Ubuntu.OnlineAccounts.Plugin 1.0

OAuthMain {}

    b) qml-weibo.ubuntu_plugin.provider,它的内容为:

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Weibo</name>
  <icon>weibo/icon.png</icon>
  <translations>unity-scope-weibo</translations>
  <plugin>generic-oauth</plugin>
  <domains>.*weibo\.com</domains>
  <single-account>true</single-account>

  <template>
    <group name="auth">
      <setting name="method">oauth2</setting>
      <setting name="mechanism">web_server</setting>
      <group name="oauth2">
        <group name="web_server">
          <setting name="Host">api.weibo.com</setting>
          <setting name="AuthPath">oauth2/authorize</setting>
          <setting name="TokenPath">oauth2/access_token</setting>
          <setting name="RedirectUri">https://api.weibo.com/oauth2/default.html</setting>
          <setting name="ResponseType">code</setting>
          <setting name="ClientId">your developer key</setting>
          <setting type="as" name="AllowedSchemes">['https','http']</setting>
          <setting name="ClientSecret">your developer secret</setting>
          <setting name="ForceClientAuthViaRequestBody" type="b">true</setting>
      </group>
      </group>
    </group>
  </template>
</provider>

在上面的文件中一定要输入自己的“your developer key”及“your developer secret”。这个你需要在微博API的网站上去申请。整个文件的架构为:




4)修改manifest.json文件如下

{
    "name": "qml-weibo.ubuntu",
    "description": "description of qml-weibo",
    "architecture": "all",
    "title": "qml-weibo",
    "hooks": {
        "qmlweibo": {
            "account-application": "weibo.application",
            "account-service": "weibo.service",
            "apparmor": "qmlweibo.apparmor",
            "desktop": "qmlweibo.desktop"
        },
        "plugin": {
            "account-provider": "plugin/qml-weibo.ubuntu_plugin.provider",
            "account-qml-plugin": "plugin/qml"
        }
    },
    "version": "0.1",
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "framework": "ubuntu-sdk-14.10"
}


这里添加了account及plugin的一些设置。


5)为了能够让.service,.application及.provider文件能正常得到显示,我们对“qml-weibo.qmlproject”添加如下的东西

    Files {
        filter: "*.service"
    }
    Files {
        filter: "*.application"
    }
    Files {
        filter: "*.provider"
    }


到这里,我们已经基本上已经修改或添加好我们所需要的文件。


3)设计应用的UI



我们来修改我们的main.qml文件。我们使用了一个Column的列layout manager来管理我们的控件。

  • 使用一个ListView (accountsList)来显示所有我们相关的账号。这个可以在“系统设置”中的“账号”可以看到

  • 使用一个叫做“createAccountBtn”的按钮来创建一个weibo的online账号
  • 使用另外一个ListView (weiboList)列表来显示微博的内容


import QtQuick 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1 as ListItem
import Ubuntu.OnlineAccounts 0.1
import Ubuntu.OnlineAccounts.Client 0.1

import "weiboapi.js" as API

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

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

    property string accesstoken:""

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

    Page {
        id: root
        clip:true
        title: i18n.tr("Weibo")

        Column {
            spacing: units.gu(2)
            width: parent.width

            ListView {
                id: accountsList

                anchors.horizontalCenter: parent.Center
                anchors.verticalCenter: parent.Center

                height: contentHeight
                width: parent.width

                spacing: units.gu(1)
                model: accountsModel

                delegate: Rectangle {
                    id: wrapper
                    width: accountsList.width
                    height: units.gu(10)
                    color: accts.enabled ? "green" : "red"

                    AccountService {
                        id: accts
                        objectHandle: accountServiceHandle
                        onAuthenticated: {
                            console.log("reply: " + JSON.stringify(reply));
                            var obj = JSON.parse(JSON.stringify(reply));
                            console.log("AccessToken: " + obj.AccessToken);
                            main.accesstoken = obj.AccessToken;

                            // Make the authenticate button invisile
                            accountsList.visible = false;

                            // Start to get the data
                            API.getStatus();
                        }
                        onAuthenticationError: {
                            console.log("Authentication failed, code " + error.code)
                            //                          accountsList.headerItem.text = "Error " + error.code
                            authenticateBtn.text = displayName + " Error " + error.code + " please do it again";
                        }
                    }

                    Button {
                        id: authenticateBtn

                        anchors.fill: parent
                        anchors.margins: units.gu(2)
                        text: i18n.tr("Authenticate %1").arg(model.displayName + " " + model.providerName)

                        onClicked: {
                            var params = {}
                            accts.authenticate(params)
                        }
                    }
                }

            }

            Button {
                id: createAccountBtn

                anchors.horizontalCenter: parent.horizontalCenter
                width: parent.width
                height: units.gu(8)
                text: i18n.tr("Request access")
                onClicked: setup.exec()
            }


            ListModel {
                id: modelWeibo
            }

            ListView {
                id: weiboList
                width: parent.width
                height: parent.height
                spacing: units.gu(1)


                model: modelWeibo

                delegate: Rectangle {
                    width: parent.width
                    height: units.gu(8)
                    color: "lightgrey"

                    BorderImage {
                        id: image
                        source: {return JSON.parse('' + JSON.stringify(user) + '').profile_image_url}
                        width: parent.height; height: parent.height
                        border.left: 5; border.top: 5
                        border.right: 5; border.bottom: 5
                    }

                    Column {
                        spacing: units.gu(1)
                        x: image.width + 10
                        width: parent.width - image.width - 20
                        height: parent.height

                        Text {
                            id: title

                            text: {return JSON.parse('' + JSON.stringify(user) + '').name }
                        }

                        Text {
                            id: subtitle

                            text: {return model.text }
                        }
                    }
                }
            }
        }
    }

    AccountServiceModel {
        id: accountsModel
        //        serviceType: ""
        applicationId: "qml-weibo.ubuntu_qmlweibo"
    }

    Setup {
        id: setup
        providerId: "qml-weibo.ubuntu_plugin"
        applicationId: "qml-weibo.ubuntu_qmlweibo"
        onFinished: {
            createAccountBtn.visible = false;
        }
    }

    Component.onCompleted: {
        if ( accountsModel.count == 0 ) {
            // If there is no such an account, we need to create such one
            setup.exec();
        } else {
            // hide the button since the account has already been created
            createAccountBtn.visible = false
        }
    }
}



我们通过设计weiboapi.js文件来帮我们来得到微博的具体的内容:

//////////////////////////////////////////////////////////////////////////////////////     weibo
//home status
function weiboHomeStatus(token, page , observer)
{
    var url = "https://api.weibo.com/2/statuses/home_timeline.json?access_token=" + token + "&page=" + page

    console.log("home status start...")
    console.log("url: " + url);

    var doc = new XMLHttpRequest();
    doc.onreadystatechange = function() {
            if (doc.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
                console.log(" home status in progress...");
            }
            else if (doc.readyState == XMLHttpRequest.DONE)
            {
                if(doc.status != 200) {
                    console.log("!!!Network connection failed!")
                    observer.update("error", doc.status)
                }
                else {
                    console.log("-- home status succeeded !");

                    if(doc.responseText == null) {
                        observer.update("null", doc.status)
                    }
                    else {
                        // console.log("home status: done" + doc.responseText)
                        console.log("Some data received!")
                        var json = JSON.parse('' + doc.responseText+ '');
                        observer.update("fine", json);
                    }
                }
            }
    }

    doc.open("GET", url, true);
    doc.setRequestHeader("Content-type", "application/json");
    doc.send();
}

function getStatus() {
    // We can get some data from the web and display them
    console.log("Going to get some data from web")
    function observer() {}
    observer.prototype = {
        update: function(status, result)
        {
            console.log("got updated!");

            if(status != "error"){
                if( result.error ) {
                    console.log("result.error: " + result.error);
                }else {
                    console.log("got updated1!");
                    for ( var i = 0; i < result.statuses.length; i++) {
                        modelWeibo.append( result.statuses[i] )
                    }
                }
            }else{
                console.log("Something is wrong!");
            }
        }
    }

    weiboHomeStatus(main.accesstoken, 1, new observer() );
}

整个应用的源码在地址 

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

作者:UbuntuTouch 发表于2015/3/10 12:33:33 原文链接
阅读:361 评论:0 查看评论

Read more
UbuntuTouch

尽管我们的developer网站有丰富的API介绍,但是,有些API的介绍可能并不全,有些API也在不断地演进中。为了得到更详细的API,我们可以通过如下的命令来得到更加详细的信息。比如我们对“SingleDownload”API来得到更加多的信息。


$qmlplugindump Ubuntu.DownloadManager 0.1



显示的结果如下:


import QtQuick.tooling 1.1


// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump Ubuntu.DownloadManager 0.1'


Module {
    Component {
        name: "Ubuntu::DownloadManager::DownloadError"
        prototype: "QObject"
        exports: ["Error 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "type"; type: "string"; isReadonly: true }
        Property { name: "message"; type: "string"; isReadonly: true }
    }
    Component {
        name: "Ubuntu::DownloadManager::SingleDownload"
        prototype: "QObject"
        exports: ["SingleDownload 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "autoStart"; type: "bool" }
        Property { name: "errorMessage"; type: "string"; isReadonly: true }
        Property { name: "isCompleted"; type: "bool"; isReadonly: true }
        Property { name: "downloadInProgress"; type: "bool"; isReadonly: true }
        Property { name: "allowMobileDownload"; type: "bool" }
        Property { name: "throttle"; type: "qulonglong" }
        Property { name: "progress"; type: "int"; isReadonly: true }
        Property { name: "downloading"; type: "bool"; isReadonly: true }
        Property { name: "downloadId"; type: "string"; isReadonly: true }
        Property { name: "headers"; type: "QVariantMap" }
        Signal {
            name: "canceled"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "finished"
            Parameter { name: "path"; type: "string" }
        }
        Signal {
            name: "paused"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "processing"
            Parameter { name: "path"; type: "string" }
        }
        Signal {
            name: "progressReceived"
            Parameter { name: "received"; type: "qulonglong" }
            Parameter { name: "total"; type: "qulonglong" }
        }
        Signal {
            name: "resumed"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "started"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "errorFound"
            Parameter { name: "error"; type: "DownloadError&" }
        }
        Signal { name: "errorChanged" }
        Method {
            name: "registerError"
            Parameter { name: "error"; type: "Error"; isPointer: true }
        }
        Method {
            name: "bindDownload"
            Parameter { name: "download"; type: "Download"; isPointer: true }
        }
        Method {
            name: "unbindDownload"
            Parameter { name: "download"; type: "Download"; isPointer: true }
        }
        Method {
            name: "onFinished"
            Parameter { name: "path"; type: "string" }
        }
        Method {
            name: "onProgress"
            Parameter { name: "received"; type: "qulonglong" }
            Parameter { name: "total"; type: "qulonglong" }
        }
        Method {
            name: "onPaused"
            Parameter { name: "wasPaused"; type: "bool" }
        }
        Method {
            name: "onResumed"
            Parameter { name: "wasResumed"; type: "bool" }
        }
        Method {
            name: "onStarted"
            Parameter { name: "wasStarted"; type: "bool" }
        }
        Method {
            name: "onCanceled"
            Parameter { name: "wasCanceled"; type: "bool" }
        }
        Method { name: "start" }
        Method { name: "pause" }
        Method { name: "resume" }
        Method { name: "cancel" }
        Method {
            name: "download"
            Parameter { name: "url"; type: "string" }
        }
    }
    Component {
        name: "Ubuntu::DownloadManager::UbuntuDownloadManager"
        prototype: "QObject"
        exports: ["DownloadManager 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "autoStart"; type: "bool" }
        Property { name: "cleanDownloads"; type: "bool" }
        Property { name: "errorMessage"; type: "string"; isReadonly: true }
        Property { name: "downloads"; type: "QVariantList"; isReadonly: true }
        Signal { name: "errorChanged" }
        Method {
            name: "download"
            Parameter { name: "url"; type: "string" }
        }
    }
}


我们通过使用“finished”信号可以得到下载完成的事件而进行分别的处理!


另外一个例子(Push notification)


liuxg@liuxg:~$ qmlplugindump Ubuntu.PushNotifications 0.1
import QtQuick.tooling 1.1

// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump Ubuntu.PushNotifications 0.1'

Module {
    Component {
        name: "PushClient"
        prototype: "QObject"
        exports: ["PushClient 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "appId"; type: "string" }
        Property { name: "token"; type: "string"; isReadonly: true }
        Property { name: "notifications"; type: "QStringList"; isReadonly: true }
        Property { name: "status"; type: "string"; isReadonly: true }
        Property { name: "persistent"; type: "QStringList"; isReadonly: true }
        Property { name: "count"; type: "int" }
        Signal {
            name: "countChanged"
            Parameter { type: "int" }
        }
        Signal {
            name: "notificationsChanged"
            Parameter { type: "QStringList" }
        }
        Signal {
            name: "persistentChanged"
            Parameter { type: "QStringList" }
        }
        Signal {
            name: "appIdChanged"
            Parameter { type: "string" }
        }
        Signal {
            name: "error"
            Parameter { type: "string" }
        }
        Signal {
            name: "tokenChanged"
            Parameter { type: "string" }
        }
        Signal {
            name: "statusChanged"
            Parameter { type: "string" }
        }
        Method { name: "getNotifications" }
        Method {
            name: "notified"
            Parameter { name: "appId"; type: "string" }
        }
        Method { name: "emitError" }
        Method {
            name: "clearPersistent"
            Parameter { name: "tags"; type: "QStringList" }
        }
    }
}


作者:UbuntuTouch 发表于2015/3/12 14:55:15 原文链接
阅读:339 评论:5 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将使用Ubuntu SDK所提供的online account API来访问微博的API并显示所需要的内容。这篇文章的重点是展示如何在HTML 5中使用online account API。我们将创建一个简单的HTML 5应用。我们最终显示的画面如下:


  


更多关于HTML5应用开发的资料可以在地址:https://developer.ubuntu.com/en/apps/html-5/


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


我们还是像以前一样使用我们的Ubuntu SDK来创建一个最基本的weibo HTML 5应用。


  

  

这样我们就创建了一个最基本的weibo HTML 5应用。你可以使用热键Ctrl + R来运行它虽然它并不能做什么事。






2)加入online account所需要的文件



我们可以参考文章来对online account API有更深的了解。为了能够访问,需要创建如下的文件:


1)weibo.application

<?xml version="1.0" encoding="UTF-8"?>
<application>
  <description>Weibo scope</description>
  <desktop-entry>weibo.ubuntu_weibo.desktop</desktop-entry>
  <services>
    <service id="html5-weibo.ubuntu_html5weibo">
      <description>Watch your favorite Weibo messages</description>
    </service>
  </services>
</application>

2)weibo.service


<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>sharing</type>
  <name>Weibo scope</name>
  <icon>html5-weibo.png</icon>
  <provider>html5-weibo.ubuntu_account-plugin</provider>
  <translations>unity-scope-weibo</translations>
</service><strong>
</strong>

3)创建一个plugin文件目录并在它的下面创建

 a)Main.qml, 它的内容为:

import Ubuntu.OnlineAccounts.Plugin 1.0

OAuthMain {}

    b) qml-weibo.ubuntu_plugin.provider,它的内容为:

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Weibo</name>
  <icon>weibo.png</icon>
  <translations>unity-scope-weibo</translations>
  <plugin>generic-oauth</plugin>
  <domains>.*weibo\.com</domains>
  <single-account>true</single-account>

  <template>
    <group name="auth">
      <setting name="method">oauth2</setting>
      <setting name="mechanism">web_server</setting>
      <group name="oauth2">
        <group name="web_server">
          <setting name="Host">api.weibo.com</setting>
          <setting name="AuthPath">oauth2/authorize</setting>
          <setting name="TokenPath">oauth2/access_token</setting>
          <setting name="RedirectUri">https://api.weibo.com/oauth2/default.html</setting>
          <setting name="ResponseType">code</setting>
          <setting name="ClientId">Your developer key</setting>
          <setting type="as" name="AllowedSchemes">['https','http']</setting>
          <setting name="ClientSecret">Your developer secret</setting>
          <setting name="ForceClientAuthViaRequestBody" type="b">true</setting>
      </group>
      </group>
    </group>
  </template>
</provider>


在上面的文件中一定要输入自己的“your developer key”及“your developer secret”。这个你需要在微博API的网站上去申请。整个文件的架构为:




4)修改manifest.json文件如下

{
    "name": "html5-weibo.ubuntu",
    "description": "description of html5-weibo",
    "architecture": "all",
    "title": "html5-weibo",
    "hooks": {
        "html5weibo": {
            "apparmor": "html5weibo.apparmor",
            "desktop": "html5weibo.desktop",
            "account-application": "weibo.application",
            "account-service": "weibo.service"
        },
        "account-plugin": {
            "account-provider": "plugin/html5-weibo.ubuntu_account-plugin.provider",
            "account-qml-plugin": "plugin/qml"
        }
    },
    "version": "0.1",
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "framework": "ubuntu-sdk-14.10"
}

这里添加了account及plugin的一些设置。


5)为了能够让.service,.application及.provider文件能正常得到显示,我们对“qml-weibo.qmlproject”添加如下的东西

    Files {
        filter: "*.service"
    }
    Files {
        filter: "*.application"
    }
    Files {
        filter: "*.provider"
    }


到这里,我们已经基本上已经修改或添加好我们所需要的文件。


3)设计应用的UI


修改我们的“index.html”文件:

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

    <!--
         Application stylesheets
         Include your own stylesheets below to style your application.
      -->
    <link rel="stylesheet" href="css/app.css" type="text/css" />

    <!-- 
        Ubuntu UI javascript imports - Ambiance theme
        Ubuntu provides building blocks that you can use in your application. For more information, you can check out the documentation at http://design.ubuntu.com/apps.
     -->
    <!-- Ubuntu UI Style imports - Ambiance theme -->
    <link href="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />    
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/popovers.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/list.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/toolbars.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/shape.js"></script>

</head>
<body>

    <div data-role="mainview">
      <header data-role="header">
      </header>

      <div data-role="content">
        <div data-role="pagestack">

          <div data-role="page" id="main" data-title="Weibo Status">

            <section data-role="list" id="statuslist"></section>

            <div id='results'></div>
          </div> <!-- main -->

        </div> <!-- pagestack -->
      </div> <!-- content -->
    </div> <!-- mainview -->

    <script src="js/app.js"></script>

  </body>
</html>

这里的UI非常简单,我只使用了一个list控件。我们大部分的代码将在“app.js”中实现:

var token = '';

window.onload = function () {
    console.log("this is so cool....!")

    var UI = new UbuntuUI();

    UI.init();
    UI.pagestack.push('main');
    var api = external.getUnityObject('1.0');
    var oa = api.OnlineAccounts;

    // UI.button('getstatus').click(auth);

    auth();

    function auth() {
        console.log("getstatus button is clicked!");

        var filters = {'provider': 'html5-weibo.ubuntu_account-plugin', 'service': ''};

        oa.api.getAccounts(filters, function(accounts) {
            console.log("total length: " + accounts.length);

            if (accounts.length < 1) {
                // setResults('No accounts available');
                oa.api.requestAccount(
                    "html5-weibo.ubuntu_html5weibo",
                    unescape("html5-weibo.ubuntu_account-plugin"),

                    function() {
                        console.log("requestAccount callback...");
                        auth();
                });
                return;
            } else {
                // setResults("Available accounts: " + accounts.length);
            }

            function authcallback(res) {
                token = res['data']['AccessToken'];
                console.log("AccessToken: " + token);
//                setResults("AccessToken: " + token);

                getWeiboStatus(token, function(statuses) {
                    console.log("the length: " + statuses.length);

                    if (statuses) {
                        // create the 'ul' element
                        var ul = document.createElement('ul');
//                        var results = document.getElementById('results');
                        var results = document.querySelector('#results');
                        var statuslist = document.querySelector('#statuslist');
                        var ul = document.createElement('ul');

                        for (var i = 0; i < statuses.length; i ++) {

                            var li = document.createElement('li');
                            ul.appendChild(li);

                            var aside = document.createElement('aside');
                            li.appendChild(aside);

                            var img = document.createElement('img');
                            img.setAttribute('src', statuses[i]['profile_image_url']);
                            img.setAttribute('width', "50");
                            img.setAttribute('height', "50");
                            aside.appendChild(img);

                            var a = document.createElement('a');
                            a.setAttribute('href', '#');
                            a.innerHTML = statuses[i]['text'];
                            li.appendChild(a);

                            var right = document.createElement('label');
                            right.innerHTML = ""
                            li.appendChild(right);

                        }

                        console.log("it is done6");
                        statuslist.appendChild(ul);
                    }
                    else {
                        setResults('<br><br>ERROR');
                    }
                });
            }

            accounts[0].authenticate(authcallback);
        }); //getAccounts
    } //auth

    function getWeiboStatus(accessToken, callback) {
        var http = new XMLHttpRequest()
        var url = "https://api.weibo.com/2/statuses/home_timeline.json?access_token=" + accessToken + "&page=" + 1

        console.log('url: \n' + url)

        http.open("GET", url, true);
        var statuses = [];
        http.onreadystatechange = function() {
            if (http.readyState === 4){
                if (http.status == 200) {
                    var response = JSON.parse(http.responseText);
                    console.log("it succeeds! lenght: " );

                    for (i = 0; i < response['statuses'].length; i++ ) {
                        var time = JSON.stringify(response['statuses'][i]['created_at']);
//                        console.log("time: " + time );

                        var text = JSON.stringify(response['statuses'][i]['text']);
//                        console.log("text: " + text);

                        var name = JSON.stringify(response['statuses'][i]['user']['name']);
//                        console.log("name: " + name);

                        var profile_image_url = JSON.stringify(response['statuses'][i]['user']['profile_image_url']);
                        profile_image_url = profile_image_url.slice(1, profile_image_url.length-1);
//                      console.log("profile_image_url: " + profile_image_url);

                        var id = JSON.stringify(response['statuses'][i]['idstr']);

                        statuses.push({'time': time,
                                       'text': text,
                                       'name': name,
                                       'id': id,
                                       'profile_image_url': profile_image_url});
                    }

                    callback(statuses);

                } else {
                    console.log("error: " + http.status)
                    callback(null);
                }
            }
        };
        http.send(null);
    }

    function setResults(data) {
        var results = document.getElementById('results');
        results.innerHTML = data;
    };

}; //onload

我们可以使用如下的句子来得到online account API的接口:

   var api = external.getUnityObject('1.0');
   var oa = api.OnlineAccounts;


在这里,我们使用HTML 5的online account API来检查是否有如下的provider:

var filters = {'provider': 'html5-weibo.ubuntu_account-plugin', 'service': ''};

如果没有,我们使用如下的代码来创建weibo账号:

           if (accounts.length < 1) {
                // setResults('No accounts available');
                oa.api.requestAccount(
                    "html5-weibo.ubuntu_html5weibo",
                    unescape("html5-weibo.ubuntu_account-plugin"),

                    function() {
                        console.log("requestAccount callback...");
                        auth();
                });
                return;
            }

等创建成功了,我们使用“auth()”来得到访问weibo的access token。进而我们可以得到我们所需要的任何的信息。

被创建的账号,可以在“系统设置”里的“账号”中可以看到:



另外我们可以在手机或模拟器中的如下的目录中看到我们所创建的文件:



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

作者:UbuntuTouch 发表于2015/3/16 10:07:57 原文链接
阅读:399 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们来介绍如何使用在线(online)的Webapp生成器来生产在Ubuntu手机或模拟器中可以安装的click安装包。Webapp生成器的地址:https://developer.ubuntu.com/webapp-generator/


1)打开生成器网页,并填入相应的信息


我们在浏览器中打开网页:https://developer.ubuntu.com/webapp-generator/, 并填入应用所需要的信息。请注意:如果你还没有Ubuntu One账号,请注册一个,否则你将不能进入。Ubuntu One账号在上传应用到商店时也是必须有的账号。






等填完我们所需要的信息后,我们点击按钮“Submit”,就可以在屏幕的做下方出现下载的一个叫做类似“wqqcom-uii.liu-xiao-guo_0.1_all.click”的Click安装包。这个文件通常应该在:

/home/HOST_NAME/Downloads

文件夹中(这里“HOSE_NAME”)根据自己的电脑名字的不同而不同。

2)安装应用到我们的手机或模拟器中


我们通过如下的命令来把我们生产的click安装包安装到手机或模拟器中。注意我们上面所生产的click包的名字中含有“all",说明它是对手机和模拟器都适用的安装包。我们打开一个Terminal,并在其中打入如下的命令:

liuxg@liuxg:~/Downloads$ adb push wqqcom-uii.liu-xiao-guo_0.1_all.click /home/phablet



紧接着,我们打入如下的命令:

liuxg@liuxg:~/Downloads$ adb shell
phablet@ubuntu-phablet:~$ pkcon --allow-untrusted install-local wqqcom-uii.liu-xiao-guo_0.1_all.click 





这样我们就把应用安装到手机或模拟器中。如果在手机桌面上没有找到该安装的应用,可以尝试搜索“qq”(对我们的这个例子而言,它的名字中含有qq)。

  


在模拟器中的安装和运行,显示如下:

     

欢迎大家生产自己喜欢的Webapps,并把它上传到我们的商店中。另外值得指出的是:我们也可以适用SDK中的“Web app”模版来生产我们喜欢的在线Webapps。




更多关于在线Webapp的开发的信息可以在地址https://developer.ubuntu.com/zh-cn/web/找到。

作者:UbuntuTouch 发表于2015/3/19 9:31:27 原文链接
阅读:357 评论:0 查看评论

Read more
UbuntuTouch

几天以前,有一个开发者问道如何得到TextArea中的slider的位置信息。目前在QML中的TextArea中并没有这个信息,那么我们如何得到这个信息呢?


1)通过遍历TextArea中的child来得到这个信息


我们可以通过研究TextArea的代码,我们可以发现其中是有一个叫做Flickable的child在里面的。它的里面的contentY含有这个信息,但是可惜的是它不暴露这个信息给我们,那么我们怎么才能得到这个信息呢?

我们设计了如下的测试程序:

import QtQuick 2.0
import Ubuntu.Components 1.1

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "textareatest.ubuntu"

    /*
     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: 450
    height: 600

    Page {
        title: i18n.tr("Simple")
        clip:true

        Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

        TextArea {
            id: textArea
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            height: 450

            readOnly: true
            wrapMode: TextEdit.Wrap
            text: "this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n"

            onContentHeightChanged: {
                console.log("ContentHeight: " + contentHeight);
                console.log("height: " + height);
            }

//            onContentYChanged: {
//                console.log("contentY: " + contentY);
//            }
        }
    }
}

界面如下:



我们按下“Get contentY”按钮后,可以看到在“Application output”窗口中显示的contentY的值。我们其实使用了如下的句子来实现的:

       Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

通过遍历textArea的所有的child,并尝试得到contentY的属性。我们尝试滚动TextArea里的内容,就可以得到不同的contentY的值了。

所有的代码在地址:git clone https://gitcafe.com/ubuntu/TextArea1.git


2)通过重写Ubuntu的TextArea来实现


这个方法可能会导致在不同的framework上不兼容。我们下载相应平台的TextArea,并创建自己的MyTextArea如下:

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

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components 1.1 as Ubuntu
import "mathUtils.js" as MathUtils

/*!
    \qmltype TextArea
    \inqmlmodule Ubuntu.Components 1.1
    \ingroup ubuntu
    \brief The TextArea item displays a block of editable, scrollable, formatted
    text.

    The TextArea supports fix-size and auto-expanding modes. In fix-size mode the
    content is scrolled when exceeds the boundaries and can be scrolled both horizontally
    and vertically, depending on the contentWidth and contentHeight set. The following
    example will scroll the editing area both horizontally and vertically:

    \qml
    TextArea {
        width: units.gu(20)
        height: units.gu(12)
        contentWidth: units.gu(30)
        contentHeight: units.gu(60)
    }
    \endqml

    The auto-expand mode is realized using two properties: autoSize and maximumLineCount.
    Setting autoSize will set implicitHeight to one line, and the height will follow
    the line count, meaning when lines are added the area will expand and when
    removed the area will shrink. The maximumLineCount specifies how much the
    editor should be expanded. If this value is set to 0, the area will always
    expand vertically to fit the content. When autoSize is set, the contentHeight
    property value is ignored, and the expansion only happens vertically.

    \qml
    TextArea {
        width: units.gu(20)
        height: units.gu(12)
        contentWidth: units.gu(30)
        autoSize: true
        maximumLineCount: 0
    }
    \endqml

    TextArea comes with 30 grid-units implicit width and one line height on auto-sizing
    mode and 4 lines on fixed-mode. The line size is calculated from the font size and the
    ovarlay and frame spacing specified in the style.

    \section2 Scrolling and text selection
    The input is activated when the tap or mouse is released after being pressed
    over the component.

    Scrolling the editing area can happen when the size is fixed or in auto-sizing mode when
    the content size is bigger than the visible area. The scrolling is realized by swipe
    gestures, or by navigating the cursor.

    The content can be selected in the following ways:
    \list
    \li - double tapping/left mouse clicking over the content, when the word that
          had been tapped over will be selected
    \li - by pressing and dragging the selection cursor over the text input. Note
          that there has to be a delay of approx. 200 ms between the press and drag
          gesture, time when the input switches from scroll mode to selection mode
    \endlist

    The input is focused (activated) upon tap/left mouse button release. The cursor
    will be placed at the position the mouse/tap point at release time. If the click
    is happening on a selected area, the selection will be cleared. Long press above
    a selected area brings up the clipboard context menu. When the long press happens
    over a non-selected area, the cursor will be moved to the position and the component
    enters in selection mode. The selection mode can also be activated by tapping and
    keeping the tap/mouse over for approx 300 ms. If there is a move during this time,
    the component enters into scrolling mode. The mode is exited once the scrolling
    finishes. During the scrolling mode the selected text is preserved.

    \note During text selection all interactive parent Flickables are turned off.
  */

StyledItem {
    id: control
    implicitWidth: units.gu(30)
    implicitHeight: (autoSize) ? internal.minimumSize : internal.linesHeight(4)

    property alias contentY: flicker.contentY

    // new properties
    /*!
      The property presents whether the TextArea is highlighted or not. By
      default the TextArea gets highlighted when gets the focus, so can accept
      text input. This property allows to control the highlight separately from
      the focused behavior.
      */
    property bool highlighted: focus
    /*!
      Text that appears when there is no focus and no content in the component
      (hint text).

      \qmlproperty string placeholderText
      */
    property alias placeholderText: hint.text

    /*!
      This property contains the text that is displayed on the screen. May differ
      from the text property value when TextEdit.RichText format is selected.

      \qmlproperty string displayText
      */
    readonly property alias displayText: internal.displayText

    /*!
      The property drives whether text selection should happen with the mouse or
      not. The default value is true.
      \qmlproperty bool selectByMouse
      */
    property alias selectByMouse: editor.selectByMouse

    /*!
      \deprecated
      This property specifies whether the text area expands following the entered
      text or not. The default value is false.
      The property is deprecated, use autoSize instead
      */
    property bool autoExpand
    /*! \internal */
    onAutoExpandChanged: console.debug("WARNING: autoExpand deprecated, use autoSize instead.")

    /*!
      This property specifies whether the text area sizes following the line count
      or not. The default value is false.
      */
    property bool autoSize: false

    /*!
      The property holds the maximum amount of lines to expand when autoSize is
      enabled. The value of 0 does not put any upper limit and the control will
      expand forever.

      The default value is 5 lines.
      */
    property int maximumLineCount: 5

    // altered TextEdit properties
    /*!
      The property folds the width of the text editing content. This can be equal or
      bigger than the frame width minus the spacing between the frame and the input
      area defined in the current theme. The default value is the same as the visible
      input area's width.
      */
    property real contentWidth: control.width - 2 * internal.frameSpacing

    /*!
      The property folds the height of the text editing content. This can be equal or
      bigger than the frame height minus the spacing between the frame and the input
      area defined in the current theme. The default value is the same as the visible
      input area's height.
      */
    property real contentHeight: control.height - 2 * internal.frameSpacing

    /*!
      The property overrides the default popover of the TextArea. When set, the
      TextArea will open the given popover instead of the default one defined.
      The popover can either be a component or a URL to be loaded.
      */
    property var popover

    // forwarded properties
    /*!
      Whether the TextArea should gain active focus on a mouse press. By default this
      is set to true.
      \qmlproperty bool activeFocusOnPress
      */
    property alias activeFocusOnPress: editor.activeFocusOnPress

    /*!
      This property specifies a base URL which is used to resolve relative URLs within
      the text. The default value is the url of the QML file instantiating the TextArea
      item.

      \qmlproperty url baseUrl
      */
    property alias baseUrl: editor.baseUrl

    /*!
      Returns true if the TextArea is writable and the content of the clipboard is
      suitable for pasting into the TextArea.

      \qmlproperty bool canPaste
      */
    property alias canPaste: editor.canPaste

    /*!
      Returns true if the TextArea is writable and there are undone operations that
      can be redone.

      \qmlproperty bool canRedo
      */
    property alias canRedo: editor.canRedo

    /*!
      Returns true if the TextArea is writable and there are previous operations
      that can be undone.

      \qmlproperty bool canUndo
      */
    property alias canUndo: editor.canUndo

    /*!
      The text color.

      \qmlproperty color color
      */
    property alias color: editor.color

    /*!
      The delegate for the cursor in the TextArea.

      If you set a cursorDelegate for a TextArea, this delegate will be used for
      drawing the cursor instead of the standard cursor. An instance of the delegate
      will be created and managed by the text edit when a cursor is needed, and
      the x and y properties of delegate instance will be set so as to be one pixel
      before the top left of the current character.

      Note that the root item of the delegate component must be a QQuickItem or
      QQuickItem derived item.
      */
    property Component cursorDelegate: null

    /*!
      The position of the cursor in the TextArea.

      \qmlproperty int cursorPosition
      */
    property alias cursorPosition: editor.cursorPosition

    /*!
      The rectangle where the standard text cursor is rendered within the text
      edit. Read-only.

      The position and height of a custom cursorDelegate are updated to follow
      the cursorRectangle automatically when it changes. The width of the delegate
      is unaffected by changes in the cursor rectangle.

      \qmlproperty rectangle cursorRectangle
      */
    property alias cursorRectangle: editor.cursorRectangle

    /*!
      If true the text edit shows a cursor.

      This property is set and unset when the text edit gets active focus, but it
      can also be set directly (useful, for example, if a KeyProxy might forward
      keys to it).

      \qmlproperty bool cursorVisible
      */
    property alias cursorVisible: editor.cursorVisible

    /*!
      Presents the effective horizontal alignment that can be different from the one
      specified at horizontalAlignment due to layout mirroring.

      \qmlproperty enumeration effectiveHorizontalAlignment
      */
    property alias effectiveHorizontalAlignment: editor.effectiveHorizontalAlignment

    /*!
      The property holds the font used by the editing.

      \qmlproperty font font
      */
    property alias font: editor.font

    /*!
      Sets the horizontal alignment of the text within the TextAre item's width
      and height. By default, the text alignment follows the natural alignment
      of the text, for example text that is read from left to right will be
      aligned to the left.

      Valid values for effectiveHorizontalAlignment are:
        \list
        \li TextEdit.AlignLeft (default)
        \li TextEdit.AlignRight
        \li TextEdit.AlignHCenter
        \li TextEdit.AlignJustify
        \endlist

      \qmlproperty enumeration horizontalAlignment
      */
    property alias horizontalAlignment: editor.horizontalAlignment

    /*!
      This property holds whether the TextArea has partial text input from an
      input method.

      While it is composing an input method may rely on mouse or key events
      from the TextArea to edit or commit the partial text. This property can
      be used to determine when to disable events handlers that may interfere
      with the correct operation of an input method.

      \qmlproperty bool inputMethodComposing
      */
    property alias inputMethodComposing: editor.inputMethodComposing

    /*!
    Provides hints to the input method about the expected content of the text
    edit and how it should operate.

    The value is a bit-wise combination of flags or Qt.ImhNone if no hints are set.

    Flags that alter behaviour are:
    \list
    \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
    \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method in any persistent storage like predictive user dictionary.
    \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case when a sentence ends.
    \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
    \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
    \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
    \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
    \li Qt.ImhDate - The text editor functions as a date field.
    \li Qt.ImhTime - The text editor functions as a time field.
    \endlist
    Flags that restrict input (exclusive flags) are:

    \list
    \li Qt.ImhDigitsOnly - Only digits are allowed.
    \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
    \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
    \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
    \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
    \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
    \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
    \endlist
    Masks:

    \list
    \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
    \endlist

      \qmlproperty enumeration inputMethodHints
      */
    property alias inputMethodHints: editor.inputMethodHints

    /*!
      Returns the total number of plain text characters in the TextArea item.

      As this number doesn't include any formatting markup it may not be the
      same as the length of the string returned by the text property.

      This property can be faster than querying the length the text property
      as it doesn't require any copying or conversion of the TextArea's internal
      string data.

      \qmlproperty int length
      */
    property alias length: editor.length

    /*!
      Returns the total number of lines in the TextArea item.

      \qmlproperty int lineCount
      */
    property alias lineCount: editor.lineCount

    /*!
      Specifies how text should be selected using a mouse.
        \list
        \li TextEdit.SelectCharacters - The selection is updated with individual characters. (Default)
        \li TextEdit.SelectWords - The selection is updated with whole words.
        \endlist
      This property only applies when selectByMouse is true.

      \qmlproperty enumeration mouseSelectionMode
      */
    property alias mouseSelectionMode: editor.mouseSelectionMode

    /*!
      Whether the TextArea should keep the selection visible when it loses active
      focus to another item in the scene. By default this is set to true;

      \qmlproperty enumeration persistentSelection
      */
    property alias persistentSelection: editor.persistentSelection

    /*!
      Whether the user can interact with the TextArea item. If this property is set
      to true the text cannot be edited by user interaction.

      By default this property is false.

      \qmlproperty bool readOnly
      */
    property alias readOnly: editor.readOnly

    /*!
      Override the default rendering type for this component.

      Supported render types are:
        \list
        \li Text.QtRendering - the default
        \li Text.NativeRendering
        \endlist
      Select Text.NativeRendering if you prefer text to look native on the target
      platform and do not require advanced features such as transformation of the
      text. Using such features in combination with the NativeRendering render type
      will lend poor and sometimes pixelated results.

      \qmlproperty enumeration renderType
      */
    property alias renderType: editor.renderType

    /*!
      This read-only property provides the text currently selected in the text edit.

      \qmlproperty string selectedText
      */
    property alias selectedText: editor.selectedText

    /*!
      The selected text color, used in selections.

      \qmlproperty color selectedTextColor
      */
    property alias selectedTextColor: editor.selectedTextColor

    /*!
      The text highlight color, used behind selections.

      \qmlproperty color selectionColor
      */
    property alias selectionColor: editor.selectionColor

    /*!
      The cursor position after the last character in the current selection.

      This property is read-only. To change the selection, use select(start, end),
      selectAll(), or selectWord().

      See also selectionStart, cursorPosition, and selectedText.

      \qmlproperty int selectionEnd
      */
    property alias selectionEnd: editor.selectionEnd

    /*!
      The cursor position before the first character in the current selection.

      This property is read-only. To change the selection, use select(start, end),
      selectAll(), or selectWord().

      See also selectionEnd, cursorPosition, and selectedText.

      \qmlproperty int selectionStart
      */
    property alias selectionStart: editor.selectionStart

    /*!
      The text to display. If the text format is AutoText the text edit will
      automatically determine whether the text should be treated as rich text.
      This determination is made using Qt::mightBeRichText().

      \qmlproperty string text
      */
    property alias text: editor.text

    /*!
      The way the text property should be displayed.
        \list
        \li TextEdit.AutoText
        \li TextEdit.PlainText
        \li TextEdit.RichText
        \endlist
      The default is TextEdit.PlainText. If the text format is TextEdit.AutoText
      the text edit will automatically determine whether the text should be treated
      as rich text. This determination is made using Qt::mightBeRichText().

      \qmlproperty enumeration textFormat
      */
    property alias textFormat: editor.textFormat

    /*!
      Sets the vertical alignment of the text within the TextAres item's width
      and height. By default, the text alignment follows the natural alignment
      of the text.

      Valid values for verticalAlignment are:

        \list
        \li TextEdit.AlignTop (default)
        \li TextEdit.AlignBottom
        \li TextEdit.AlignVCenter
        \endlist

        \qmlproperty enumeration verticalAlignment
      */
    property alias verticalAlignment: editor.verticalAlignment

    /*!
      Set this property to wrap the text to the TextEdit item's width. The text
      will only wrap if an explicit width has been set.
        \list
        \li TextEdit.NoWrap - no wrapping will be performed. If the text contains
            insufficient newlines, then implicitWidth will exceed a set width.
        \li TextEdit.WordWrap - wrapping is done on word boundaries only. If a word
            is too long, implicitWidth will exceed a set width.
        \li TextEdit.WrapAnywhere - wrapping is done at any point on a line, even
            if it occurs in the middle of a word.
        \li TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise
            it will occur at the appropriate point on the line, even in the middle of a word.
        \endlist
       The default is TextEdit.Wrap

       \qmlproperty enumeration wrapMode
      */
    property alias wrapMode:editor.wrapMode

    // signals
    /*!
      This handler is called when the user clicks on a link embedded in the text.
      The link must be in rich text or HTML format and the link string provides
      access to the particular link.
      */
    signal linkActivated(string link)

    // functions
    /*!
      Copies the currently selected text to the system clipboard.
      */
    function copy()
    {
        editor.copy();
    }

    /*!
      Moves the currently selected text to the system clipboard.
      */
    function cut()
    {
        editor.cut();
    }

    /*!
      Removes active text selection.
      */
    function deselect()
    {
        editor.deselect();
    }

    /*!
      Inserts text into the TextArea at position.
      */
    function insert(position, text)
    {
        editor.insert(position, text);
    }

    /*!
      Returns the text position closest to pixel position (x, y).

      Position 0 is before the first character, position 1 is after the first
      character but before the second, and so on until position text.length,
      which is after all characters.
      */
    function positionAt(x, y)
    {
        return editor.positionAt(x, y);
    }

    /*!
      Returns true if the natural reading direction of the editor text found
      between positions start and end is right to left.
      */
    function isRightToLeft(start, end)
    {
        return editor.isRightToLeft(start, end)
    }

    /*!
      Moves the cursor to position and updates the selection according to the
      optional mode parameter. (To only move the cursor, set the cursorPosition
      property.)

      When this method is called it additionally sets either the selectionStart
      or the selectionEnd (whichever was at the previous cursor position) to the
      specified position. This allows you to easily extend and contract the selected
      text range.

      The selection mode specifies whether the selection is updated on a per character
      or a per word basis. If not specified the selection mode will default to whatever
      is given in the mouseSelectionMode property.
      */
    function moveCursorSelection(position, mode)
    {
        if (mode === undefined)
            editor.moveCursorSelection(position, mouseSelectionMode);
        else
            editor.moveCursorSelection(position, mode);
    }

    /*!
      \preliminary
      Places the clipboard or the data given as parameter into the text input.
      The selected text will be replaces with the data.
    */
    function paste(data) {
        if ((data !== undefined) && (typeof data === "string") && !editor.readOnly) {
            var selTxt = editor.selectedText;
            var txt = editor.text;
            var pos = (selTxt !== "") ? txt.indexOf(selTxt) : editor.cursorPosition
            if (selTxt !== "") {
                editor.text = txt.substring(0, pos) + data + txt.substr(pos + selTxt.length);
            } else {
                editor.text = txt.substring(0, pos) + data + txt.substr(pos);
            }
            editor.cursorPosition = pos + data.length;
        } else
            editor.paste();
    }

    /*!
      Returns the rectangle at the given position in the text. The x, y, and height
      properties correspond to the cursor that would describe that position.
      */
    function positionToRectangle(position)
    {
        return editor.positionToRectangle(position);
    }

    /*!
      Redoes the last operation if redo is \l{canRedo}{available}.
      */
    function redo()
    {
        editor.redo();
    }

    /*!
      Causes the text from start to end to be selected.

      If either start or end is out of range, the selection is not changed.

      After calling this, selectionStart will become the lesser and selectionEnd
      will become the greater (regardless of the order passed to this method).

      See also selectionStart and selectionEnd.
      */
    function select(start, end)
    {
        editor.select(start, end);
    }

    /*!
      Causes all text to be selected.
      */
    function selectAll()
    {
        editor.selectAll();
    }

    /*!
      Causes the word closest to the current cursor position to be selected.
      */
    function selectWord()
    {
        editor.selectWord();
    }

    /*!
      Returns the section of text that is between the start and end positions.

      The returned text will be formatted according the textFormat property.
      */
    function getFormattedText(start, end)
    {
        return editor.getFormattedText(start, end);
    }

    /*!
      Returns the section of text that is between the start and end positions.

      The returned text does not include any rich text formatting. A getText(0, length)
      will result in the same value as displayText.
      */
    function getText(start, end)
    {
        return editor.getText(start, end);
    }

    /*!
      Removes the section of text that is between the start and end positions
      from the TextArea.
      */
    function remove(start, end)
    {
        return editor.remove(start, end);
    }

    /*!
      Undoes the last operation if undo is \l{canUndo}{available}. Deselects
      any current selection, and updates the selection start to the current
      cursor position.
      */
    function undo()
    {
        editor.undo();
    }

    // logic
    /*!\internal - to remove warnings */
    Component.onCompleted: {
        editor.linkActivated.connect(control.linkActivated);
    }

    // activation area on mouse click
    // the editor activates automatically when pressed in the editor control,
    // however that one can be slightly spaced to the main control area
    MouseArea {
        anchors.fill: parent
        enabled: internal.frameSpacing > 0
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        // activate input when pressed on the frame
        preventStealing: false
        Ubuntu.Mouse.forwardTo: [inputHandler]
    }

    //internals

    opacity: enabled ? 1.0 : 0.3

    /*!\internal */
    onVisibleChanged: {
        if (!visible)
            control.focus = false;
    }

    LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
    LayoutMirroring.childrenInherit: true

    QtObject {
        id: internal
        // public property locals enabling aliasing
        property string displayText: editor.getText(0, editor.length)
        property real frameSpacing: control.__styleInstance.frameSpacing
        property real minimumSize: units.gu(4)

        function linesHeight(lines)
        {
            var lineHeight = editor.font.pixelSize * lines + inputHandler.lineSpacing * lines
            return lineHeight + 2 * frameSpacing;
        }

        function frameSize()
        {
            if (control.autoSize) {
                var max = (control.maximumLineCount <= 0) ?
                            control.lineCount :
                            Math.min(control.maximumLineCount, control.lineCount);
                control.height = linesHeight(MathUtils.clamp(control.lineCount, 1, max));
            }
        }
    }

    // grab Enter/Return keys which may be stolen from parent components of TextArea
    // due to forwarded keys from TextEdit
    Keys.onPressed: {
        if ((event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)) {
            if (editor.textFormat === TextEdit.RichText) {
                // FIXME: use control.paste("<br />") instead when paste() gets sich text support
                editor.insert(editor.cursorPosition, "<br />");
            } else {
                control.paste("\n");
            }
            event.accepted = true;
        } else {
            event.accepted = false;
        }
    }
    Keys.onReleased: event.accepted = (event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)

    // holding default values
    Label { id: fontHolder }

    //hint
    Label {
        id: hint
        anchors {
            fill: parent
            margins: internal.frameSpacing
        }
        // hint is shown till user types something in the field
        visible: (editor.getText(0, editor.length) == "") && !editor.inputMethodComposing
        color: Theme.palette.normal.backgroundText
        fontSize: "medium"
        elide: Text.ElideRight
        wrapMode: Text.WordWrap
    }

    //scrollbars and flickable
    Scrollbar {
        id: rightScrollbar
        flickableItem: flicker
    }
    Scrollbar {
        id: bottomScrollbar
        flickableItem: flicker
        align: Qt.AlignBottom
    }
    Flickable {
        id: flicker
        objectName: "input_scroller"
        anchors {
            fill: parent
            margins: internal.frameSpacing
        }
        clip: true
        contentWidth: editor.paintedWidth
        contentHeight: editor.paintedHeight
        // do not allow rebounding
        boundsBehavior: Flickable.StopAtBounds

        // editor
        // Images are not shown when text contains <img> tags
        // bug to watch: https://bugreports.qt-project.org/browse/QTBUG-27071
        TextEdit {
            objectName: "text_input"
            readOnly: false
            id: editor
            focus: true
            width: control.contentWidth
            height: Math.max(control.contentHeight, editor.contentHeight)
            wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
            mouseSelectionMode: TextEdit.SelectWords
            selectByMouse: true
            activeFocusOnPress: true
            cursorDelegate: TextCursor {
                handler: inputHandler
            }
            color: control.__styleInstance.color
            selectedTextColor: Theme.palette.selected.foregroundText
            selectionColor: Theme.palette.selected.selection
            font.pixelSize: FontUtils.sizeToPixels("medium")
            // forward keys to the root element so it can be captured outside of it
            // as well as to InputHandler to handle PageUp/PageDown keys
            Keys.forwardTo: [control, inputHandler]

            // autosize handling
            onLineCountChanged: internal.frameSize()

            // input selection and navigation handling
            Ubuntu.Mouse.forwardTo: [inputHandler]
            InputHandler {
                id: inputHandler
                anchors.fill: parent
                main: control
                input: editor
                flickable: flicker
                frameDistance: Qt.point(flicker.x, flicker.y)
            }
        }
    }

    style: Theme.createStyleComponent("TextAreaStyle.qml", control)
}

这里我们加入了:

    property alias contentY: flicker.contentY

通过这样的方法,我们可以使得flicker的contentY的属性得以暴露,并被外面main.qml所使用:

import QtQuick 2.0
import Ubuntu.Components 1.1

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "textareatest.ubuntu"

    /*
     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: 450
    height: 600

    Page {
        title: i18n.tr("Simple")
        clip:true

        Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

        MyTextArea {
            id: textArea
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            height: 450

            readOnly: true
            wrapMode: TextEdit.Wrap
            text: "this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n"

            onContentHeightChanged: {
                console.log("ContentHeight: " + contentHeight);
                console.log("height: " + height);
            }

            onContentYChanged: {
                console.log("contentY: " + contentY);
            }
        }
    }
}





当我们滚动时,就可以看见contentY的属性的变化。

这个工程的代码在: git clone https://gitcafe.com/ubuntu/TextArea2.git
作者:UbuntuTouch 发表于2015/4/13 9:26:54 原文链接
阅读:136 评论:0 查看评论

Read more
UbuntuTouch

我们可以在Ubuntu QML的API文档中看到Camera的用法,但是里面没有写到任何的前置Camera的调用方法。对于一些应用来说,前置Camera的使用是重要的。我们必须使用Qt C++代码来实现这个功能。在这篇文章中,我们来介绍如何使用Ubuntu手机中的前置照相机。


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


   

   

这样我们就生成了一个含有plugin的项目。

2)在项目中加入CameraSelector来选择照相机


为了能够调用Camera的一些API,我们在plugin中加入如下的CameraSelector类:

#ifndef CAMERA_SELECTOR_H
#define CAMERA_SELECTOR_H

#include <QObject>
#include <QCamera>
#include <QVideoDeviceSelectorControl>

class CameraSelector : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QObject* cameraObject READ cameraObject WRITE setCameraObject)
    Q_PROPERTY(int selectedCameraDevice READ selectedCameraDevice WRITE setSelectedCameraDevice)

public:
    QObject* cameraObject() const;
    void setCameraObject(QObject *cam);

    int selectedCameraDevice() const;
    void setSelectedCameraDevice(const int cameraId);

private:
    QCamera *m_camera;
    QVideoDeviceSelectorControl *m_deviceSelector;
};

#endif // CAMERA_SELECTOR_H


#include "cameraselector.h"

#include <QMediaService>

void CameraSelector::setCameraObject(QObject *cam)
{
    // get the QCamera from the declarative camera's mediaObject property.
    m_camera = qvariant_cast<QCamera*>(cam->property("mediaObject"));

    // get the video device selector control
    QMediaService *service = m_camera->service();
    m_deviceSelector = qobject_cast<QVideoDeviceSelectorControl*>(service->requestControl(QVideoDeviceSelectorControl_iid));
}


QObject *CameraSelector::cameraObject() const
{
    return m_camera;
}

int CameraSelector::selectedCameraDevice() const
{
     return 0;
}

void CameraSelector::setSelectedCameraDevice(const int cameraId)
{
    // A camera might already be started, make sure it's unloaded
    m_camera->unload();

    m_deviceSelector->setSelectedDevice(cameraId);
}


我们在“backend”中的CMakeLists.txt中加入“MultiMedia"库以调用QCamera。

include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}
)

set(
    FrontCamerabackend_SRCS
    modules/FrontCamera/backend.cpp
    modules/FrontCamera/mytype.cpp
    modules/FrontCamera/cameraselector.cpp
)

add_library(FrontCamerabackend MODULE
    ${FrontCamerabackend_SRCS}
)

set_target_properties(FrontCamerabackend PROPERTIES
         LIBRARY_OUTPUT_DIRECTORY FrontCamera)

qt5_use_modules(FrontCamerabackend Gui Qml Quick Multimedia)

# Copy qmldir file to build dir for running in QtCreator
add_custom_target(FrontCamerabackend-qmldir ALL
    COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/modules/FrontCamera/qmldir ${CMAKE_CURRENT_BINARY_DIR}/FrontCamera
    DEPENDS ${QMLFILES}
)

# Install plugin file
install(TARGETS FrontCamerabackend DESTINATION ${QT_IMPORTS_DIR}/FrontCamera/)
install(FILES   modules/FrontCamera/qmldir DESTINATION ${QT_IMPORTS_DIR}/FrontCamera/)


同时在backend.cpp中加入如下的句子:

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


为了使用Camera,我们对我们的FrontCamera.qml做如下的修改:

import QtQuick 2.0
import Ubuntu.Components 1.1
import FrontCamera 1.0
import QtMultimedia 5.0

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

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

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

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

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

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

    Page {
        title: i18n.tr("App with backend")

        Button {
            id: activateRearCamera
            text: "Rear Camera"

            onClicked: {
                selector. selectedCameraDevice = 0;
                camera.start();
            }
        }

        Button {
            id: activateFrontCamera
            text: "Front camera"
            anchors.left : activateRearCamera.right
            anchors.leftMargin: units.gu(2)
            onClicked: {
                selector. selectedCameraDevice = 1;
                camera.start();
            }
        }

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

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

            flash.mode: Camera.FlashRedEyeReduction

            imageCapture {
                onImageCaptured: {
                    photoPreview.source = preview  // Show the preview in an Image
                }
            }
        }

        CameraSelector {
            id: selector
            cameraObject: camera
        }

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

        Image {
            id: photoPreview
        }
    }
}


运行我们的应用:

  


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

作者:UbuntuTouch 发表于2015/4/8 12:55:25 原文链接
阅读:165 评论:0 查看评论

Read more
UbuntuTouch

前一段时间,有个开发者问我能否在Ubuntu手机中使用QtQuick.Dialogs来实现FileDialog。目前在手机上没有Qt这个库的实现。最主要的原因是它不使用unit grid的方式来布局,所以在真的手机上显得非常小。那么我们怎么才能实现同样的功能呢?


我们首先来查看一下我们的Ubuntu Qt所提供的API Dialog。这里我们有提供一个Dialog的control.我们可以仿照上面的例程来写出我们所需要的例程。另外,我们也需要使用另外一个API FolderListModel。通过这个API我们可以得到我们所需要的文件的目录的所有的文件:


通过对这两个API的了解和和使用,我们可以很快的做出如下的测试应用:


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Popups 0.1
import Qt.labs.folderlistmodel 2.1

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "filedialog.ubuntu"

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

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

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

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

        Button {
            id: launcher
            text: i18n.tr("Open")
            width: units.gu(16)
            onClicked: PopupUtils.open(dialog, null)
        }

        Component {
            id: dialog
            Dialog {
                id: dialogue

                title: "FileList Dialog"
                text: "Show the files"

                ListView {
                    id: listview
                    width: parent.width
                    height: 200

                    FolderListModel {
                        id: folderModel
                        nameFilters: ["*.qml"]                      
                    }

                    Component {
                        id: fileDelegate
                        Text {
                            id: text
                            text: fileName
                            MouseArea {
                                anchors.fill: parent
                                onClicked: {
                                    console.log("it is clicked");
                                    listview.currentIndex = index;
                                }
                            }
                        }
                    }

                    // Define a highlight with customized movement between items.
                    Component {
                        id: highlightBar
                        Rectangle {
                            width: fileDelegate.width; height: 50
                            color: "red"
                            y: listview.currentItem.y;
                            Behavior on y { SpringAnimation { spring: 2; damping: 0.1 } }
                        }
                    }

                    focus: true
                    model: folderModel
                    delegate: fileDelegate
                    highlight: highlightBar

                }

                Row {
                    id: row
                    width: parent.width
                    spacing: units.gu(1)
                    Button {
                        width: parent.width/2
                        text: "Cancel"
                        onClicked: PopupUtils.close(dialogue)
                    }

                    Button {
                        width: parent.width/2
                        text: "Confirm"
                        color: UbuntuColors.green
                        onClicked: {
                            console.log("caller: " + dialogue.caller);
                            console.log("currentIndex: " + listview.currentIndex);
                            console.log(folderModel.get(listview.currentIndex, "fileName"));
                            launcher.update();
                            PopupUtils.close(dialogue)
                        }
                    }
                }
            }
        }
    }
}

运行我们的应用,我们可以看到:

  


这里必须指出的是FolderListModel并不是可以访问系统的任何一个目录的。我们可以参考文章“Ubuntu OS应用Runtime Enviroment”来得到哪些目录是可以访问的。对访问另外一个应用所拥有的文件来说,我们可以通过使用contenthub来访问。


所有应用的源码在:git clone https://gitcafe.com/ubuntu/filedialog.git




作者:UbuntuTouch 发表于2015/4/15 11:04:28 原文链接
阅读:182 评论:0 查看评论

Read more
UbuntuTouch

我们知道目前Ubuntu手机平台有些类似iPhone平台,是一个单任务的操作系统,虽然系统本身具有多任务的功能。如果当前的应用被推到后台的话,应用将会被自动挂起,而不会被系统所运行。在这个时候如果我们的应用需要等待一个消息,比如就想微信之类的信息,我们就要使用Ubuntu平台所提供的Push Notification机制来实现我们的类似多任务的东西。当通知被收到后,我们就可以直接点击接受到的通知,应用又会被重新运行到前台。


关于Push notification,在我们的开发者网站上,有一篇文章(client)和一篇文章(server)详细介绍了它的机制。这里我不想讲太多的东西。有兴趣的同学们可以详读那篇文章。今天在这里,我来和大家分析一个具体的实例,以更好地了解如何在Ubuntu手机上实现这个功能。




在上述的图中可以看出来,整个系统的组成分两部分:客户端及服务器端。在服务器端,又分为一个PushSever (https://push.ubuntu.com)及一个App Server。App server是用来管理我们的用户的Nick Name及Token的。在它的里面,有一个数据库。


为了测试,开发者必须有一个Ubuntu One的账号。我们需要在手机的“系统设置”里的账号中创建这个账号。


当一个QML应用在使用:


import Ubuntu.PushNotifications 0.1

PushClient {
    id: pushClient
    Component.onCompleted: {
        notificationsChanged.connect(messageList.handle_notifications)
        error.connect(messageList.handle_error)
    }
    appId: "com.ubuntu.developer.push.hello_hello"
}


当我们使用上面的API后,push server将向我们的客户端发送一个token。这个token依赖于手机自己的参数及上面所看到的“appId”。利用这个token,我们可以向我们的应用服务器注册,并存于应用服务器端中。当我们需要发送信息的时候,我们必须注册一个类似nickname的东西。这个nickname将和我们手机客户端的token绑定。每当另外一个nickname想像我们发送信息时,应用服务器端可以通过数据库的查询来得到我们的token,从而更进一步通过push server来向我们的客服端推送信息。如果我们的客户端想向其它的客户端发送信息,这其中的道理,也是和刚才一样。


目前,在我们的开发者网站并没有PushClient的具体的介绍。我们可以使用在文章“ 如何得到QML package的详细API接口”中的方法来了解这个API。

Push server是用来推送信息。它位于 https://push.ubuntu.com。它只有一个endpoint:/notify。为了向一个用户发送推送信息。应用服务器可以向Push Sever发送一个含有“Content-type: application/json”的HTTP POST信息来推送我们的信息。下面是一个POST body的一个样板内容:


{
        "appid": "com.ubuntu.music_music",
        "expire_on": "2014-10-08T14:48:00.000Z",
        "token": "LeA4tRQG9hhEkuhngdouoA==",
        "clear_pending": true,
        "replace_tag": "tagname",
        "data": {
                "message": "foobar",
                "notification": {
                        "card": {
                                "summary": "yes",
                                "body": "hello",
                                "popup": true,
                                "persist": true
                        }
                        "sound": "buzz.mp3",
                        "tag": "foo",
                        "vibrate": {
                                "duration": 200,
                                "pattern": (200, 100),
                                "repeat": 2
                        }
                        "emblem-counter": {
                                "count": 12,
                                "visible": true
                        }
                }
        }
}



appid: ID of the application that will receive the notification, as described in the client side documentation.
expire_on: Expiration date/time for this message, in ISO8601 Extendend format
token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
data: A JSON object.
从上面的信息格式,我们可以看出来,token是非常重要的一个信息。有了它,我们就可以向我们需要的终端发送推送信息。


我们可以利用我们的SDK来创建一个简单的例程。下面简单介绍一下我们的主要的文件main.qml:


import QtQuick 2.0
import Qt.labs.settings 1.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1 as ListItem
import Ubuntu.PushNotifications 0.1
import "components"

MainView {
    id: "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.ralsina.hello"

    automaticOrientation: true
    useDeprecatedToolbar: false

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

    Settings {
        property alias nick: chatClient.nick
        property alias nickText: nickEdit.text
        property alias nickPlaceholder: nickEdit.placeholderText
        property alias nickEnabled: nickEdit.enabled
    }

    states: [
        State {
            name: "no-push-token"
            when: (pushClient.token == "")
            PropertyChanges { target: nickEdit; readOnly: true}
            PropertyChanges { target: nickEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: false}
            PropertyChanges { target: loginButton; enabled: false}
            PropertyChanges { target: loginButton; text: "Login"}
        },
        State {
            name: "push-token-not-registered"
            when: ((pushClient.token != "") && (chatClient.registered == false))
            PropertyChanges { target: nickEdit; readOnly: false}
            PropertyChanges { target: nickEdit; text: ""}
            PropertyChanges { target: nickEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: false}
            PropertyChanges { target: loginButton; enabled: true}
            PropertyChanges { target: loginButton; text: "Login"}
        },
        State {
            name: "registered"
            when: ((pushClient.token != "") && (chatClient.registered == true))
            PropertyChanges { target: nickEdit; readOnly: true}
            PropertyChanges { target: nickEdit; text: "Your nick is " + chatClient.nick}
            PropertyChanges { target: messageEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: true}
            PropertyChanges { target: loginButton; enabled: true}
            PropertyChanges { target: loginButton; text: "Logout"}
        }
    ]

    state: "no-push-token"

    ChatClient {
        id: chatClient
        onError: {messageList.handle_error(msg)}
        token: {
            var i = {
                "from" : "",
                "to" :  "",
                "message" : "Token: " + pushClient.token
            }

            if ( pushClient.token )
                messagesModel.insert(0, i);
            console.log("token is changed!");
            return pushClient.token;
        }
    }

    PushClient {
        id: pushClient
        Component.onCompleted: {
            notificationsChanged.connect(messageList.handle_notifications)
            error.connect(messageList.handle_error)
            onTokenChanged: {
                console.log("token: +" + pushClient.token );
                console.log("foooooo")
            }
        }
        appId: "com.ubuntu.developer.ralsina.hello_hello"

    }

    TextField {
        id: nickEdit
        placeholderText: "Your nickname"
        inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
        anchors.left: parent.left
        anchors.right: loginButton.left
        anchors.top: parent.top
        anchors.leftMargin: units.gu(.5)
        anchors.rightMargin: units.gu(1)
        anchors.topMargin: units.gu(.5)
        onAccepted: { loginButton.clicked() }
    }

    Button {
        id: loginButton
        anchors.top: nickEdit.top
        anchors.right: parent.right
        anchors.rightMargin: units.gu(.5)
        onClicked: {
            if (chatClient.nick) { // logout
                chatClient.nick = ""
            } else { // login
                chatClient.nick = nickEdit.text
            }
        }
    }

    TextField {
        id: messageEdit
        inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
        anchors.right: parent.right
        anchors.left: parent.left
        anchors.top: nickEdit.bottom
        anchors.topMargin: units.gu(1)
        anchors.rightMargin: units.gu(.5)
        anchors.leftMargin: units.gu(.5)
        placeholderText: "Your message"
        onAccepted: {
            console.log("sending " + text)
            var idx = text.indexOf(":")
            var nick_to = text.substring(0, idx).trim()
            var msg = text.substring(idx+1, 9999).trim()
            var i = {
                "from" :  chatClient.nick,
                "to" :  nick_to,
                "message" : msg
            }
            var o = {
                enabled: annoyingSwitch.checked,
                persist: persistSwitch.checked,
                popup: popupSwitch.checked,
                sound: soundSwitch.checked,
                vibrate: vibrateSwitch.checked,
                counter: counterSlider.value
            }
            chatClient.sendMessage(i, o)
            i["type"] = "sent"
            messagesModel.insert(0, i)
            text = ""
        }
    }
    ListModel {
        id: messagesModel
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Register by typing your nick and clicking Login."
        }
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Send messages in the form \"destination: hello\""
        }
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Slide from the bottom to control notification behaviour."
        }
    }

    UbuntuShape {
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: notificationSettings.bottom
        anchors.top: messageEdit.bottom
        anchors.topMargin: units.gu(1)
        ListView {
            id: messageList
            model: messagesModel
            anchors.fill: parent
            delegate: Rectangle {
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if (from != "") {
                            messageEdit.text = from + ": "
                            messageEdit.focus = true
                        }
                    }
                }
                height: label.height + units.gu(2)
                width: parent.width
                Rectangle {
                    color: {
                        "info": "#B5EBB9",
                        "received" : "#A2CFA5",
                        "sent" : "#FFF9C8",
                        "error" : "#FF4867"}[type]
                    height: label.height + units.gu(1)
                    anchors.fill: parent
                    radius: 5
                    anchors.margins: units.gu(.5)
                    Text {
                        id: label
                        text: "<b>" + ((type=="sent")?to:from) + ":</b> " + message
                        wrapMode: Text.Wrap
                        width: parent.width - units.gu(1)
                        x: units.gu(.5)
                        y: units.gu(.5)
                        horizontalAlignment: (type=="sent")?Text.AlignRight:Text.AlignLeft
                    }
                }
            }

            function handle_error(error) {
                messagesModel.insert(0, {
                     "from" :  "",
                     "to" :  "",
                     "type" :  "error",
                     "message" : "<b>ERROR: " + error + "</b>"
                })
            }

            function handle_notifications(list) {
                list.forEach(function(notification) {
                    var item = JSON.parse(notification)
                    item["type"] = "received"
                    messagesModel.insert(0, item)
                })
            }
        }
    }

    Panel {
        id: notificationSettings
        anchors {
            left: parent.left
            right: parent.right
            bottom: parent.bottom
        }
        height: item1.height * 9
        UbuntuShape {
            anchors.fill: parent
            color: Theme.palette.normal.overlay
            Column {
                id: settingsColumn
                anchors.fill: parent
                ListItem.Header {
                    text: "<b>Notification Settings</b>"
                }
                ListItem.Standard {
                    id: item1
                    text: "Enable Notifications"
                    control: Switch {
                        id: annoyingSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Enable Popup"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: popupSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Persistent"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: persistSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Make Sound"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: soundSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Vibrate"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: vibrateSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Counter Value"
                    enabled: annoyingSwitch.checked
                    control: Slider {
                        id: counterSlider
                        value: 42
                    }
                }
                Button {
                    text: "Set Counter Via Plugin"
                    onClicked: { pushClient.count = counterSlider.value; }
                }
                Button {
                    text: "Clear Persistent Notifications"
                    onClicked: { pushClient.clearPersistent([]); }
                }
            }
        }
    }
}

这里,在上面创建一个nickname的输入框及一个login按钮。紧接着,我们创建一个输入信息的对话框。再紧接着,我们创建了一个listview来显示状态,提示信息,或来往的信息。




ChatClient.qml文件的定义如下:


import QtQuick 2.0
import Ubuntu.Components 0.1

Item {
    property string nick
    property string token
    property bool registered: false
    signal error (string msg)
    onNickChanged: {
        if (nick) {
            console.log("Nick is changed!");
            register()
        } else {
            registered = false
        }
    }
    onTokenChanged: {
        console.log("Token is changed!");
        register()
    }

    function register() {
        console.log("registering ", nick, token);
        if (nick && token) {
            console.log("going to make a request!");

            var req = new XMLHttpRequest();
            req.open("post", "http://direct.ralsina.me:8001/register", true);
//            req.open("post", "http://127.0.0.1:8001/register", true);
            req.setRequestHeader("Content-type", "application/json");
            req.onreadystatechange = function() { // Call a function when the state changes.
                if(req.readyState == 4) {
                    if (req.status == 200) {
                        console.log("response: " + JSON.stringify(req.responseText));
                        registered = true;
                    } else {
                        error(JSON.parse(req.responseText)["error"]);
                    }
                }
            }

            console.log("content: " + JSON.stringify(JSON.stringify({"nick" : nick.toLowerCase(),
                                                                     "token": token
                                                                    })));
            req.send(JSON.stringify({
                "nick" : nick.toLowerCase(),
                "token": token
            }))
        }
    }

    /* options is of the form:
      {
          enabled: false,
          persist: false,
          popup: false,
          sound: "buzz.mp3",
          vibrate: false,
          counter: 5
      }
    */
    function sendMessage(message, options) {
        var to_nick = message["to"]
        var data = {
            "from_nick": nick.toLowerCase(),
            "from_token": token,
            "nick": to_nick.toLowerCase(),
            "data": {
                "message": message,
                "notification": {}
            }
        }
        if (options["enabled"]) {
            data["data"]["notification"] = {
                "card": {
                    "summary": nick + " says:",
                    "body": message["message"],
                    "popup": options["popup"],
                    "persist": options["persist"],
                    "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]
                }
            }
            if (options["sound"]) {
                data["data"]["notification"]["sound"] = options["sound"]
            }
            if (options["vibrate"]) {
                data["data"]["notification"]["vibrate"] = {
                    "duration": 200
                }
            }
            if (options["counter"]) {
                data["data"]["notification"]["emblem-counter"] = {
                    "count": Math.floor(options["counter"]),
                    "visible": true
                }
            }
        }
        var req = new XMLHttpRequest();
        req.open("post", "http://direct.ralsina.me:8001/message", true);
        req.setRequestHeader("Content-type", "application/json");
        req.onreadystatechange = function() {//Call a function when the state changes.
            if(req.readyState == 4) {
                if (req.status == 200) {
                    registered = true;
                } else {
                    error(JSON.parse(req.responseText)["error"]);
                }
            }
        }
        req.send(JSON.stringify(data))
    }
}

这个是用来向应用服务器发送注册信息及发送信息的。这里我们使用了一个已经建立好的应用服务器在http://direct.ralsina.me:8001。


这里,我们必须在手机或者我们的模拟器中创建一个Ubuntu One的账号,否则应用将不会运行成功。


我们同时运行我们的手机和模拟器,我们可以看到如下的画面:

   


  


整个“hello”的源码在:git clone https://gitcafe.com/ubuntu/example-client.git


整个server的源码在地址:git clone https://gitcafe.com/ubuntu/example-server.git


为了能够运行应用服务器,我们必须在服务器上安装相应的component,并选好自己的口地址(比如8001),这个在服务器代码中的config.js中可以找到:


module.exports = config = {
    "name" : "pushAppServer"
    ,"app_id" : "appEx"
    ,"listen_port" : 8000
    ,"mongo_host" : "localhost"
    ,"mongo_port" : 27017
    ,"mongo_opts" : {}
    ,"push_url": "https://push.ubuntu.com"
    ,"retry_batch": 5
    ,"retry_secs" : 30
    ,"happy_retry_secs": 5
    ,"expire_mins": 120
    ,"no_inbox": true
    ,"play_notify_form": true
}

然后运行:

$nodejs server.js

这样服务器就搭建好了。





作者:UbuntuTouch 发表于2015/4/9 17:47:56 原文链接
阅读:115 评论:0 查看评论

Read more
UbuntuTouch

我们发现在使用SDK创建HTML5应用的时候,模版应用会产生如下的代码:


    <link href="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />    
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/popovers.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/list.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/toolbars.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/shape.js"></script>


上述代码显示,当我们的应用在不同的平台上依赖于系统所提供ambiance文件,同样的应用可能会有不同的表现形式。为了使得我们的HTML在不同的平台上显示相同,我们可以使用一个工具来完成。


1)首先我们来检查我们的awk


在Shell中打入如下的命令:

 ls -altr /etc/alternatives/awk



在我的地方显示为gawk,如果你的版本是:

liuxg@liuxg:~$ ls -altr /etc/alternatives/awk
lrwxrwxrwx 1 root root 13  9月 27  2014 /etc/alternatives/awk -> /usr/bin/mawk
liuxg@liuxg:~$ 

请按照如下的步骤来安装gwak:

$sudo apt-get install gawk

2)下载Ubuntu-html5-theme脚本


我们可以在地址

http://bazaar.launchpad.net/~dbarth/ubuntu-html5-theme/cmdline-tool/download/head:/ubuntuhtml5theme-20150319111737-5oucox80hsx3rmj1-1/ubuntu-html5-theme

下载ubuntu-html5-theme脚本,并把它修改为可以执行的文件:

$chmod +x ubuntu-html5-theme

我们可以把这个脚本放到任何一个可以被执行的目录中,比如/usr/local/bin/,这样这个文件可以在任何一个地方就像一个系统的命令一样来执行.


3)创建我们HTML5应用

我们可以按照我们的SDK的步骤来创建我们的HTML5应用,并进入我们的HTML5应用的根目录。打入如下的指令:

# To list the available HTML5 toolkit / theme releases:
$ ubuntu-html5-theme list
trunk
14.10
14.04
13.10
rtm-14.09

# To install a toolkit release in the current project directory
$ ubuntu-html5-theme install 14.10
Downloading release 14.10...
Branched 177 revisions.

# To convert the index.html file of an existing project
$ ubuntu-html5-theme convert

当我们打入“ubuntu-html5-theme install 14.10”命令后,我们的项目的根目录下会产生一个叫做“ambiance”的子目录:






当我们打入“ubuntu-html5-theme convert”命令后,我们的index.html文件中的如下行发生变化:

    <script src="ambiance/js/fast-buttons.js"></script>
    <script src="ambiance/js/core.js"></script>
    <script src="ambiance/js/buttons.js"></script>
    <script src="ambiance/js/dialogs.js"></script>
    <script src="ambiance/js/page.js"></script>
    <script src="ambiance/js/pagestacks.js"></script>
    <script src="ambiance/js/tab.js"></script>
    <script src="ambiance/js/tabs.js"></script>

我们可以看到在文章一开始中的“/usr/share/ubuntu-html5-ui-toolkit/0.1/”被剔除了,我们使用本地应用自己所带的文件。

在本应用中所用的源码在地址:git clone https://gitcafe.com/ubuntu/html5test.git



作者:UbuntuTouch 发表于2015/4/8 15:27:13 原文链接
阅读:114 评论:0 查看评论

Read more
UbuntuTouch

我们知道对于很多的网路应用来说,网路的连接信息对于我们来说非常重要。我们有必要对网路的连接信息进行监测。一旦网路连接断开,我们需要提醒用户或做一些处理。在Ubuntu平台上,我们可以使用connectivity库来查看。


我们可以利用SDK的模版来创建一个最简单的QML应用。因为我们要使用connectivity,所以我们必须加入“connectivity”的security policy。




在我们的开发者网站上虽然也有NetworkStatus的介绍,但是可能并不是很全,我们可以使用如下的命令来得到更多的信息:


$qmlplugindump Ubuntu.Connectivity 1.0


显示的结果如下:


import QtQuick.tooling 1.1

// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump Ubuntu.Connectivity 1.0'

Module {
    Component {
        name: "NetworkingStatus"
        prototype: "ubuntu::connectivity::NetworkingStatus"
        exports: ["NetworkingStatus 1.0"]
        isCreatable: false
        isSingleton: true
        exportMetaObjectRevisions: [0]
        Property { name: "online"; type: "bool"; isReadonly: true }
        Property { name: "limitedBandwith"; type: "bool"; isReadonly: true }
        Signal {
            name: "onlineChanged"
            Parameter { name: "value"; type: "bool" }
        }
        Signal {
            name: "limitedBandwithChanged"
            Parameter { name: "value"; type: "bool" }
        }
    }
    Component {
        name: "ubuntu::connectivity::NetworkingStatus"
        prototype: "QObject"
        Enum {
            name: "Limitations"
            values: {
                "Bandwith": 0
            }
        }
        Enum {
            name: "Status"
            values: {
                "Offline": 0,
                "Connecting": 1,
                "Online": 2
            }
        }
        Property { name: "limitations"; type: "QVector<Limitations>"; isReadonly: true }
        Property { name: "status"; type: "Status"; isReadonly: true }
        Signal {
            name: "statusChanged"
            Parameter { name: "value"; type: "Status" }
        }
    }
}

这里我们可以看到有一个叫做“status”的属性。我们可以通过监测这个属性的变化而得到网路的连接状态的变化。我们的main.qml的文件如下:


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Connectivity 1.0

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "networkstatus.ubuntu"

    /*
     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)

    property real margins: units.gu(2)
    property real buttonWidth: units.gu(9)

    Connections {
        target: NetworkingStatus
        // full status can be retrieved from the base C++ class
        // status property
        onStatusChanged: {
            console.log("name: " + value );

            if (value === NetworkingStatus.Offline)
                console.log("Status: Offline")
            if (value === NetworkingStatus.Connecting)
                console.log("Status: Connecting")
            if (value === NetworkingStatus.Online)
                console.log("Status: Online")
        }
    }

    Page {
        title: i18n.tr("Networking Status")

        Column {
            anchors.centerIn: parent
            Label {
                // use the online property
                text: NetworkingStatus.online ? "Online" : "Not online"
                fontSize: "large"
            }
            Label {
                // use the limitedBandwith property
                text: NetworkingStatus.limitedBandwith ? "Bandwith limited" : "Bandwith not limited"
                fontSize: "large"
            }
        }
    }
}

最终运行我们的应用,我们可以看到在wifi断开和连接上时的状态变化:


  


测试代码在: git clone https://gitcafe.com/ubuntu/networkstatus.git




作者:UbuntuTouch 发表于2015/4/13 11:12:13 原文链接
阅读:114 评论:2 查看评论

Read more