Canonical Voices

Stéphane Graber

This is the tenth blog post in this series about LXD 2.0.

LXD logo

Introduction

Juju is Canonical’s service modeling and deployment tool. It supports a very wide range of cloud providers to make it easy for you to deploy any service you want on any cloud you want.

On top of that, Juju 2.0 also includes support for LXD, both for local deployments, ideal for development and as a way to co-locate services on a cloud instance or physical machine.

This post will focus on the local use case, going through the experience of a LXD user without any pre-existing Juju experience.

 

Requirements

This post assumes that you already have LXD 2.0 installed and configured (see previous posts) and that you’re running it on Ubuntu 16.04 LTS.

Setting up Juju

The first thing to do is to install Juju 2.0. On Ubuntu 16.04, it’s as simple as:

stgraber@dakara:~$ sudo apt install juju
Reading package lists... Done
Building dependency tree 
Reading state information... Done
The following additional packages will be installed:
 juju-2.0
Suggested packages:
 juju-core
The following NEW packages will be installed:
 juju juju-2.0
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 39.7 MB of archives.
After this operation, 269 MB of additional disk space will be used.
Do you want to continue? [Y/n] 
Get:1 http://us.archive.ubuntu.com/ubuntu xenial-updates/main amd64 juju-2.0 amd64 2.0~beta7-0ubuntu1.16.04.1 [39.6 MB]
Get:2 http://us.archive.ubuntu.com/ubuntu xenial-updates/main amd64 juju all 2.0~beta7-0ubuntu1.16.04.1 [9,556 B]
Fetched 39.7 MB in 0s (53.4 MB/s)
Selecting previously unselected package juju-2.0.
(Reading database ... 255132 files and directories currently installed.)
Preparing to unpack .../juju-2.0_2.0~beta7-0ubuntu1.16.04.1_amd64.deb ...
Unpacking juju-2.0 (2.0~beta7-0ubuntu1.16.04.1) ...
Selecting previously unselected package juju.
Preparing to unpack .../juju_2.0~beta7-0ubuntu1.16.04.1_all.deb ...
Unpacking juju (2.0~beta7-0ubuntu1.16.04.1) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up juju-2.0 (2.0~beta7-0ubuntu1.16.04.1) ...
Setting up juju (2.0~beta7-0ubuntu1.16.04.1) ...

Once that’s done, we can bootstrap a new “controller” using LXD. This means that Juju will not modify anything on your host, it will instead install its management service inside a LXD container.

Here, we’ll be creating a controller called “test” with:

stgraber@dakara:~$ juju bootstrap test localhost
Creating Juju controller "local.test" on localhost/localhost
Bootstrapping model "admin"
Starting new instance for initial controller
Launching instance
 - juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0
Installing Juju agent on bootstrap instance
Preparing for Juju GUI 2.1.2 release installation
Waiting for address
Attempting to connect to 10.178.150.72:22
Logging to /var/log/cloud-init-output.log on remote host
Running apt-get update
Running apt-get upgrade
Installing package: curl
Installing package: cpu-checker
Installing package: bridge-utils
Installing package: cloud-utils
Installing package: cloud-image-utils
Installing package: tmux
Fetching tools: curl -sSfw 'tools from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s ' --retry 10 -o $bin/tools.tar.gz <[https://streams.canonical.com/juju/tools/agent/2.0-beta7/juju-2.0-beta7-xenial-amd64.tgz]>
Bootstrapping Juju machine agent
Starting Juju machine agent (jujud-machine-0)
Bootstrap agent installed
Waiting for API to become available: upgrade in progress (upgrade in progress)
Waiting for API to become available: upgrade in progress (upgrade in progress)
Waiting for API to become available: upgrade in progress (upgrade in progress)
Bootstrap complete, local.test now available.

This should take about a minute, at which point you’ll see a new LXD container running:

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |          IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+

On the Juju side of things, you can confirm that it’s responding and that nothing is running yet:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 

[Machines] 
ID STATE DNS INS-ID SERIES AZ

You can also access the Juju GUI in your web browser with:

stgraber@dakara:~$ juju gui
Opening the Juju GUI in your browser.
If it does not open, open this URL:
https://10.178.150.72:17070/gui/97fa390d-96ad-44df-8b59-e15fdcfc636b/

Juju web UI

Though I prefer the command line so that’s what I’ll be using next.

Deploying a minecraft server

So lets start with something very trivial, just deploy a service that uses a single Juju unit in a single container.

stgraber@dakara:~$ juju deploy cs:trusty/minecraft
Added charm "cs:trusty/minecraft-3" to the model.
Deploying charm "cs:trusty/minecraft-3" with the charm series "trusty".

This should return pretty much immediately. It however doesn’t mean the service is already up and running. Instead you’ll want to look at “juju status”:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
minecraft maintenance false cs:trusty/minecraft-3 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
minecraft/1 maintenance executing 2.0-beta7 1 10.178.150.74 (install) Installing java 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
1 started 10.178.150.74 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 trusty 

Here we can see it’s currently busy installing java in the LXD container it just created.

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |          IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 | RUNNING | 10.178.150.74 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+----------------------+------+------------+-----------+

After a little while, the service will be done deploying as can be seen here:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
minecraft active false cs:trusty/minecraft-3 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
minecraft/1 active idle 2.0-beta7 1 25565/tcp 10.178.150.74 Ready 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
1 started 10.178.150.74 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-1 trusty

At which point you can fire up your minecraft client, point it at 10.178.150.74 on port 25565 and play with your all new minecraft server!

When you want to get rid of it, just run:

stgraber@dakara:~$ juju destroy-service minecraft

Wait a few seconds and everything will be gone.

Deploying a more complex web application

Juju’s main focus is on modeling complex services and deploying them in a scallable way.

To better show that, lets deploy a Juju “bundle”. This bundle is a basic web service, made of a website, an API endpoint, a database, a static web server and a reverse proxy.

So that’s going to expand to 4, inter-connected LXD containers.

stgraber@dakara:~$ juju deploy cs:~charmers/bundle/web-infrastructure-in-a-box
added charm cs:~hp-discover/trusty/node-app-1
service api deployed (charm cs:~hp-discover/trusty/node-app-1 with the series "trusty" defined by the bundle)
annotations set for service api
added charm cs:trusty/mongodb-3
service mongodb deployed (charm cs:trusty/mongodb-3 with the series "trusty" defined by the bundle)
annotations set for service mongodb
added charm cs:~hp-discover/trusty/nginx-4
service nginx deployed (charm cs:~hp-discover/trusty/nginx-4 with the series "trusty" defined by the bundle)
annotations set for service nginx
added charm cs:~hp-discover/trusty/nginx-proxy-3
service nginx-proxy deployed (charm cs:~hp-discover/trusty/nginx-proxy-3 with the series "trusty" defined by the bundle)
annotations set for service nginx-proxy
added charm cs:~hp-discover/trusty/website-3
service website deployed (charm cs:~hp-discover/trusty/website-3 with the series "trusty" defined by the bundle)
annotations set for service website
related mongodb:database and api:mongodb
related website:nginx-engine and nginx:web-engine
related api:website and nginx-proxy:website
related nginx-proxy:website and website:website
added api/0 unit to new machine
added mongodb/0 unit to new machine
added nginx/0 unit to new machine
added nginx-proxy/0 unit to new machine
deployment of bundle "cs:~charmers/bundle/web-infrastructure-in-a-box-10" completed

A few seconds later, you’ll see all the LXD containers running:

stgraber@dakara:~$ lxc list juju-
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
|                         NAME                        |  STATE  |           IPV4        | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-745d1be3-e93d-41a2-80d4-fbe8714230dd-machine-0 | RUNNING | 10.178.150.72 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-2 | RUNNING | 10.178.150.98 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-3 | RUNNING | 10.178.150.29 (eth0)  |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-4 | RUNNING | 10.178.150.202 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+
| juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-5 | RUNNING | 10.178.150.214 (eth0) |      | PERSISTENT | 0         |
+-----------------------------------------------------+---------+-----------------------+------+------------+-----------+

After a couple of minutes, all the services should be deployed and running:

stgraber@dakara:~$ juju status
[Services] 
NAME STATUS EXPOSED CHARM 
api unknown false cs:~hp-discover/trusty/node-app-1 
mongodb unknown false cs:trusty/mongodb-3 
nginx unknown false cs:~hp-discover/trusty/nginx-4 
nginx-proxy unknown false cs:~hp-discover/trusty/nginx-proxy-3 
website false cs:~hp-discover/trusty/website-3 

[Relations] 
SERVICE1 SERVICE2 RELATION TYPE 
api mongodb database regular 
api nginx-proxy website regular 
mongodb mongodb replica-set peer 
nginx website nginx-engine subordinate 
nginx-proxy website website regular 

[Units] 
ID WORKLOAD-STATUS JUJU-STATUS VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE 
api/0 unknown idle 2.0-beta7 2 8000/tcp 10.178.150.98 
mongodb/0 unknown idle 2.0-beta7 3 27017/tcp,27019/tcp,27021/tcp,28017/tcp 10.178.150.29 
nginx-proxy/0 unknown idle 2.0-beta7 5 80/tcp 10.178.150.214 
nginx/0 unknown idle 2.0-beta7 4 10.178.150.202 
 website/0 unknown idle 2.0-beta7 10.178.150.202 

[Machines] 
ID STATE DNS INS-ID SERIES AZ 
2 started 10.178.150.98 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-2 trusty 
3 started 10.178.150.29 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-3 trusty 
4 started 10.178.150.202 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-4 trusty 
5 started 10.178.150.214 juju-97fa390d-96ad-44df-8b59-e15fdcfc636b-machine-5 trusty

At which point, you can hit the reverse proxy on port 80 with http://10.178.150.214 and you’ll hit the Juju academy web service.

Juju Academy web service

Cleaning everything up

If you want to get rid of all the containers Juju created and don’t mind having to bootstrap again next time, the easiest way to destroy everything is with:

stgraber@dakara:~$ juju destroy-controller test --destroy-all-models
WARNING! This command will destroy the "local.test" controller.
This includes all machines, services, data and other resources.

Continue [y/N]? y
Destroying controller
Waiting for hosted model resources to be reclaimed
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines, 5 services
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 4 machines
Waiting on 1 model, 2 machines
Waiting on 1 model
Waiting on 1 model
All hosted models reclaimed, cleaning up controller machines

And we can confirm that it’s all gone:

stgraber@dakara:~$ lxc list juju-
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

Conclusion

Juju 2.0’s built-in LXD support makes for a very clean way to test a whole variety of services.

There are quite a few pre-made “bundles” for you to deploy in the Juju charm store and even more “charms” that you can use to piece together the architecture you want.

Juju with LXD is the perfect solution for easily developing anything from a small web service to a big scale out infrastructure, all on your own machine, without creating a mess on your system!

Extra information

The Juju website can be found at: http://www.ubuntu.com/cloud/juju
The Juju charm store is available at: https://jujucharms.com

The main LXD website is at: https://linuxcontainers.org/lxd
Development happens on Github at: https://github.com/lxc/lxd
Mailing-list support happens on: https://lists.linuxcontainers.org
IRC support happens in: #lxcontainers on irc.freenode.net
Try LXD online: https://linuxcontainers.org/lxd/try-it

Read more
facundo

Universidad y finales


El otro día estaba charlando con un amigo sobre algo relacionado a la Universidad, a una materia que cursé en la misma, y no me acordaba cuando la había rendido. En el momento no le dí importancia, pensé "cuando vuelvo a casa me fijo en la libreta universitaria".

Claro, nunca me fijé, porque la libreta está ahí escondida en un cajón de dificil acceso, el típico lugar donde alguien más o menos ordenado tiene los títulos viejos, certificados, papeles importantes diversos, y eso (los que no son medianamente ordenados en general no tienen puta idea donde están estas cosas).

Primera página de la libreta

Entones, se me ocurrió que, habiendo sido la Universidad una etapa tan importante en mi vida, podría tener la info de cuando rendí las materias mucho más a mano.

Busqué la libreta, pasé todas las materias (con fecha de final y la nota), y ahora guardo todo eso acá. Lástima que no tengo los nombres de las/los profesoras/es (me acuerdo algunos, pero la mayoría no...).

Read more
Daniel Holbach

Announcing the Snappy Playpen

With snaps and the store, it finally became easy again to publish software in Ubuntu. Snappy Playpen is a project in which we want to collaboratively snap software, learn from each other and document best-practices.

Snappy Playpen is on Github and it's where we want to work together on snapping new software. This will provide excellent examples to new users of snapcraft, we will be able to document best practices, learn from each other and create an incubator for new snaps to be added to the store.

Snappy Playpen won't be a collection of production-ready snaps, we are treating it a bit like a combination of research project and documentation.

If you are curious, just check out our main github page and read the docs there. It's easy and we're quite accessible. Find us on gitter, IRC or the mailing list to find out how to get involved.

You can get started at any time and contribute whatever you feel makes sense, but we want to host themed "sprint" weeks as well. If you have suggestions (e.g. a IoT-related week, a KDE-related week, server app, etc.), let us know. For those weeks we will make sure we have experts there to help us figure this out together.

Next week will be our first Snappy Playpen sprint and it will be a "free for all" week. This will help us to figure out the details and learn about what you all exactly want to do.

On Tuesday, 7th June 2016, we will make a big push and make sure our snapd and snapcraft engineers are there to answer questions and help figure out solutions together. Mark the day in your calendar and check out our docs to find out how to get started.

  • WHAT: Snappy playpen sprint
  • WHEN: Tuesday, 7th June 2016 all day
  • WHERE: Join us on gitter or IRC

Read more
liam zheng

Ubuntu手机现在迎来第十一个重要更新:OTA-11,这次更新的亮点主要为Wifi Display(无线投射模式),借助Wifi Display的功能用户可以体验无线投射屏幕加桌面融合(convergence)的巨大便利。只要将Ubuntu手机连接至显示器或电视,桌面版的Ubuntu模式即可使用,一个移动设备可变身集多窗口模式的全尺寸桌面。目前该功能仅支持魅族PRO5 Ubuntu版,后续还将支持其他型号Ubuntu手机。

Ubuntu手机今天迎来第十一个重要更新:OTA-11,这次更新主要的亮点为Wifi Display(屏幕无线投射),借助Wifi Display的功能用户可以体验无线投射屏幕加桌面融合(convergence)的巨大便利。只要将Ubuntu手机连接至显示器或电视(需要支持Miracast),桌面版的Ubuntu界面即可使用,一个移动设备将变身集多窗口模式的全尺寸桌面环境。目前该功能仅支持魅族PRO5 Ubuntu版,后续还将支持其他型号Ubuntu手机。

 

Wifi Display:点击观看

Wifi Display使用的是魅族PRO 5 Ubuntu版内建的p2p(peer-to-peer)连接方式启动Ubuntu桌面模式,如直接将手机通过Wifi连接显示器或电视,那么手机将充当触摸板的功能,如已连接蓝牙鼠标、键盘,则将拥有传统桌面模式的体验,重要的是,手机的短信、电话功能可展现在外接显示器上。

 

Scope支持自动横屏模式

在OTA-11以前,Unity 8 Dash的Scope只能竖屏显示。而在OTA-11更新后,Scope将可以横屏显示,对于喜欢横屏的用户来说又多一个选择。并且主页Scope(今日、Nearby)会在解锁屏幕前完成更新,解锁屏幕后可获取最新的信息。

 

新增繁体中文输入法

Ubuntu 手机OTA-11又一新特点是支持繁体中文输入法——注音键盘布局。可通过设置——语言&文字——键盘布局,选择注音输入法即可使用。

 

DGU让Scope、App自动适配多端显示

桌面融合(convergence)作为Ubuntu手机的杀手锏功能,已经让给很多经常背包的用户减轻不必要的负担,作为开发者而言,Unity 8用户界面将支持DGU(dynamic grid units),在开发应用和Scope时更简单,一次开发就可以在多个显示端自动适配。

 

平板:M10性能改善

OTA-11是BQ M10 Ubuntu版的第一个大版本更新,改善操控体验,图形处理,提示性能。

 

其他改进总结:

  • 地理位置服务改善,获取地理位置更加准确;

  • 网络管理器更新到1.2版,在上网时更加安全;

  • 应用程序支持多窗口显示(M10桌面融合);

  • UITK滚动条设计更新,Head支持副标题;

  • VPN支持用户名和密码认证;

  • 浏览器应用改进;支持google hangout,重新设计的权限提示对话框;

  • 在桌面融合模式蓝牙鼠标反应更敏捷;

  • 修复了以下bug:语言包翻译,性能问题,自定义通知声音

Read more
UbuntuTouch

由于Ubuntu手机平台安全的限制,在Ubuntu手机的应用中我们只能访问自己的空间.如果我们所需要访问的文件不存在于我们应用所在的空间,我们是没有办法访问到的.我们的应用只能访问系统提供的库.如果系统没有所需要的库的话,我们通过可以通过如下的方法实现:

  1. 在我的应用中把应用的所需要的第三方的源码放入到我的应用项目中,并形成plugin(通常是C++代码)从而被应用调用
  2. 在我们的应用中把第三方的库打包进我们的应用包中,并在我的plugin(通常是C++代码)中调用,进而被应用调用
我们可以选择Ubuntu SDK所提供的标准的带有C++ plugin的模版来实现.对于第二种情况,我们同样需要在我们的项目中加入所需要的文件,并打包到我们的应用中去.第二种方法对于一些不存在于Ubuntu SDK所提供的标准库里的情况是非常有用的.比如我们可以得到PDF reader的库的源码,并编译成为我们自己的库.最终我们可以把这些库一起打包并安装到我们的手机中使用.

在今天的这篇文章中,我们就第二种方法来详细介绍.


1)创建一个最小的shared库


我已经创建好了一个最基本的shared库.开发者可以通过如下的方式来下载我们的代码:

$ git clone https://github.com/liu-xiao-guo/studentlib_shared

我们的这个库非常地简单:

Student.h

#include<string>

class Student{
private:
	std::string name;
public:
	Student(std::string);
	virtual void display();
};

student.cpp

#include <iostream>
#include "Student.h"
using namespace std;

Student::Student(string name):name(name){}

void Student::display(){
	cout << "A student with name " << this->name << endl;
}

接下来,我们以armhf chroot为例,当然我们也可以对i386使用同样的方法.我们通过我们已经安装好的armhf chroot来编译我们的这个库.首先我们通过如下的方式进入到我的chroot:

$ click chroot -aarmhf -fubuntu-sdk-15.04 run  

当我们进入到我买的chroot中后,我们就可以开始编译我们的应用库了.我们进入到我们的项目的根目录中,并打入如下的命令

$ mkdir build
$ cd build 
$ cmake ..
$ make

这样在我们的build子目录下就可以看到我们希望的库了.

 

(click-ubuntu-sdk-15.04-armhf)liuxg@liuxg:~/qml/studentlib_shared/build$ ls
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake  libtestStudent.so
(click-ubuntu-sdk-15.04-armhf)liuxg@liuxg:~/qml/studentlib_shared/build$ 


2)创建一个带有plugin的最基本的应用.


我们选择"QML App with C++ plugin (qmake)"模版来创建一个最基本的testplugin应用.在这个应用中,我们将展示如何使用我们上面已经创建好的shared库.



接下来,我们修改我们的mytype.cpp及mytype.h文件:

mytype.h


#ifndef MYTYPE_H
#define MYTYPE_H

#include <QObject>

class MyType : public QObject
{
    Q_OBJECT
    Q_PROPERTY( QString helloWorld READ helloWorld WRITE setHelloWorld NOTIFY helloWorldChanged )

public:
    explicit MyType(QObject *parent = 0);
    Q_INVOKABLE void testlib();
    ~MyType();

Q_SIGNALS:
    void helloWorldChanged();

protected:
    QString helloWorld() { return m_message; }
    void setHelloWorld(QString msg) { m_message = msg; Q_EMIT helloWorldChanged(); }

    QString m_message;
};

#endif // MYTYPE_H

mytype.cpp

#include "mytype.h"
#include "Student.h"

MyType::MyType(QObject *parent) :
    QObject(parent),
    m_message("")
{

}

void MyType::testlib()
{
     Student student("johnddfsfdfdfds");
     student.display();
}

MyType::~MyType() {

}

在上面的代码中,我们已经使用了我们shared库中的display()方法.如果这个时候我们去运行我们的应用的话,我们会发现我们的应用肯定是有错误的.这是因为它根本找不到它所需要的库.在下面我们来尝试把我们的库打包到我们的项目中来.

我们首先在我们的项目中创建一个叫做libs的目录,并把我们的shared库考入到这个目录中.这样整个的项目的目录就像:

liuxg@liuxg:~/qml/testplugin$ tree -L 3
.
├── backend
│   ├── Testplugin
│   │   ├── backend.cpp
│   │   ├── backend.h
│   │   ├── mytype.cpp
│   │   ├── mytype.h
│   │   ├── qmldir
│   │   └── Testplugin.pro
│   └── tests
│       └── unit
├── libs
│   ├── libs.pro
│   ├── libtestStudent.so
│   └── Student.h
├── manifest.json.in
├── po
│   └── testplugin.liu-xiao-guo.pot
├── testplugin
│   ├── Main.qml
│   ├── testplugin.apparmor
│   ├── testplugin.desktop
│   ├── testplugin.png
│   ├── testplugin.pro
│   └── tests
│       ├── autopilot
│       └── unit
├── testplugin.pro
└── testplugin.pro.user

在上面的显示中,我们可以看到在libs目录中除了一个我们看到的那个libtestStudent.so及header文件外,还有一个叫做libs.pro的文件.它的内容如下:

libs.pro

TEMPLATE = lib

load(ubuntu-click)

filetypes = qml png svg js qmltheme jpg qmlproject desktop wav so

OTHER_FILES = filetypes

for(filetype, filetypes) {
  OTHER_FILES += *.$$filetype
}

other_files.path = $${UBUNTU_CLICK_PLUGIN_PATH}
other_files.files = $$OTHER_FILES

INSTALLS += other_files

message(The project1 will be installed in $${UBUNTU_CLICK_PLUGIN_PATH})

在上面的文件中,我们可以通过上面的方法把我们需要的.so文件拷入到我们所需要的目录中.

最后在我们的"testplugin.pro"文件中需要加入我们的目录"libs".

SUBDIRS += testplugin \
           backend/Testplugin \
           libs

在我们的"TestPlugin.pro"中,我们需要加入如下的句子:

LIBS += -L$$PWD/../../libs/ -ltestStudent
INCLUDEPATH += $$PWD/../../libs
DEPENDPATH += $$PWD/../../libs

在我们的QML文件中,我们是这样来调用的:

Main.qml

        ...
        Button {
            anchors.centerIn: parent
            text: "Test lib"

            onClicked: {
                myType.testlib();
            }
        }
        ...

最终,我们来编译并部署我们的应用到我们的手机上:



我们可以查看在我们手机中的安装文件.在我们的desktop中打入如下的命令:

$ adb shell

通过上面的命令进入到手机中,并进入到如下的目录:

/opt/click.ubuntu.com/testplugin.liu-xiao-guo/current/lib/arm-linux-gnueabihf/

我们可以看到我们所需要的文件已经被成功安装到该目录中:




当我们点击在应用中的按钮"Test lib"时,我们可以在我们的SDK IDE中看见输出:



上面输出的字符串就是我们利用我们的库来输出的:

     Student student("johnddfsfdfdfds");
     student.display();

整个项目的源码在https://github.com/liu-xiao-guo/testplugin


作者:UbuntuTouch 发表于2016/5/9 11:28:37 原文链接
阅读:1115 评论:0 查看评论

Read more
UbuntuTouch

我们可以利用Ubuntu SDK中的GeocodeModel来在进行地理编码.比如说,我们给出一个地名,通过GeocodeModel来查询该地理位置的具体的信息,比如经度维度,街道信息等.在今天的文章中,我们通过一个简单的例程来展示如何查询一个地点.

就像我们的API文档中介绍的那样:

    GeocodeModel {
        id: geocodeModel
        plugin: plugin
        autoUpdate: false

        onStatusChanged: {
            mymodel.clear()
            console.log("onStatusChanged")
            if ( status == GeocodeModel.Ready ) {
                var count = geocodeModel.count
                console.log("count: " + geocodeModel.count)
                for ( var i = 0; i < count; i ++ ) {
                    var location = geocodeModel.get(i);
                    mymodel.append( {"location": location})
                }
            }
        }

        onLocationsChanged: {
            console.log("onStatusChanged")
        }

        Component.onCompleted: {
            query = "中国 北京 朝阳 望京"
            update()
        }
    }
在上面的代码中,当我们的GeocodeModel被装载后,我们发送一个查询的请求:

            query = "中国 北京 朝阳 望京"
            update()
当这个请求有结果回来后,在我们的onStatusChanged中返回我们所需要的结果.我们可以通过列表的方式显示我们所需要的结果.我们所有的代码为:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtLocation 5.3
import QtPositioning 5.2

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

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

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

    Plugin {
        id: plugin
        name: "osm"
    }

    ListModel {
        id: mymodel
    }

    PositionSource {
        id: me
        active: true
        updateInterval: 1000
        preferredPositioningMethods: PositionSource.AllPositioningMethods
        onPositionChanged: {
            console.log("lat: " + position.coordinate.latitude + " longitude: " +
                        position.coordinate.longitude);
            console.log(position.coordinate)
            console.log("mapzoom level: " + map.zoomLevel)
            map.coordinate = position.coordinate
        }

        onSourceErrorChanged: {
            console.log("Source error: " + sourceError);
        }
    }

    GeocodeModel {
        id: geocodeModel
        plugin: plugin
        autoUpdate: false

        onStatusChanged: {
            mymodel.clear()
            console.log("onStatusChanged")
            if ( status == GeocodeModel.Ready ) {
                var count = geocodeModel.count
                console.log("count: " + geocodeModel.count)
                for ( var i = 0; i < count; i ++ ) {
                    var location = geocodeModel.get(i);
                    mymodel.append( {"location": location})
                }
            }
        }

        onLocationsChanged: {
            console.log("onStatusChanged")
        }

        Component.onCompleted: {
            query = "中国 北京 朝阳 望京"
            update()
        }
    }

    Page {
        id: page
        header: standardHeader

        PageHeader {
            id: standardHeader
            visible: page.header === standardHeader
            title: "Geocoding"
            trailingActionBar.actions: [
                Action {
                    iconName: "edit"
                    text: "Edit"
                    onTriggered: page.header = editHeader
                }
            ]
        }

        PageHeader {
            id: editHeader
            visible: page.header === editHeader
            leadingActionBar.actions: [
                Action {
                    iconName: "back"
                    text: "Back"
                    onTriggered: {
                        page.header = standardHeader
                    }
                }
            ]
            contents: TextField {
                id: input
                anchors {
                    left: parent.left
                    right: parent.right
                    verticalCenter: parent.verticalCenter
                }
                placeholderText: "input words .."
                text: "中国 北京 朝阳 望京"

                onAccepted: {
                    geocodeModel.query = text
                    geocodeModel.update()
                }
            }
        }

        Item  {
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
                top: page.header.bottom
            }

            Column {
                anchors.fill: parent

                ListView {
                    id: listview
                    clip: true
                    width: parent.width
                    height: parent.height/3
                    opacity: 0.5
                    model: mymodel
                    delegate: Item {
                        id: delegate
                        width: listview.width
                        height: layout.childrenRect.height + units.gu(0.5)

                        Column {
                            id: layout
                            width: parent.width

                            Text {
                                width: parent.width
                                text: location.address.text
                                wrapMode: Text.WordWrap
                            }

                            Text {
                                text: "(" + location.coordinate.longitude + ", " +
                                      location.coordinate.latitude + ")"
                            }

                            Rectangle {
                                width: parent.width
                                height: units.gu(0.1)
                                color: "green"
                            }
                        }

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                console.log("it is clicked")
                                map.coordinate = location.coordinate
                                // We do not need the position info any more
                                me.active = false
                            }
                        }
                    }
                }

                Map {
                    id: map
                    width: parent.width
                    height: parent.height*2/3
                    property var coordinate

                    plugin : Plugin {
                        name: "osm"
                    }

                    zoomLevel: 14
                    center: coordinate

                    MapCircle {
                        center: map.coordinate
                        radius: units.gu(3)
                        color: "red"
                    }

                    Component.onCompleted: {
                        zoomLevel = 14
                    }
                }
            }
        }

        Component.onCompleted: {
            console.log("geocodeModel limit: " + geocodeModel.limit)
        }
    }
}

运行我们的应用:

   
 

整个项目的源码:https://github.com/liu-xiao-guo/geocodemodel

作者:UbuntuTouch 发表于2016/6/7 13:54:14 原文链接
阅读:130 评论:0 查看评论

Read more
UbuntuTouch

有兴趣的朋友可以阅读我们全球开发者网站的文章"On Screen Keyboard tricks".里面讲了许多的东西.在今天的文章中,我们直接来一个活生生的例程来阐述这个问题.


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

    // anchorToKeyboard: true

    Page {
        title: i18n.tr("onscreenkeyboard")
        anchors.fill: parent

        Flickable {
            id: sampleFlickable

            clip: true
            contentHeight: mainColumn.height + units.gu(5)
            anchors {
                top: parent.top
                left: parent.left
                right: parent.right
                bottom: createButton.top
                bottomMargin: units.gu(2)
            }

            Column {
                id: mainColumn

                spacing: units.gu(2)

                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                    margins: units.gu(2)
                }

                TextField {
                    id: username
                    width: parent.width
                    placeholderText: "username"
                }

                TextField {
                    id: password
                    width: parent.width
                    placeholderText: "password"
                }

                TextField {
                    id: email
                    width: parent.width
                    placeholderText: "email"
                }
            }
        }

        Button {
            id: createButton
            text: "Create Account"
            anchors {
                horizontalCenter: parent.horizontalCenter
                bottom: parent.bottom
                margins: units.gu(2)
            }
            onClicked: {
                console.log("it is clicked")
            }
        }
    }
}

在上面的例程中,我们有意识地把:

    anchorToKeyboard: true

注释掉,我们可以运行我们的应用:


从上面的截图中可以看出来,当我们点击最上面的任何一个输入框时,我们最下面的"Createt Account"按钮就被键盘所在的位置遮住了.那么我们怎么才能可以见到我们的按钮呢?答案就是在我们的MainView中,把如下的开关打开:

    anchorToKeyboard: true

再次重新运行我们的应用:



从上面我们可以看出来.当我们点击email进行输入的时候,"Create Account"按钮也自动跑到我们键盘的上面.这样当我们输入完我们的内容的时候,我们可以直接按下按钮来进行创建一个账号,而不用先把键盘弄消失掉,再提交.

整个项目的源码在:https://github.com/liu-xiao-guo/osk

我们来用另外一个例程来展示OSK的用法:

Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

  //  anchorToKeyboard: true

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("osk1")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        TextField {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: units.gu(2)
            placeholderText: "please input something"
        }
    }
}

在上面的例程中,当我们注释掉:

    anchorToKeyboard: true

运行我们的应用,并在最下面的输入框中点击进行输入:

  

从上面可以看出来,当我们进行输入的时候,我们的输入框被键盘挡住了.我们看不见我们的输入框.而当我们把下面的属性设为真时:

  anchorToKeyboard: true

在重新运行我们的应用:

  



从上面我们可以看出来,当我们输入的时候我们可以看见我们的输入框,并可以输入为我们所需要的内容.

整个项目的源码在:https://github.com/liu-xiao-guo/osk1



作者:UbuntuTouch 发表于2016/4/18 13:54:51 原文链接
阅读:269 评论:0 查看评论

Read more
UbuntuTouch

细心开发者如果发现我的以前开发的应用,主页面能够切换的地方,我放置了一个向右的箭头.比如在文章"从零开始创建一个Ubuntu应用 -- 一个小小的RSS阅读器 (1)"最下面的图片中,我们可以看到在列表中,有一个向右的箭头.



在我们的Ubuntu应用中,我们可以利用ListItemLayout来作为我们列表中的每一项的delegate.在这种情况下,我们可以充分利用ProgressionSlot来为我们添加上一个向右的">"符号.比如在下面的例子中:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("progressionslot")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        ListItem {
            anchors {
                left: parent.leftt
                right: parent.right
                top: parent.header.bottom
            }

            height: layout.height + (divider.visible ? divider.height : 0)
            ListItemLayout {
                id: layout
                title.text: "Hello developers!"
                subtitle.text: "I'm a subtitle, I'm tiny!"
                summary.text: "Ubuntu!"
                CheckBox { SlotsLayout.position: SlotsLayout.Leading }
                Icon {
                    name: "message"
                    SlotsLayout.position: SlotsLayout.Trailing;
                    width: units.gu(2)
                }

                ProgressionSlot {}
            }
        }
    }
}

我们利用了一个ProgressionSlot.这样我们的ListItem显示为:



我们可以在上面的显示中,清楚地看见一个向右的箭头.这就是使用ProgressionSlot所带来的.


作者:UbuntuTouch 发表于2016/4/29 11:04:55 原文链接
阅读:303 评论:0 查看评论

Read more
UbuntuTouch

[原]利用SVG在QML中画图

在我先前的文章"在QML应用中使用Canvas来画图"中,我展示如何利用QML中的Canvas来画我们的曲线.今天在我们的文章中,我们来展示如何利用SVG在我们的例程中画我们所需要的曲线.


我们可以直接使用一个已经存在的SVG画画库simple-svg.它的源码位于"https://code.google.com/archive/p/simple-svg/downloads".对于一下开发者来说可能需要VPN才可以下载哦.


我们可以利用我们的SDK中提供的一个标准的"QML App with C++ plugin (qmake)"来创建一个简单的模版.同时,我们修改我们的plugin的代码如下:

mytype.h


#ifndef MYTYPE_H
#define MYTYPE_H

#include <QObject>

class MyType : public QObject
{
    Q_OBJECT
    Q_PROPERTY( QString helloWorld READ helloWorld WRITE setHelloWorld NOTIFY helloWorldChanged )

public:
    explicit MyType(QObject *parent = 0);
    ~MyType();
    Q_INVOKABLE void draw(const int &width, const int &height, const QString &array);

Q_SIGNALS:
    void helloWorldChanged();

protected:
    QString helloWorld() { return m_message; }
    void setHelloWorld(QString msg) { m_message = msg; Q_EMIT helloWorldChanged(); }

    QString m_message;
};

#endif // MYTYPE_H

在上面我们添加了一个叫做"draw"的方法.它的定义如下:

mytype.cpp


#include "mytype.h"
#include "simple_svg.h"

using namespace svg;

MyType::MyType(QObject *parent) :
    QObject(parent),
    m_message("")
{

}

MyType::~MyType() {

}

void MyType::draw(const int &width, const int &height, const QString &array)
{
    // Create the SVG doc
    Dimensions dimensions(width, height);
    Document doc("../svggraph/svggraph/graph.svg", Layout(dimensions, Layout::BottomLeft));

    // Parse our string into an array
    std::istringstream buf(array.toStdString());
    std::istream_iterator<std::string> beg(buf), end;
    std::vector<std::string> tokens(beg, end);

    // Create a line
    Polyline polyline_a(Stroke(1.5, Color::Cyan));

    // Iterate over our array to define line start/end points
    for( int a = 0; a < tokens.size(); a = a + 1 )
    {
        if (tokens.size() < 2) {
            polyline_a << Point(width/(tokens.size())*(a), atoi(tokens[a].c_str())) << Point(width/(tokens.size())*(a+1), atoi(tokens[a].c_str()));
        } else {
            polyline_a << Point(width/(tokens.size()-1)*(a), atoi(tokens[a].c_str()));
        }
    }
    doc << polyline_a;

    // Save the doc
    doc.save();
}

在这里,我们把从地址https://code.google.com/archive/p/simple-svg/downloads下载的文件重新命令为"simple_svg.h",并把它存于位于plugins所在目录的根目录中,也即:

svggraph/backend/Svggraph

我们可以重新编译我们的应用.

在我们的Main.qml中,我们可以利用我们刚刚创建的plugin来画我们的UI.和先前不同的是,这里,我们使用一个叫做Image的而不是一个Canvas.

Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.3
import Svggraph 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: "svggraph.liu-xiao-guo"

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

    MyType {
        id: myType
    }

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("svggraph")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        Rectangle {
            anchors {
                top:page.header.bottom
                left: page.header.left
                right: page.header.right
            }
            height: parent.height - page.header.height

            TextField{
                id:txt
                width:parent.width - units.gu(4)
                anchors.top:parent.top
                anchors.horizontalCenter:parent.horizontalCenter
                anchors.margins:units.gu(2)
                text: "0 10 50 80 60 90"
            }
            Row {
                anchors.top:txt.bottom
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.margins: units.gu(2)
                spacing: units.gu(2)
                Button {
                    id:drawButton
                    anchors.margins:units.gu(2)
                    text:i18n.tr("Draw")
                    enabled:(txt.length)
                    color: UbuntuColors.orange
                    onClicked: {
                        myType.draw(page.width, page.height, txt.text)
                        img.source = ""
                        img.source = Qt.resolvedUrl("graph.svg")
                    }
                }
                Button {
                    id:clearButton
                    anchors.margins:units.gu(2)
                    text:i18n.tr("Clear")
                    onClicked: {
                        myType.draw(page.width, page.height, "")
                        img.source = ""
                        img.source = Qt.resolvedUrl("graph.svg")
                    }
                }
            }
            Image {
                id:img
                anchors.fill:parent
                anchors.margins:units.gu(2)
                cache:false
                source: Qt.resolvedUrl("graph.svg")
            }
        }
    }
}

在这里,我们通过按钮"Draw"把我们所需要的数据传入到plugin中,并生产graph.svg文件,并在UI的Image中得以显示:






整个项目的源码在:https://github.com/liu-xiao-guo/svggraph
作者:UbuntuTouch 发表于2016/4/25 10:36:03 原文链接
阅读:198 评论:0 查看评论

Read more
UbuntuTouch

我们知道在许多的项目中,当切换到一个页面时我们需要对一些资源进行监控.每当发送事件改变时,我们需要进行更新我们的页面.当然每当我们离开我们的页面时,我们就不想做这样的动作.我们可以取消或释放我们所需要的资源以节省资源.当我们需要这么做的时候,我们希望得到页面切换时的事件这样我们可以在合适的时机做出合适的动作.Qt中的Component.incubateObject()为我们提供了这样一个监控的机制.


下面我们用一个简单的例程来说明这个问题:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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: "pagedestroyevent.liu-xiao-guo"

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

    property int index: 0

    Component {
        id: page2Component

        Page {
            id: page


            header: PageHeader {
                id: header
                title: "Second Page"
            }

            Column {
                anchors.centerIn: parent
                spacing: units.gu(5)

                Text {
                    text: "Page: " + index
                }

                Button {
                    text: "Close me"
                    onClicked: pageStack.removePages(pageStack.primaryPage);
                }
            }
        }
    }

    AdaptivePageLayout {
        id: pageLayout
        anchors.fill: parent
        primaryPage: Page {
            header: PageHeader {
                title: "Primary Page"
                flickable: listView
            }

            ListView {
                id: listView
                anchors.fill: parent
                model: 10
                delegate: ListItem {
                    Label { text: modelData }
                    onClicked: {
                        mainview.index = index
                        var incubator = pageLayout.addPageToNextColumn(pageLayout.primaryPage, page2Component);
                        if (incubator && incubator.status === Component.Loading) {
                            incubator.onStatusChanged = function(status) {
                                if (status === Component.Ready) {
                                    // connect page's destruction to decrement model
                                    incubator.object.Component.destruction.connect(function() {
                                        console.log("it is destructed! " + index)
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

在上面的例程中我们运用AdaptivePageLayout来实现我们的布局.运行我们的应用:

  



每当我们打开ListView中的一项的时候,我们通过:
   var incubator = pageLayout.addPageToNextColumn(pageLayout.primaryPage, page2Component);
把我们的新的一页加入到pageStack中,同时,我们可以利用incubator对刚刚加入的页面进行lifecycle的管理:

                        if (incubator && incubator.status === Component.Loading) {
                            incubator.onStatusChanged = function(status) {
                                if (status === Component.Ready) {
                                    // connect page's destruction to decrement model
                                    incubator.object.Component.destruction.connect(function() {
                                        console.log("it is destructed! " + index)
                                    });
                                }
                            }
                        }

每当一个页面被销毁时,我们将收到一个输出:

qml: it is destructed! 0
qml: it is destructed! 5
qml: it is destructed! 5
qml: it is destructed! 3
qml: it is destructed! 0
qml: it is destructed! 4
qml: it is destructed! 7

就像我们上面显示的那样.我们可以在这个输出的地方加入任何一个我们想要做的东西,比如释放内存及取消监视事件等.

作者:UbuntuTouch 发表于2016/4/26 11:20:08 原文链接
阅读:171 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubnutu手机中的Theme颜色

在我们设计应用时,我们可能很想知道所有在Ubuntu手机设计中的Theme颜色.在今天的例程中,我们将动态地检测我们系统里所有的Theme的颜色,并显示它们.开发者可以充分利用这些颜色在自己的设计中.在我们的设计中,开发者可以通过向左或向右滑动手势来导航列表中的内容.这个项目的设计有点像是一个Property browser.可以扩展到其它的项目中.关于Ubuntu手机中的Theme更多的知识,请阅读https://developer.ubuntu.com/en/phone/apps/qml/tutorials/ubuntu-ui-toolkit-palette/.该应用可以在Ubuntu商店中下载"ThemeViewer".


   


   


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import Qt.labs.settings 1.0

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

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

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

    property var props: [theme]
    property var propnames : ["theme"]

    ListModel {
        id: mymodel
    }

    Settings {
        id: settings
        property bool selectedAmbiance: true
    }

    function goUp() {
        if ( propnames.length > 1) {
            // We only do it when there is more than one item
            propnames.pop();
            header.text = propnames.join(".");
            props.pop();
            getProperties(props[props.length -1])
            console.log("swipe left: " + propnames)
        }
    }

    function getProperties(obj) {
        mymodel.clear();

        var keys = Object.keys(obj);
        for(var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var type = typeof obj[key];
            console.log(key + ' : ' + obj[key] + " " + type);

            if ( type === 'object' && obj[key] ) {
                if ( propnames.length === 3 ) {
                    mymodel.append({"name": key, "value": ""+obj[key] })
                } else {
                    mymodel.append({"name": key, "value": "white" })
                }
            }
        }
    }

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("Theme Viewer")
            trailingActionBar.actions: [
                Action {
                    iconSource: settings.selectedAmbiance ?
                                "images/ambiance.png" : "images/dark.png"
                    text: "Ambiance"
                    onTriggered: {
                        settings.selectedAmbiance = !settings.selectedAmbiance

                        theme.name = (settings.selectedAmbiance ?
                                          "Ubuntu.Components.Themes.Ambiance" :
                                          "Ubuntu.Components.Themes.SuruDark" )

                        // We need to do it from the very beginning
                        props = [theme]
                        propnames = ["theme"]
                        header.text = propnames.join(".")
                        getProperties(props[props.length -1])
                    }
                }
            ]

            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        SystemPalette { id: __palette }

        Component {
            id: delegate
            Rectangle {
                width: parent.width
                height: propname.height * 1.7
                color: "transparent"

                Rectangle {
                    height: parent.height
                    width: parent.width*.2
                    anchors {
                        right: parent.right
                        top: parent.top
                    }
                    color: value
                    visible: propnames.length === 3
                }

                Label {
                    id: propname
                    text: name
                    fontSize: "x-large"
                    anchors.verticalCenter: parent.verticalCenter
                }

                MouseArea {
                    id: mouseRegion
                    anchors.fill: parent

                    onDoubleClicked: {
                        props.push(props[props.length -1][propname.text])
                        propnames.push(propname.text)
                        header.text = propnames.join(".")
                        getProperties(props[props.length -1])
                    }

                    onClicked: {
                        list.currentIndex = index;
                    }
                }

                SwipeArea {
                    id: swiperight
                    anchors.fill: parent
                    direction: SwipeArea.Rightwards

                    onDraggingChanged: {
                        if ( dragging ) {
                            console.log("swipe right: " + propname.text)
                            props.push(props[props.length -1][propname.text])
                            propnames.push(propname.text)
                            header.text = propnames.join(".")
                            getProperties(props[props.length -1])
                        }
                    }
                }
            }
        }

        Column {
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
                top: page.header.bottom
            }

            Row {
                width: parent.width
                height: header.height

                Button {
                    id: upButton
                    height: parent.height
                    width: units.gu(5)
                    iconSource: "qrc:///images/up.png"
                    onClicked:  {
                        goUp()
                    }
                }

                Label {
                    id: header
                    text: { return propnames.join(".") }
                    fontSize: "large"
                }

            }

            UbuntuListView {
                id: list
                clip:true
                width: page.width
                height: page.height - header.height
                focus: true
                model: mymodel
                highlight: Rectangle {
                    color: __palette.midlight
                    border.color: Qt.darker(__palette.window, 1.3)
                }
                highlightMoveDuration: -1
                highlightMoveVelocity: -1
                highlightFollowsCurrentItem: true
                delegate: delegate
            }

            Component.onCompleted: {
                getProperties(props[props.length -1])
            }
        }
    }

    SwipeArea {
        id: swipeleft
        direction:  SwipeArea.Leftwards
        anchors.fill: parent

        onDraggingChanged: {
            if ( dragging ) {
                goUp()
            }
        }
    }
}

整个项目的源码在:https://github.com/liu-xiao-guo/themeviewer
作者:UbuntuTouch 发表于2016/4/28 15:03:32 原文链接
阅读:276 评论:0 查看评论

Read more
UbuntuTouch

很多开发者可能希望能够在不改变现有的Ubuntu Toolkit中的控件的情况下,有个性地来改变一些属性.这样使得自己的界面更加个性化.目前我们在我们的官方网址可以查看到我们已经有的一些Style的一些控件.这个列表并不完善.更加详细的列表在地址可以看到.


下面,我们来用一个具体的例子来说明如何是用这些API来个性化我们的应用的:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Themes 1.3
import Ubuntu.Components.Styles 1.3

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

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

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

    Action {
        id: action1
        text: "action 1"
        iconName: "compose"
        onTriggered: print("one!")
    }

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("pageheaderstyle")

            style: PageHeaderStyle {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
                contentHeight: units.gu(10)
                Label {
                    anchors.centerIn: parent
                    fontSize: "x-large"
                    text: styledItem.title
                }
                implicitHeight: contentHeight
            }
        }

        Image {
            id: pressed
            source: "images/pressed.jpg"
            visible: false
        }

        Image {
            id: unpressed
            source: "images/unpressed.jpg"
            visible: false
        }

        Column {
            anchors.centerIn: parent
            spacing: units.gu(5)

            Button {
                id: button1
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    defaultColor: button1.pressed ? "blue" : "red"
                }
            }

            Button {
                id: button2
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    backgroundSource: button2.pressed ? pressed : unpressed
                }
            }
        }
    }
}

在上面的代码中,我们使用了PageHeaderStyle来个性化我们的page header.我们它的高度设置为units.gu(10),同时我们也让我们的文字居中.

在下面的代码中,我们利用StyleHints来修改我们已经在使用的style的一些属性.比如:

            Button {
                id: button1
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    defaultColor: button1.pressed ? "blue" : "red"
                }
            }

在上面的代码中,当我们的按钮按下时,颜色会变为蓝色而不是通常状态下的红色:

 

另外,如下的代码可以使得我们的按钮在按下,和不按下的情况下显示不同的图片:

            Button {
                id: button2
                width: units.gu(40)
                height: units.gu(15)
                text: "Nice"
                StyleHints {
                    backgroundSource: button2.pressed ? pressed : unpressed
                }
            }


 

关于ButtonStyle的更多介绍请参阅连接

整个项目的源码在:https://github.com/liu-xiao-guo/style


作者:UbuntuTouch 发表于2016/5/3 11:50:27 原文链接
阅读:206 评论:0 查看评论

Read more
UbuntuTouch

在这里,我来展示一个使用QML来做单位转换的例程:


Main.qml


/*
 * 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.4
import Ubuntu.Components 1.3
import "conversion.js" as Converter

/*!
  \brief Example unit converter application.

  The application demonstrates the usage of
    - i18n
    - units
    - Label
    - Tabs
    - Tab
    - Page
    - ToolbarActions
    - Action
    - TextField and
    - Button components

  The application converts length and weight units between several metrics.
  Related units are grouped in the same page (Tab) and conversion happens when
  pressing Enter/Return after entering a number in one of the input fields (i.e.
  accepting the entered text), or by pressing the "Convert" button.

  The navigation between converter pages is provided by the Tabs component.
*/

MainView {
    id: root
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"
    
    // Note! applicationName needs to match the .desktop filename
    applicationName: "unit-converter"
    
    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    automaticOrientation: true
    
    width: units.gu(100)
    height: units.gu(75)

    property real margins: units.gu(2)
    property real labelWidth: units.gu(12)

    // Length conversion model; the unit is Mile
    property var lengthModel: [
        {"unit": "Inch", "rate": 63360.0},
        {"unit": "Meter", "rate": 1609.344},
        {"unit": "Miles", "rate": 1.0},
        {"unit": "Feets", "rate": 5280.0},
        {"unit": "Yards", "rate": 1760.0},
        {"unit": "Kilometers", "rate": 1.609344},
    ]

    // Weight conversion model; the base unit is Pound
    property var weightModel: [
        {"unit": "Pounds", "rate": 1.0},
        {"unit": "Kilograms", "rate": 0.45359237},
        {"unit": "Ounces", "rate": 16},
        {"unit": "Stones", "rate": 0.0714285714},
        {"unit": "US Tons", "rate": 0.0005},
        {"unit": "UK Tons", "rate": 0.000446428571},
    ]

    // converter page template
    Component {
        id: pageContent
        Page {
            // expose Repeater's model for reusability, so we can set it from
            // outside, when we build the tabs
            property alias model: converter.model

            // remove the input panel when pressed outside of any text input
            MouseArea {
                anchors.fill: parent
                onPressed: Qt.inputMethod.hide();
            }

            Flickable {
                id: flickable
                anchors {
                    fill: parent
                    margins: root.margins
                }
                flickableDirection: Flickable.VerticalFlick
                contentWidth: pageLayout.width
                contentHeight: pageLayout.height
                Column {
                    id: pageLayout
                    width: flickable.width
                    height: childrenRect.height

                    spacing: units.gu(1.2)
                    // show as many lines as many units we have in the model
                    // it is assumed that the model has "unit" and "rate" roles
                    Repeater {
                        id: converter
                        Row {
                            spacing: units.gu(1)
                            Label {
                                text: i18n.tr(modelData.unit)
                                textSize: Label.Large
                                width: root.labelWidth
                                height: input.height
                                verticalAlignment: Text.AlignVCenter
                            }
                            // input field performing conversion
                            TextField {
                                id: input
                                //errorHighlight: false
                                validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
                                width: pageLayout.width - root.labelWidth - spacing
                                text: "0.0"
                                font.pixelSize: FontUtils.sizeToPixels("large")
                                height: units.gu(4)
                                // on-the-fly conversion
                                onTextChanged: if (activeFocus) Converter.convert(input, converter, index)
                                onAccepted: Qt.inputMethod.hide()
                            }
                        }
                    }
                }
            }

            head.actions: Action {
                objectName: "action"
                iconName: "clear"
                text: i18n.tr("Clear")
                onTriggered: Converter.clear(converter)
            }
        }
    }
    
    Tabs {
        id: tabs
        
        // First tab begins here
        Tab {
            objectName: "Tab1"
            
            title: i18n.tr("Lengths")
            
            // Tab content begins here
            page: Loader {
                sourceComponent: pageContent
                onStatusChanged: {
                    if (status === Loader.Ready && item) {
                        item.parent = parent;
                        item.model = lengthModel;
                    }
                }
            }
        }
        
        // Second tab begins here
        Tab {
            objectName: "Tab2"
            
            title: i18n.tr("Weights")
            page: Loader {
                sourceComponent: pageContent
                onStatusChanged: {
                    if (status === Loader.Ready && item) {
                        item.parent = parent;
                        item.model = weightModel;
                    }
                }
            }
        }
    }
}

运行我们的例程:

  

作者:UbuntuTouch 发表于2016/5/3 13:58:11 原文链接
阅读:194 评论:0 查看评论

Read more
UbuntuTouch

在今天的文章中,我们将介绍如何利用Map API来展示如何显示我们的坐标信息,并展示如何在地图上标记我们的位置.我可以通过这个简单的应用显示在我们手机中所支持的Plugin的Providers(提供者).同时,我们也可以列出目前所支持的所有的MapType,并通过选择它们来展示我们的结果.


1)显示我们所有的Plugin的Service Providers



我们可以通过Plugin的API接口:

    Plugin {
        id: plugin

        // Set the default one
        Component.onCompleted: {
            name = availableServiceProviders[0]
        }
    }

来获取,我们目前所支持的所有的plugin.目前在我们的MX4手机上所展示的Providers:



就像上面的图中显示的那样,我们可以发现"nokia"及"osm"两个service providers.目前的情况是在中国"nokia"是用不了的(这可能是由于license的原因).在国外,应该可以使用NokiaHere地图的.

2)如何在地图中标注


简单地,我们可以使用MapCircle

MapCircle {
    center: me.position.coordinate
    radius: units.gu(3)
    color: "red"
}

在我们的地图中用一个全来标注我们所感兴趣的点.在这里,"center"用来表示圆圈的中心坐标位置.
我们可以MapQuickItem来用任何一个Item来标注我们的兴趣点.在我们的例程中,我们使用了如下的方法:

                    MapQuickItem {
                        id: mylocation
                        sourceItem: Item {
                            width: units.gu(6)
                            height: info.height

                            Label {
                                id: info
                                anchors.centerIn: parent
                                anchors.verticalCenterOffset: -units.gu(2)
                                text: "(" + me.position.coordinate.longitude.toFixed(2) + "," + me.position.coordinate.latitude.toFixed(2) + ")"
                                color: "blue"
                            }


                            Rectangle {
                                width: units.gu(2)
                                height: width
                                radius: width/2
                                color: "red"
                                x: parent.width/2
                                y: parent.height/2
                            }
                        }

我们使用了一个圆点及一个可以显示坐标的Label.



我们可以在代码中动态地生产我们需要的QML Component并标注我们的位置信息:

           MouseArea {
                        anchors.fill: parent

                        onPressed: {
                            if ( setMarks.checked ===false ) {
                                mouse.accepted = false
                                return;
                            }

                            console.log("mouse: " + mouseX + " " + mouseY)
                            var coord = map.toCoordinate(Qt.point(mouseX, mouseY))

                            console.log("creating the component")
                            var component = Qt.createComponent("MapMarkItem.qml")
                            console.log("creating the item")
                            var item = component.createObject(map, { coordinate: coord })
                            console.log("adding the item")
                            map.addMapItem(item)

//                            var circle = Qt.createQmlObject('import QtLocation 5.3; MapCircle {}', map)
//                            circle.center = coord
//                            circle.radius = units.gu(4)
//                            circle.color = 'green'
//                            circle.border.width = 3
//                            map.addMapItem(circle)

                            mouse.accepted = true;
                        }
                    }


在上面我们通过Qt.createQmlObject来动态地创建一个MapCirle的控件.我们通过map.addMapItem来添加进去.当我们点击地图时,我们把标注加入到地图中去:



我们可以获得任何一个地点的位置信息.这在中国的手机地图中是得不到这个信息的:)

3)如何通过手势操作放大/缩小/移动地图



在默认的情况下,我们可以直接通过手势的操作来进行Zoom及Pan我们的地图.我们也可以定义我们自己的gesture来启动或禁止这个手势的操控:

                    gesture {
                        enabled: !setMarks.checked
                        activeGestures: MapGestureArea.ZoomGesture | MapGestureArea.PanGesture

                        onPanStarted:  {
                            console.log("onPanStarted")
                        }

                        onPanFinished: {
                            console.log("onPanFinished")
                        }

                        onPinchStarted: {
                            console.log("onPinchStarted")
                        }

                        onPinchFinished: {
                            console.log("onPinchFinished")
                        }

                        onPinchUpdated: {
                            console.log("onPinchUpdated")
                            console.log("point1: " + "(" + pinch.point1.x + pinch.point1.y + ")")
                        }
                    }

当我们的enabled项设为false时,我们可以进行任何的zoom或pan.


4)如何获得所有的MapType



我们可以通过设置Map的MapType来查看卫星,夜间,地形图等信息.我们可以通过如下的API来列表所有的MapType:

            leadingActionBar {
                id: leadbar
                actions: {
                    var supported = map.supportedMapTypes;
                    console.log("count: " + supported.length)
                    var acts = []
                    console.log("Going to add the types")
                    for ( var i = 0; i < supported.length; i++ ) {
                        var item = supported[i]

                        console.log("map type name: " + item.name)
                        console.log("map style: " + item.style)
                        console.log("type des:" + item.description)
                        var action = creatAction(leadbar, "info", item)
                        acts.push(action)
                    }

                    return acts
                }
            }

在这里,我们使用了"map.supportedMapTypes"来列表所有的被支持的地图形式.最终在我们的应用中:


  

  


5)如何获得当前位置的位置信息



我们可以通过PositionSource接口获取我们的当前的位置信息:

    PositionSource {
        id: me
        active: true
        updateInterval: 1000
        preferredPositioningMethods: PositionSource.AllPositioningMethods
        onPositionChanged: {
            console.log("lat: " + position.coordinate.latitude + " longitude: " +
                        position.coordinate.longitude);
            console.log(position.coordinate)
            console.log("mapzoom level: " + map.zoomLevel)
        }

        onSourceErrorChanged: {
            console.log("Source error: " + sourceError);
        }
    }

我们可以通过设置updateInterval来得到获取位置的频率.

整个项目的源码在:https://github.com/liu-xiao-guo/gps




作者:UbuntuTouch 发表于2016/6/2 13:36:31 原文链接
阅读:180 评论:0 查看评论

Read more
UbuntuTouch

最近发现一个新的API叫做LayoutMirroring.运用这个API,我们可以在水平方向把我们的布局进行进行镜像显示.

我们还是先来看一个例子:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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: "layoutmirroring.liu-xiao-guo"

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

    LayoutMirroring.enabled: rtl
    LayoutMirroring.childrenInherit: true
    property bool rtl: Qt.application.layoutDirection == Qt.RightToLeft

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("LayoutMirroring")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }

            trailingActionBar.actions: [
                Action {
                    text: i18n.tr('Right to Left')
                    iconName: 'flash-on'
                    visible: !rtl
                    onTriggered: rtl = !rtl
                },
                Action {
                    text: i18n.tr('Left to Right')
                    iconName: 'flash-off'
                    visible: rtl
                    onTriggered: rtl = !rtl
                }
            ]
        }

        Row {
            anchors {
                left: parent.left
                right: parent.Right
                top: page.header.bottom
                bottom: parent.bottom
            }
            spacing: units.gu(1)

            Repeater {
                model: 5

                Rectangle {
                    color: "red"
                    opacity: (5 - index) / 5
                    width: units.gu(5); height: units.gu(5)

                    Text {
                        text: index + 1
                        anchors.centerIn: parent
                    }
                }
            }
        }

    }
}

在上面的例程中,我们在我们的MainView中使用了一个attached property: LayoutMirroring.当它的属性LayoutMirroring.enabled为真时,它使得我们的界面的UI布局将在在水平方向上从右向左布局.


运行我们的例程的效果:

 

当我们点击上面图中的闪电的图标时,我们会看见布局在水平方向发生改变,并变为从右侧开始的.当我们点击禁止闪电标志的图标时,就会恢复以前的布局.

作者:UbuntuTouch 发表于2016/5/4 10:08:01 原文链接
阅读:196 评论:0 查看评论

Read more
UbuntuTouch

细心的开发者可能在我先前的应用"Ubuntu文件浏览器 - 开发Scope/应用利器"已经看到过在我们应用的header的有上角位置上已经显示了一个ActionBar用来显示一排图标按钮供我们做出我们的选择.



事实上,我们也可以在我们的应用的其它位置也可以使用ActionBar.在今天的例程中,我们来展示如何利用ActionBar来实现我们所需要的一些功能.事实上,我们上面的Browser应用中,也可以在我们的"/"位置提供一个这样的ActionBar来选择不同的drive.当然这目前不在我们的考虑范围之内.

我们还是来看一下我们的例子:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

    property list<Action> shortActionList: [
        Action {
            iconName: "share"
            text: "Share"
            onTriggered: {
                console.log("share is clicked")
            }
        },
        Action {
            iconName: "starred"
            text: "Favorite"
            onTriggered: {
                console.log("starred is clicked")
            }
        }
    ]

    property list<Action> actionList:  [
        Action {
            iconName: "alarm-clock"
            text: "Tick tock"
            onTriggered: {
                console.log("alarm-clock is clicked")
            }
        },
        Action {
            iconName: "appointment"
            text: "Date"
            onTriggered: {
                console.log("appointment is clicked")
            }
        },
        Action {
            iconName: "attachment"
            text: "Attach"
            onTriggered: {
                console.log("attachment is clicked")
            }
        },
        Action {
            iconName: "contact"
            text: "Contact"
            onTriggered: {
                console.log("contact is clicked")
            }
        },
        Action {
            iconName: "like"
            text: "Like"
            onTriggered: {
                console.log("like is clicked")
            }
        },
        Action {
            iconName: "lock"
            text: "Lock"
            onTriggered: {
                console.log("lock is clicked")
            }
        }
    ]

    Page {
        id: page
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("actionbar")
            StyleHints {
                foregroundColor: UbuntuColors.orange
                backgroundColor: UbuntuColors.porcelain
                dividerColor: UbuntuColors.slate
            }
        }

        Column {
            anchors.centerIn: parent
            spacing: units.gu(3)

            ActionBar {
                // no numberOfSlots specified. Using default value.
                id: shortBar
                actions: shortActionList
            }

            ActionBar {
                id: bar
                numberOfSlots: 3
                actions: actionList
            }

            ActionBar {
                id: bar1
                actions: actionList

                StyleHints {
                    overflowIconName: "grip-large"
                    overflowText: "More"
                    defaultNumberOfSlots: 2
                }
            }
        }
    }
}
 
在这里我创建了三个ActionBar.第一个没有什么特别的.它显示连个并排的图标按钮.第二个ActionBar在图标比较多的情况下,它把多余的图标显示在一个Popup的菜单中.我们可以在ActionBar中定义可以最少显示的图标个数(numberOfSlots).在第三个ActionBar中,我们展示了如何去通过style的方式来修改/定制一个ActaionBar.运行我们的应用:

   


作者:UbuntuTouch 发表于2016/5/4 15:40:37 原文链接
阅读:197 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu SDK 安装介绍视频

最近我做了一个Ubuntu SDK的安装视频的介绍.如果有兴趣的朋友,请观看地址:


http://v.youku.com/v_show/id_XMTU1Nzg0NTI2NA==.html?from=s1.8-1-1.2

Ubuntu SDK安装

开发者也可以参阅我的文章"如何快速地安装Ubuntu SDK"得到更多的帮助.

作者:UbuntuTouch 发表于2016/5/5 9:46:13 原文链接
阅读:207 评论:0 查看评论

Read more
UbuntuTouch

在今天的练习中,我们来做一个设计.在我们的ListView的列表中,我们想点击它的项时,它的项能够展开.这对于我们的有些设计是非常用的.比如我们不希望打开另外一个页面,但是我们可以展示我们当前项的更多的信息.我们可以使用Ubuntu SDK提供的Expandable.这个设计的图片为:


 


如果每个项的详细信息并不多的时候,我们可以利用这种方法来展示我们的每个项的内容.具体的代码为:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 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: "expandable.liu-xiao-guo"

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

    ListModel {
        id: listmodel
        ListElement { name: "image1.jpg" }
        ListElement { name: "image2.jpg" }
        ListElement { name: "image3.jpg" }
        ListElement { name: "image4.jpg" }
        ListElement { name: "image5.jpg" }
        ListElement { name: "image6.jpg" }
        ListElement { name: "image7.jpg" }
        ListElement { name: "image8.jpg" }
        ListElement { name: "image9.jpg" }
        ListElement { name: "image10.jpg" }
        ListElement { name: "image11.jpg" }
    }

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("expandable")
        }

        Item {
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
                top: pageHeader.bottom
            }

            UbuntuListView {
                id: listview
                anchors.fill: parent
                height: units.gu(24)
                model: listmodel
                delegate: ListItem.Expandable {
                    id: exp
                    expandedHeight: units.gu(15)
                    expanded: listview.currentIndex == index

                    Row {
                        id: top
                        height: collapsedHeight
                        spacing: units.gu(2)
                        Image {
                            height: parent.height
                            width: height
                            source: "images/" + name
                        }

                        Label {
                            text: "This is the text on the right"
                        }
                    }

                    Label {
                        anchors.top: top.bottom
                        anchors.topMargin: units.gu(0.5)
                        text: "This is the detail"
                    }

                    onClicked: {
//                        expanded = true;
                        listview.currentIndex = index
                    }
                }
            }
        }

    }
}



作者:UbuntuTouch 发表于2016/6/15 17:31:13 原文链接
阅读:47 评论:0 查看评论

Read more
UbuntuTouch

通常我们在Scope的设计中,我们可以通过搜寻在query中发生一个请求.事实上,我们也可以在我们的preview中通过按钮的方式来发生一个query请求,并生成显示结果.在今天的文章中,我们将讲述如何在preview中发生一个请求.


1)请求的种类


我们可以通过"CannedQuery"中如下的两个API来生产我们需要的query请求:
  • set_query_string (std::string const &query_str)
  • set_user_data (Variant const &value)
上面的这两个API的区别在于前面一个API可以在搜索框里输入我们所需要的字符串再进行搜索.对于set_user_data来说,它的搜索的字符串已经在传人的参数中,我们可以直接进行解析,并使解析的参数成为我们API请求的一部分.对于这种情况,通常我们不需要再进行搜索.

下面我们通过实际的例子来展示如果使用.


2)set_query_string


在我们的preview中,我们加入如下的代码:

preview.cpp


    sc::CannedQuery new_query(SCOPE_INSTALL_NAME);
    std::string str("carousel");
    new_query.set_query_string(str);
    builder1.add_tuple({
        {"id", Variant("user_videos")},
        {"label", Variant("Show query string")},
        {"uri", Variant(new_query.to_uri())}
    });

从上面的代码中可以看出来我们在query中预先设置了一个字符串"carousel".在我们的query中,我们通过如下的代码来识别这种情况:


query.cpp


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

        // Let's first check the query string
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        // Get the query string
        string query_string = query.query_string();

        QString queryStr = QString::fromStdString(query_string);
        qDebug() << "query: " << queryStr;

        if ( queryStr.startsWith("carousel") ) {
            qDebug() << "it is from the preview";

            int count = images_.count();
            auto cat = reply->register_category( "Carousel", "Carousel" ,
                                                 "", sc::CategoryRenderer(CAT_RENDERER9) );

            for ( int i = 0; i < count; i ++ ) {
                pushResult( reply, &cat, i);
            }

        } 

  ....
}

在上面的代码中,我们可以对有carousel字符串进行匹配,如果请求德尔是以这个为开始的字符串,我们做分别的处理.运行我们的Scope:

 

当我们在preivew中点击"Show query string"的时候,我们的query显示的图片如右图所示.在搜寻框中显示"carousel"字样.并显示我们所需要的carousel图片.我们也可以在输入框中接着输入我们所需要的任何一个字符串,只要前面的几个字符串是"carousel"即可.


3)set_user_data


在我们的preview中,我们写上如下的代码:

preview.cpp


    sc::CannedQuery new_query1(SCOPE_INSTALL_NAME);
    new_query1.set_user_data(Variant("num=" + std::to_string(4)));
    builder1.add_tuple({
        {"id", Variant("user_videos")},
        {"label", Variant("User data")},
        {"uri", Variant(new_query1.to_uri())}
    });

在这里,我们把搜寻的代码中直接写入所需要搜索的参数,我们可以在query中进行解析,并展示结果:

query.cpp


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

        // Let's first check the query string
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
        // Get the query string
        string query_string = query.query_string();

        QString queryStr = QString::fromStdString(query_string);
        qDebug() << "query: " << queryStr;

      if (sc::SearchQueryBase::query().has_user_data()) {
            string userdata = sc::SearchQueryBase::query().user_data().get_string();
            qDebug() << "it has user data in it";
            cerr << "user_data: " << userdata << endl;
            const std::string prefix("num=");
            if (!userdata.compare(0, prefix.size(), prefix)) {
                qDebug() << "prefix is found!";
                std::string num = userdata.substr(prefix.size());
                cerr << "num: " << num << endl;
                int number = num.back() - '0';
                qDebug() << "number: " << number;

                // Let's display a vertical-journal according to the number
                auto cat = reply->register_category( "vertical-journal", "vertical-journal" ,
                                                "", sc::CategoryRenderer(CAT_RENDERER12) );

                for ( int i = 0; i < number; i ++ ) {
                    pushResult( reply, &cat, i);
                }
            }
        }

 ...
}

在上面,我们通过检测有没有user_data.如果有,我们也做分别的处理.这种情况和上面的那种情况不之处在于我们不需要再输入任何额外的参数给我们的搜索,因为我们可以通过解析user_data来得到搜寻所需要的所有的参数.运行我们的scope:

   

在上面,我们可以点击"User data"按钮来直接进入到我们的query界面.我们不需要输入任何的参数.当前显示我们输入的4个grid图片.

作者:UbuntuTouch 发表于2016/5/17 13:39:00 原文链接
阅读:195 评论:0 查看评论

Read more