Canonical Voices

Joseph Williams

Juju Lithuania sprint recap

Last month the Juju design team was away in Lithuania, Vilnius on a working sprint. We met up with the distributed development team to help tackle technical and design issues.

These sprints take away the barrier of time zones — which usually make it harder to ask engineers questions about features that are being designed.

13616221_10209952745325460_1919613808_o

Everyday started with a run down of the workshops and discussions that would happen on that day. This made sure everyone was aware and welcome (and very much encouraged) to join the sessions and give their valuable insight or just widen their knowledge of the product.

13589133_10209952745245458_1275611017_o

The day would then play out in an enjoyable whirlwind of knowledge, clarity and alarming amounts of progress as queries would be answered in real time without the assistance of an email client or a hangout session.

At lunch time the Juju team took the fantastic opportunity to explore the city. Walking the cobbled streets and enjoying the vast range of foods from a variety of different cultures. While we were there we ate everything from classic Lithuanian cuisine to a big shared spread of Mexican food.

13616195_10209952745285459_595293251_o

At the end of the day we would go through all of the things which were accomplished and have lightning talks (short presentations of work to the rest of the team) about the work that had been completed. This is a fantastic exercise for design as it lets us show wireframes, prototypes, research or just flat visuals for the features that are being implemented within Juju and gives the engineers a chance to see and discuss the work directly with the designers. Lightning talks are also great for engineering as they can show features that are under development or just talk through the back end of the solution.

This has been my fourth sprint with Canonical and they never cease to be valuable as they promote collaboration, forward thinking and team bonding. I look forward to the team’s next adventure and the challenges that we will conquer.

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 原文链接
阅读:1332 评论: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 原文链接
阅读:318 评论: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 原文链接
阅读:264 评论: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 原文链接
阅读:351 评论: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 原文链接
阅读:285 评论: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 原文链接
阅读:333 评论: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 原文链接
阅读:349 评论: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 原文链接
阅读:208 评论: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 原文链接
阅读:333 评论:0 查看评论

Read more
UbuntuTouch

在实际的Scope中,如果我们的搜寻的结果多过一个屏幕,这个时候,我们可以通过创建一些按钮来帮我们进行翻页.当然我们也可以使用按钮来做一些其它的功能,这完全依赖于我们对我们的Scope的具体的设计.比如,在下面的Scope的画面中,我们创建了"Next","Previous"及"Top".在这篇文章中,我们将介绍如何创建这些按钮,并捕获这些按钮的事件.




下面,我们来介绍如何创建这些按钮.


1)在query中创建按钮


query.cpp


我们定义如下的函数:

void Query::showNext(sc::SearchReplyProxy const& reply, sc::CategorisedResult res, int next_breadcrumb)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means show the next page of results
    std::string show_more =  _("Next");
    res.set_title("<b>" + show_more + "</b>");
    res["get_next"] = "true";
    res.set_intercept_activation();
    res["api_page"] = sc::Variant(next_breadcrumb);
    if (!reply->push(res)) {
        return;
    }
}

void Query::showPrevious(sc::SearchReplyProxy const& reply, sc::CategorisedResult res)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means show the previous page of results
    std::string show_prev =  _("Previous");
    res.set_title("<b>" + show_prev + "</b>");
    res["get_previous"] = "true";
    res.set_intercept_activation();
    if (!reply->push(res)) {
        return;
    }
}

void Query::showTop(sc::SearchReplyProxy const& reply, sc::CategorisedResult res)
{
    res.set_uri("http://ubuntu.com");
    //Translators: this means return to the top page (the first page) of results
    std::string top = _("Top");
    res.set_title("<b>" + top + "</b>");
    res["go_to_top"] = "true";
    res.set_intercept_activation();
    if (!reply->push(res)) {
        return;
    }
}

void Query::createButtons(const unity::scopes::SearchReplyProxy &reply) {
    auto buttons_cat = reply->register_category("buttons", "", "",
                                                sc::CategoryRenderer(BUTTONS_TEMPLATE));
    // Show the next button
    sc::CategorisedResult res(buttons_cat);
    showNext(reply, res, 1);

    // show the preview button
    sc::CategorisedResult prev_res(buttons_cat);
    showPrevious(reply, prev_res);

    // show the top button
    sc::CategorisedResult top_res(buttons_cat);
    showTop(reply, top_res);
}

它们被用来创建我们所需要的按钮.注意上面的"res.set_intercept_activation()".它可以让我们在我们下面的Action中捕获这个事件..在这里,BUTTONS_TEMPLATE的定义如下:

const static string BUTTONS_TEMPLATE =
        R"(
{
        "schema-version": 1,
        "template": {
        "category-layout": "vertical-journal",
        "card-size": "small",
        "card-background":"color:///#12a3d8"
        },
        "components": {
        "title": "title"
        }
        }
        )";

我们在query的run方法中调用我们上面创建的createButtons:

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

        // We can play a two buttons on the top
        createButtons(reply);
        ...

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

这样我们就可以在最上面显示我们所需要的按钮.事实上,就是通过push的方法来生产一个我们喜欢颜色的方块.


2)捕获按钮事件


按照如下的方法来捕获我们的按钮事件.我们必须在scope.cpp中创建如下的方法:

scope.cpp


sc::ActivationQueryBase::UPtr Scope::activate(sc::Result const& result,
                                              sc::ActionMetadata const& metadata)
{
    cerr << "activate" << endl;
    return sc::ActivationQueryBase::UPtr(new Action(result, metadata, *this));
}

action.cpp


创建我们所需要的构造函数:

Action::Action(us::Result const& result,
               us::ActionMetadata const& metadata,
               scope::Scope & scope):
    result_(result),
    scope_(scope),
    ActivationQueryBase(result, metadata)
{
}

在activate()方法中处理我们所需要的按键事件.

sc::ActivationResponse Action::activate()
{
    qDebug() << "in activate()";

    try {
        std::string val = result_["get_previous"].get_string();
        qDebug() << "PREVIOUS button is clicked!";
        us::CannedQuery cq("scopetemplates.xiaoguo_scopetemplates");
        // restore the previous search. We can save the state in query
        // and save the states into the members of scope.
        //        cq.set_filter_state(scope_.filter_state);
        //        cq.set_query_string(scope_.previous_query);
        return us::ActivationResponse(cq);
    }
    catch ( unity::LogicException &e){
    }

    try {
        std::string val = result_["get_next"].get_string();
        qDebug() << "NEXT button is clicked!";
        us::CannedQuery cq("scopetemplates.xiaoguo_scopetemplates");
        // restore the previous search
        //        cq.set_filter_state(scope_.filter_state);
        //        if (scope_.previous_query.empty())
        //            cq.set_department_id(scope_.previous_dept_id);
        //        cq.set_query_string("");
        return us::ActivationResponse(cq);
    }
    catch ( unity::LogicException &e){
    }

    ...
}

我们可以在Activation中捕获这些按钮的事件.当我们返回的时候,我们可以参照文章"如果在Scope中的Preview中发起一个query请求"及文章"运用link query特性query自己的Scope中department或其它scope中的department"调到我们需要的department中或通过set_query_string生产一个新的query.

在Desktop下运行,我们可以看到我们在按下按钮时的事件:



作者:UbuntuTouch 发表于2016/5/18 11:28:51 原文链接
阅读:291 评论:1 查看评论

Read more
UbuntuTouch

在QML的设计中,在很多的情况下,我们可以把我们的逻辑代码通过Javascript来书写,而且可以把我们的JS代码掩埋在我们的QML代码中,比如典型的代码如下:

            Button {
                text: "Calculate"
                onClicked: {
                    console.log("Change button is clicked")
                    console.log(Method.factorial(10));
                    console.log(Method.factorialCallCount())
                }
            }


在上面的代码中,我们定义了一个Button按钮.每当我们的按钮的触碰事件click发生时,我们可以在我们的QML文件中直接按照上面的方法来执行我们的Javascript代码.在onClicked后面的""及""中间其实就是我们的Javascript代码来实现我们的逻辑处理.

事实上,如果我们的逻辑比较复杂,并且有时,我们想把我们的逻辑及UI (由QML语言描述)分开的话,这时候,我们可以直接把我们的逻辑代码放入到一个叫做Javascript的文件之中.然后再在我们的QML文件中进行直接import.这样的好处是界面和逻辑分开,也可以使得我们的QML代码简单明了.

在QML中引入Javascript有两种方式.有兴趣的开发者可以参阅Qt的官方文档.在该文档中,如果不是自己亲手去实验,其实也很晦涩难懂.简单地说,这两种方式是:

  • stateful:在这种方式下,js模块中所定义的变量在每次被import后,都会被拷贝一份.所以如果有被多次import就有多个拷贝,而且每个的值都可能会有不同. 在这种过情况下,js文件可以直接访问在我们QML文件中所定义的object
  • statelss:在这种情况下,js所定义的模块就像一个公共分享的库一样.它里面的方法都可以被使用,并且模块里定义的变量在所以被import的QML代码中独一份,无论被import多少次.另外,它不可以直接访问QML文件中的object尽管可以通过参数的传人对所要求的object进行修改.

1)stateful


我们特意在我们的例子中加入我们一个特有的MyButton.qml:

MyButton.qml


// MyButton.qml
import QtQuick 2.0
import "my_button_impl.js" as Logic // a new instance of this JavaScript resource is loaded for each instance of Button.qml
import "factorial.js" as Method

Rectangle {
    id: rect
    width: 200
    height: 100
    color: "red"
    property int count: 0
    property alias text: mytext.text
    signal clicked()

    Text {
        id: mytext
        anchors.centerIn: parent
        font.pixelSize: units.gu(3)
    }

    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: {
            rect.clicked()
            count = Logic.onClicked(rect)
            console.log(Method.factorialCallCount())
        }
    }
}

my_button_impl.js


// this state is separate for each instance of MyButton
var clickCount = 0;

function onClicked(obj) {
    clickCount += 1;
    if ((clickCount % 5) == 0) {
        obj.color = Qt.rgba(1,0,0,1);
    } else {
        obj.color = Qt.rgba(0,1,0,1);
    }

    return clickCount;
}

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

在我们的MyButton.qml中,我们import了my_button_impl.js这个文件.由于这个js文件的最前面没有".pragma library",所以它是一个stateful的Javascript.如果我们有多个MyButton的实例,那么每个按钮都分别有自己的clickCount变量,并且它们之间毫无关系.

我们在我们的Main.qml中加入两个我们设计的MyButton按钮:

Main.qml


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

            MyButton {
                anchors.horizontalCenter: parent.horizontalCenter
                text: "Button " + count
                onClicked: {
                    console.log("Button 1 is clicked!")
                }
            }

            MyButton {
                anchors.horizontalCenter: parent.horizontalCenter
                width: parent.width
                text: "Button " + count
                onClicked: {
                    console.log("Button 2 is clicked!")
                }
            }
         }

运行我们的代码:



从上面的输出可以看出,两个按钮的count可以是完全不同的.它们之间完全独立.

另外,我们也在我们的my_button_impl.js中设计了如下的函数:

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

显然它可以直接访问我们在Main.qml中的object Main.运行我们应用,点击"Change color"按钮:



可以看见我们的MainView的背景颜色已经发生改变了.

2)stateless



针对这种情况,在Javascript文件的开头部分必须是一下的语句:

.pragma library

我们一个完整的factorial.js文件如下:

.pragma library

var factorialCount = 0;

function factorial(a) {
    // a = parseInt(a);

    // factorial recursion
    if (a > 0)
        return a * factorial(a - 1);

    // shared state
    factorialCount += 1;

    // recursion base-case.
    return 1;
}

function factorialCallCount() {
    return factorialCount;
}

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

function changeBackground(obj) {
    obj.backgroundColor = "green"
}


在这个模块中,我们定义了一个factorialCount的变量.由于是stateless,所有这个变量对于所有的import文件来说,只有一个.有点类似于C++类中static变量.在我们的Main.qml中,我们定义了一个按钮:

Main.qml


            Button {
                text: "Calculate"
                onClicked: {
                    console.log("Calculate button is clicked")
                    console.log(Method.factorial(10));
                    console.log(Method.factorialCallCount())
                }
            }

按下这个按钮,factorial将会帮我们计算我们的结果,同时会显示factorial被调用多少次.在我们的MyButton.qml中,我们也在点击的地方展示这个数据:

MyButton.qml


    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: {
            rect.clicked()
            count = Logic.onClicked(rect)
            console.log(Method.factorialCallCount())
        }
    }

如果我们点击"Calculate"按钮,在点击MyButton按钮,我们将可以看到同样的factorialCount值.



同样的,如果在我们的factorial.js中直接访问object main来修改它的属性,就像:

function changeBackgroundColor() {
    main.backgroundColor = "green"
}

这样是不可以的.我们必须通过如下的方法来修改:

function changeBackground(obj) {
    obj.backgroundColor = "green"
}

我们在我们的Main.qml中使用如下的代码来实现:

            Button {
                text: "Change color via stateless "
                onClicked: {
                    Method.changeBackground(main)
                }
            }

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



作者:UbuntuTouch 发表于2016/5/19 11:12:00 原文链接
阅读:243 评论:0 查看评论

Read more
UbuntuTouch

在先前的文章" 如何在Ubuntu手机中使得一个应用是全屏的应用 - Ubuntu.Components 1.3",我们介绍了如何实现一个全屏的应用.但是在那里的文章中,我们的方法不能完全覆盖手机的状态显示区域.比如:


  


从上面的两张图中,我们可以看出来一些差别.左边的一个图里,还是有手机状态的显示.究其原因,还是因为MainView不让我们完全覆盖所有的区域.那么我们怎么实现一个完全的全屏应用呢?


在我们的下面的例子中,我们完全抛弃MainView.我们选用Window来完成我们的界面:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtQuick.Window 2.2

//Rectangle {
//    width: Screen.width
//    height: Screen.height

//    color:"red"
//}

Window {
    id: main
    width: Screen.width
    height: Screen.height
    // special flag only supported by Unity8/MIR so far that hides the shell's
    // top panel in Staged mode
    flags: Qt.Window | 0x00800000

    Image {
        anchors.fill: parent
        source: "images/pic.jpg"
    }

    Label {
        anchors.centerIn: parent
        text: "This a full screen app"
        fontSize: "x-large"
    }

    Component.onCompleted: {
        console.log("visibility: " + main.visibility )
        console.log("width: " + Screen.width + " height: " + Screen.height )
    }
}

在上面的代码中,我们同时也设置如下的flags:

    flags: Qt.Window | 0x00800000

这样我们就可以完全实现一个全屏的应用了.

作者:UbuntuTouch 发表于2016/5/24 13:37:49 原文链接
阅读:307 评论:0 查看评论

Read more
UbuntuTouch

在我们先前的文章"如何在QML应用中创建一个Context Menu",我们使用了一个popup的方法来显示一个我们自己需要的context menu.那个方法虽好,但是显示起来和背景的颜色很相近,不容易看出来.当然我们可以通过一些方法来改进我的设计.在今天的例程中,我们通过另外一种方法来做一个同样的context menu.这个方法的好处是,我们可以随意来设计我们所需要的效果.我们从另外一个角度来实现同样的东西.我们设计的最终效果为:


    


我们可以通点击上图中的图标来弹出我们所需要的选项,在选项中,我们可以做出我们的选择.当选择完后,我们可以点击图标收回选项.基于这个设计我们还可以更进一步做更多的改进.比如点击区域以外的地方自动让选项列表消失.当然,我们也可以加入我们的动画效果.


上面图中的一个图标及一个文字,我们是通过一个AbstractButton控件来实现的.当然,我们也可以采用一个ListView来实现.


OptionValueButton.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

AbstractButton {
    id: optionValueButton

    implicitHeight: units.gu(5)

    property alias label: label.text
    property alias iconName: icon.name
    property bool selected
    property bool isLast
    property int columnWidth
    property int marginSize: units.gu(1)

    width: marginSize + iconLabelGroup.width + marginSize

    Item {
        id: iconLabelGroup
        width: childrenRect.width
        height: icon.height

        anchors {
            left: (iconName) ? undefined : parent.left
            leftMargin: (iconName) ? undefined : marginSize
            horizontalCenter: (iconName) ? parent.horizontalCenter : undefined
            verticalCenter: parent.verticalCenter
            topMargin: marginSize
            bottomMargin: marginSize
        }

        Icon {
            id: icon
            anchors {
                verticalCenter: parent.verticalCenter
                left: parent.left
            }
            width: optionValueButton.height - optionValueButton.marginSize * 2
            color: "white"
            opacity: optionValueButton.selected ? 1.0 : 0.5
            visible: name !== ""
        }

        Label {
            id: label
            anchors {
                left: icon.name != "" ? icon.right : parent.left
                verticalCenter: parent.verticalCenter
                leftMargin: units.gu(0.5)
            }

            color: "white"
            opacity: optionValueButton.selected ? 1.0 : 0.5
            width: paintedWidth
        }
    }

    Rectangle {
        anchors {
            left: parent.left
            bottom: parent.bottom
        }
        width: parent.columnWidth
        height: units.dp(1)
        color: "red"
        opacity: 0.5
        visible: true
    }
}

我们的Main.qml设计也比较简单.只是在这里面,我们使用了Item中mapFromItem来得到我们点击的控件的位置信息.利用这个位置信息,我们来定位我们的选项的optionValueSelector位置.整个设计如下:

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

    width: units.gu(60)
    height: units.gu(85)
    theme.name :"Ubuntu.Components.Themes.SuruDark"
    property bool optionValueSelectorVisible: false

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

        ListModel {
            id: model
            property int selectedIndex: 0

            ListElement {
                icon: "account"
                label: "On"
                value: "flash-on"
            }
            ListElement {
                icon: "active-call"
                label: "Auto"
                value: "Auto"
            }
            ListElement {
                icon: "call-end"
                label: "Off"
                value: "flash-off"
            }
        }

        Column {
            id: optionValueSelector
            objectName: "optionValueSelector"
            width: childrenRect.width
            spacing: units.gu(1)

            property Item caller

            function toggle(model, callerButton) {
                if (optionValueSelectorVisible && optionsRepeater.model === model) {
                    hide();
                } else {
                    show(model, callerButton);
                }
            }

            function show(model, callerButton) {
                optionValueSelector.caller = callerButton;
                optionsRepeater.model = model;
                alignWith(callerButton);
                optionValueSelectorVisible = true;
            }

            function hide() {
                optionValueSelectorVisible = false;
                optionValueSelector.caller = null;
            }

            function alignWith(item) {
                // horizontally center optionValueSelector with the center of item
                // if there is enough space to do so, that is as long as optionValueSelector
                // does not get cropped by the edge of the screen
                var itemX = parent.mapFromItem(item, 0, 0).x;
                var centeredX = itemX + item.width / 2.0 - width / 2.0;
                var margin = units.gu(1);

                if (centeredX < margin) {
                    x = itemX;
                } else if (centeredX + width > item.parent.width - margin) {
                    x = itemX + item.width - width;
                } else {
                    x = centeredX;
                }

                // vertically position the options above the caller button
                y = Qt.binding(function() { return item.y - height - units.gu(2) });

                console.log("x: " + x + " y: " + y)
            }

            visible: opacity !== 0.0
            onVisibleChanged: if (!visible) optionsRepeater.model = null;
            opacity: optionValueSelectorVisible ? 1.0 : 0.0
            Behavior on opacity {UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration}}

            Repeater {
                id: optionsRepeater

                delegate: OptionValueButton {
                    anchors.left: optionValueSelector.left
                    columnWidth: optionValueSelector.childrenRect.width
                    label: model.label
                    iconName: model.icon
                    selected: optionsRepeater.model.selectedIndex == index
                    isLast: index === optionsRepeater.count - 1
                    onClicked: {
                        optionsRepeater.model.selectedIndex = index
                        console.log(optionsRepeater.model.get(index).value)
                    }
                }
            }
        }

        Icon {
            id: optionButton
            width: units.gu(3)
            height: width
            anchors.centerIn: parent
            name: model.get(model.selectedIndex).icon

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    console.log("optionValueSelectorVisible: " + optionValueSelectorVisible)
                    optionValueSelector.toggle(model, optionButton)
                }
            }
        }

        Component.onCompleted: {
            console.log("width: " + width + " height: " +height)
        }
    }

}

在我们的实际应用中,我们可以把它设计为Component供我们其它的应用直接使用.我们可以通过InverseMouseArea来更进一步完成整个Component的可用性.




作者:UbuntuTouch 发表于2016/5/25 10:07:36 原文链接
阅读:250 评论:0 查看评论

Read more
UbuntuTouch

针对Scope的设计,有些时候我们不想,或并没有必要切换到我们的preview界面.比如,我们设计一个汇率转换Scope,在query界面中直接显示汇率的结果即可.并没有必要切换到我们的preview界面.针对我之前设计的"在Ubuntu平台上开发快递邮件查询Scope",我们也没有设计自己的preview界面.直接在主界面中输出自己的结果即可.那么我们如何来实现这个目的呢?


 


在我们的设计中,我们必须在我们的template中:


const static string CAT_RENDERER102
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "large",
            "card-background": "#00FF00",
            "overlay": true,
            "non-interactive":"true"
        },
        "components" :
        {
            "title" : "title",
            "art" : "art",
            "subtitle": "subtitle",
            "mascot": "mascot",
            "emblem": "emblem",
            "summary": "summary",
            "overlay-color": "overlay-color",
            "attributes": {
                "field": "attributes",
                "max-count": 2
            }
        }
    }
    )"
};

加入non-interactive":"true"这么一项.它表明,我们不需要交互,也就是只能显示第一个query的界面.我们还是用我们的scopetemplate例程来展示:




就如上面显示的那样.点击上面的图片,它不会带我们去到preivew的页面.

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


作者:UbuntuTouch 发表于2016/5/26 17:18:47 原文链接
阅读:203 评论:0 查看评论

Read more
UbuntuTouch

在最新的QtMultiMedia 5.6中,AudioAPI中有一个playlist属性.我们可以充分利用这个属性来做一个简单的音乐播放器.在我们的官方文档中,写的是QttMultiMedia 5.4.正确的是5.6版本.Audio API可以让我们很方便地播放我们所需要的音乐.在我们之前的文章"如何在Ubuntu QML应用中播放音乐"也讨论过如何利用MediaPlayerSoundEffect来播放音乐.


如何利用Audio 中的playlist呢?我们先来看一个简单的例子:


        Audio {
            id: player;
            autoPlay: true
            autoLoad: true
            playlist: Playlist {
                id: playlist
            }

            Component.onCompleted: {
                console.log(playlist.addItem(Qt.resolvedUrl("sounds/song1.mp3")))
                console.log(playlist.addItem(Qt.resolvedUrl("sounds/background.mp3")))
                console.log("playlist count: " + playlist.itemCount)
                console.log("metaData type: " + typeof(meta))

                play()
            }
        }

在上面的代码中,我们向我们的playlist表中添加我们的音乐文件.当然,我们也可以动态地添加它们到我们的列表中.我们也可以通过如下的方式来添加我们的音乐列表:


    Audio {
        id: player;
        playlist: Playlist {
            id: playlist
            PlaylistItem { source: "song1.ogg"; }
            PlaylistItem { source: "song2.ogg"; }
            PlaylistItem { source: "song3.ogg"; }
        }
    }
    ListView {
        model: playlist;
        delegate: Text {
            font.pixelSize: 16;
            text: source;
        }
    }

目前在Ubuntu手机上测试.这种方法有一个bug.如果音乐文件存在的话,那么在列表中将会有两个相同的项被显示.目前,我们还是使用前面介绍的方法来做我们的例程.


Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.3
import QtMultimedia 5.6

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

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

    width: units.gu(60)
    height: units.gu(85)
    property var meta: player.metaData

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

        Audio {
            id: player;
            autoPlay: true
            autoLoad: true
            playlist: Playlist {
                id: playlist
            }

            Component.onCompleted: {
                console.log(playlist.addItem(Qt.resolvedUrl("sounds/song1.mp3")))
                console.log(playlist.addItem(Qt.resolvedUrl("sounds/background.mp3")))
                console.log("playlist count: " + playlist.itemCount)
                console.log("metaData type: " + typeof(meta))

                console.log("The properties of metaData is:")
                var keys = Object.keys(meta);
                for( var i = 0; i < keys.length; i++ ) {
                    var key = keys[ i ];
                    var data = key + ' : ' + meta[ key ];
                    console.log( key + ": " + data)
                }

                play()
            }
        }

        Flickable {
            anchors {
                left: parent.left
                right: parent.right
                top: pageHeader.bottom
                bottom: parent.bottom
            }
            contentHeight: layout.childrenRect.height +
                           layout1.height + layout1.spacing

            Column {
                id: layout
                anchors.fill: parent

                ListView {
                    anchors.left: parent.left
                    anchors.right: parent.right
                    height: page.height/2

                    model: playlist;
                    delegate: Label {
                        fontSize: "x-large"
                        text: {
                            var filename = String(source);
                            var name = filename.split("/").pop();
                            return name;
                        }

                        MouseArea {
                            anchors.fill: parent

                            onClicked: {
                                if (player.playbackState != Audio.PlayingState) {
                                    player.playlist.currentIndex = index;
                                    player.play();
                                } else {
                                    player.pause();
                                }
                            }
                        }
                    }
                }

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

                Slider {
                    id: defaultSlider
                    anchors.horizontalCenter: parent.horizontalCenter
                    width: parent.width * 0.8
                    maximumValue: 100
                    value: player.position/player.duration * maximumValue
                }

                CustomListItem {
                    title.text:  {
                        switch (player.availability ) {
                        case Audio.Available:
                            return "availability: available";
                        case Audio.Busy:
                            return "availability: Busy";
                        case Audio.Unavailable:
                            return "availability: Unavailable";
                        case Audio.ResourceMissing:
                            return "availability: ResourceMissing";
                        default:
                            return "";
                        }
                    }
                }

                CustomListItem {
                    title.text: "bufferProgress: " + player.bufferProgress;
                }

                CustomListItem {
                    title.text: "duration: " + player.duration/1000 + " sec"
                }

                CustomListItem {
                    title.text: "hasAudio: " + player.hasAudio
                }

                CustomListItem {
                    title.text: "hasVideo: " + player.hasVideo
                }

                CustomListItem {
                    title.text: "loops: " + player.loops
                }

                CustomListItem {
                    title.text: "muted: " + player.muted
                }

                CustomListItem {
                    title.text: "playbackRate: " + player.playbackRate
                }

                CustomListItem {
                    title.text: {
                        switch (player.playbackState) {
                        case Audio.PlayingState:
                            return "playbackState : PlayingState"
                        case Audio.PausedState:
                            return "playbackState : PausedState"
                        case Audio.StoppedState:
                            return "playbackState : StoppedState"
                        default:
                            return ""
                        }
                    }
                }

                CustomListItem {
                    title.text: "seekable: " + player.seekable
                }

                CustomListItem {
                    title.text: "url: " + String(player.source)
                }

                CustomListItem {
                    title.text: "volume: " + player.volume
                }

                CustomListItem {
                    title.text: {
                        switch (player.status) {
                        case Audio.NoMedia:
                            return "status: NoMedia"
                        case Audio.Loading:
                            return "status: Loading"
                        case Audio.Loaded:
                            return "status: Loaded"
                        case Audio.Buffering:
                            return "status: Buffering"
                        case Audio.Stalled:
                            return "status: Stalled"
                        case Audio.Buffered:
                            return "status: Buffered"
                        case Audio.EndOfMedia:
                            return "status: EndOfMedia"
                        case Audio.InvalidMedia:
                            return "status: InvalidMedia"
                        case Audio.UnknownStatus:
                            return "status: UnknownStatus"
                        default:
                            return ""
                        }
                    }
                }

            }
        }

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

            Button {
                text: "Previous"
                onClicked: {
                    console.log("Previouse is clicked")
                    var previousIndex = player.playlist.previousIndex(1)
                    console.log("previousIndex: " + player.playlist.previousIndex(1))
                    if ( previousIndex == -1 ) {
                        player.playlist.currentIndex = player.playlist.itemCount - 1;
                    } else {
                        player.playlist.previous();
                    }

                    player.play()
                }
            }

            Button {
                text: "Next"
                onClicked: {
                    console.log("Next is clicked")
                    var nextIndex = player.playlist.nextIndex(1)
                    console.log("nextIndex: " + nextIndex )
                    if (nextIndex == -1) {
                        player.playlist.currentIndex = 0
                    } else {
                        player.playlist.next();
                    }

                    player.play();
                }
            }
        }
    }
}

整个的设计还是非常简单.屏幕的上面是一个音乐的列表.下面是一个该音乐文件的状态.由于一些原因,我们目前还不能得到音乐文件的metaData.

   


我们可以通过点击列表中的像来播放或停止正在播放的音乐.





作者:UbuntuTouch 发表于2016/5/27 12:18:52 原文链接
阅读:241 评论:0 查看评论

Read more
UbuntuTouch

在我们的Ubuntu SDK中,我们提供了QtOrganizer API接口.通过这个API我们可以来管理我们自己的日历,也可以利用这个API来导入一个日历.在今天的例程中,我们来详细了解这个API的具体的用法.在练习的开始,我们可以在我们的手机中的"日历"应用中创建一些我们所需要的事件.比如:

  



1)如何得到手机中所有的manager 


我们可以通过这个OrganizerModel API来列举在我们的系统中所支持的所有的managers.

availableManagers : list<string>

这里的每个manager对应于一个数据库.这样我们就可以对它们进行操作.在目前的MX4手机中,我们列出的manager为:



在上面列出了三个manager: invalid,memory及eds.这里的memory可以被用在来导入我们在文件中的一个文件,并最终形成我们可以对一个文件中的事件进行操作.当然,我们也可以实现同步的功能,或导出的动作.具体的例程可以参阅文章"Qt Quick Organizer List View Example".虽然例程中使用的API的有些接口和我们现有的会有所不同,但是我们可以实现同样的功能.在上面图中显示的"eds"就是我们的手机中自动应用"日历"所使用的manager.在接下来的操作中,我们主要来通过这个来进行剖析.


2)如何得到所有的event


我们可以通过OrganizerModel中的如下方法来得到所有的event的列表:

items : list<OrganizerItem>

通过这个API,我们可以得到在我们的日历中所有的事件的列表:



就如上图所示的那样,我们可以得到所有的事件的列表信息.


3)如何得到每个事件的详细信息



我们可以通过OrganizerItem来得到每个事件的具体信息.我们可以通过:

itemDetails : list<Detail>

来得到事件已经拥有的那些属性.我们可以通过如下的API来得到该项的所有的具体的信息:

Detail detail(type)

比如在上面图中显示的时间信息.对于一个事件来说:



从上面的图中可以看出来,每个事件的具体信息,并展示它所支持的具体的属性项目.我们可以尝试点击最下面的按钮"Change the description to I LOVE"来改变我们的事件的description. 修改过后的列表为:




4)如何删除一个事件


我们可以通过OrganizerModel中的:

removeItem(OrganizerItem item)

来删除我们不想要的事件.在我们的应用中,我们可以通过向左滑动,显示出删除图标.点击删除图标即可达到删除的目的.

  

可以看出来,我们已经成功地删除了我们的"day event on June 8th".

当然我们可以更多地来explore更过的关于QtOrganizer的API接口.这个练习就留给开发者自己来做.在这里我们就只抛砖引玉地展示其中的一部分.

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtOrganizer 5.0

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

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

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

    OrganizerModel {
        id: organizerModel

        endPeriod: {
            var date = new Date();
            date.setDate(date.getDate() + 10);
            return date
        }

        sortOrders: [
            SortOrder {
                id: sortOrder

                detail: Detail.EventTime
                field: EventTime.startDateTime
                direction: Qt
            }
        ]

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

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

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

        onModelChanged: {
            console.log("onModelChanged")
            console.log("item count: " + organizerModel.itemCount)
            mymodel.clear();
            var count = organizerModel.itemCount
            for ( var i = 0; i < count; i ++ ) {
                var item = organizerModel.items[i];
                mymodel.append( {"item": item })
            }
        }

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

        manager: "eds"
    }

    ListModel {
        id: mymodel
    }

    Component {
        id: highlight
        Rectangle {
            width: parent.width
            color: "lightsteelblue"; radius: 5
            Behavior on y {
                SpringAnimation {
                    spring: 3
                    damping: 0.2
                }
            }
        }
    }

    PageStack {
        id: pagestack
        anchors.fill: parent

        Component.onCompleted: {
            pagestack.push(main)
        }

        Page {
            id: main
            header: PageHeader {
                id: pageHeader
                title: i18n.tr("organizer")
            }
            visible: false

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

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

                    Label {
                        text: "Organizer managers:"
                    }

                    ListView {
                        id: listview
                        width: parent.width
                        height: units.gu(10)
                        highlight: highlight
                        model:organizerModel.availableManagers
                        delegate: Label {
                            width: listview.width
                            text: modelData
                            fontSize: "large"

                            MouseArea {
                                anchors.fill: parent
                                onClicked: {
                                    listview.currentIndex = index
                                }
                            }
                        }
                    }

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

                    ListView {
                        clip: true
                        width: parent.width
                        height: parent.height - divider.height - listview.height
                        model: mymodel
                        delegate: ListItem {
                            Label {
                                text: item.description
                            }

                            Label {
                                anchors.right: parent.right
                                text : {
                                    var evt_time = item.detail(Detail.EventTime)
                                    var starttime = evt_time.startDateTime;
                                    console.log("time: " + starttime.toLocaleDateString())
                                    return starttime.toLocaleDateString()
                                }
                            }

                            onClicked: {
                                detailpage.myitem = item
                                pagestack.push(detailpage)
                            }

                            trailingActions: ListItemActions {
                                actions: [
                                    Action {
                                        iconName: "delete"
                                        onTriggered: {
                                            console.log("delete is triggered!");
                                            organizerModel.removeItem(item)
                                        }
                                    }
                                ]
                            }
                        }
                    }
                }
            }

            Component.onCompleted: {
                console.log("manage name: " + organizerModel.manager)
            }
        }

        Page {
            id: detailpage
            header: PageHeader {
                id: header
                title: i18n.tr("Detail page")
            }
            visible: false

            property var myitem

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

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

                    Label {
                        id: col1
                        width: parent.width
                        text: "collectionId: " + detailpage.myitem.collectionId
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col2
                        width: parent.width
                        text: "description: " + detailpage.myitem.description
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col3
                        width: parent.width
                        text: "displayLabel: " + detailpage.myitem.displayLabel
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col4
                        width: parent.width
                        text: "guid: " + detailpage.myitem.guid
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col5
                        width: parent.width
                        text: "itemId: " + detailpage.myitem.itemId
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col6
                        width: parent.width
                        text: "itemType : " + detailpage.myitem.itemType
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col7
                        width: parent.width
                        text: "manager : " + detailpage.myitem.manager
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col8
                        width: parent.width
                        text: "modified  : " + detailpage.myitem.modified
                        wrapMode: Text.WordWrap
                    }

                    Label {
                        id: col9
                        width: parent.width
                        text: "Item details are:"
                    }

                    ListView {
                        width: parent.width
                        height: parent.height - col1.height - col2.height -col3.height - col9.height -
                                col4.height - col5.height - col6.height - col7.height - col8.height
                        model: detailpage.myitem.itemDetails
                        delegate: Label {
                            text: {
                                switch (modelData.type) {
                                case Detail.Undefined:
                                    return "Undefined";
                                case Detail.Classification:
                                    return "Classification"
                                case Detail.Comment:
                                    return "Comment";
                                case Detail.Description:
                                    return "Description"
                                case Detail.DisplayLabel:
                                    return "DisplayLabel"
                                case Detail.ItemType:
                                    return "ItemType"
                                case Detail.Guid:
                                    return "Guid"
                                case Detail.Location:
                                    return "Location"
                                case Detail.Parent:
                                    return "Parent"
                                case Detail.Priority:
                                    return "Priority"
                                case Detail.Recurrence:
                                    return "Recurrence"
                                case Detail.Tag:
                                    return "Tag"
                                case Detail.Timestamp:
                                    return "Timestamp"
                                case Detail.Version:
                                    return "Version"
                                case Detail.Reminder:
                                    return "Reminder"
                                case Detail.AudibleReminder:
                                    return "AudibleReminder"
                                case Detail.EmailReminder:
                                    return "EmailReminder"
                                case Detail.VisualReminder:
                                    return "VisualReminder"
                                case Detail.ExtendedDetail:
                                    return "ExtendedDetail"
                                case Detail.EventAttendee:
                                    return "EventAttendee"
                                case Detail.EventRsvp:
                                    return "EventRsvp"
                                case Detail.EventTime:
                                    return "EventTime"
                                case Detail.JournalTime:
                                    return "JournalTime"
                                case Detail.TodoTime:
                                    return "TodoTime"
                                case Detail.TodoProgress:
                                    return "TodoProgress"
                                default:
                                    return "Unknown type"
                                }
                            }
                        }
                    }
                }

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

                    text: "Change the description to I LOVE"
                    onClicked: {
                        console.log("changing the description")
                        detailpage.myitem.description = "I LOVE YOU"
                        organizerModel.saveItem(detailpage.myitem)
                    }
                }
            }

        }
    }
}







作者:UbuntuTouch 发表于2016/6/6 10:01:30 原文链接
阅读:170 评论:0 查看评论

Read more
UbuntuTouch

在我们先前的文章"如何在Ubuntu手机中使用前置照相机"对如何使用前置照相机给出了一个解决方案.事实上,在我们最新的Camera API中 (QtMultiMedia 5.6),已经有新的API来完成这个功能了.我们不再需要额外的C++代码来完成这个功能.


在新的Camera API中,我们可以指定:

deviceId : string
来使得我们的Camera来选择我们需要的前后相机.那么我们如何得到这个deviceId呢?在新的QtMultiMedia的接口中,我们可以通过如下的API来得到所有的Camera的描述信息:

QtMultimedia.availableCameras

通过这个API,我们可以得到一个Camera的如下信息:


基于上面的信息,我们做了如下的一个简单的照相机应用:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import QtMultimedia 5.6

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

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

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

    Camera {
        id: camera

        imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

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

        flash.mode: Camera.FlashRedEyeReduction

        imageCapture {
            id: capture
            onImageCaptured: {
                console.log("onImageCaptured!")
                // Show the preview in an Image
                photoPreview.source = preview
            }

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

    Component {
        id: highlight
        Rectangle {
            width: parent.width
            height: listview.delegate.height
            color: "lightsteelblue"; radius: 5
            Behavior on y {
                SpringAnimation {
                    spring: 3
                    damping: 0.2
                }
            }
        }
    }

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

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

            Column {
                anchors.fill: parent

                ListView {
                    id: listview
                    width: page.width
                    height: units.gu(20)

                    model: QtMultimedia.availableCameras
                    highlight:highlight
                    delegate: Item {
                        id: delegate
                        width: listview.width
                        height: layout.childrenRect.height + units.gu(0.5)

                        Column {
                            id: layout
                            width: parent.width

                            Text {
                                text: "deviceId: " + modelData.deviceId
                            }

                            Text {
                                text: "displayName: " + modelData.displayName
                            }

                            Text {
                                text: {
                                    switch(modelData.position) {
                                    case Camera.UnspecifiedPosition:
                                        return "position: UnspecifiedPosition"
                                    case Camera.BackFace:
                                        return "position: BackFace";
                                    case Camera.FrontFace:
                                        return "position: FrontFace"
                                    default:
                                        return "Unknown"
                                    }
                                }
                            }

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

                        }

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                camera.deviceId = modelData.deviceId
                                listview.currentIndex = index
                            }
                        }
                    }
                }

                VideoOutput {
                    id: output
                    source: camera
                    width: parent.width
                    height: parent.height - listview.height
                    focus : visible

                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            console.log("going to capture to: " + path)
                            capture.captureToLocation(path);
                            output.visible = false
                            photoPreview.visible = true
                        }
                    }
                }

                Image {
                    id: photoPreview
                    width: parent.width
                    height: parent.height - listview.height
                    fillMode: Image.PreserveAspectFit

                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            photoPreview.visible = false
                            output.visible = true
                        }
                    }

                    Component.onCompleted: {
                        visible = false
                    }
                }
            }
        }
    }
}

由于我们希望存储当前相机的所照的照片,我们必须得到能够存储照片的位置信息.我们在我们的Main.cpp中使用了如下的方法:

main.cpp

QString getPrivatePath()
{
    QString writablePath = QStandardPaths::
            writableLocation(QStandardPaths::DataLocation);
    qDebug() << "writablePath: " << writablePath;

    QString absolutePath = QDir(writablePath).absolutePath();
    qDebug() << "absoluePath: " << absolutePath;

    absolutePath += ".liu-xiao-guo/photos";

    // We need to make sure we have the path for storage
    QDir dir(absolutePath);
    if ( dir.mkpath(absolutePath) ) {
        qDebug() << "Successfully created the path!";
    } else {
        qDebug() << "Fails to create the path!";
    }

    qDebug() << "private path: " << absolutePath;

    return absolutePath;
}

    QString path = getPrivatePath();
    qDebug() << "final path: " << path;
   ...
    context->setContextProperty("path", QVariant::fromValue(path));

通过这样的方法,我们可以在我们的QML的代码中直接引用path来存储我们的照片.

运行我们的应用:

   


从上面的图中,我们可以看到所有相机的列表信息.我们可以随意选择我们所需要的相机,并进行拍摄.当然我们也可以进行录像的动作.具体的实现,可以参阅文章"在Ubuntu手机平台中使用Camera API来录像".


作者:UbuntuTouch 发表于2016/5/30 11:47:27 原文链接
阅读:205 评论:0 查看评论

Read more
UbuntuTouch

在Ubuntu的手机设计中,我们可以利用ContactModel API接口来读取我们的电话本信息.我们可以通过如下的API接口:

availableManagers 

来获取我们所有的电话本的manager.也就是有多少个电话本.通过设置:

manager 
来选择不同的电话本来读取信息.所有的电话本信息,我们可以在如下的API中读取:

contacts : list<Contact>
每个Contact包含一个电话本每个项的所有的信息:

address : Address
addresses : list<Address>
anniversary : Anniversary
avatar : Avatar
birthday : Birthday
contactDetails : list<ContactDetail>
contactId : int
displayLabel : DisplayLabel
email : EmailAddress
emails : list<EmailAddress>
extendedDetail : ExtendedDetail
extendedDetails : list<ExtendedDetail>
family : Family
favorite : Favorite
gender : Gender
geolocation : GeoLocation
globalPresence : GlobalPresence
guid : Guid
hobby : Hobby
manager : string
modified : bool
name : Name
nickname : Nickname
note : Note
onlineAccount : OnlineAccount
organization : Organization
organizations : list<Organization>
phoneNumber : PhoneNumber
phoneNumbers : list<PhoneNumber>
presence : Presence
ringtone : Ringtone
syncTarget : SyncTarget
tag : Tag
timestamp : Timestamp
type : enumeration
url : Url
urls : list<Url>
version : Version
我们可以根据我们所需要的信息来显示.

当我们显示信息的时候,我们可以通过SortOrder来排序我们的电话本中的项.

  ContactModel {
        id: mymodel
        sortOrders: [
            SortOrder {
                id: sortOrder

                detail: ContactDetail.Name
                field: Name.FirstName
                direction: Qt.DescendingOrder
            }
        ]
  ...
  }

我们也可以通过filter来过滤我们所需要的项,比如:

        filter: DetailFilter {
            id: favouritesFilter

            detail: ContactDetail.Favorite
            field: Favorite.Favorite
            value: "Yang"
            matchFlags: DetailFilter.MatchExactly
        }
这个filter将显示所有的Favorite的电话本,而如下的filter将显示lastName中有"Yang"的电话本:

        filter: DetailFilter {
            id: nameFilter

            detail: ContactDetail.Name
            field: Name.LastName
            value: "Yang"
            matchFlags: DetailFilter.MatchExactly
        }

我们的一个简单的例程如下:

Main.qml

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

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


    ContactModel {
        id: mymodel
        sortOrders: [
            SortOrder {
                id: sortOrder

                detail: ContactDetail.Name
                field: Name.FirstName
                direction: Qt.DescendingOrder
            }
        ]

        fetchHint: FetchHint {
            detailTypesHint: [ContactDetail.Avatar,
                ContactDetail.Name,
                ContactDetail.PhoneNumber]
        }

//        filter: DetailFilter {
//            id: favouritesFilter

//            detail: ContactDetail.Favorite
//            field: Favorite.Favorite
//            value: "Yang"
//            matchFlags: DetailFilter.MatchExactly
//        }

        filter: DetailFilter {
            id: nameFilter

            detail: ContactDetail.Name
            field: Name.LastName
            value: "Yang"
            matchFlags: DetailFilter.MatchExactly
        }
    }

    Component {
        id: highlight
        Rectangle {
            width: parent.width
            height: manager.delegate.height
            color: "lightsteelblue"; radius: 5
            Behavior on y {
                SpringAnimation {
                    spring: 3
                    damping: 0.2
                }
            }
        }
    }


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

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

            Column {
                anchors.fill: parent
                spacing: units.gu(0.5)

                Label {
                    text: "The contact managers:"
                    fontSize: "x-large"
                }

                ListView {
                    id: manager
                    clip: true
                    width: parent.width
                    height: units.gu(8)
                    highlight: highlight
                    model: mymodel.availableManagers
                    delegate: Item {
                        id: delegate
                        width: manager.width
                        height: man.height
                        Label {
                            id: man
                            text: modelData
                            fontSize: "large"
                        }

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                manager.currentIndex = index
                                // set the contact model manager
                                mymodel.manager = modelData
                            }
                        }
                    }
                }

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

                CustomListItem {
                    id: storage
                    title.text: {
                        switch (mymodel.storageLocations ) {
                        case ContactModel.UserDataStorage:
                            return "UserDataStorage";
                        case ContactModel.SystemStorage:
                            return "SystemStorage";
                        default:
                            return "Unknown storage"
                        }
                    }
                }

                // Display the contact info here
                ListView {
                    width: parent.width
                    height: parent.height - manager.height - divider.height - storage.height
                    model: mymodel
                    delegate: ListItem.Subtitled {
                        text: contact.name.firstName + " " + contact.name.lastName
                        subText: contact.phoneNumber.number
                    }
                }
            }
        }

        Component.onCompleted: {
            console.log("count of manager: " + mymodel.availableManagers.length)
        }

    }
}

运行我们的代码:








作者:UbuntuTouch 发表于2016/5/31 11:24:06 原文链接
阅读:182 评论:0 查看评论

Read more