Canonical Voices

What Ubuntu Touch Development in CSDN (Chinese) talks about

UbuntuTouch

[原]使用Ubuntu SDK开发Flickr应用教程

在这篇文章中我们将一步一步地教大家怎么在Ubuntu手机平台来开发一个QML的应用。我们知道QML开发对很多初学者来说并不难。我们需要有一些简单的javascript的基础就可以开始我们的开发了。QML应用的调试也是很方便的。我们通过这个教程的学习,掌握基本的开发流程及界面设计。最终的应用显示的图片如下:

     

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

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

  

这样我们就产生了一个最基本的QML应用。我们可以在desktop上运行它(通过按下Ctrl+R)键或点击SDK左下方的绿色的运行键。我们同时修改main.qml中的尺寸为:

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

修改应用的title为:

        title: i18n.tr("Flickr")


应用中有一个按钮,大家可以点击并看看有什么变化。

2)实现一个最基本的列表

回头看看我们的设计,我们需要有一个列表来列出我们的图片。我们这里来使用一个列表的控件来实现它。在Ubuntu SDK的toolkit中,有一个conrtol叫做UbuntuListView。我们可以使用它来设计我们的列表。当然我们也可以使用QML中的ListView。UbuntuListView有一些更多的功能比如pullToRefresh。我们可以在下面用到。我们首先通过如下的方式添加一个文件“PictureListView.qml”到项目中。



    



这样我们就基本上创建了一个叫做“PictureListView.qml”的文件。在这里,它实际上创建一个叫做“PictureListView”的Component。一个Component的名称通常是以大写字母为开始的。通常它是以“Item”为基础的元素。Item在QML中是所有可视元素的最基础的元素。相当于在我们C++或JAVA编程中最基础的一个类。PictureListView.qml的设计如下:

import QtQuick 2.0

Item {
    anchors.fill: parent

}

为了能够在main.qml中调用它,我们修改我们的main.qml如下:

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

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

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

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

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

    Page {
        id:mainPage
        title: i18n.tr("Flickr")
        clip:true

        PictureListView {
            anchors.fill: parent
        }
    }
}

重新运行运行程序,显示如下。目前我们的应用应该没有做上面东西。代码可以在如下的地址找到:

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

2)完成UbuntuListView

我们再次回顾一下我们最上面的设计。里面是一个ListView。这里我们来使用UbuntuListView来完成该View。我们重新设计我们的PictureListView.qml文件。为了能够显示我们的ListView,我们对PictureListView做了一个简单的展示:

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

Item {
    id: mainScreen

    UbuntuListView {
        id: listView
        width: mainScreen.width
        height:mainScreen.height

        model: 10

        delegate:
            Text {
                text: "Hi"
            }

        Scrollbar {
            flickableItem: listView
        }
    }
}

重新运行程序,我们可以看到显示为10个“Hi”,证明ListView已经是在工作,虽然不是我们所需要的展示。在我们的设计中,我们的ListView的左边是一个图片,右边是一些文字。为了快速设计的方便,我们们将先只显示一行字。同时我们可以在网上的任何一个位置下载一个我们自己喜欢的图片并存放到项目根目录下的一个叫做“images”的目录中,并重新命令为sample.jpg。在这里,我选择网上的一个图片。我们也需要下载另外一个图片list.png来完成我们的这个步骤(存放于images目录中)。我们可以同时参阅Qt网页里的ListView。我们修改我们的程序如下:

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

Item {
    id: mainScreen

    ListModel {
        id: sampleListModel

        ListElement {
            imagePath: "images/sample.jpg"
            title: "First picture"
        }
        ListElement {
            imagePath: "images/sample.jpg"
            title: "Second picture"
        }
        ListElement {
            imagePath: "images/sample.jpg"
            title: "Third picture"
        }
    }

    Component {
        id: listDelegate
        BorderImage {
            source: "images/list.png"
            border { top:4; left:4; right:100; bottom: 4 }
            anchors.right: parent.right
            // width: mainScreen.width
            width: ListView.view.width

            property int fontSize: 25

            Rectangle {
                id: imageId
                x: 6; y: 4; width: parent.height - 10; height:width; color: "white"; smooth: true
                anchors.verticalCenter: parent.verticalCenter

                BorderImage {
                    source: imagePath; x: 0; y: 0
                    height:parent.height
                    width:parent.width
                    anchors.verticalCenter: parent.verticalCenter
                }

            }

            Text {
                x: imageId.width + 20
                y: 15
                width: ListView.view.width*2/3
                text: title; color: "white"
                font { pixelSize: fontSize; bold: true }
                elide: Text.ElideRight; style: Text.Raised; styleColor: "black"
            }
        }
    }

    UbuntuListView {
        id: listView
        width: mainScreen.width
        height:mainScreen.height

        model: sampleListModel

        delegate: listDelegate
    }

    Scrollbar {
        flickableItem: listView
    }
}

特别值得注意的是,我们这里使用了一个叫做listDelegate的Component来显示每一个list中的项。同时我们也使用了一个人工假想的sampleListModel来填充我们的数据。重新运行我们的应用。我们可以看到如下的显示:



到这里,我们基本上已经完成了整个ListView的显示。整个的源代码在如下的网址可以下载:

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


3)创建toolbar

在这个章节里,我们来完成我们的toolbar的设计。再回到我们最上面图显示的最初的设计,我们需要有一个输入的框来输入我们的关键词以来搜索。

    BorderImage {
        id:toolbar

        anchors.bottom:parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter

        width: parent.width
        height: units.gu(6)

        source: "images/toolbar.png"
        border.left: 4; border.top: 4
        border.right: 4; border.bottom: 4

        Row {
            id:inputcontainer
            anchors.centerIn:toolbar
            anchors.verticalCenterOffset:6
            spacing:12
            Text {
                id:label
                font.pixelSize:30
                anchors.verticalCenter:parent.verticalCenter;
                text: i18n.tr("Search:")
                font.bold:true
                style:Text.Raised
                styleColor:"#fff"
                color:"#444"
            }

            TextField {
                id:input
                placeholderText: "Please input a text to search:"
                width:220
                text:"Beijing"
            }
        }

        Image {
            id: backbutton
            opacity:0
            source: "images/back.png"
            anchors.verticalCenter:parent.verticalCenter
            anchors.verticalCenterOffset:0
            anchors.left:parent.left
            anchors.leftMargin:8

            MouseArea{
                anchors.fill:parent
                onClicked: mainScreen.state=""
                scale:4
            }
        }
    }

整个的设计也是非常直接的。我们使用了一个toolbar.png作为背景。使用一个Row来管理我们的“Search”及TextField输入框。同时我们还有一个在默认情况下opacity为零的back.png的Image。它用来显示在detail屏中来返回到ListView.默认情况下是不可见的。我们需要下载toolbar.pngback.png来完成该步骤。为了能够得到整屏的显示,我们也对ListView的高度进行调整如下:

    UbuntuListView {
        id: listView
        width: mainScreen.width
        height:mainScreen.height - toolbar.height
        model: sampleListModel
        delegate: listDelegate
    }

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



虽然现在还不能做什么,但是我们应用的基本框架已经搭好了。在接下来的章节中,我们来更进一步把真实的数据填进来。这个步骤的代码在如下的地址可以找到:

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

3)对ListView添加真实的数据

虽然上面我们已经有一个显示,但是毕竟是虚假的数据。在这个章节中,我们将使用XmlListModel来对Flickr网站进行请求并得到真实的数据。首先,我们在PictureListView.qml的开始部分加入

import QtQuick.XmlListModel 2.0

在sampleListModel的上面加入如下的代码:

XmlListModel {
    id: feedModel

//     source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2"
    source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2&tags=" + escape(input.text)
    query: "/rss/channel/item"  // flickr
    namespaceDeclarations:  "declare namespace media=\"http://search.yahoo.com/mrss/\";"

    // Flickr
    XmlRole { name: "title"; query: "title/string()" }
    XmlRole { name: "imagePath"; query: "media:thumbnail/@url/string()" }
    XmlRole { name: "photoAuthor"; query: "author/string()" }
    XmlRole { name: "photoDate"; query: "pubDate/string()" }

    XmlRole { name: "url"; query: "media:content/@url/string()" }
    XmlRole { name: "description"; query: "description/string()" }
    XmlRole { name: "tags"; query: "media:category/string()" }
    XmlRole { name: "photoWidth"; query: "media:content/@width/string()" }
    XmlRole { name: "photoHeight"; query: "media:content/@height/string()" }
    XmlRole { name: "photoType"; query: "media:content/@type/string()" }
}

同时修改listView的model为feedModel:

    UbuntuListView {
        id: listView
        width: mainScreen.width
        height:mainScreen.height - toolbar.height
        model: feedModel
        delegate: listDelegate
    }

这里我们使用了XmlListModel来查询我们的数据。我们可以在浏览器中输入地址http://api.flickr.com/services/feeds/photos_public.gne?format=rss2&tags=beijing。我们可以看到如下的显示



我们通过XmlListModel进行查询,并同时转换为我们所需要的数据进行显示。比如我们用到的“title”及“imagePath”。在查询时我们也同时使用了input输入框中的text。我们运行我们刚生成的应用,并同时修改在输入框中的内容,比如“shanghai”或“tianjing”等。我们会看到内容的改变。

  

为了使得图片的大小更加适合于在手机上显示,我们也同时修改listDelegate中BorderImage的高度为180.

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


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

4)显示照片的详细信息

我们虽然已经显示了照片的详细信息,我们希望在点击每个ListView中的每个项目的时候,能够更加清楚地看得到更加详细的照片信息。为了能够达到这个目的,我们可以在ListView的右边放置一个同样大小的屏幕。当我们点击ListView中的每一项时,我们就把屏幕向右移动。这样就可以看到照片的详细信息。当我们看完照片时,我们也可以把屏幕向左移动这样又回到ListView的页面。为了达到这样的设计,我们在我们的项目中加入一个新的叫做“SpinnerImage.qml”的Component。它的创建方法和上面介绍的“PictureListView.qml”是一样的。它的设计如下:

import QtQuick 2.0

Image {
    id:image

    property bool loading:status != Image.Ready

    Image {
        id: container
        property bool on: false
        source: "images/spinner.png";
        visible: loading
        anchors.centerIn:parent
        NumberAnimation on rotation {
            running: loading ; from: 0; to: 360;
            loops: Animation.Infinite; duration: 2000
        }
    }
}

我们可以看到,这是一个Image中含有一个Image的Component。当id为image中的Image还在下载时,里面的container中的spinner.png将不断地旋转。我们需要下载spinner.png文件。

我们需要修改ListView的设计如下:

    Row {
        id:viewcontainer

        UbuntuListView {
            id: listView
            width: mainScreen.width
            height:mainScreen.height - toolbar.height

            model: feedModel
            delegate: listDelegate

            Scrollbar {
                flickableItem: listView
            }
        }

        SpinnerImage {
            id:viewscreen
            clip:true
            width:mainScreen.width
            height:mainScreen.height - toolbar.height
            smooth:true
            fillMode:Image.PreserveAspectFit
        }
    }

在这里我们使用了一个Row布局,从而产生了一个和mainScreen宽度两倍的显示。在起始显示的时候,只有最左边的显示可以在屏幕中展示。为了能够相应对触屏的响应,我们在我们的listDeleage中最外层的BorderImage中加入如下的代码:

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    viewcontainer.x = -mainScreen.width
                    viewscreen.source = imagePath
                }
            }

重新运行应用。我们发现,当我们点击listview中的每一项时,就会很快地切换到另外一个页面,并展示该图片。在实际体验中,这个可能并不是最好的,有时我们希望能看到一个平滑过度的中间状态。我们在这里可以使用QML中的状态来实现这个功能。在PictureListView.qml中,加入如下的代码(放在mainScreen):

    states: [
        State {
            name: "view"
            PropertyChanges {
                target: viewcontainer
                x:-mainScreen.width
            }
            PropertyChanges {
                target: backbutton
                opacity:1
            }
            PropertyChanges {
                target: inputcontainer
                opacity:0
            }
        }
    ]

    transitions: [
        Transition {
            NumberAnimation { target: viewcontainer; property: "x"; duration: 500
                easing.type:Easing.OutSine}
            NumberAnimation { target: inputcontainer; property: "opacity"; duration: 200}
            NumberAnimation { target: backbutton; property: "opacity"; duration: 200}
        }
    ]

在这里我们定义了一个新的状态“view”。默认的状态为"",即一个空字符串。在状态放生切换时我们可以使用transitions来实现动画的展示。在上面,我们使得在x发生改变时,需要500毫秒来完成。这样我们就可以看到状态的变化。加入了状态后,我们重新改写listDelegate中MouseArea中的代码:

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    viewscreen.source = imagePath.replace("_s", "_m")
                    mainScreen.state = "view"
                }
            }

在这里,我们同时也修改了imagePath中的内容,从而使得我们能够在展示详细图片时,使用的是“中”(m)等大小的图片而不是“小”(s)的图片。重新运行我们的应用:


   

当我们点击在ListView中的每个项时,就会展示它的详细的图片的情况。我们可以点击详细图片下方的箭头回到ListView中去。这个代码在toolbar的如下代码中可以到:

        Image {
            id: backbutton
            opacity:0
            source: "images/back.png"
            anchors.verticalCenter:parent.verticalCenter
            anchors.verticalCenterOffset:0
            anchors.left:parent.left
            anchors.leftMargin:8

            MouseArea{
                anchors.fill:parent
                onClicked: mainScreen.state=""
                scale:4
            }
        }

在这里我们可以看到,当我们点击的时候,mainScreen的状态state有设为"",也就是应用最起始的状态。在x坐标发生变化时,我们可以通过transitions来实现动画的效果。

这个应用在手机的图片为:

  


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

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


5)更进一步完善应用

细心的开发者如果在手机上运行并输入字符串时,可能会发现跳出的键盘挡住了我们的输入框。我们可以通过打开在main.qml中的MainView中的如下的项:

anchorToKeyboard: true

更多的阅读可以参照文章来详细了解。

另外,在ListView中,当照片还在下载时,我们也希望能够看到不断选择的spinner。我们可以把listDelegate中的imageId修改为:

            Rectangle {
                id: imageId
                x: 6; y: 4; width: parent.height - 10; height:width; color: "white"; smooth: true
                anchors.verticalCenter: parent.verticalCenter

                SpinnerImage {
                    source: imagePath; x: 0; y: 0
                    height:parent.height
                    width:parent.width
                    anchors.verticalCenter: parent.verticalCenter
                }
            }

我们可以在UbuntuListView中加入“PullToRefresh”如下的代码:

        UbuntuListView {
            id: listView
            width: mainScreen.width
            height:mainScreen.height - toolbar.height

            model: feedModel
            delegate: listDelegate

            // let refresh control know when the refresh gets completed
            PullToRefresh {
                refreshing: listView.model.status === XmlListModel.Loading
                onRefresh: listView.model.reload()
            }

            Scrollbar {
                flickableItem: listView
            }
        }

这样,当我们往下拉ListView时,会自动更新我们的ListView中的内容。

我们也可以通过修改Flickr.png来实现我们自己的定制的icon。整个应用的最终版本在如下的地址可以找到:

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

另外一个更加复杂的版本在如下地址可以下载:

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



作者:UbuntuTouch 发表于2014-10-29 15:44:42 原文链接
阅读:15 评论:0 查看评论

Read more
UbuntuTouch

[原]使用URL dispatcher的范例

在上面的一篇文章中,我们介绍了如何使用URL disptacher。在这篇文章中,我们来通过一个范例更进一步来了解如何实现它。


1)创建一个具有URL dispatcher的应用

我们首先打开我们的SDK,然后创建一个最基本的QML template应用。我们把该应用叫做“MyApp”。我们首先在“MyApp”的根目录添加一个文件叫做“MyApp.url-dispatcher”文件,这里面的内容如下:

[
	{
		"protocol": "launchmyapp"
	}
]

这样的定义使得任何在Qt.openUrlExternally()中具有以“launchmyapp:///”开头的调用,就可以打开该应用。比如:

Qt.openUrlExternally("launchmyapp:///123");

同时,我们修改我们的manifest.json文件如下:

{
    "architecture": "all",
    "description": "description of MyApp",
    "framework": "ubuntu-sdk-14.10-dev2",
    "hooks": {
        "MyApp": {
            "apparmor": "MyApp.apparmor",
            "desktop": "MyApp.desktop",
	    "urls": "MyApp.url-dispatcher"
        }
    },
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "name": "com.ubuntu.developer.unknown.myapp",
    "title": "MyApp",
    "version": "0.1"
}

注意这里的“urls”项。到这里,我们基本上就可以让我们的应用能够被其它的应用调用了。为了能够得到调用应用传来的参数,我们也同时修改我们的desktop文件如下:

[Desktop Entry]
Name=MyApp
Exec=qmlscene $@ main.qml -- %u
Icon=MyApp.png
Terminal=false
Type=Application
X-Ubuntu-Touch=true

注意这里的"-- %u",这是加入的部分。为了能够在程序中显示得到的URL信息,我们对程序做了如下的修改:


import QtQuick 2.0
import Ubuntu.Components 1.1

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

MainView {
    id:root

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.unknown.myapp"

    Component.onCompleted: {
        mylabel.text = "aaaa";
        console.log( "arg length: " + myarg.arguments.length );

        if ( myarg.defaultArgument === undefined) {
            mylabel.text = "undefined";
        } else {
            mylabel.text = "args: " + myarg.defaultArgument.at(0);
        }

        console.log("argument: " + myarg.defaultArgument.at(0));
        console.log("")
    }

    Arguments {
        id: myarg
        defaultArgument.help: "Expects URL of the media to play."
        defaultArgument.valueNames: ["URL"]
    }

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

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

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

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

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

            Row {
                spacing: units.gu(2)

                Label {
                    id: mylabel
                    objectName: "label"

                    text: i18n.tr("Received parameters: ")
                }

                Label {
                    id: label
                    objectName: "label"

                    text: i18n.tr("")
                }

                Connections {
                    target: UriHandler
                    onOpened: {
                        // root.applicationName = "good"
                        mylabel.text = "dddddd";

                        var para = "";
                        for (var i = 0; i < uris.length; ++i) {
                            // application.parseArgument(uris[i])
                            console.log( uris[i] );
                            para +=  uris[i];
                        }

                        label.text = para;
                    }
                }
            }
        }
    }

}

注意这里的“UriHandler”部分,当应用在运行时,url dispatcher被调用时,该部分代码会被执行。当应用没有运行时,我们通过传人的参数从而得到输入的参数值:

    Arguments {
        id: myarg
        defaultArgument.help: "Expects URL of the media to play."
        defaultArgument.valueNames: ["URL"]
    }

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

https://code.launchpad.net/~liu-xiao-guo/debiantrial/myapp


2)创建调用应用


这个应用其实很简单。我们直接创建一个基本的QML template应用,同时修改我们的main.qml如下:

import QtQuick 2.0
import Ubuntu.Components 1.1

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.unknown.launchmyapp"

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

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

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

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

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

            Button {
                objectName: "button"
                width: parent.width

                text: i18n.tr("Launch MyApp")

                onClicked: {
                    Qt.openUrlExternally("launchmyapp:///123");
                }
            }

            Button {
                objectName: "button"
                width: parent.width

                text: i18n.tr("Open MyApp")

                onClicked: {
                    Qt.openUrlExternally("appid://com.ubuntu.developer.unknown.myapp/MyApp/current-user-version");
                }
            }
        }
    }
}

这里我们使用了两种方法来调用我们的“MyApp”。第一种是通过:

 Qt.openUrlExternally("launchmyapp:///123");

这种方法的好处是可以传人我们需要的参数,并解析,从而对于不同的参数可以得到不同的响应。

另外一种方式是通过:

Qt.openUrlExternally("appid://com.ubuntu.developer.unknown.myapp/MyApp/current-user-version");

这种方法不能解析任何的参数,它可以把应用启动起来。我们可以通过如下的方法得到应用的一些信息:


运行我们的应用:



我们按下第一个按钮,如果“MyApp”没有运行时,会显示如下的在左边的画面。如果“MyApp”在已经运行的情况下,可以看到如下的右边的画面:

   

如果,我们点击应用下面的按钮的话,可以看到如下的画面:



整个项目的源码在如下的地址可以找到:


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




作者:UbuntuTouch 发表于2014-10-28 12:49:37 原文链接
阅读:156 评论:0 查看评论

Read more
UbuntuTouch

URL dispatcher 是在Ubuntu OS上的一个服务。它可以让我们的应用(confined,i.e, click package应用)来启动其它的应用。这些应用通常是一个特别的URL来 识别的。可能最常见的例子就是向网页浏览器发送http:// URL来启动网页浏览器,但是像music或其它的应用也可以支持。对于大多数的应用来说,这是一个最有用的方式来退出现有的应用来启动另外一个应用。


对于Qt应用来说,它访问URL dispatcher的方法是通过Qt的desktop plugin。C++应用可以通过使用QDesktopServices.openURL()方法。QML应用可以使
用 Qt.openUrlExternally(string)方法。对于其它的应用来说,它们可以利用根据自己的需求使用Platform API或直接使用liburl-dispatcher。

URL dispatcher项目可以在Launchpad的链接找到。

支持的URL格式

基本URL格式


在URL dispatcher中有几种被支持的格式:



application:///$(app_id).desktop
The application URL can be used to start an application with a known Application ID. For applications that install their desktop file in /usr/share/applications the application ID should just be the name of the desktop file.
appid://$(pkg)/$(app)/$(version) Allows for launching an application using an application ID. Also provides for wild cards. The $(app) can be first-listedlast-listed-app or only-listed-app to select the appropriate applicaiton using the click manifest. Also the $(version) can be current-user-version to select the version in the manifest

这里的第二种方法是推荐的方法。比如对于Clock应用来说,我们可以看到如下的应用URL:


appid://com.ubuntu.clock/clock/current-user-version

我们可以通过如下的方法得到在手机中应用click package的信息:


     


应用URL

对于一些应用来说,我们可以在启动应用的时候同时传人一些参数来启动该应用。我们可以通过在应用的Click manifest文件中加入URL的定义来注册该应用可以被一个或多个URL来启动。为了达到这个目的,我们可以在该文件中的“hooks”部分加入一个和“desktop”并列的小的json文件的申明。一个简单的manifest就像下面定义的。


{
        "name": "My App",
        "version": "1.2.3",
        "hooks": {
                "foo": {
                        "desktop": "foo.desktop",
                        "urls": "foo.url-dispatcher"
                }
        }
}

这里它指向另外一个在click包中的JSON文件。它定义了什么URL将被接受。一个简单的文件就像如下的格式:

[
        {
                "protocol": "foo",
                "domain-suffix": "bar.com"
        }
]

通过上面的定义,每当有任何一个像“foo://*.bar.com”格式URL请求,由foo.desktop代表的应用将被自动被调用。在这里,如果“domain-suffix”项被省去的话,该应用将接受所有以“foo”代表的protocol格式的请求。URL dispatcher的定义是一个数组,这样有很多的这样的格式可以在应用中被定义。

一个例程显示如何调用Music及Clock可以在如下的地址找到:

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

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

Read more
UbuntuTouch

对很多的开发者来说,你们可能使用的不是Ubuntu操作系统。在这种情况下,开发者需要在自己的操作系统中(OS X及Windows)安装virtualbox,并在VirtualBox中安装Ubuntu及Ubuntu SDK。为了方便大家的安装,我们已经制定好了一个Image。这个Image中包含Ubuntu Utopic (14.10)及Ubuntu SDK。大家可以一次性地下载并安装SDK。下面介绍其安装步骤。

1)从https://www.virtualbox.org/wiki/Downloads下载最新的VirtualBox

Download VirtualBox


注意:当我们下载VirtualBox是,一定要根据自己的系统选择合适的版本。


2)双击刚下载的VirtualBox文件,并安装它

3)下载Ubuntu virtual machine (最小的Ubuntu 14.10 desktop版本及已经在里面安装好的Ubuntu SDK)

4)等下完后,双击已经下载的文件“ubuntu+sdk.ova”来导入到VirtualBox中,并运行它


注意:在整个安装过程中,需要用到的用户名及密码是“ubuntu/ubuntu”

在安装完整个SDK后,我们可以参照文章“怎么在Virtualbox下安装Ubuntu OS”来设置自己的中文输入法及文件分享。可以参照文章“Ubuntu SDK 安装”来进一步安装自己的“armhf”及“i386” chroot。整个安装chroot的过程可能需要一定的时间。需要耐心等待。等整个安装过程完成了,我们就可以进行下一步的开发了。

作者:UbuntuTouch 发表于2014-10-17 9:50:48 原文链接
阅读:259 评论:0 查看评论

Read more
UbuntuTouch

我在以前的文章中,讲述了如何使用U1dbSQLite offline storage API来存储应用的一些状态。在这篇文章中,我将介绍如何使用Qt.labs.settings来存储应用的状态。更加详细的介绍,请参阅链接


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

import QtQuick 2.0
import Ubuntu.Components 1.1
import Qt.labs.settings 1.0

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

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

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

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

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

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

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

        Column {
            anchors.fill: parent
            anchors.centerIn: parent
            anchors.horizontalCenter: parent.center


            Label {
                text: "Please input a string below:"
                fontSize: "large"
            }

            TextField {
                id: myTextField
                text: settings.input
                placeholderText: "please input a string"

                onTextChanged: {
                    settings.input = text
                }
            }

            Button {
                text: "Get category"
                onClicked: {
                    console.log("settings category:" + settings.category);
                }
            }
        }

        Settings {
            id: settings
            property string input: "unknown"
        }

        Component.onDestruction: {
            settings.input = myTextField.text
        }
    }
}

记得这里我们一定要加入Qt.labs.settings。我们首先绑定myTextField的值为settings中的input。在程序退出的时候,我们通过如下的方式进行存储:


        Component.onDestruction: {
            settings.input = myTextField.text
        }

在我们的应用中,我们使用如下的方法。每当myTextField变化时,我们就存一下。这依赖于我们最终程序的需求是什么样的。

            TextField {
                id: myTextField
                text: settings.input
                placeholderText: "please input a string"

                onTextChanged: {
                    settings.input = text
                }
            }

运行我们的应用,我们会发现,当我们修改myTextField中的值,并退出后。下次启动时,可以看到,上次输入的值被读取,并存放于myTextField中。



整个测试的源码在 bzr branch lp:~liu-xiao-guo/debiantrial/settingsqml

作者:UbuntuTouch 发表于2014-10-16 15:18:13 原文链接
阅读:223 评论:0 查看评论

Read more
UbuntuTouch

QML入门必备基础知识之——UI布局管理


概述

使用 Qt 做过 UI 后一定对 QHBoxLayout, QVBoxLayout, 和 QGridLayout 这三个最重要也最常使用的 layout managers 非常熟悉。那么在 QML 中又是如何控制和管理 UI 布局的呢?那么我们这篇文章就为大家介绍这些基础知识。

首先,QML 同样允许大家使用硬编码的方式将位置数值直接写到代码中,但是这样做首先难以适应 UI 的调整,其次代码维护起来也很困难。因此我们推荐大家不要直接写数值,而是使用下列三种布局管理器:Row,、Column、Grid,以及使用 Anchor 进行布局。

Row

QML 中的 Row 元素会将其子控件都排列在同一行,相互不重叠。我们还可以使用它的 spacing 属性来定义子控件之间的距离。比如下列代码就会产生如图所示的效果:

Row { 
spacing: 2
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
}

Row.png




Column

QML 中的 Column 元素会将其子控件都排列在同一行,相互不重叠。我们可以使用它的 spacing 属性来定义子控件之间的距离。比如下列代码就会产生如图所示的效果:

Column { 
spacing: 2
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
}

Column.png





Grid

QML 中的 Grid 元素会将其子控件都均匀地排列在一个网格内,相互不重叠,每一个子控件都被放置在一个网格单元的(0,0)位置,也就是左上角。Grid的 rows 和 columns 属性定义网格的行数和列数,列数默认是4。我们还可以使用 Grid 的spacing 属性来定义网格单元之间的距离,这里注意水平和垂直方向的 spacing 都是一样的。比如下列代码就会产生如图所示的效果:

Grid { 
columns: 3
spacing: 2
Rectangle { color: "red"; width: 50; height: 50 }
Rectangle { color: "green"; width: 20; height: 50 }
Rectangle { color: "blue"; width: 50; height: 20 }
Rectangle { color: "cyan"; width: 50; height: 50 }
Rectangle { color: "magenta"; width: 10; height: 10 }
}

Grid.png




混合应用

我们还可以将 Grid、Row 和 Column 进行混合应用。比如下面的代码会产生如图所示的效果:

Column {
spacing: 2
Rectangle { color: "red"; width: 50; height: 50 }
Row {
spacing: 2
Rectangle { color: "yellow"; width: 50; height: 50 }
Rectangle { color: "black"; width: 20; height: 50 }
Rectangle { color: "blue"; width:50; height: 20 }
}
Rectangle { color: "green"; width: 20; height: 50 }
}

Combine.png






Anchor

每一个 item 都可以被认为具有 7 条隐藏的“anchor lines":left、 horizontalCenter、 right、 top、 verticalCenter、baseline、以及 bottom,如下图所示: Anchor1.png

其中 baseline 是指的文本所在的线,在上图中并未标出,如果 item 没有文字的话 baselinw 就和 top 的位置是相同的。 除此之外,Anchor 系统还提供了margins 和 offsets。margins 是指一个 item 和外界之间所留有的空间,而 offsets 则可以通过使用 center anchor lines 来进行布局。如下图所示:

Anchor2.png





使用 QML anchoring 系统,我们可以定义不同 items 之间的 anchor lines 之间的关系。例如:

Rectangle { id: rect1; ... }
Rectangle { id: rect2; anchors.left: rect1.right; anchors.leftMargin: 5; ... }

执行效果:Anchor3.png




我们还可以使用多个 anchors:

Rectangle { id: rect1; ... }
Rectangle { id: rect2; anchors.left: rect1.right; anchors.top: rect1.bottom; ... }

执行效果:Anchor4.png





通过定义多个水平或垂直的 anchors,我们还可以控制 item 的大小,例如:

Rectangle { id: rect1; x: 0; ... }
Rectangle { id: rect2; anchors.left: rect1.right; anchors.right: rect3.left; ... }
Rectangle { id: rect3; x: 150; ... }

执行效果:Anchor5.png


注意:出于效率方面的考虑,我们只允许对一个 item 的邻居和直接父亲使用 anchor 定义。比如下面的定义是不合法的:

 Item {
id: group1
Rectangle { id: rect1; ... }
}
Item {
id: group2
Rectangle { id: rect2; anchors.left: rect1.right; ... } // invalid anchor!
}
本文原文出“QML入门必备基础知识之——UI布局管理
作者:UbuntuTouch 发表于2014-10-16 9:41:16 原文链接
阅读:129 评论:0 查看评论

Read more
UbuntuTouch

在前面的一些文章中,我们已经介绍了一些怎么利用Qt和C++ API来创建一个Scope。它们都是一些基本的Scope。在这篇文章中,我们将介绍department Scope,并掌握开发它的方法。Department Scope将会在许多的Scope中进行分类搜寻。更多关于Scope的介绍可以在网址http://developer.ubuntu.com/scopes/找到。我们最终的Scope的界面如下:


      


1)什么是department Scope


首先,坦率地说,我们很难找到一个很确切的中文词来描述它。姑且叫它部门Scope吧。在上面的左图上,我们可以看到在“美食”的正右边,有一个向下的下拉箭头。点击它后,就可以看到如中间图所示的菜单。这也就是说,我们可以对点评网的每个category进行分别地搜索,而不是把不同领域的搜索结果都罗列出来。比方,我想找吃的,我只想知道和餐馆相关的信息,而不要和美容,娱乐相关的搜寻结果。通过我们对点评API的接口

http://api.dianping.com/v1/business/find_businesses?appkey=3562917596&sign=16B7FAB0AE9C04F356C9B1BE3BB3B77829F83EDA&category=美食&city=上海&latitude=31.18268013000488&longitude=121.42769622802734&sort=1&limit=20&offset_type=1&out_offset_type=1&platform=2

进行分析,我们可以把“category”设置为我们的部门,这样我们就可以对每个领域进行分别的搜寻。我们也可以通API接口来得到所有点评的category:

http://api.dianping.com/v1/metadata/get_categories_with_businesses

关于这个API的接口具体可以在链接找到。

2)创建一个基本的Scope

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



我们给我们的应用一个名字“dianping”。我们同事也选择template的类型为“Empty scope”:

  
这样我们就创建了一个最基本的scope。我们可以点击它,可能没有什么太多的功能。

2)加入对Qt的支持

我们可以看到在项目的“src”目录下有两个目录:apiscope。api目录下的代码主要是为了来访问我们的web service来得到一个json或是xml的数据。在这个项目中,我们并不准备采用这个目录中的client类。有兴趣的开发者可以尝试把自己的client和scope的代码分开。

我们首先打开在“src”中的CMakeLists.txt文件,并加入如下的句子:

add_definitions(-DQT_NO_KEYWORDS)
find_package(Qt5Network REQUIRED)
find_package(Qt5Core REQUIRED)     
find_package(Qt5Xml REQUIRED)      

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

....

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

qt5_use_modules(scope Core Xml Network) 

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

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

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

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

qt5_use_modules(scope-unit-tests Core Xml Network)

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

重新编译项目,如果还有编译错误错误,请修正。

我们同时需要对scope.cpp进行修改。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制及生成一个Qt应用。我们来修改scope.h文件,并加QoreApplication的变量app及类的forward申明。我们也必须同时加入一个方法"run"。

class QCoreApplication; // added

namespace scope {
class Scope: public unity::scopes::ScopeBase {
public:
    void start(std::string const&) override;
    void stop() override;
    void run(); // added
    unity::scopes::PreviewQueryBase::UPtr preview(const unity::scopes::Result&,
                                                  const unity::scopes::ActionMetadata&) override;
    unity::scopes::SearchQueryBase::UPtr search(
            unity::scopes::CannedQuery const& q,
            unity::scopes::SearchMetadata const&) override;

protected:
    api::Config::Ptr config_;
    QCoreApplication *app; //added
};

我们同时打开scope.cpp,并做如下的修改:

#include <QCoreApplication> // added

...

void Scope::stop() {
    /* The stop method should release any resources, such as network connections where applicable */
    delete app;
}

void Scope::run()
{
    int zero = 0;
    app = new QCoreApplication(zero, nullptr);
}

这样我们的每个scope其实也是一个Qt应用在运行。重新编译我们的Scope,并在desktop上运行。至此,我们基本对我们的框架加入了基本的Qt支持。在下面的环节中,我们来一步一步地完成我的其它的部分。

3)代码讲解

src/scope/scope.cpp


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

在上一节中,我们已经基本上完成了对它的改造。对大多数的Scope来说,基本上我们不需要做很多的改变。对于我们的这个Scope,我们想使用cache来缓冲我们的数据,这样可以提高我们Scope的流畅度。这里我们对search函数做如下的修改:

sc::SearchQueryBase::UPtr Scope::search(const sc::CannedQuery &query,
                                        const sc::SearchMetadata &metadata) {

    const QString scopePath = QString::fromStdString(scope_directory());
    const QString cachePath =QString::fromStdString(cache_directory());

    // Boilerplate construction of Query
    return sc::SearchQueryBase::UPtr(new Query(query, metadata, scopePath,cachePath, config_));
}

同时我们也要对Query类中的构造函数进行修改,以便能够进行编译:

Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata, QString const& scopeDir,
        QString const& cacheDir, Config::Ptr config) :
        sc::SearchQueryBase( query, metadata ),
        m_scopeDir( scopeDir ),
        m_cacheDir( cacheDir ),
        client_(config)
{
    qDebug() << "CacheDir: " << m_cacheDir;
    qDebug() << "ScopeDir " <<  m_scopeDir;

    qDebug() << m_urlRSS;
}

当然,我们要记得在我们的Query头文件中加入数据变量m_scopeDir及m_cacheDir:

class Query: public unity::scopes::SearchQueryBase {

....

private:
    QString m_scopeDir;
    QString m_cacheDir;
    ....
}

重新编译我们的Scope。如果大家此时还有任何的问题的话,可以下载我的源码

bzr branch lp:~liu-xiao-guo/debiantrial/dianpingdept1。

大家可以此为基础向下做练习。

src/scope/query.cpp


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

  • 得到由用户输入的查询字符串
  • 向web services发送请求
  • 生成搜索的结果(根据每个scope不同而不同)
  • 创建搜索结果category(比如不同的layout-- grid/carousel)
  • 根据不同的搜寻结果来绑定不同的category以显示我们所需要的UI
  • 推送不同的category来显示给最终用户
  • 基本上所有的代码集中在"run"方法中。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制。
接下来我们对“run”进行修改来达到搜寻的目的。对dianping API的接口来说,我们需要对其输入的URL进行签名。为了方便,我定义了如下的helper方法。

QString Query::getUrl(QString addr, QMap<QString, QString> map) {
    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

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

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

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

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

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

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

这里用到的“appKey”及“secret”是两个定义的QString常量。开发者需要到点评的网站进行申请。这里的addr就是请求的url的前面部分,比如http://api.dianping.com/v1/metadata/get_categories_with_businesses。这里的map实际上是像如下的一组数据,用来存储请求的参数的。我们利用这个方法来得到我们的department的url。如下:

Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata, QString const& scopeDir,
        QString const& cacheDir, Config::Ptr config) :
        sc::SearchQueryBase( query, metadata ),
        m_scopeDir( scopeDir ),
        m_cacheDir( cacheDir ),
        // m_limit( 0 ),
        client_(config)
{
    qDebug() << "CacheDir: " << m_cacheDir;
    qDebug() << "ScopeDir " <<  m_scopeDir;

    QMap<QString,QString> map;
    map["format"] = "xml";

    m_urlRSS = getUrl(DEPARTMENTS,  map);
    qDebug() << "m_urlRSS: " << m_urlRSS;
}

我们可以把上面的代码加到Query类的构造函数中。这里的DEPARTMENTS定义如下:

const QString DEPARTMENTS = "http://api.dianping.com/v1/metadata/get_categories_with_businesses?";

我们可以通过打印的方式打印出来到Application Output窗口中:

m_urlRSS:  "http://api.dianping.com/v1/metadata/get_categories_with_businesses?appkey=3562917596&sign=4BAF8DD42A36538E17207A1C10F819571B00BF6E&format=xml"

如果我们把得到的url输入到浏览器中,我们会发现:




接下来,我们需要通过网路请求的方式得到上面的xml格式的数据并对它进行解析。为了能够得到我们需要的departments,我们对“run”方法做如下的修改:

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Create an instance of disk cache and set cache directory
    m_diskCache = new QNetworkDiskCache();
    m_diskCache->setCacheDirectory(m_cacheDir);

    QEventLoop loop;

    QNetworkAccessManager managerDepts;
    QObject::connect(&managerDepts, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&managerDepts, &QNetworkAccessManager::finished,
                     [reply,this](QNetworkReply *msg){
        if( msg->error()!= QNetworkReply::NoError ){
            qWarning() << "failed to retrieve raw data, error:" << msg->error();
            rssError(reply,ERROR_Connection);
            return;
        }
        QByteArray data = msg->readAll();

        // qDebug() << "XML data is: " << data.data();

        QString deptUrl = rssDepartments( data, reply );

        CannedQuery cannedQuery = query();
        QString deptId = qstr(cannedQuery.department_id());
        qDebug() << "department id: " << deptId;

        if (!query().department_id().empty()){ // needs departments support
            qDebug() << "it is not empty xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!";
            deptUrl = m_depts[deptId];
            qDebug() << "depatUrl: " << deptUrl;
        } else {
            qDebug() << "It is empty ===================================!";
        }

        if ( deptUrl.isEmpty() )
            return;
   });
    managerDepts.setCache(m_diskCache);
    managerDepts.get(QNetworkRequest(QUrl(m_urlRSS)));
    loop.exec();
}

这里其实很简单,我们通过对m_urlRSS的请求,并把得到的结果传给rssDepartments来解释所得到的xml格式的数据。每一个department都有一个叫做department_id来识别。它是一个独有的区别其他的String。rss_Departments的实现如下:

QString Query::rssDepartments( QByteArray &data, unity::scopes::SearchReplyProxy const& reply ) {
    QDomElement docElem;
    QDomDocument xmldoc;
    DepartmentList rss_depts;
    QString firstname = "";

    CannedQuery myquery( SCOPE_NAME );
    myquery.set_department_id( TOP_DEPT_NAME );

    Department::SPtr topDept;

    if ( !xmldoc.setContent(data) ) {
        qWarning()<<"Error importing data";
        return firstname;
    }

    docElem = xmldoc.firstChildElement("results");
    if (docElem.isNull()) {
        qWarning() << "Error in data," << "results" << " not found";
        return firstname;
    }

    docElem = docElem.firstChildElement("categories");
    if ( docElem.isNull() ) {
        qWarning() << "Error in data," << "categories" << " not found";
        return firstname;
    }

    docElem = docElem.firstChildElement("category");

    // Clear the previous departments since the URL may change according to settings
    m_depts.clear();

    int index = 0;
    while ( !docElem.isNull() ) {

        QString category = docElem.attribute("name","");
        qDebug() << "category: " << category;

        if ( !category.isEmpty() ) {
            QString url = getDeptUrl(category);

            QString deptId = QString::number(index);

            if (firstname.isEmpty()) {
                //Create the url here
                firstname = url;
                topDept = move(unity::scopes::Department::create( "",
                                                                  myquery, category.toStdString()));
            } else {
                Department::SPtr aDept = move( unity::scopes::Department::create( deptId.toStdString(),
                                              myquery, category.toStdString() ) );
                rss_depts.insert( rss_depts.end(), aDept );
            }

            m_depts.insert( QString::number(index), url );
            index++;
        }

        docElem = docElem.nextSiblingElement("category");
    }

    // Dump the deparmemts
    QMapIterator<QString, QString> i(m_depts);
    while (i.hasNext()) {
        i.next();
         qDebug() << i.key() << ": " << i.value();
    }

    topDept->set_subdepartments( rss_depts );

     try {
        reply->register_departments( topDept );
    } catch (std::exception const& e) {
        qWarning() << "Error happened: " << e.what();
    }

    return firstname;
}

这个方法通过解析,并生产相应的department。完整的代码可以在

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

运行我们的Scope。我们可以看到所生产的department。



现在显然我们还看不到任何东西因为我们没有对我们的department进行搜寻。接下来,我们可以按照文章“怎么在Ubuntu Scope中获取location地址信息”来设置获得我们所需要的位置信息。在手机上,我们可以通过网路或GPS来获得我们所需要的位置信息。在电脑上目前还没有支持。通过获得的位置信息,我们通过点评对当地的位置进行搜索。

我们接下来对“run”更进一步地修改来对我们得到的department进行查询:

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Initialize the scopes
    initScope();

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

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

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

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

    // Create an instance of disk cache and set cache directory
    m_diskCache = new QNetworkDiskCache();
    m_diskCache->setCacheDirectory(m_cacheDir);

    QEventLoop loop;

    QNetworkAccessManager managerDepts;
    QObject::connect(&managerDepts, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&managerDepts, &QNetworkAccessManager::finished,
                     [reply,this](QNetworkReply *msg){
        if( msg->error()!= QNetworkReply::NoError ){
            qWarning() << "failed to retrieve raw data, error:" << msg->error();
            rssError(reply,ERROR_Connection);
            return;
        }
        QByteArray data = msg->readAll();

        // qDebug() << "XML data is: " << data.data();

        QString deptUrl = rssDepartments( data, reply );

        CannedQuery cannedQuery = query();
        QString deptId = qstr(cannedQuery.department_id());
        qDebug() << "department id: " << deptId;

        if (!query().department_id().empty()){ // needs departments support
            qDebug() << "it is not empty xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!";
            deptUrl = m_depts[deptId];
            qDebug() << "depatUrl: " << deptUrl;
        } else {
            qDebug() << "It is empty ===================================!";
        }

        if ( deptUrl.isEmpty() )
            return;

        QEventLoop loop;
        QNetworkAccessManager managerRSS;
        QObject::connect( &managerRSS, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
        QObject::connect( &managerRSS, &QNetworkAccessManager::finished,
                         [reply,this](QNetworkReply *msg ){
            if( msg->error() != QNetworkReply::NoError ){
                qWarning() << "failed to retrieve specific dept raw data, error:" <<msg->error();
                rssError( reply, ERROR_Connection );
                return;
            }

            QByteArray data = msg->readAll();
            if( query().query_string().empty() ){
                rssImporter( data, reply, CATEGORY_HEADER );
            } else {
                rssImporter( data, reply, CATEGORY_SEARCH );
            }

        });
        managerRSS.setCache( m_diskCache );
        managerRSS.get( QNetworkRequest( QUrl(deptUrl)) );
        loop.exec();

    });
    managerDepts.setCache(m_diskCache);
    managerDepts.get(QNetworkRequest(QUrl(m_urlRSS)));
    loop.exec();
}

上面我们可以看到我们定义了另外一个QEventLoop。在这里,我们通过对刚才所得到的deptUrl做一个新的请求,并把得到的数据传到rssImporter函数中进行解析。

void Query::rssImporter(QByteArray &data, unity::scopes::SearchReplyProxy const& reply, QString title) {
    QDomElement docElem;
    QDomDocument xmldoc;
    CannedQuery cannedQuery = query();
    QString query = qstr( cannedQuery.query_string() );

    if ( !xmldoc.setContent( data ) ) {
        qWarning()<<"Error importing data";
        return;
    }

    docElem = xmldoc.documentElement();
    //find result
    docElem = docElem.firstChildElement("businesses");
    if (docElem.isNull()) {
        qWarning()<<"Error in data,"<< "result" <<" not found";
        return;
    }

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

    auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
    auto grid = reply->register_category("dianpinggrid", "", "", rdrGrid);
    bool isgrid = false;

    docElem = docElem.firstChildElement("business");

    while (!docElem.isNull()) {
        QString business_id = docElem.firstChildElement("business_id").text();
        // qDebug() << "business_id: " << business_id;

        QString name = docElem.firstChildElement("name").text();
        // qDebug() << "name: "  << name;

        // Let's get rid of the test info in the string
        name = removeTestInfo(name);

        QString branch_name = docElem.firstChildElement("branch_name").text();
        // qDebug() << "branch_name: " << branch_name;

        QString address = docElem.firstChildElement("address").text();
        // qDebug() << "address: " << address;

        QString telephone = docElem.firstChildElement("telephone").text();
        // qDebug() << "telephone: " << telephone;

        QString city = docElem.firstChildElement("city").text();
        // qDebug() << "city: " << city;

        QString photo_url = docElem.firstChildElement("photo_url").text();
        // qDebug() << "photo_url: " << photo_url;

        QString s_photo_url = docElem.firstChildElement("s_photo_url").text();
        // qDebug() << "s_photo_url: " << s_photo_url;

        QString rating_s_img_uri = docElem.firstChildElement("rating_s_img_uri").text();
        // qDebug() << "rating_s_img_uri: " << rating_s_img_uri;

        QString business_url = docElem.firstChildElement("business_url").text();
        // qDebug() << "business_url: " << business_url;

        QDomElement deals = docElem.firstChildElement("deals");
        QDomElement deal = deals.firstChildElement("deal");
        QString summary = deal.firstChildElement("description").text();
        // qDebug() << "Summary: " << summary;

        if ( !query.isEmpty() ) {
            if ( !name.contains( query, Qt::CaseInsensitive ) &&
                 !summary.contains( query, Qt::CaseInsensitive ) &&
                 !address.contains( query, Qt::CaseInsensitive ) ) {
                qDebug() << "it is going to be skipped";
                docElem = docElem.nextSiblingElement("business");
                continue;
            } else {
                qDebug() << "it is going to be listed!";
            }
        }

        docElem = docElem.nextSiblingElement("business");

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

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

        CategorisedResult catres((*top));

        catres.set_uri(business_url.toStdString());
        catres.set_dnd_uri(business_url.toStdString());
        catres.set_title(name.toStdString());
        catres["subtitle"] = address.toStdString();
        catres["summary"] = summary.toStdString();
        catres["fulldesc"] = summary.toStdString();
        catres.set_art(photo_url.toStdString());
        catres["art2"] = s_photo_url.toStdString();
        catres["address"] = Variant(address.toStdString());
        catres["telephone"] = Variant(telephone.toStdString());

        //push the categorized result to the client
        if (!reply->push(catres)) {
            break; // false from push() means search waas cancelled
        }
    }

    qDebug()<<"parsing ended";
}

请注意,我们在上面的代码中使用了如下的代码一对每个department所搜寻的结果再次根据在search输入框中所输入的字符串进行匹配,从而可以更加缩小所显示的内容:

        if ( !query.isEmpty() ) {
            if ( !name.contains( query, Qt::CaseInsensitive ) &&
                 !summary.contains( query, Qt::CaseInsensitive ) &&
                 !address.contains( query, Qt::CaseInsensitive ) ) {
                qDebug() << "it is going to be skipped";
                docElem = docElem.nextSiblingElement("business");
                continue;
            } else {
                qDebug() << "it is going to be listed!";
            }
        }


创建并注册CategoryRenderers

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

[html] view plaincopy
  1. std::string CR_GRID = R"(  
  2.     {  
  3.         "schema-version" : 1,  
  4.         "template" : {  
  5.             "category-layout" : "grid",  
  6.             "card-size": "small"  
  7.         },  
  8.         "components" : {  
  9.             "title" : "title",  
  10.             "art" : {  
  11.                 "field": "art",  
  12.                 "aspect-ratio": 1.6,  
  13.                 "fill-mode": "fit"  
  14.             }  
  15.         }  
  16.     }  
  17. )";  

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

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册:

  1. CategoryRenderer rdrGrid(CR_GRID);  
  2. CategoryRenderer rdrCarousel(CR_CAROUSEL);  
  3.   
  4. QString title = queryString + "美味";  
  5.   
  6. auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);  
  7. auto grid = reply->register_category("dianpinggrid""""", rdrGrid);  


我们可以把得到的数据通过qDebug的方式进行打印并调试。这里加入的代码很多,你可以在如下的地址下载我的代码:


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

运行我们的Scope, 我们可以看到如下的画面:

  

大家可以点击图片。我们其实还没完成preview的部分,在下面我们会加入更多的信息来显示我们所得到的信息。我们可以输入一个字符串并使得list的内容更少。比如,我们可以输入”朝阳区“来更进一步地缩小我们的显示的list。

src/dianping-preview.cpp

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

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

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

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

Preview

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

Preview Widgets

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

这个例子使用了如下的widgets

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

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

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

我们的代码在如下的地址可以找到:

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


重新运行我们的Scope,我们可以看到如下的画面。在Preview的画面中,我们可以看到更多的信息,比如电话号码。同时我们可以点击“Open”,并进入到网页看到更多的信息。

  

  


4)加入设置

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

// The followoing function is used to retrieve the settings for the scope
void Query::initScope()
{
    qDebug() << "Going to retrieve the settings!";

    unity::scopes::VariantMap config = settings();  // The settings method is provided by the base class
    if (config.empty())
        qDebug() << "CONFIG EMPTY!";

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

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

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Initialize the scopes
    initScope();

 ....
}

同时不要忘记在“data”目录下生产相应的.ini文件(/dianping/data/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini)。其内容如下:

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

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

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

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

我们可以运行一下“Run CMake”,这样,我们在Project中可以看到新添加的.ini文件。重新运行我们的Scope,并在Scope的右上角的设置图标(像有锯齿的 )去尝试改变limit的值,看看效果是什么样的。

  

我们也可以同时修改“data”目录下的logo及icon文件来使得我们的Scope更像一个branded Scope。最终所有的源码可以在如下的地址下载:

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

大家如果有什么建议,请告诉我。



作者:UbuntuTouch 发表于2014-10-14 16:01:44 原文链接
阅读:137 评论:0 查看评论

Read more
UbuntuTouch

在本遍文章中,我们来讲解怎么对我们的Ubuntu Scope进行设置。对Scope而言,有些时候我们希望能够使用设置来改变我们的显示,或对我们的搜索进行重新定义。关于更多Scope的开发,请参阅网站:http://developer.ubuntu.com/scopes/

1)首先创建一个最基本的Scope

我们首先打开SDK,并选择“Unity Scope”模版。我们选择一个项目的名称为“settingscope”:



接下来,我们选择“Empty scope”。这样我们就创建了我们的一个最基本的scope了。



2)加入代码来完成设置功能


首先,我们打开项目中的“data”文件夹,并创建一个如下的文件名:

com.ubuntu.developer.liu-xiao-guo.settingscope_settingscope-settings.ini

注意这个文件名和Scope的设置文件

com.ubuntu.developer.liu-xiao-guo.settingscope_settingscope.ini

只有细小的差别。只是在它的后面加上“-settings"即可。记住千万不要改变这个规则。注意这个文件名和项目的名称的不同而不同

为了能够对这个文件进行设置和安装,我们也同时需要对“data”目录下的“CMakeLists.txt”文件加入如下的内容:


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

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

这样我们的设置文件就可以安装到目标中了。下面,我们可以对我们的设置文件进行配置。打开我们的设置文件:

[location]
type = string
defaultValue = London
displayName = Location


[distanceUnit]
type = list
defaultValue = 1
displayName = Distance Unit
displayName[de] = Entfernungseinheit
displayValues = Kilometers;Miles
displayValues[de] = Kilometer;Meilen


[age]
type = number
defaultValue = 23
displayName = Age


[enabled]
type = boolean
defaultValue = true
displayName = Enabled


# Setting without a default value
[color]
type = string
displayName = Color


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



在这里,我们定义了一些设置的名称,比如“location”。它被定义为“string”,同时它还有一个默认的值“London”。显示的提示为“Location”,当然我们也可以把它修改为“位置”(对中文而言)。


为了能够在应用中访问我们,我们可以修改我们的代码如下:

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

    // Read the settings
    initScope();

    try {
        // Start by getting information about the query
        const sc::CannedQuery &query(sc::SearchQueryBase::query());

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

        Client::ResultList results;
        if (query_string.empty()) {
            // If the string is empty, pick a default
            results = client_.search("default");
        } else {
            // otherwise, use the search string
            results = client_.search(query_string);
        }

        // Register a category
        auto cat = reply->register_category("results", "Results", "",
                                            sc::CategoryRenderer(CATEGORY_TEMPLATE));

        for (const auto &result : results) {
            sc::CategorisedResult res(cat);

            cerr << "it comes here: " << m_limit << endl;

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

            // res.set_title(result.title);
            res.set_title( m_location );
            res["subtitle"] = std::to_string(m_limit);

            // Set the rest of the attributes, art, description, etc
            res.set_art(result.art);
            res["description"] = result.description;

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

void Query::initScope()
{
    unity::scopes::VariantMap config = settings();  // The settings method is provided by the base class
    if (config.empty())
        cerr << "CONFIG EMPTY!" << endl;

    m_location = config["location"].get_string();     // Prints "London" unless the user changed the value
    cerr << "location: " << m_location << endl;

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

这里“initScope”在“Run”中被调用。在InitScope中,我们通过“settings()”来读取设置的值。为了显示的方便,我们在“Run”中,也对读取的值进行简单的显示:

            // res.set_title(result.title);
            res.set_title( m_location );
            res["subtitle"] = std::to_string(m_limit);

我们重新运行我们的Scope,并可以看到如下的图片:

   

我们也可以在我们的Application Output窗口中看到设置的变化:



整个项目的源码可以在如下的地址下载:

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




作者:UbuntuTouch 发表于2014-10-14 13:12:28 原文链接
阅读:233 评论:0 查看评论

Read more
UbuntuTouch

Location信息对很多有地址进行搜索的应用来说非常重要。比如对dianping这样的应用来说,我们可以通过地址来获取当前位置的一些信息。在这篇文章中,我们来介绍如何获取Scope架构中的位置信息。这个位置信息可以对我们很多的搜索是非常重要的。


1)创建一个简单的Scope应用


我们首先打开SDK,并选择“Unity Scope”模版:



接下来,我们选择“Empty scope”。这样我们就创建了我们的一个最基本的scope了。



我们可以运行我们的Scope。这是一个最基本的Scope。

2)加入代码获取Location信息

为了获取位置信息,我们对我们的代码进行设置。首先打开"data"文件夹中的.ini文件,并加入LocationDataNeeded=true。这样整个文件显示为:

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

LocationDataNeeded=true

[Appearance]
PageHeader.Logo = logo.png

同时我们打开scope.cpp文件,并修改为:

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

....


void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        cerr << "starting to get the location" << endl;

        auto metadata = search_metadata();
        if (metadata.has_location()) {

            cerr << "it has location data" << endl;

            auto location = metadata.location();

            if (location.has_country_code()) {
                cerr << "country code: " << location.country_code() << endl;
             }

            if ( location.has_area_code() ) {
                cerr << "area code: " << location.area_code() << endl;
            }

            if ( location.has_city() ) {
               cerr << "city: " << location.city() << endl;
            }

            if ( location.has_country_name() ) {
                cerr << "" << location.country_name() << endl;
            }

            if ( location.has_altitude()) {
                cerr << "altitude: " << location.altitude() << endl;
                cerr << "longitude: " << location.longitude() << endl;
                cerr << "latitude: " << location.latitude() << endl;
            }

            if ( location.has_horizontal_accuracy()) {
                cerr << "horizotal accuracy: " << location.horizontal_accuracy() << endl;
            }

            if ( location.has_region_code() ) {
                cerr << "region code: " << location.region_code() << endl;
            }

            if ( location.has_region_name() ) {
                cerr << "region name: " << location.region_name() << endl;
            }

            if ( location.has_zip_postal_code() ) {
                cerr << "zip postal code: " << location.zip_postal_code() << endl;
            }
        }

 ....

}

我们通过打印的方式来查看我们所收到的位置信息。在手机上运行,并同时在desktop上执行如下的命令:





我们可以看到我们所需要的位置信息。通过这些信息,我们可以在我们的Scope中使用。

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

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



作者:UbuntuTouch 发表于2014-10-10 13:07:50 原文链接
阅读:280 评论:0 查看评论

Read more
UbuntuTouch

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

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


1) 生成Click Package

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


  • 选择IDE左下方的目标架构为"Ubuntu Device (GCC armhf-ubuntu-sdk-14.10-utopic)"
  • 选中IDE 左侧的"Publish",在这个框中我们可以设置我们所需要的一些东西,比如说应用的Title等

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

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

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

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




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



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

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


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


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

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

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


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

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

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

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

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



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

$ click --help

来查看它的具体的用法。

5) 登陆到手机

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

$ adb shell

我们也可以通过如下的命令来切换到"phablet"账号中

$ root@ubuntu-phablet:~# su - phablet

如果需要安装软件需要密码的话,密码是"phablet"


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


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

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

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


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

Read more
UbuntuTouch

[原]Ubuntu SDK 安装

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


操作系统选择

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

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

添加Phablet Tools PPA

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

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

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

添加Ubuntu SDK 发布 PPA中

按照一下方式添加Ubuntu SDK 发布 PPA (https://launchpad.net/~ubuntu-sdk-team/+archive/ppa)。
输入你的Linux管理员密码

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

安装 Ubuntu SDK

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

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

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

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

启动Ubuntu SDK IDE

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


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

$ ubuntu-sdk 

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

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



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

     


如果我们在SDK启动时,选择不再显示安装wizard,并且我们选择不安装armhf及i386架构,我们也可以在下面的步骤中来安装它们。

安装Ubuntu SDK armhf chroot

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

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


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

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

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

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

安装Ubuntu SDK i386 chroot

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



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

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

安装模拟器

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

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


有了这个模拟器,我们就可以在模拟器中运行我们开发的应用了。我们可以选择刚才生成的模拟器(myinstance),并运行它:



实际运行的效果图如下,



开发者也可以参阅https://wiki.ubuntu.com/Touch/Emulator文章来安装自己的模拟器。

安装搜狗中文输入法


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

总结

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

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

Read more
UbuntuTouch

[原]调试QML应用

Console API

Log

console.log, console.debug, console.info, console.warnconsole.error 可以用来打印输出调试信息到console.。比如:

function f(a, b) {
  console.log("a is ", a, "b is ", b);
}


这个输出是用使用Qt C++中的qDebug, qWarning, qCritical 方法(可以参考http://doc.qt.nokia.com/latest/debug.html#warning-and-debugging-messages)。

设置环境变量QML_CONSOLE_EXTENDED也将输出在源码中的调用。

Assert

console.assert 是用来测试表达式是真或是否。如果不是真,它将输出一个可选的输出信息到console中,并打印出stack trace。


function f() {

  var x = 12
  console.assert(x == 12, "This will pass");
  console.assert(x > 12, "This will fail");
}

Timer

console.time 及 console.timeEnd 用来记录以毫秒计算的在调用函数之间所花费的时间。两个都带有一个字符串参数来标示测量时间。比如:

function f() {
    console.time("wholeFunction");
    console.time("firstPart");
    // first part
    console.timeEnd("firstPart");
    // second part
    console.timeEnd("wholeFunction");
}

Trace

console.trace 用来打印Javascript在被执行时当前代码的stack trace。 stack trace 的信息含有函数的名称,文件名称,代码行的数字及列数。 stack trace 仅限于最后的10个stack frames.

Count

console.count 用来输出输出某个特别代码已经被执行多少次,同时显示一个信息。

function f() {
  console.count("f called");
}

上述代码将输出 f called: 1f called: 2 ... 无论 f() 在任何时候被执行。

Profile

console.profile 启动 QML 及 JavaScript profilers。嵌套的调用不被支持。如果是这样的情况,一个警告的信息将被输出到console。

console.profileEnd 关掉QML及JavaScript profilers。在没有调用console.profile的情况下,调用这个函数将输出一个警告信息到console。一个profiling的客户端应该在调用这个函数之前已经连接起来以使得这个函数能够接受并存储profiling的数据。比如:

function f() {
    console.profile();
    //Call some function that needs to be profiled.
    //Ensure that a client is attached before ending
    //the profiling session.
    console.profileEnd();
}

Exception

console.exception 用来输出一个错误的信息并生成在Javascript代码被执行处的stack trace。

Debugging module imports

QML_IMPORT_TRACE 环境变量可以用来被设置为启动输出来自QML的import loading机制的调试信息。

比如,一个简单的QML文件:

import QtQuick 2.0
Rectangle { width: 100; height: 100 }

如果你设置QML_IMPORT_TRACE=1,之后运行QML Scene (or或是你的QML C++应用),你将看到类似如一下的输出:

QQmlImportDatabase::addImportPath "/qt-sdk/imports"

QQmlImportDatabase::addImportPath "/qt-sdk/bin/QMLViewer.app/Contents/MacOS"
QQmlImportDatabase::addToImport 0x106237370 "." -1.-1 File as ""
QQmlImportDatabase::addToImport 0x106237370 "Qt" 4.7 Library as ""
QQmlImportDatabase::resolveType "Rectangle" = "QDeclarativeRectangle"

Debugging with Qt Creator

Qt Creator 提供了一个集成好的QML调试工具。QML项目及单独的C++应用程序(使用QML)可以在desktop或是在remote装置上(比如说是用USB连接的手机)。关于这方面的更多信息请参阅Qt Creator使用手册。


作者:UbuntuTouch 发表于2014-8-25 8:38:33 原文链接
阅读:147 评论:0 查看评论

Read more
UbuntuTouch

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

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


1)下载VirtualBox文件

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



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

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

2)安装VirtualBox

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



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



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



3) 下载Ubuntu Desktop Image

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

4) 在VirtualBox中安装Ubuntu OS

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



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



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



接下来:



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



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



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

点击VirtualBox中的“Start




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



再选择“Install Ubuntu



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











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



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




5) 安装VirtualBox Extensions

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



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



点击"Extensions", 



重新启动我们创建的VM。



然后选择:



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



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



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

6) 设置VirtualBox


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




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



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

$sudo usermod -a -G vboxsf username

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

 

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




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



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


7) 安装中文输入法

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

8) 安装Ubuntu SDK

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


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

Read more
UbuntuTouch

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




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



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


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

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




2) 选中“关于此手机”




3)点击“开发者模式”




4) 打开开关




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

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




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

Read more
UbuntuTouch

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


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


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



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

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

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



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

3)安装Ubuntu OS的语言支持

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



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



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



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



4) 配置搜狗中文输入法


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



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


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

5)添加对Qt的中文支持

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

$ sudo apt-get install fcitx-frontend-qt5

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



6) 对Qt Creator 定制

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



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



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

$ fcitx-config-gtk3

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

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

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

Read more
UbuntuTouch

我们知道QML虽然是很强大的,但是有时我们觉得它有些功能还是需要C++来拓展的话,这时我们可以使用IDE提供的plugin架构来创建一个新的plugin。这个plugin可以在QML里直接调用。它的使用就像先前定义好的控件一样。首先我们来看一下我们最终设计的界面。这里是一个交通灯的示范例程:


                 

这里显示的是几个交通灯变化的画面。它们也同时对应我们现实生活中的交通灯变化的几个状态。


在这篇文章里,我们将学到如下的东西:

  • 学习怎么制作一个plugin
  • 学习property binding
  • 学习QML的状态(state)及过渡(transition)
  • 学习怎么制作Component

1) 创建一个基本的应用

首先我们启动Qt Creator IDE,然后选择如下所示的template (App with QML extension Library):



创建一个称作"TrafficLight"的项目:



同时也把应用的名字设定为“Light":






然后,我们按IDE提示的步骤完成以后的过程创建出我们的最原始的一个template应用。我们可以开始在desktop及"armhf"的环境中运行。如果我们还有问题,这可能是我们的安装程序有些问题。请参考我们的SDK安装文章进行修正。

2) 完成TrafficLight plugin


我们知道QML提供了很好的图形控件。QML有一个称作LinearGradient的Gradient,但他不是我们想要的RadialGradient。经过寻扎,我们在C++中找到相应的QRadialGradient类,它可以实现我们想要的功能。我们怎来把它使用到我们的C++应用中呢?

首先我们找到项目中的文件目录“TrafficLight/backend/modules/Light”,创建“trafficlight.h"及“trafficlight.cpp"文件,并输入如下的代码

#ifndef TRAFFICLIGHT_H
#define TRAFFICLIGHT_H

#include <QtQuick/QQuickPaintedItem>
#include <QColor>

class TrafficLight : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)

public:
    explicit TrafficLight(QQuickItem *parent = 0);
    void paint(QPainter *painter);

    QColor color() const;
    void setColor(const QColor &color);

signals:
    void colorChanged();

public slots:

private:
    QColor m_color;
};

#endif // TRAFFICLIGHT_H


<pre style="margin-top: 0px; margin-bottom: 0px;"><!--StartFragment--><span style=" color:#000080;"></span>

#include <QPainter>
#include <QRadialGradient>

#include "trafficlight.h"

TrafficLight::TrafficLight(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
}

QColor TrafficLight::color() const
{
    return m_color;
}

void TrafficLight::setColor(const QColor &color)
{
    if ( color == m_color )
        return;
    else {
        m_color = color;
        update();   // repaint with the new color
        emit colorChanged();
    }
}

void TrafficLight::paint(QPainter *painter)
{
    QRectF rect(boundingRect());

    QPen pen;
    pen.setWidthF( 0 );
    pen.setColor(m_color);
    painter->setPen( pen );

    QRadialGradient g( rect.width()/2, rect.height()/2,
                       rect.width()/2, rect.width()/2, height()/2 );

    g.setColorAt( 0.0, Qt::white );
    g.setColorAt( 1.0, m_color );
    painter->setBrush( g );

    painter->drawEllipse(rect);
}

这里我们定义了一个称作为“TrafficLight”的类。根据不同的颜色,用它来画一个“QRadialGradient”。我们可以看到在这个类中我们定义了一个称作“color"的property。
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)

在下面的QML语言中,我们可以直接通过绑定这个property,也可以对它进行修改从而使得界面发生改变。

我们找到"TrafficLight/backend/CMakeLists.txt"文件,并加入如下的语句:

set(
    Lightbackend_SRCS
    modules/Light/backend.cpp
    modules/Light/mytype.cpp
    modules/Light/trafficlight.cpp
)

这时,我们可以关闭项目再打开项目,或者直接在项目框中点击右键,并运行“Run CMake”以使刚加入的文件在项目框中可见。


找到“backend.cpp"文件,并加入如下语句:

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

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

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

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

这里注册的目的是为了让“TrafficLight”类在QML文件中可以被实例化。第二个"TrafficLight"可以是任何你喜欢的名字,只要它可以和QML中的引用对应即可。

这样我们基本上完成了对plugin的设计。我们这时可以编译一下看一下有什么问题。如果没有的话,我们可以直接进入下面的章节。

3)在QML中引用TrafficLight类型


我们下面来做一个简单的实验,看TrafficLight是否被正确地调用。修改“TrafficLight.qml”文件如下:

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

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

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

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

  TrafficLight{
        id: redlight
        width: 100
        height: 100
        color:"red"
    }
}

在Desktop上运行,我们可以看到:



我们可以看到plugin是真的被调用了。我们可以改变图形的位置或颜色来看看有什么变化等。

4)应用设计

通过修改"TrafficLight.qml"文件,我们首先来看一看我们设计的程序如下:

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

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

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

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

    Page {
        id:main
        anchors.fill: parent
        property int radius: 155

        Image{
            anchors.horizontalCenter: parent.horizontalCenter
            height:parent.height
            source: "light2.png"
            fillMode: Image.PreserveAspectFit

            Column {
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter

                spacing: 28

                TrafficLight{
                    id: redlight
                    width: main.radius
                    height: main.radius
                    color:"red"
                }

                TrafficLight{
                    id: yellowlight
                    width: main.radius
                    height: main.radius
                    color:"yellow"
                }

                TrafficLight{
                    id: greenlight
                    width: main.radius
                    height: main.radius
                    color:"green"
                }
            }
        }
    }
}

在这里我们使用了一个"Column"的layout管理器,它可以使得在它之内部件(Component)从上到下进行显示和管理。如果屏幕的尺寸发生变化,它也会帮我们自动适配及调整。你可以看出我们创建了从上到下的三个“红”,“黄”及“绿”的交通灯。你们也可看到我在这里也使用了一个背景的图片以使得显示效果更加逼真。如果开发者没有这个图片也没有关系。我在下面贴出来(右边的图)。如果确实没有,也没关系。运行时可能看不到背景的图片而已。开发者可以把图片考到和“trafficlight.qml”相同的目录。

   


5)加入状态及过渡

我们知道,上面显示的情况显然不是我们常见的交通灯的情况。现在我们来加入状态来在不同的情况下,让各个等在不同的状态下进行关掉或熄灭。程序如下:


                states: [
                    State {
                        name: "red_on"
                        PropertyChanges {
                            target: redlight
                            color:"red"
                            scale: 1.0
                        }
                        PropertyChanges {
                            target: greenlight
                            color: "black"
                        }
                        PropertyChanges {
                            target: yellowlight
                            color: "black"
                        }
                    },

                    State {
                        name: "red_yellow_on"
                        PropertyChanges {
                            target: redlight
                            color: "red"
                        }
                        PropertyChanges {
                            target: yellowlight
                            color: "yellow"
                        }
                        PropertyChanges {
                            target: greenlight
                            color: "black"
                        }
                    },

                    State {
                        name: "green_on"
                        PropertyChanges {
                            target: redlight
                            color: "black"
                        }
                        PropertyChanges {
                            target: yellowlight
                            color: "black"
                        }
                        PropertyChanges {
                            target: greenlight
                            color: "green"
                        }
                    },

                    State {
                        name: "yellow_on"
                        PropertyChanges {
                            target: redlight
                            color: "black"
                        }

                        PropertyChanges {
                            target: yellowlight
                            color: "yellow"
                        }

                        PropertyChanges {
                            target: greenlight
                            color: "black"
                        }
                    }
                ]

在这里我们定义了4个不同的状态"red_on", "red_yellow_on", "yellow_on" 及“green_on"。这几个状态显然我们常见的几个交通灯的状况。在每个状态下,我们可以看到各个灯的”开”及”关"的情况。

QML也同时提供给我们在不同状态之间进行转换时的动画效果。我们可以通过过渡来实现:

                transitions: [
                    Transition {
                        from: "*"
                        to: "*"

                        PropertyAnimation {
                            target: redlight
                            properties: "scale, color"
                            duration: 1000
                            easing.type: Easing.InQuad
                        }
                        PropertyAnimation {
                            target: greenlight
                            properties: "scale, color"
                            duration: 1000
                            easing.type: Easing.InQuad
                        }
                        PropertyAnimation {
                            target: yellowlight
                            properties: "scale, color"
                            duration: 1000
                            easing.type: Easing.InQuad
                        }
                    }
                ]

在这里我们定义了无论从哪种状态“*”到哪种状态“*”,我们变化的时间是1000毫秒,同时要使得它的scale,及颜色发生相应的渐变。这样可以产生我们所希望的动画效果。

注意这两段代码必须是加到"Column"所在的块中。

6)加入逻辑使得他们在不同的状态之间转换


我们知道只通过简单的状态定义并不能使得应该在不同的状态之间转换。我们必须定义一个逻辑或事件使得它们在某种条件下转换。对于我们的例程,我们可以使用一个Timer来实现:

                Timer {
                    interval: 1000
                    running: true
                    repeat: true
                    property int count: 0

                    onTriggered: {
                        if (parent.state == "red_on" && count >= 5)
                        {
                            parent.state = "red_yellow_on"
                            count = 0
                        }
                        else if ( parent.state == "red_yellow_on" )
                        {
                            parent.state = "green_on"
                            count++
                        }
                        else if ( parent.state == "green_on" && count >= 5 )
                        {
                            parent.state = "yellow_on"
                            count ++
                        }
                        else if ( parent.state == "yellow_on" ) {
                            parent.state = "red_on"
                            count = 0
                        }
                        else {
                            count++
                        }
                    }
                }


这个Timer每一秒触发一次,onTriggered是以个callback方法。通过这个事件我们可以使得程序在不同的状态下转换。




至此,我们这个部分的程序已经设计完成。整个完整的代码可以在如下的地址下载:

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

7)为程序设计Component


我们刚才设计的程序在某种程度上能够完成我们的功能。但是,设想一下,如果我们想在程序中需要更多的交通灯怎么办呢?我们可以把刚才设计的程序改一下,重新包装成一个Component。这样这个控件在许多的程序中可以复用这个控件。

在"TrafficLight.qml"所在的目录中,我们重新生成一个新的文件叫做”MyLight.qml"。这里记得文件的名称一定要大写。同时我们删除在"TrafficLight.qml"中相应的部分的设计。把相关的代码移过去。
这样重新生成的代码如下:

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

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

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

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

    width: units.gu(120)
    height: units.gu(80)

    Page {
        id:main
        anchors.fill: parent

        Row {
            id: myrow
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter
            spacing: units.gu(5)

            MyLight {
                id:light1
                width: main.width/5
                height: width*3
            }

            MyLight {
                id:light2
                width: main.width/5
                height: width*3
            }

            MyLight {
                id:light3
                width: main.width/5
                height: width*3
            }
        }

    }
}


MyLight.qml 代码

import QtQuick 2.0
import Ubuntu.Components 0.1
import Light 1.0

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

    Rectangle {
        id: background
        anchors.fill: parent
        color: "black"
        property int size: width*0.7

        Column {
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter

            spacing: units.gu(3)

            TrafficLight{
                id: redlight
                width: background.size
                height: background.size
                color:"red"
            }

            TrafficLight{
                id: yellowlight
                width: background.size
                height: background.size
                color:"yellow"
            }

            TrafficLight{
                id: greenlight
                width: background.size
                height: background.size
                color:"green"
            }

            state: "red_on"

            states: [
                State {
                    name: "red_on"
                    PropertyChanges {
                        target: redlight
                        color:"red"
                        scale: 1.0
                    }
                    PropertyChanges {
                        target: greenlight
                        color: "black"
                    }
                    PropertyChanges {
                        target: yellowlight
                        color: "black"
                    }
                },

                State {
                    name: "red_yellow_on"
                    PropertyChanges {
                        target: redlight
                        color: "red"
                    }
                    PropertyChanges {
                        target: yellowlight
                        color: "yellow"
                    }
                    PropertyChanges {
                        target: greenlight
                        color: "black"
                    }
                },

                State {
                    name: "green_on"
                    PropertyChanges {
                        target: redlight
                        color: "black"
                    }
                    PropertyChanges {
                        target: yellowlight
                        color: "black"
                    }
                    PropertyChanges {
                        target: greenlight
                        color: "green"
                    }
                },

                State {
                    name: "yellow_on"
                    PropertyChanges {
                        target: redlight
                        color: "black"
                    }

                    PropertyChanges {
                        target: yellowlight
                        color: "yellow"
                    }

                    PropertyChanges {
                        target: greenlight
                        color: "black"
                    }
                }
            ]

            transitions: [
                Transition {
                    from: "*"
                    to: "*"

                    PropertyAnimation {
                        target: redlight
                        properties: "scale, color"
                        duration: 1000
                        easing.type: Easing.InQuad
                    }
                    PropertyAnimation {
                        target: greenlight
                        properties: "scale, color"
                        duration: 1000
                        easing.type: Easing.InQuad
                    }
                    PropertyAnimation {
                        target: yellowlight
                        properties: "scale, color"
                        duration: 1000
                        easing.type: Easing.InQuad
                    }
                }
            ]

            Timer {
                interval: 1000
                running: true
                repeat: true
                property int count: 0

                onTriggered: {
                    if (parent.state == "red_on" && count >= 5)
                    {
                        parent.state = "red_yellow_on"
                        count = 0
                    }
                    else if ( parent.state == "red_yellow_on" )
                    {
                        parent.state = "green_on"
                        count++
                    }
                    else if ( parent.state == "green_on" && count >= 5 )
                    {
                        parent.state = "yellow_on"
                        count ++
                    }
                    else if ( parent.state == "yellow_on" ) {
                        parent.state = "red_on"
                        count = 0
                    }
                    else {
                        count++
                    }
                }
            }
        }
    }
}

运行效果如下:



所有的源码可以在如下地方找到:

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

作者:UbuntuTouch 发表于2014-8-15 7:34:37 原文链接
阅读:111 评论:0 查看评论

Read more
UbuntuTouch

信号及槽(signal-slot)是Qt语言最基本的,也是最有用的一个机制。这是它区分和其他一起语言的一个显据的标志。


我们先来下载我已经写好的应用:


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


使用Ubuntu SDK来打开我们已经创建好的应用。然后再打开文件“MyLight.qml”。在文件的开始部分加入如下的语句:


Item {
    <strong>id: root</strong>
    width: units.gu(100)
    height: units.gu(75)

    signal redLightOn
    signal greenLightOn
    signal yellowLightOn

    Rectangle {
        id: background
        anchors.fill: parent
        color: "black"
        property int size: width*0.7

我们可以看到我们已经定义了三个信号。它们分别是"redLightOn", "greenLightOn"及“yellowLightOn”。我们定义这三个信号的目的是为了当红色,黄色及绿色的灯亮的时候,我们用这些信号来发送一个通知。这样只要有兴趣的代码可以对它进行截获,并处理。这里必须注意的是信号的第一个字母为小写


接下来,我们在我们的JS代码中来发送这些信号。代码如下:


            Timer {
                interval: 1000
                running: true
                repeat: true
                property int count: 0

                onTriggered: {
                    if (parent.state == "red_on" && count >= 5)
                    {
                        parent.state = "red_yellow_on"
                       <strong> root.redLightOn();
                        root.yellowLightOn();</strong>
                        count = 0
                    }
                    else if ( parent.state == "red_yellow_on" )
                    {
                        parent.state = "green_on"
                        <strong>root.greenLightOn();</strong>
                        count++
                    }
                    else if ( parent.state == "green_on" && count >= 5 )
                    {
                        parent.state = "yellow_on"
                       <strong> root.yellowLightOn();</strong>
                        count ++
                    }
                    else if ( parent.state == "yellow_on" ) {
                        parent.state = "red_on"
                        redLightOn();
                        count = 0
                    }
                    else {
                        count++
                    }
                }
            }


发送信号其实非常简单。直接发送,就像调用一个方法一样。


为了在我们的代码部分截取这个应用,我们可以使用如下的方法。在“TrafficLight.qml”中,修改代码为:

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

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

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

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

    width: units.gu(120)
    height: units.gu(80)

    Page {
        id:main
        anchors.fill: parent

        Row {
            id: myrow
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter
            spacing: units.gu(5)

            MyLight {
                id:light
                width: main.width/5
                height: width*3

                onRedLightOn: {
                    console.log("red light is on")
                }
            }

           Connections {
               target: light
               onYellowLightOn: {
                   console.log("yellow light is on")
               }
           }
        }

        function greenLightOn() {
                 console.log("green light is on")
         }

        Component.onCompleted: {
            light.greenLightOn.connect(greenLightOn);
        }

    }
}

为了说明清楚,我写了三种方法。一种是直接把信号的第一个字母变为大写, 并同时在前面加上"on“。第二种方法使用”Connections"来实现槽的连接。第三种方法,我们可以直接 把信号连接到一个JS的函数上。运行程序,我们可以在应用的输出窗口看到如下的输出:

green light is on
yellow light is on
red light is on
red light is on
yellow light is on
green light is on
yellow light is on
red light is on
red light is on
yellow light is on


事实上所有的控件的property都可以发出一个信号。让我们来看一下我们先前完成的“color” property。


void TrafficLight::setColor(const QColor &color)
{
    if ( color == m_color )
        return;
    else {
        m_color = color;
        update();   // repaint with the new color
        emit colorChanged();
    }
}

从这里可以看出,每当property的值发生改变时,就会发生一个叫做“colorChanged”的信号。我们可以在我们的QML中截获这个信号。比如在我们的代码中,我们可以使用如下的代码:


            TrafficLight{
                id: redlight
                width: background.size
                height: background.size
                color:"red"

                onColorChanged: {
                    console.log("Color is changed to " + color);
                }
            }

当我们运行时,我们可以看到好多的颜色变化的事件。这是由于颜色在transition时发生很多的颜色的变化。同样我们也可以对任何一个property进行事件的捕获。比如:


            TrafficLight{
                id: redlight
                width: background.size
                height: background.size
                color:"red"

                onColorChanged: {
                    console.log("Color is changed to " + color);
                }

                onWidthChanged: {
                    console.log("width is changed to " + width);
                }
            }

这段代码可以对"width"的变化进行捕获!


更多阅读可以在连接找到http://qt-project.org/doc/qt-4.8/qmlevents.html



作者:UbuntuTouch 发表于2014-8-15 14:37:41 原文链接
阅读:99 评论:0 查看评论

Read more
UbuntuTouch

我们知道在Ubuntu Touch上面,我们可以创建Qt/QML的应用,同时,我们也可以使用Ubuntu SDK来创建一些跨平台的HTML 5的应用。这些应该虽然是在Ubuntu Touch的SDK上面创建的,但也是可以修改在其它的平台上运行。下面,我们来简单介绍一些怎么在SDK中创建并部署.


在Ubuntu Touch上面,我们可以使用HTML 5的一些tag来些,我们的应用,同时我们也可以使用Cordova API接口来扩展我们的功能。更多的阅读,可以阅读我们的官方网站


你可以在Ubuntu的开发者网站找到更多的关于这些API的介绍:http://developer.ubuntu.com/api/html5/development/

1) 创建一个基本的框架


首先我们打开我们的SDK,可以看到如下的界面:




接下来我们可以创建一个称作“WebSysInfo"的应用。这个应用的目的是为了显示一些系统的一些信息。



紧接着,按照SDK的提示完成剩下的步骤来创建一个最基本的框架应用。由于HTML 5的应用是跨平台的,我们可以直接在电脑上运行这个最基本的应用,也可以直接部署到手机上。如果你在这个步骤不能正确地运行你的应用,请查看我的SDK安装指南
如果你运行程序正常,你可以看到如下的画面:





至此,我们已经完成了一个最基本的应用。下面我们来学习如何添加Cordova API 到我们的应用中来扩展我们的一些功能。

2)添加Cordova API到应用中

我们知道Cordova API有很多有用的功能。可以是我们的应用更加丰富。下面我们来学习怎么把Cordova API加到我们的应用中去。首先,我们选中我们的项目,同时选择菜单“ Tool”==>"Ubuntu" ==>"Add Cordova runtime to HTML 5 project"。



这时应用开始下载最新的Cordova API的包,并安装到我们所在的应用中。目前由于一些原因,需要关闭项目(最终的版本应该解决这个问题),再重新打开项目来查看在项目栏更新后的目录结构。




为了使用这些API,我们可以对"index.html"的代码做如下的修改。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>An Ubuntu HTML5 application</title>
    <meta name="description" content="An Ubuntu HTML5 application">
    <!-- due to a bug in the SDK, the following line will be used in the final SDK
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> -->
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Ubuntu UI Style imports - Ambiance theme -->
    <link href="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />

    <!-- Ubuntu UI javascript imports - Ambiance theme -->
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/dialogs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tab.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>

    <!-- Cordova platform API access - Uncomment this to have access to the Cordova Javascript APIs -->
    <!-- NOTE:
         Make sure that you have imported the Cordova native runtime and JS libraries into your project first.
	 In order to do that, in QtCreator, select the "Add Cordova runtime to HTML5 project" menu item
         in Tools > Ubuntu.
      -->
    <script src="cordova/cordova.js"></script>

    <!-- Application script -->
    <script src="js/app.js"></script>
  </head>

  <body>

    <div data-role="mainview">

      <header data-role="header">
        <ul data-role="tabs">
          <li data-role="tabitem" data-page="mainpage">System Info</li>
        </ul>
      </header>

      <div data-role="content">

          <!-- The application main page -->

          <div data-role="tab" id="mainpage">

            <section data-role="list" id="info">
                <header class="large-font">System Info</header>
            </section>

          </div>

      </div>
      
    </div>
  </body>
</html>

这里我们定义了一个“System Info”的tab。它对应着"mainpage"。当它被点击的时候,对应的"mainpage"就会被打开。在“mainpage"中我们定义了一个section, 在section中,定义了一个list。我们为list也定义了一个叫做"System Info”的"header"。当然我么也可以定义更多的"header"。值得注意的是我们通过一下语句

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

来加载Cordova API以使我们能够正常使用们。同时,

    <!-- Application script -->
    <script src="js/app.js"></script>

我们可以使用"app.js"来对我们的HTML进行操作。


3)怎么调试应用

当我们运行应用的时候,我们可以在"Application output"窗口看到如下的信息:

unity::action::ActionManager::ActionManager(QObject*):
	Could not determine application identifier. HUD will not work properly.
	Provide your application identifier in $APP_ID environment variable.
Using "/home/liuxg/cases/WebSysInfo/www" as working dir 
"file:///home/liuxg/cases/WebSysInfo/www/index.html" 
Using "/home/liuxg/cases/WebSysInfo/www" as working dir 
"file:///home/liuxg/cases/WebSysInfo/www/index.html" 
Inspector server started successfully. Try pointing a WebKit browser to http://192.168.199.228:9221

显然它告诉我们我们可以在Chrome浏览器中打开http://192.168.199.228:9221地址查看我们所需要的信息。我们不妨在“app.js"文件里输入如下的句子

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

    console.log("we are so glad to see it works!")

    // Add an event listener that is pending on the initialization
    //  of the platform layer API, if it is being used.
    document.addEventListener("deviceready", function() {
        if (console && console.log)
            console.log('Platform layer API ready');
    }, false);
};


运行程序,同时在Chrome中打开http://192.168.199.228:9221。可以看到如下的画面:



我们可以在“Console”里看到我们需要的输出。必须注意的是,如果一个应用有两个同时运行的实例,那么最新的实例将不会有任何的输出。在Console里只能看到第一个实例的输出。当你运行第二个实例的时候,你可以在Application Output 窗口中看到如下的输出:

Couldn't start the inspector server on bind address "192.168.199.228" and port "9221". In case of invalid input, try something like: "12345" or "192.168.2.14:12345"

另外应用必须先启动,然后再看地址http://192.168.199.228:9221。否则你将进不去。

4) 加入JS代码实现Cordova API的调用

现在我们来修改项目中“app.js”文件来对UI进行改变。首先我们在"index.html"中加入如下的句子:

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

这样,我们就可以在"app.js"中使用如下的句子来得到我们的list了:

    var info = UI.list('#info');

有了变量info,我们就可以向其中添加我们所需要的东西了。

处理window.onload事件

对于Web开发者来讲,window.onload事件并不陌生。当DOM被完全载入时,这个事件会被发生。这个事件对于处理一些需要在DOM完全载入紧接着执行一些代码。

window.onload = function () {
....
}

处理deviceready事件

对于使用Cordova API的应用来说,我们需要使用deviceready事件来完成对Cordova API的使用。这个事件是用来告诉Cordova runtime已经准备好了可以使用了。一般来说,它的使用是这样的

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

    console.log("we are so glad to see it works!")

    var info = UI.list('#info');


    // Add an event listener that is pending on the initialization
    //  of the platform layer API, if it is being used.
    document.addEventListener("deviceready", function() {
        if (console && console.log)
            console.log('Platform layer API ready');

        info.append("Cordova version: " + device.cordova, null, null, null);
        info.append("Device model: " + device.model, null, null, null);
        info.append("Version: " + device.version, null, null, null);
        info.append("Platform: " + device.platform, null, null, null);
        info.append("UUID: " + device.uuid, null, null, null);
        info.append("Available: " + device.available, null, null, null);
        info.append("Screen width: " + screen.width);
        info.append("Screen height: " + screen.height);
        info.append("Colordepth: " + screen.colorDepth);
        info.append("UserAgent: " + navigator.userAgent);
    }, false);
};

这样我们的一个简单的应用就已经做好了。在Nexus 4上面我们可以看到如下的画面



至此,我们一个最基本的HTML 5的应用已经做好了。源码可以在如下的地址找到。

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

作者:UbuntuTouch 发表于2014-8-19 16:05:13 原文链接
阅读:113 评论:0 查看评论

Read more
UbuntuTouch

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


        


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


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



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

   

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




我们直接在电脑的Desktop上运行我们的应用。我们可以在“Unity Scope tool”中输入北京,我们就可以看到北京的天气的情况:



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

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




2)完成我们的Client API代码


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

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

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

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

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

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

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

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

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

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


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

#ifndef API_CONFIG_H_
#define API_CONFIG_H_

#include <memory>
#include <string>

namespace api {

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

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

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

}

#endif /* API_CONFIG_H_ */


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

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

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

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

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

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

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

    Client(Config::Ptr config);

    virtual ~Client() = default;

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

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

    virtual Config::Ptr config();

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

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

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

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

}

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

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

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

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

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

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

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

    Forecast result;

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

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

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

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

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

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

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

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

    tm *localtm = localtime(&now);

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

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

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

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

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

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

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

            localtm->tm_mday++;

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

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

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

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

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

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

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

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

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

        }
    }

    return result;
}

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

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

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


3)代码讲解


src/scope/scope.cpp


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

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

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

src/scope/query.cpp


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

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

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

创建并注册CategoryRenderers

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


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

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

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

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


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

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

        Client::Forecast forecast;

        cerr << "query_string: " << query_string;

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

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

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

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

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


            res.set_title(ss.str());

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

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

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

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

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

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

        }

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

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

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

    

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

src/scope/preview.cpp

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

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

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

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

Preview

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

Preview Widgets

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

这个例子使用了如下的widgets

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

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

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

最终的程序如下:

#include <scope/preview.h>

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

#include <iostream>

namespace sc = unity::scopes;

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

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

void Preview::cancelled() {
}

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

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

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

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

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

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

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

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

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

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

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

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


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

      


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

   

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

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

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

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

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

4)调试应用

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



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




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

Read more
UbuntuTouch

前面我们已经学习了如何在Ubuntu Touch上面制作一个Scope应用。Scope也是Ubuntu上面一个非常重要的,又和其他平台区分的一种应用。它能很好地把web services整合到手机平台中,就像是系统的一部分。Scope也对手机制造商来说也是非常重要的。它可以让他们深度定制自己的服务到系统中。


值得指出的是:由于一些原因,目前所有的Scope的开发必须是在Ubuntu OS Utopic (14.10)版本之上的。在Ubuntu OS 14.04上是不可以的。

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


首先打开我们的Ubuntu SDK。选择“Unity Scope"模版。



然后选择好项目的路径,并同时选好自己的项目名称"dianping"。我们选择“Empty scope” template。






接下来,我们就完成剩下的步骤来完成一个最基本的Scope应用。我们可以直接在电脑上运行。当然我们也可以把它运行到手机中。




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


2)加入对Qt的支持


我们可以看到在项目的“src”目录下有两个目录:api及scope。api目录下的代码主要是为了来访问我们的web service来得到一个json或是xml的数据。在这个项目中,我们并不准备采用这个目录中的client类。有兴趣的开发者可以尝试把自己的client和scope的代码分开。


我们首先打开在“src”中的CMakeLists.txt文件,并加入如下的句子:

find_package(Qt5Network REQUIRED) 
find_package(Qt5Core REQUIRED)      

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

....

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

qt5_use_modules(scope Core Network)

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


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


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

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

qt5_use_modules(scope-unit-tests Core Network)

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


3)代码讲解


src/dianping-scope.cpp

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

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

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


我们再重新编译我们的应用,如果我们没有错误的话,我们的Scope可以直接在desktop下直接运行。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制及生成一个Qt应用。我们来修改scope.h文件,并加QoreApplication的变量app及forward申明。我们也必须同时加入一个方法"run"。


class QCoreApplication; // added

namespace scope {
class Scope: public unity::scopes::ScopeBase {
public:
    void start(std::string const&) override;
    void stop() override;
    void run(); // added
    unity::scopes::PreviewQueryBase::UPtr preview(const unity::scopes::Result&,
                                                  const unity::scopes::ActionMetadata&) override;
    unity::scopes::SearchQueryBase::UPtr search(
            unity::scopes::CannedQuery const& q,
            unity::scopes::SearchMetadata const&) override;

protected:
    api::Config::Ptr config_;
    QCoreApplication *app; // added
};

我们同时打开scope.cpp,并做如下的修改:

#include <QCoreApplication> // added

...

void Scope::stop() {
    /* The stop method should release any resources, such as network connections where applicable */
    delete app;
}

void Scope::run()
{
    int zero = 0;
    app = new QCoreApplication(zero, nullptr);
}

src/dianping-query.cpp


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

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

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

基本上所有的代码集中在"run"方法中。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制。


#include <boost/algorithm/string/trim.hpp>

#include <scope/query.h>

#include <unity/scopes/Annotation.h>
#include <unity/scopes/CategorisedResult.h>
#include <unity/scopes/CategoryRenderer.h>
#include <unity/scopes/QueryBase.h>
#include <unity/scopes/SearchReply.h>

#include <iomanip>
#include <sstream>

// The following headers are added
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QCoreApplication>

namespace sc = unity::scopes;
namespace alg = boost::algorithm;

using namespace std;
using namespace api;
using namespace scope;
using namespace unity::scopes; // added

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

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

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


Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata,
             Config::Ptr config) :
    sc::SearchQueryBase(query, metadata), client_(config) {
}

void Query::cancelled() {
    client_.cancel();
}

void Query::run(sc::SearchReplyProxy const& reply) {
    // Firstly, we get the query string here.
    CannedQuery query = SearchQueryBase::query();
    QString queryString = QString::fromStdString(query.query_string());

    QString uri;
    if ( queryString.isEmpty() ) {
        queryString = QString("北京");
        uri = getQueryString(queryString);
    } else  {
        uri = getQueryString(queryString);
    }

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

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

    QString title = queryString + "美味";

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

    QEventLoop loop;

    QNetworkAccessManager manager;

    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));

    QObject::connect(&manager, &QNetworkAccessManager::finished,
            [reply, carousel, grid, this](QNetworkReply *msg){
                QByteArray data = msg->readAll();
                QString json = data;
                // qDebug() << "Data:" << json;
                QJsonParseError err;
                QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &err);

                if (err.error != QJsonParseError::NoError) {
                    qCritical() << "Failed to parse server data: " << err.errorString();
                } else {
                    // Find the "payload" array of results
                    QJsonObject obj = doc.object();
                    QJsonArray results = obj["businesses"].toArray();

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

                    bool isgrid = false;
                    //loop through results of our web query with each result called 'result'
                    for(const auto &result : results) {

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

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

                        //treat result as Q JSON
                        QJsonObject resJ = result.toObject();

                        // load up vars with from result
                        auto name = resJ["name"].toString();
//                        qDebug() << "name: " << name;
                        name = removeTestInfo( name );

                        auto business_uri = resJ["business_url"].toString();
//                        qDebug() << "business_uri: " << business_uri;

                        auto s_photo_uri = resJ["s_photo_url"].toString();
//                        qDebug() << "s_photo_url: " << s_photo_uri;

                        auto photo_uri = resJ["photo_url"].toString();
//                        qDebug() << "photo_url: " << photo_uri;

                        auto rating_s_img_uri = resJ["rating_s_img_url"].toString();
//                        qDebug() << "rating_s_img_uri: " << rating_s_img_uri;

                        auto address = resJ["address"].toString();
                        auto telephone = resJ["telephone"].toString();

                        //set our CateogroisedResult object with out searchresults values
                        catres.set_uri(business_uri.toStdString());
                        catres.set_dnd_uri(business_uri.toStdString());
                        catres.set_title(name.toStdString());
                        catres.set_art(photo_uri.toStdString());

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

                        //push the categorized result to the client
                        if (!reply->push(catres)) {
                            break; // false from push() means search waas cancelled
                        }
                    }
                }
            }
            );

    qDebug() << "Uri:" << uri ;
    manager.get(QNetworkRequest(QUrl(uri)));
    loop.exec();
}

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

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

    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

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

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

//    qDebug() << sign;

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

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

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

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

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

我们可以参阅http://developer.dianping.com/来注册成为dianping网站的开发者。在网址http://developer.dianping.com/app/tutorial可以找到开发指南。可以通过getQueryString()方法来得到所需要请求的uri。

创建并注册CategoryRenderers

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


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

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

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册:

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

    QString title = queryString + "美味";

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


我们可以运行我们所得到的程序,看看我们的结果。



src/dianping-preview.cpp

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

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

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

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

Preview

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

Preview Widgets

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

这个例子使用了如下的widgets

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

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

 PreviewWidget w_header("headerId", "header");

最终的程序如下:

#include <scope/preview.h>

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

#include <iostream>

#include <QString>

namespace sc = unity::scopes;

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

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

void Preview::cancelled() {
}

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

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

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

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

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

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

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

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

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

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

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


运行的效果图如下:

  




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


     


整个完整的代码在如下的网址可以看到:

bzr push lp:~liu-xiao-guo/debiantrial/dianpingqtjson


作者:UbuntuTouch 发表于2014-8-20 16:35:02 原文链接
阅读:191 评论:0 查看评论

Read more