Canonical Voices

Colin Ian King

Firmware Related Blogs

More often than not, I'm looking at ACPI and UEFI related issues, so I was glad to see that  Vincent Zimmer has collected up various useful links to blogs that are Firmware Related.   Very useful, thanks Vincent!

Read more
Nicholas Skaggs

It's never been easier to write tests for your application! I wanted to share some details on the new documentation and other tidbits that will help you ensure your application has a nice testsuite. If you've used the SDK in the past, you understand how nice it can make your development workflow. Writing code and running it on your desktop, device, or emulator is a snap.

Fortunately, having a nice testsuite for your application can also be just as easy. First, you will notice that now all of the wizards inside the SDK now come with nice testsuites already in place. They are ready for you to simply add-on more tests. The setup and heavy lifting is done. See for yourself!


Secondly, developer.ubuntu.com has a great section on every level of testing; no matter which language you use with the SDK. You'll find API references for the tools and technology used, along with helpful guides to get you in the proper mindset.

For autopilot itself, there's also API documentation for the various 'helpers' that will make writing tests much easier for you. In addition, there's a guide to running autopilot tests. This has been made even easier by the addition of Akiva's Autopilot plugin inside the SDK. I'll be sharing details on this as soon as it's packaged, but you can see a sneak peek in this video.

Finally, you will find a guide on how to structure your functional tests. These are the most demanding to write, and it's important to ensure you write your tests in a maintainable way. Don't forget about the guide on writing good functional tests either.

No matter what language or level you write tests for, the guides are there to help you. Why not trying adding a test or two to your project? If you are new, check out one of the wizards and try adding a simple testcase. Then apply the same knowledge (and templated code!) to your own project. Happy test writing!

Read more
Nicholas Skaggs

Snappy Open House!

Introducing Snappy Open Houses! Snappy represents some new and exciting possibilities for ubuntu. A snappy open house is your chance to get familiar with the technology while helping test and break things ! We plan to do an open house before each release as a chance for everyone to interact and provide feedback and help with testing. As such, this is a great way to get started in the snappy world!

So what exactly do I mean by an open house? We want to encourage the community to test with us, explore new features, check for possible regressions and exercise the documentation.  An open house is a chance to come and meet the snappy team developers and help QA test the new image.

During the open house, we'll host a live broadcast on ubuntuonair.com. As part of the broadcast, we'll speak with some of the developers behind snappy and show off new features in the upcoming release. We'll also demonstrate how to flash and test the new release so you can follow along and help test. Finally we'll answer any questions you have and stick around on IRC for a bit to discuss any issues found during testing.

In other words, it's time intended for you to come and try out snappy! I know what you are thinking, "I don't have a cool IoT device to run snappy with". You are in luck! You can run snappy on your desktop and laptop. You don't need a device as you can install snappy on your local machine via kvm. If you do have a device, bring it and prepare to have some fun!

The first of these snappy open houses will be July 7th at 1400 UTC. Please stop by and help test with us, try out snappy, and meet the snappy team!

You can find out more information on the wiki. Mark your calendars and see you next Tuesday!

Read more
David Callé

Add a C++ backend to your QML UI

Whether you are creating a new app or porting an existing one from another ecosystem, you may need more backend power than the QML + JavaScript duo proposed in the QML app tutorial.

Let's have a peek at how to to add a C++ backend to your application, using system libraries or your own, and vastly increase its performance and potential features.

In this tutorial, you will learn how to use and invoke C++ classes from QML and integrate a 3rd party library into your project.

Read the tutorial

Read more
Prakash

I had been thinking of my Touch Table project for a long time. My research on existing solutions was a bit disappointing: mostly insanely expensive, large, or platform locked, they did not fit my vision of a [Android or Linux-powered] ‘desktop’ that would allow me to fit it into my existing workflow, rather than hope that applications would support it (like the Microsoft Surface).

Read more at http://www.ikeahackers.net/2015/06/hemnes-multitouch-table.html

 




Read more
UbuntuTouch

我们可以看到在Ubuntu SDK中有一个自己的WebView。它没有采用Qt标准的Webkit库。在Ubuntu上,我们对下面的引擎做了很多的优化(oxide引擎),这样使得我们的WebView性能更加优越。


下面我们通过一个例子来设计出一个简单的Browser。


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Web 0.2
import QtQuick.Layouts 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: "browser.liu-xiao-guo"

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

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

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

    Page {
        title: i18n.tr("")

        RowLayout {
            id:toolbar
            anchors.top: parent.top
            width: parent.width
            height: units.gu(8)
            spacing: units.gu(1)

            Icon {
                id: back
                anchors.verticalCenter: parent.verticalCenter
                name: "go-previous"
                height: input.height
                width: height
                visible: webview.canGoBack

                MouseArea {
                    anchors.fill: parent
                    onClicked: webview.goBack();
                }
            }
            Icon {
                id: forward
                anchors.verticalCenter: parent.verticalCenter
                name: "go-next"
                height: input.height
                width: height
                visible: webview.canGoForward
                MouseArea {
                    anchors.fill: parent
                    onClicked: webview.goForward();
                }
            }

            TextField {
                id: input
                anchors.verticalCenter: parent.verticalCenter
                height: parent.height - units.gu(1)
                Layout.maximumWidth: parent.width
                Layout.preferredWidth: parent.width - back.width - forward.width
                text: "http://www.baidu.com"

                onAccepted: {
                    webview.url = input.text
                }
            }
        }

        WebView {
            id: webview
            anchors.top: toolbar.bottom
            height: parent.height - toolbar.height
            width: parent.width

            url: "http://www.baidu.com"
        }

    }
}


在这里,我们使用了:

import Ubuntu.Web 0.2

模块。在上面我们使用了两个Icon来返回或向前看。同时我们设计了一个TextField来输入我们想要去的地址。注意地址必须是以http开始的字符串。当我们按下enter键后,就会自己打开页面。


  


  


代码不多,但是它完成了我们想要完成的东西。

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



作者:UbuntuTouch 发表于2015/6/8 14:14:41 原文链接
阅读:325 评论:0 查看评论

Read more
UbuntuTouch

我们在Ubuntu QML应用设计中,我们可以使用DatePicker来选择我们所需要的时间。在这里,我们主要利用在Ubuntu API网站介绍的资料来做一个demo来显示到底是什么一个东西。


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Pickers 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: "datapicker.liu-xiao-guo"

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

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

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

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

        Flickable {
            anchors.fill: parent
            contentHeight: content.childrenRect.height

            Column {
                id: content
                anchors.centerIn: parent

                spacing: units.gu(1)
                Label {
                    text: "Selected date: W" + datePicker.week + " - " +
                          Qt.formatDate(datePicker.date, "dddd, dd-MMMM-yyyy")
                }
                DatePicker {
                    id: datePicker
                }

                Label {
                    text: "Selected month: " + Qt.formatDate(datePicker1.date, "MMMM-yyyy")
                }
                DatePicker {
                    id: datePicker1
                    mode: "Years|Months"
                }

                Label {
                    text: "Selected time: " + Qt.formatTime(datePicker2.date, "hh:mm:ss")
                }
                DatePicker {
                    id: datePicker2
                    mode: "Hours|Minutes|Seconds"
                }

                Label {
                    text: "Selected date: " + Qt.formatDate(datePicker3.date, "dddd, dd-MMMM-yyyy")
                }
                DatePicker {
                    id: datePicker3
                    minimum: {
                        var d = new Date();
                        d.setFullYear(d.getFullYear() - 1);
                        return d;
                    }
                    maximum: Date.prototype.getInvalidDate.call()
                }
            }
        }
    }
}


我们应用的显示的结果如下:


  


项目的代码在: git clone https://gitcafe.com/ubuntu/datepicker.git

作者:UbuntuTouch 发表于2015/6/5 13:10:01 原文链接
阅读:202 评论:0 查看评论

Read more
UbuntuTouch

我们知道画图应用设计中比较重要,虽然QML有很多可以帮我们渲染的控件。我们可以在QML应用中使用Canvas来画我们所需要的图。比如我们可以利用Canvas来画股票的曲线。Canvas中的画图的API和HTTML5中的API是一样的。事实上,我们很容易使用这个API来把很多的HTML5的应用移植到Qt平台中。


ColorSquare.qml


import QtQuick 2.0

Rectangle {
    id: root
    width: 48; height: 48
    color: "green"
    signal clicked
    property bool active: false
    border.color: active? "#666666" : "#f0f0f0"
    border.width: 2

    MouseArea {
        id: area
        anchors.fill :parent
        onClicked: {
            root.clicked()
        }
    }
}

这个是用来显示一个可以接受鼠标点击的Rectangle。边界在active和inactive时显示的是不同的。

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

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

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

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

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

        Row {
            id: colorTools
            anchors {
                horizontalCenter: parent.horizontalCenter
                top: parent.top
                topMargin: 8
            }
            property color paintColor: "#33B5E5"
            spacing: 4
            Repeater {
                model: ["#33B5E5", "#99CC00", "#FFBB33", "#FF4444"]
                ColorSquare {
                    id: red
                    color: modelData
                    active: parent.paintColor == color
                    onClicked: {
                        parent.paintColor = color
                    }
                }
            }
        }
        // <<M1

        Rectangle {
            anchors.fill: canvas
            border.color: "#666666"
            border.width: 4
        }

        // M2>>
        Canvas {
            id: canvas
            anchors {
                left: parent.left
                right: parent.right
                top: colorTools.bottom
                bottom: parent.bottom
                margins: 8
            }
            property real lastX
            property real lastY
            property color color: colorTools.paintColor

            onPaint: {
                var ctx = getContext('2d')
                ctx.lineWidth = 5.0
                ctx.strokeStyle = canvas.color
                ctx.beginPath()
                ctx.moveTo(lastX, lastY)
                lastX = area.mouseX
                lastY = area.mouseY
                ctx.lineTo(lastX, lastY)
                ctx.stroke()
            }
            MouseArea {
                id: area
                anchors.fill: parent
                onPressed: {
                    canvas.lastX = mouseX
                    canvas.lastY = mouseY
                }
                onPositionChanged: {
                    canvas.requestPaint()
                }
            }

        }
    }
}

我们在上面设计了四个ColorSqaure。在下面我们使用了一个Canvas来画我们所需要的图。

运行我们的应用:


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

作者:UbuntuTouch 发表于2015/6/5 13:46:51 原文链接
阅读:270 评论:0 查看评论

Read more
UbuntuTouch

在先前的例子中,我们可以“使用SQLite offline storage API来存储应用的设置”。我们也在例程“如何在QML应用中动态修改ListModel中的数据并存储它为JSON格式”中展示如何把我们需要的JSON存储到一个本地的文件中。在这篇文章中,我们将使用QtQuick所提供的LocalStorage来存储我们所需要的数据。


为了说明问题,我首先来创建一个基于“QtQuick App with QML UI (qmake)”的模版。首先我们修改我们的main.cpp如下:


Main.qml


#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QDebug>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQuickView view;
    qDebug() << "Local storage path: " << view.engine()->offlineStoragePath();
    QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit()));

    view.setSource(QUrl(QStringLiteral("qrc:///Main.qml")));
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.show();
    return app.exec();
}

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

Local storage path:  "/home/phablet/.local/share/localstorage/QML/OfflineStorage"

这个路径显然是和我们在实际在手机上运行时产生的实际的路径是不同的:



这也说明在Ubuntu上的实现和标准的Qt上的实现还是不同的。从上面我们可以看出数据库的时间路径为:

phablet@ubuntu-phablet:~/.local/share/localstorage.liu-xiao-guo/Databases$ ls
4ff10001f402923590ceb1d12a0cffc6.ini  4ff10001f402923590ceb1d12a0cffc6.sqlite


为了能够使得应用能够自己退出,我们添加了如下的语句:

  QObject::connect(view.engine(), SIGNAL(quit()), qApp, SLOT(quit()));

这样,在我们的QML代码中使用Qt.quit()时可以让应用退出。这和我们一般的设计是不同的。我们一般情况下是不需要这样做的。对于本应用来说,我们希望在退出时得到一个事件来保存我们的设置,所有我们有这样一个特殊的处理。


为了能够对SQLite数据库访问,我们设计了如下的database.js文件:

database.js


.import QtQuick.LocalStorage 2.0 as Sql

var db;

function initDatabase() {
    print('initDatabase()')

    db = Sql.LocalStorage.openDatabaseSync("CrazyBox", "1.0", "A box who remembers its position", 100000);
    db.transaction( function(tx) {
        print('... create table')
        tx.executeSql('CREATE TABLE IF NOT EXISTS data(name TEXT, value TEXT)');
    });
}

function readData() {
    print('readData()')

    if(!db) { return; }
    db.transaction( function(tx) {
        print('... read crazy object')
        var result = tx.executeSql('select * from data where name="crazy"');
        if(result.rows.length === 1) {
            print('... update crazy geometry')
            // get the value column
            var value = result.rows[0].value;
            // convert to JS object
            var obj = JSON.parse(value)
            // apply to object
            crazy.x = obj.x;
            crazy.y = obj.y;
        }
    });
}

function storeData() {
    print('storeData()')

    if(!db) { return; }
    db.transaction( function(tx) {
        print('... check if a crazy object exists')
        var result = tx.executeSql('SELECT * from data where name = "crazy"');
        // prepare object to be stored as JSON
        var obj = { x: crazy.x, y: crazy.y };
        if(result.rows.length === 1) {// use update
            print('... crazy exists, update it')
            result = tx.executeSql('UPDATE data set value=? where name="crazy"', [JSON.stringify(obj)]);
        } else { // use insert
            print('... crazy does not exists, create it')
            result = tx.executeSql('INSERT INTO data VALUES (?,?)', ['crazy', JSON.stringify(obj)]);
        }
    });
}


这里,我们可以创建一个叫做“CrazyBox”的数据库,并在它里面生产一个叫做data的表。我们可以向表里更新数据。从这个例子里,我们可以看出来,用这个方法来存储我们的JSON数据而不使用到C++代码(参考例程“如何在QML应用中动态修改ListModel中的数据并存储它为JSON格式”)。这对大多数不很熟悉C++代码的开发者来说是一个好消息。


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import "database.js" as DB

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

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

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

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

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

        Rectangle {
            id: crazy
            objectName: 'crazy'
            width: 200
            height: 200
            x: 50
            y: 50
            color: "#53d769"
            border.color: Qt.lighter(color, 1.1)

            Text {
                anchors.centerIn: parent
                text: Math.round(parent.x) + '/' + Math.round(parent.y)
            }

            MouseArea {
                anchors.fill: parent
                drag.target: parent
            }
        }


        Component.onCompleted: {
            DB.initDatabase();

            print("it is going to read data");
            DB.readData();
        }

        Component.onDestruction: {
            print("it is going to save data");
            DB.storeData();
        }

        Button {
            anchors.bottom: parent.bottom
            anchors.bottomMargin: units.gu(1)
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Close"
            onClicked: {
                Qt.quit();
            }
        }
    }
}


我们的Main.qml设计非常简单。我们在UI被装载完后,初始化数据库,并读取已经存储的数据。如果数据已经存在,就读出来,并初始化我们的Rectangle “crazy”。在应用退出的时候,我们存储我们的数据。这里应该注意的是,当我们用我们的手指拖动关掉应用时,我们的应用接受不到如下的事件:

        Component.onDestruction: {
            print("it is going to save data");
            DB.storeData();
        }

我们必须通过选择“Quit”按钮来得到这个事件。

运行我们的应用:

  


每当我们退出应用,并重新启动应用后,我们可以发现我们的绿色的方块是在和上次退出时一样的位置。

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

作者:UbuntuTouch 发表于2015/6/9 10:40:49 原文链接
阅读:232 评论:0 查看评论

Read more
UbuntuTouch

在这个例子中,我们将介绍如何在QML应用中使用QML语言提供的threading功能,实现多任务。更多的阅读在:http://doc.qt.io/qt-5/qtquick-threading-example.html


我们使用Ubuntu SDK来创建以个最基本的QML项目:


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

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

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

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

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

        ListView {
            anchors.fill: parent
            model: listModel
            delegate: Component {
                Text { text: time }
            }

            ListModel { id: listModel }

            WorkerScript {
                id: worker
                source: "dataloader.js"
            }

            Timer {
                id: timer
                interval: 2000; repeat: true
                running: true
                triggeredOnStart: true

                onTriggered: {
                    var msg = {'action': 'appendCurrentTime', 'model': listModel};
                    worker.sendMessage(msg);
                }
            }
        }
    }
}


在这里,我们使用了一个ListView来显示从worker thread发送来的信息(通过更新model)。这里:

  WorkerScript {
                id: worker
                source: "dataloader.js"
            }

WorkerScript定义了一个worker thread。它的执行代码在dataloader.js中:

dataloader.js


// ![0]
WorkerScript.onMessage = function(msg) {
    if (msg.action == 'appendCurrentTime') {
        var data = {'time': new Date().toTimeString()};
        msg.model.append(data);
        msg.model.sync();   // updates the changes to the list
    }
}
// ![0]

在Main.qml中,我们定义了一个Timer,每2秒发送一个请求给worker thread。它的参数是一个如下定义的object:

{'action': 'appendCurrentTime', 'model': listModel};

在work thread接受到发送来的信息后,在上面的代码中,检查msg中的action,并同时更新传入的model。

运行我们的例程:




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

作者:UbuntuTouch 发表于2015/6/9 15:14:17 原文链接
阅读:240 评论:0 查看评论

Read more
UbuntuTouch

我们知道Ubuntu手机平台是一个单任务的系统。一个用户可以开启很多个应用,但是只有前台的应用是可以正在运行的应用。很多被推到后台的应用被驻存到内存中。如果有很多这样的被驻存的应用的话,内存迟早会被用完的。操作系统可以选择一些应用被杀死从而保证系统的正常运行。为了能够保证应用在退出时的状态,在Ubuntu系统上,我们设计了StateSaver这样的一个接口。它可以用来帮我们保存应用在非正常退出的状态,以便在应用重新启动后恢复以前的状态。


参照文章的设计,我们设计了如下的代码:


import QtQuick 2.0
import Ubuntu.Components 1.1

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

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

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

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

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

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

    Page {
        id: mainpage
        title: i18n.tr("State saver")

        Rectangle {
            id: root
            anchors.fill: parent
            color: "green"
            StateSaver.properties: "color"

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

                Button {
                    anchors.horizontalCenter: parent.horizontalCenter
                    text: "Change color"

                    onClicked: {
                        root.color = Qt.rgba(Math.random(1), Math.random(1), Math.random(1), 1);
                    }
                }

                Button {
                    anchors.horizontalCenter: parent.horizontalCenter
                    text: "Quit"

                    onClicked: Qt.quit()
                }

            }
        }
    }
}

运行我们的应用:




我们可以按下“Change color”来改变我们的颜色。当我们按下“Quit”后,退出应用。但是,等我们重新启动我们的应用后,在应用退出时的颜色还是当初始化的颜色绿色。也就是说我们在程序中使用的:

StateSaver.properties: "color"

没有帮我们保存下我们的颜色。


接下来,我们重新运行我们的应用,并调整好我们所需要的颜色,比如蓝色:




我们在电脑上打开我们的Terminal,并输入如下的命令:



我们可以看到我们的应用被无情地杀死了。我们再次在手机中打开我们的应用。我们可以看到应用启动后,颜色还是上次在被杀死时的蓝色,而不是应用在初始化的绿色。也就是说,颜色的值在进程被杀死的时候被保存下来了。


我们可以参阅文章来保存多个properties。


整个应用的代码在:git clone https://gitcafe.com/ubuntu/statesaver.git



作者:UbuntuTouch 发表于2015/6/10 6:53:42 原文链接
阅读:281 评论:0 查看评论

Read more
UbuntuTouch

我们可以在Ubuntu SDK的文档中可以看到UbuntuApplication API。但是我们看不到它的具体的用法。在这篇文章中,我们来通过如下的方法来查看一下该如何使用该API。


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

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

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

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

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

        Text {
            text: UbuntuApplication.applicationName
        }

        Component.onCompleted: {
            var keys = Object.keys(UbuntuApplication);
            for(var i = 0; i < keys.length; i++) {
                var key = keys[i];
                // prints all properties, signals, functions from object
                console.log(key + ' : ' + UbuntuApplication[key]);

                if (key === "x") {
                    rect[key] = 100;
                }
            }
        }
    }
}


运行我们的应用:




Starting /usr/lib/x86_64-linux-gnu/qt5/bin/qmlscene...
qml: objectName : 
qml: applicationName : ubuntuapplication.liu-xiao-guo
qml: objectNameChanged : function() { [code] }
qml: applicationNameChanged : function() { [code] }


从上面的输出可以看出来,UbuntuApplication目前没有多少的属性让我们来使用。其中的applicationName,我们也可以使用Qt.application.name来的到。 

作者:UbuntuTouch 发表于2015/6/11 12:34:17 原文链接
阅读:196 评论:0 查看评论

Read more
UbuntuTouch

我们可以在Ubuntu的官方网站中得到关于Scope的更多的开发信息。在今天的这篇文章中,我们来主要介绍如何在Scope中截获在Preview中的按钮事件。在事件处理中,我们来做我们所需要的事情。在这篇文章中,我们将使用我们以前开发的weibo例子为例。在微博中,我们来发送一个我们需要的信息到微博中。最后我们的画面如下:


  




为了能够完成我们所需要的功能,我们首先下载我先前的代码: https://gitcafe.com/ubuntu/weibo


我们首先在query.cpp中的run方法中加入如下的代码:


query.cpp


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

    // We will not show anything if the user does not login
    if( !login(reply) )
        return;

    sc::CategoryRenderer rdr_new_blog(NEW_BLOG);
    sc::Category::SCPtr cat_new_blog;
    cat_new_blog = reply->lookup_category("new_blog");
    if (!cat_new_blog)
        cat_new_blog = reply->register_category("new_blog", "", "", rdr_new_blog);
    sc::CategorisedResult res_new_blog(cat_new_blog);
    res_new_blog.set_title(_("Create new blog post"));
    res_new_blog.set_uri("http://weibo.com");
    res_new_blog["getusertext"] = "key_existence_only_has_meaning_not_this_value";//whether to get text from user in preview
    res_new_blog["newblog"] = "key_existence_only_has_meaning_not_this_value";//new blog, not reply
    res_new_blog["noart"] = "key_existence_only_has_meaning_not_this_value";//whether preview has art
    reply->push(res_new_blog);

....
}

这里,我们创建了一个类似按钮的叫做“Create new blog post”的显示:



同时,我们在“CategorisedResult”加入了一些我们可以区别其它的显示的项比如:

    res_new_blog["getusertext"] = "key_existence_only_has_meaning_not_this_value";//whether to get text from user in preview
    res_new_blog["newblog"] = "key_existence_only_has_meaning_not_this_value";//new blog, not reply
    res_new_blog["noart"] = "key_existence_only_has_meaning_not_this_value";//whether preview has art
    reply->push(res_new_blog);

这些项没有任何的实际的意义,只是在Preview中用来区分和其它的项有所不同。这样我们可以动态创建一些像,以使得对该项的处理和别的项不同。

preview.cpp


void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts
    sc::ColumnLayout layout1col(1), layout2col(2), layout3col(3);

    // We define 3 different layouts, that will be used depending on the
    // device. The shell (view) will decide which layout fits best.
    // If, for instance, we are executing in a tablet probably the view will use
    // 2 or more columns.
    // Column layout definitions are optional.
    // However, we recommend that scopes define layouts for the best visual appearance.

    // Single column layout
    layout1col.add_column( { "image", "header", "reviewId", "actions" });

    // Two column layout
    layout2col.add_column( { "image" });
    layout2col.add_column( { "header", "reviewId", "actions" });

    // Three cokumn layout
    layout3col.add_column( { "image" });
    layout3col.add_column( { "header", "reviewId", "actions" });
    layout3col.add_column( { });

    // Register the layouts we just created
    reply->register_layout( { layout1col, layout2col, layout3col });

    // Define the header section
    sc::PreviewWidget w_header("header", "header");
    // It has title and a subtitle properties
    w_header.add_attribute_mapping("title", "title");
    w_header.add_attribute_mapping("subtitle", "subtitle");

    // Define the image section
    sc::PreviewWidget w_image("image", "image");
    // It has a single source property, mapped to the result's art property
    w_image.add_attribute_mapping("source", "art");

    Result result = PreviewQueryBase::result();
    QString urlString(result["uri"].get_string().c_str());

    // Create an Open button and provide the URI to open for this preview result
    sc::PreviewWidget w_actions("actions", "actions");
    sc::VariantBuilder builder;
    builder.add_tuple({
            {"id", Variant("open")},
            {"label", Variant("Open")},
            {"uri", sc::Variant(urlString.toStdString())} // uri set, this action will be handled by the Dash
        });
    w_actions.add_attribute_value("actions", builder.end());

     PreviewWidgetList widgets({ w_header });

    //used for blogging
    PreviewWidget w_review("reviewId", "rating-input");
    w_review.add_attribute_value("submit-label", Variant("Publish"));
    w_review.add_attribute_value("visible", Variant("review"));
    w_review.add_attribute_value("required", Variant("review"));
    std::string reply_label = "Reply";
    std::string max_chars_label = "140 characters max";
    if (result.contains("newblog"))
        w_review.add_attribute_value("review-label", Variant(max_chars_label));
    if (result.contains("replyblog"))
        w_review.add_attribute_value("review-label", Variant(reply_label + ": " + max_chars_label));
    if (result.contains("getusertext"))
        widgets.emplace_back(w_review);

    if (!result.contains("noart"))
    {
        widgets.emplace_back(w_image);
        widgets.emplace_back(w_actions);
    }

    // Push each of the sections
    reply->push( widgets );
}

在Preview的run中,我们加入了reviewId,它在result中包含有“getusertext”才显示。当其它项不包含“noart”时,才显示image及action。

  


添加对action的支持

scope.cpp


sc::ActivationQueryBase::UPtr Scope::perform_action(us::Result const& result,
                                                     us::ActionMetadata const& metadata,
                                                     std::string const& widget_id,
                                                     std::string const& action_id)
{
    return sc::ActivationQueryBase::UPtr(new Activation(result, metadata, widget_id, action_id, accessToken_));
}

为例对任何一个action (比如上面图中的publish及open)在按钮被按下时,我们可以截获这个时间,我们需要创建一个独立的类:

activation.cpp


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

#include <scope/activation.h>

#include <unity/UnityExceptions.h>

#include <QDebug>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>


namespace us = unity::scopes;

Activation::Activation(us::Result const& result, us::ActionMetadata const& metadata, std::string const& widget_id, std::string const& action_id, std::string accessToken):
        ActivationQueryBase(result, metadata),
        action_id_(action_id),
        accessToken_(accessToken)
{
}

Activation::~Activation()
{
}

void Activation::cancelled()
{
}

QString qstr_(std::string str)
{
    return QString::fromStdString(str);
}

us::ActivationResponse Activation::activate()
{
    qDebug() << "==== in activate(). action_id: "<< QString::fromStdString(action_id_);

    bool dash_handles = false;
    if (result().contains("dash_activation"))
        dash_handles = true;
    else if (action_id_ == "profile_url")
        dash_handles = true;

    if (dash_handles)
    {
        qDebug() << "==== dash_activation";
        return us::ActivationResponse(us::ActivationResponse::Status::NotHandled);
    }

    QString uri;
    QString accessToken = QString::fromStdString(accessToken_);
    QString params_qs;
    QString message = QString("%1").arg(qstr_(action_metadata().scope_data().get_dict()["review"].get_string()));
    if (result().contains("newblog"))
    {
        qDebug() << "==== NEW BLOG";
        uri = QString("https://api.weibo.com/2/statuses/update.json?access_token=%1").arg(accessToken);
        params_qs = QString("status=%1").arg(message);
    }
    else if (result().contains("replyblog"))
    {
        qDebug() << "==== REPLY BLOG";
        //qDebug() << "==== token: " << accessToken;
        uri = QString("https://api.weibo.com/2/statuses/repost.json?access_token=%1").arg(accessToken);
        QString id_ = QString("id=%1").arg(qstr_(result()["idstring"].get_string()));
        QString msg_ = QString("status=%1").arg(message);
        params_qs = QString("%1&%2").arg(msg_,id_);
        //qDebug() << "==== params_qs: " << params_qs;
    }

    us::ActionMetadata am = action_metadata();
    //TODO: ensure text is 140 chars or less or warn user somehow
    qDebug() << "====  microblog url:\n" << uri;
    QByteArray params;
    params.append(params_qs);
    QEventLoop loop;
    QNetworkAccessManager manager;
    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&manager, &QNetworkAccessManager::finished, [](QNetworkReply *netReply) {
        netReply->deleteLater();
        QByteArray data_ba = netReply->readAll();
        QJsonParseError err;
        QJsonDocument doc = QJsonDocument::fromJson(data_ba, &err);
        if (err.error != QJsonParseError::NoError) {
            qCritical() << "Failed to parse server data: " << err.errorString();
        }
    });
    QUrl url = QUrl(uri);
    QNetworkRequest request(url);
    QString mUserAgent = QString("%1 (Ubuntu)").arg("weibo-scope");
    request.setRawHeader("User-Agent", mUserAgent.toStdString().c_str());
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
    manager.post(request, params);
    loop.exec();

    return us::ActivationResponse(us::ActivationResponse::Status::ShowDash);
}

当任何一个按钮被按下后,“activate()”方法将被调用。我们可以通过截获这个事件来做任何我们想要做的事情。

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


作者:UbuntuTouch 发表于2015/6/15 10:19:11 原文链接
阅读:96 评论:0 查看评论

Read more
UbuntuTouch

[原]如何在QML应用中启动Scope

在这篇文章中,我们将介绍如何在QML应用中调用Scope,并把搜索的关键词传进去。这对有些QML应用需要用到Scope的情况非常有用。更多关于url-dispatcher的知识,请在文章“使用URL dispatcher的范例”看到。


Scope ID


首先我们来讲一下什么是Scope ID。我们打开我们创建的任何一个Scope的manifest.json文件:

{
    "architecture": "@CLICK_ARCH@",
    "description": "A simple Unity scope that accesses the network",
    "framework": "ubuntu-sdk-15.04",
    "hooks": {
        "mytestscope": {
            "apparmor": "mytestscope.apparmor",
            "scope": "mytestscope"
        }
    },
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "name": "mytestscope.liu-xiao-guo",
    "title": "mytestscope",
    "version": "0.1"
}

这里我们看见了“name”。它的值为“mytestscope.liu-xiao-guo”。那么它的Scope ID为

mytestscope.liu-xiao-guo_mytestscope

这里有一个下划线,“mytestscope”是定义在“hooks”下面的字符串。

那么我们怎么查看在我们手机中的Scope呢?

首先,我们在terminal中进入到手机中:

$adb shell



在这里,我们可以看见所有已经安装的click包。假如我们想看上面所显示的“weibo.ubuntu”的包信息,我们可以打入如下的命令:

phablet@ubuntu-phablet:~$ click info weibo.ubuntu



这里我们可以看到manifest.json所看到的信息。按照上面的方法,我们可以找到我们相应的Scope ID。

另外,我们也可以通过如下的方法得到应用或者Scope的ID。




这里其实已经很清楚地告诉你Scope ID了。


在QML应用中调用Scope


在这里其实已经非常简单了。我们设计一个我们的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: "launchscopes.liu-xiao-guo"

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

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

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

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

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

            Button {
                text: "Launch Youtube scope"
                onClicked: {
                    console.log("it is clicked!");
                    Qt.openUrlExternally("scope://com.ubuntu.scopes.youtube_youtube");
                }
            }

            Button {
                text: "Launch store scope"
                onClicked: {
                    console.log("it is clicked!");
                    Qt.openUrlExternally("scope://com.canonical.scopes.clickstore?q=weibo");
                }
            }

            Button {
                text: "Launch weibo scope"
                onClicked: {
                    console.log("it is clicked!");
                    Qt.openUrlExternally("scope://weibo.ubuntu_weibo");
                }
            }
        }
    }
}


在这里,我们可以直接调用Scope ID来启动Scope,同时,我们也可以传人参数:

     Qt.openUrlExternally("scope://com.canonical.scopes.clickstore?q=weibo");

如果有空格的话,按照如下的方式:

     scope://com.ubuntu.scopes.youtube_youtube?q=Foo%20Fighters

运行我们的应用:

    


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

作者:UbuntuTouch 发表于2015/6/16 9:35:06 原文链接
阅读:270 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将展示如何在QML应用中实现drag and drop的功能。更多的阅读可以参照Qt SDK。


在这里,我们首先设计一个DropTile.qml


DropTile.qml


import QtQuick 2.0

DropArea {
    id: dragTarget

    property string colorKey
    property alias dropProxy: dragTarget

    width: mainpage.width/4 - units.gu(1)
    height: width
    keys: [ colorKey ]

    onDropped: {
        console.log("onDropped!")
    }

    onEntered: {
        console.log("onEntered!");
    }

    onExited: {
        console.log("onExited!")
    }

    onPositionChanged: {
        console.log("onPositionChanged!");
    }

    Rectangle {       
        id: dropRectangle

        anchors.fill: parent
        color: colorKey

        states: [
            State {
                when: dragTarget.containsDrag
                PropertyChanges {
                    target: dropRectangle
                    color: "grey"
                }
            }
        ]
    }
}

代码的设计非常简单。处理显示一些events之外,只显示了一个Rectange。当有Drag存在时,显示的grey的颜色。否则为“colorKey”显示的颜色。

另外,我们设计一个DragTile.qml.

DragTile.qml


import QtQuick 2.0

Item {
    id: root
    property string colorKey

    width: mainpage.width/4 - units.gu(1); height: width

    MouseArea {
        id: mouseArea

        width: parent.width
        height: width

        anchors.centerIn: parent

        drag.target: tile

        onReleased: parent = tile.Drag.target !== null ? tile.Drag.target : root

        Rectangle {
            id: tile

            width: root.width; height: width
            anchors.verticalCenter: parent.verticalCenter
            anchors.horizontalCenter: parent.horizontalCenter

            color: colorKey

            Drag.keys: [ colorKey ]
            Drag.active: mouseArea.drag.active
            Drag.hotSpot.x: 32
            Drag.hotSpot.y: 32

            Image {
                anchors.fill: parent
                source: image
            }

            states: State {
                when: mouseArea.drag.active
                ParentChange { target: tile; parent: root }
                AnchorChanges { target: tile; anchors.verticalCenter: undefined; anchors.horizontalCenter: undefined }
            }

        }
    }
}

在这个设计中,我们显示了一个图片。这个图片是在model中定义的。在mouseArea有drag实际时,我们有意地改变tile的父亲为root而不是先前的mouseArea。当鼠标在release时,如果有一个DropArea的话,它的地方将被自动放置root所展示的内容,在我们的示例中,也就是一个图片。


Main.qml


import QtQuick 2.4
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: "draganddrop.liu-xiao-guo"

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

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

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

    Page {
        id: mainpage
        title: i18n.tr("draganddrop")

        ListModel {
            id: mymodel
            ListElement {
                image: "images/pic1.jpg"
            }
            ListElement {
                image: "images/pic2.jpg"
            }
            ListElement {
                image: "images/pic3.jpg"
            }
            ListElement {
                image: "images/pic4.jpg"
            }
        }


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

            Row {
                id: bottom
                width: bottom.childrenRect.width
                height: mainpage.height/3
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: units.gu(1)

                Repeater {
                    model: mymodel.count;
                    delegate: DropTile { colorKey: "red" }
                }
            }

            Row {
                id: top
                width: top.childrenRect.width
                height: mainpage.height/3
                anchors.horizontalCenter: parent.horizontalCenter
                spacing: units.gu(1)

                Repeater {
                    model: mymodel
                    delegate: DragTile { colorKey: "red" }
                }
            }

        }
    }
}


在我们的Main.qml中,我们定义了一个model来装我们的数据,同时。我们创建了DragTile及DropTile。

运行我们的应用:

    


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


作者:UbuntuTouch 发表于2015/6/16 14:23:56 原文链接
阅读:181 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章“Scopes 关键词 (keyword)及departments探讨(英文视频)”中,我们已经对Scope中的keyword做了一些基本的介绍。但是我们没有相应的教程。在这篇文章中,我们将通过youku Scope来介绍如何使用keyword从而使得一个Scope在不同的聚合Scope中来得到不同的呈现。


如果大家对如何开发Scope还不是很熟的话,请参阅我的教程“在Ubuntu OS上创建一个dianping Scope (Qt JSON)”。如果大家对创建Scope很熟的话,相信要不了一两个小时,你就可以创建一个和我相差不多的一个Scope来。


创建一个最基本的优酷视频Scope


只要大家按照我上面介绍的例程,我们就会很快地创建一个和如下相差不多的youku视频Scope:





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

简单吧,是一个非常简单的youku Scope。

在我们的代码中,我们也同时修改“src/data”目录下的“youku.ini.in”文件:

[ScopeConfig]
_DisplayName=Youku Scope
_Description=This is a Youku scope
Art=screenshot.png
Author=Firstname Lastname
Icon=icon.png
Keywords=video;videos

[Appearance]
PageHeader.Logo=logo.png

可以看出来在这里,我们使用了Keywords “video”及“videos”。表明我们的Scope可以被Video Scope所聚合,并呈现在Video聚合Scope中。当然如果我们的Scope也是和位置相关的,我们也可以加入“nearby”在我们的Scope中。更多的关键词keywords可以在网址https://developer.ubuntu.com/en/scopes/tutorials/scope-keywords/中找到。我们在手机中打开Video聚合Scope,并打开它的设置:

  


记得在“Display results from Youku Scope”选项上勾上。我们可以看出来,在Video聚合Scope中,我们可以看到我们的youku Scope里的内容,虽然只是其中的一部分。如果,我们点击上图的“Youku Scope Features”,我们可以直接切换到我们的youku Scope中来:




这时如果我们返回的话,就会回到先前的画面中去。


对聚合时显示不同的UI


上面的设计,我们对代码没有做任何实质性的改变。我们只是对初始化文件加入了一些我们需要的关键词。那么我们如何来对聚合时显示不同的UI呢?另外对有些Scope来说,我们可能添加更多的不同种类的keyword,那么这样对不同的keyword我们可能希望有不同的展现,而不是都是一样的。

为了达到设计的目的,我们对我们的代码做了如下的修改:

query.cpp


void Query::run(sc::SearchReplyProxy const& reply) {
    auto metadata_ = search_metadata();

    if (metadata_.is_aggregated()) {
        auto keywords = metadata_.aggregated_keywords();

        if ( (keywords.find("videos") != keywords.end()) ||
             (keywords.find("video") != keywords.end()) ) {
            qDebug() << "it is a video scope";
            do_videos_search(reply);
        }

    } else {
        qDebug() << "it is a normal video scope";
        do_normal_search(reply);
    }
}


在这里,我们通过对keyword的解析,我们可以对为我们的Scope来进行不同的展示,甚至我们可以使用不同的API来得到不同的数据。为了说明问题的方便,我们特意在youku Scope在聚合时使用了一个不同的模版来展示数据:

const std::string VIDEOS_TEMPLATE = R"(
{
  "schema-version": 1,
  "template": {
    "category-layout": "grid",
    "card-size": "large",
    "overlay": true
  },
  "components": {
    "title": "title",
    "subtitle": "subtitle",
    "art" : {
      "field": "art",
      "aspect-ratio": 2.0
    }
  }
}
)";

这里显示的是一个超大的grid模版。

重新运行我们的Scope,当在没有聚合时,Scope的显示如下:




当我们的Scope在聚合时,显示如下:




显然在聚合时,显示的是和正常时不一样的模版。显示的是一个超大的图片。当然点击“Youku Scope Features”时,就会进入到我们正常的Scope界面。

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


作者:UbuntuTouch 发表于2015/6/17 15:13:10 原文链接
阅读:186 评论:0 查看评论

Read more
UbuntuTouch

我在昨天的文章中介绍了我设计的优酷Scope。在今天的练习中,我将对它的模版做一些小的改动,利用模版中的attributes项使得它的显示更加生动。


如果感兴趣的朋友,可以在如下的地址下载最新的youku scope:

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


首先,我们在query.cpp中对它的模版做如下的改动:


query.cpp


const std::string NORMAL_TEMPLATE = R"(
{
  "schema-version": 1,
  "template": {
    "category-layout": "grid",
    "card-size": "medium",
    "overlay": false
  },
  "components": {
    "title": "title",
    "subtitle": "subtitle",
    "art" : {
      "field": "art",
      "aspect-ratio": 2.0
    },
    "attributes": {
        "field": "attributes",
        "max-count": 2
    }
  }
}
)";


在这个模版中,我们添加了如下的项:


    "attributes": {
        "field": "attributes",
        "max-count": 2
    }


我们可以利用这个项来显示一些我们所感兴趣的东西。

query.cpp


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

        // Get the query string
        string query_string = query.query_string();

        // Populate current weather category

        // the Client is the helper class that provides the results
        // without mixing APIs and scopes code.
        // Add your code to retreive xml, json, or any other kind of result
        // in the client.

        Client::DataList datalist;
        datalist = client_.getData(query_string);

        CategoryRenderer rdrGrid(NORMAL_TEMPLATE);
        auto grid = reply->register_category("youku", "Normal", "", rdrGrid);

        for (const Client::Data &data : datalist) {
            CategorisedResult catres(grid);

            catres.set_uri(data.link);
            catres.set_title(data.title);
            catres.set_art(data.image);

            QString likes = QString("%1 %2").arg(qstr(u8"\u261d "),qstr(data.up_count));
            QString views = QString("%1 %2").arg(qstr(u8"   \u261f "),qstr(data.down_count));
            std::string both = qstr("%1 %2").arg(likes,views).toStdString();
            sc::VariantBuilder builder;
            builder.add_tuple({
                {"value", Variant(both)}
            });
            builder.add_tuple({
                {"value", Variant("")}
            });
            catres["attributes"] = builder.end();

            // Push the result
            if (!reply->push(catres)) {
                // If we fail to push, it means the query has been cancelled.
                // So don't continue;
                return;
            }
        }

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


在上面,我们添加了如下的句子:


        QString likes = QString("%1 %2").arg(qstr(u8"\u261d "),qstr(data.up_count));
        QString views = QString("%1 %2").arg(qstr(u8"   \u261f "),qstr(data.down_count));
        std::string both = qstr("%1 %2").arg(likes,views).toStdString();
        sc::VariantBuilder builder;
        builder.add_tuple({
           {"value", Variant(both)}
        });
        builder.add_tuple({
           {"value", Variant("")}
        });
        catres["attributes"] = builder.end();

这里的2个字符表示的是特定的字符(向上和向下的手势)。通过这样的改动,我们可以得到如下的显示:




从上面的图片中可以看出来,对于每部视频的下面,多了一项显示有多少人喜欢,有多少人讨厌这部视频。


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


作者:UbuntuTouch 发表于2015/6/18 9:38:03 原文链接
阅读:198 评论:0 查看评论

Read more
UbuntuTouch

当我第一次接触Ubuntu Scope时,我发现这个Category Renderer太神奇。它可以把我们想要的数据很简单而直接的方式呈现给我们。但是由于资料的限制,我们很难知道它最终的显示的形式是什么样的。我们可以在我们的英文的网站的文章“Customization and branding”找到一下信息。但是总觉得意犹未尽,加之没有代码,有时很难理解它到底讲的是什么。在这篇文章中,我们来详细地介绍各种模版,并看看它们的显示格式。详细这对广大的开发者来说非常有用。我们可以修改这些模版来显示我们所需要的内容。


    


创建一个最基本的Scope


我们可以利用我们的Ubuntu SDK来创建一个最基本的Scope应用。为了说明问题的方便,我们在这个Scope里,不准备从网络上抓取任何的数据。所有的数据都是从本地来。为了达到这个目的,我们在我们的scope.cpp中做了如下的修改:

scope.cpp


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

在这里,我们加入了scopePath,并把它传人到query类中。

query.cpp


Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata,
     QString scopePath, Config::Ptr config) :
    sc::SearchQueryBase(query, metadata), scopePath_(scopePath), client_(config) {

    for ( int i = 0; i < 8; i ++ ) {
        QString name = QString("image").append(QString::number(i+1)).append(".jpg");
        QString image = QString("file://%1/images/%2").arg(scopePath).arg(name);
        images_ << image;
    }

    for ( int i = 0; i < 10; i ++ ) {
        QString name = QString("pic").append(QString::number(i+1)).append(".jpg");
        QString image = QString("file://%1/images/%2").arg(scopePath).arg(name);
        icons_ << image;
    }

    background_ = QString("file://%1/images/%2").arg(scopePath).arg("background.jpg");

}

在这里,我们得到scope的路径。我们在scope的安装目录中加入一个images的目录,并考入我们所需要的所有需要的文件:

liuxg@liuxg:~/scope/scopetemplates$ tree
.
├── cmake
│   ├── FindGMock.cmake
│   └── FindIntltool.cmake
├── CMakeLists.txt
├── CMakeLists.txt.user
├── data
│   ├── CMakeLists.txt
│   ├── icon.png
│   ├── images
│   │   ├── background.jpg
│   │   ├── image1.jpg
│   │   ├── image2.jpg
│   │   ├── image3.jpg
│   │   ├── image4.jpg
│   │   ├── image5.jpg
│   │   ├── image6.jpg
│   │   ├── image7.jpg
│   │   ├── image8.jpg
│   │   ├── pic10.jpg
│   │   ├── pic1.jpg
│   │   ├── pic2.jpg
│   │   ├── pic3.jpg
│   │   ├── pic4.jpg
│   │   ├── pic5.jpg
│   │   ├── pic6.jpg
│   │   ├── pic7.jpg
│   │   ├── pic8.jpg
│   │   └── pic9.jpg
│   ├── logo.png
│   ├── scopetemplates.liu-xiao-guo_scopetemplates.ini.in
│   └── screenshot.png
...


为了能够push数据到我们所需要的界面,我们设计了如下的helper方法:

void Query::pushResult(sc::SearchReplyProxy const& reply,
                       const string renderer, int i) {
    stringstream ss;
    ss << i;
    string str = ss.str();

    auto cat = reply->register_category( "id" + str, "Template " + str ,
                                         "", sc::CategoryRenderer(renderer) );
    sc::CategorisedResult r(cat);
    r.set_uri( URI.toStdString() );
    r.set_art( images_[0].toStdString() );
    r["subtitle"] = "Subtitle " + str;
    r.set_title("Title " + str);
    r["summary"] = "Summary: " + str;
    r["fulldesc"] = "fulldesc: " + str;
    r["mascot"] = icons_[0].toStdString();
    r["emblem"] = icons_[1].toStdString();
    r["background"] = background_.toStdString();
    r["overlay-color"] = "#FF0000";

    QString likes = QString("%1 %2").arg(qstr(u8"\u261d "), "100");
    QString views = QString("%1 %2").arg(qstr(u8"   \u261f "), "99");
    std::string both = qstr("%1 %2").arg(likes,views).toStdString();
    sc::VariantBuilder builder;
    builder.add_tuple({
        {"value", Variant(both)}
    });
    builder.add_tuple({
        {"value", Variant("")}
    });
    r["attributes"] = builder.end();

    if (!reply->push(r))
        return;
}

void Query::pushResult(sc::SearchReplyProxy const& reply,
                       const std::shared_ptr<const Category> *cat, int i) {

    stringstream ss;
    ss << i;
    string str = ss.str();

    sc::CategorisedResult r(*cat);
    r.set_uri( URI.toStdString() );
    r.set_art( images_[i].toStdString() );
    r["subtitle"] = "Subtitle " + str;
    r.set_title("Title " + str);
    r["summary"] = "Summary: " + str;
    r["fulldesc"] = "fulldesc: " + str;
    r["mascot"] = icons_[0].toStdString();
    r["emblem"] = icons_[1].toStdString();

    QString likes = QString("%1 %2").arg(qstr(u8"\u261d "), "100");
    QString views = QString("%1 %2").arg(qstr(u8"   \u261f "), "99");
    std::string both = qstr("%1 %2").arg(likes,views).toStdString();
    sc::VariantBuilder builder;
    builder.add_tuple({
        {"value", Variant(both)}
    });
    builder.add_tuple({
        {"value", Variant("")}
    });
    r["attributes"] = builder.end();

    if (!reply->push(r))
        return;
}

这两个方法几乎加上了我们可以push的所有的项。如果大家需要有更多的了解,可以参阅“CategoryRenderer” API。


测试我们的CategoryRenderer模版



模版1


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

这个模版是一个非常全的模版,几乎涵盖了模版需要的所有的项。我们在可以通过如下的方式进行测试:

void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // The default is vertical
        pushResult(reply, CAT_RENDERER1, 1);


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


显示的结果如下:

   

从这里可以看出来,这是一个vertical的显示,上面是一个图片,下面是一些文字。几乎所有的东西它都有了。同时它使用了一个在上面右图显示的背景图作为背景图片。如果把背景图拿掉,这样看的更清楚一些,并使用“medium”大小的图片,显示的效果如下:

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



模版2


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



可以看出来,这是一个horizontal的显示。可以看出来,尽管我们设置了“large”的尺寸,但是最终显示的图片还是很小的。


模版3


const static string CAT_RENDERER3 = R"(
{
   "schema-version": 1,
    "template": {
        "category-layout": "grid",
        "card-layout": "horizontal",
        "card-size": "medium",
        "card-background": "#00FF00"
    },
    "components": {
        "title": "title",
        "art" : {
            "field": "art"
        },
       "subtitle": "subtitle",
        "overlay-color": "overlay-color",
        "attributes": {
            "field": "attributes",
            "max-count": 2
        }
    }
}
)";





模版4

const static string CAT_RENDERER4
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "medium"
        },
        "components" :
        {
            "title" : "title",
            "art" : "art",
            "subtitle": "subtitle",
            "overlay-color": "overlay-color",
            "attributes": {
                "field": "attributes",
                "max-count": 2
            }
        }
    }
    )"
};





模版5


const static string CAT_RENDERER5
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "medium"
        },
        "components":
        {
          "title": "title",
          "subtitle": "subtitle",
          "overlay-color": "overlay-color",
          "art" : {
            "field": "art",
            "aspect-ratio": 2.0
          },
          "attributes": {
              "field": "attributes",
              "max-count": 2
          }
        }
    }
    )"
};






这里我们使用了"aspect-ratio": 2.0,开发者可以修改这个值看看有什么改变。

模版6


const static string CAT_RENDERER6
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "medium"
        },
        "components":
        {
          "title": "title",
          "subtitle": "subtitle",
          "art" : {
            "field": "art",
            "aspect-ratio": 1.0
          },
          "overlay-color": "overlay-color",
          "attributes": {
              "field": "attributes",
              "max-count": 2
          }
        }
    }
    )"
};





模版7


const static string CAT_RENDERER7
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "medium",
            "overlay": true

        },
        "components":
        {
          "title": "title",
          "subtitle": "subtitle",
          "art" : {
            "field": "art",
            "aspect-ratio": 1.0
          },
          "overlay-color": "overlay-color",
          "attributes": {
              "field": "attributes",
              "max-count": 2
          }
        }
    }
    )"
};



在这里,显示了一个overlay的效果。同时我们也设置了一个overlay-color为红色。其实这个选项,可以加到任何一个其它的模版中去。我们可以看到有不同的效果。这个练习让开发者自己去试吧。


模版8


const static string CAT_RENDERER8
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "grid",
            "card-layout": "vertical",
            "card-size" : "medium"
        },
        "components" :
        {
            "title" : "title",
            "art" : "art",
            "subtitle": "subtitle",
            "mascot": "mascot",
            "emblem": "emblem",
            "summary": "summary",
            "overlay-color": "overlay-color",
            "attributes": {
                "field": "attributes",
                "max-count": 2
            }
        }
    }
    )"
};


为了能对这个模版有多个显示,我特意添加了更多的数据显示,并使用如下的代码:

        auto cat = reply->register_category( "Grid", "Grid" ,
                                             "", sc::CategoryRenderer(CAT_RENDERER8) );

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





我们可以省去一些项使得显示更加人性化,从而得到不同的显示。

模版9


const static string CAT_RENDERER9
{
    R"(
    {
        "schema_version" : 1,
        "template" :
        {
            "category-layout" : "carousel",
            "card-layout": "vertical",
            "card-size" : "medium"
        },
        "components" :
        {
            "title" : "title",
            "art" : "art",
            "subtitle": "subtitle",
            "mascot": "mascot",
            "emblem": "emblem",
            "summary": "summary",
            "overlay-color": "overlay-color",
            "attributes": {
                "field": "attributes",
                "max-count": 2
            }
        }
    }
    )"
};

        int count = images_.count();

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

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



模版10


const std::string CAT_RENDERER10 = R"(
{
  "schema-version": 1,
  "template": {
    "category-layout": "grid",
    "card-size": "large",
    "overlay": true
  },
  "components": {
    "title": "title",
    "subtitle": "subtitle",
    "art" : {
      "field": "art",
      "aspect-ratio": 2.0
    }
  }
}






在这个模版中,我们使用了"aspect-ratio": 2.0,这样我们得到一个从做到右的一个大的图片。我们也同时使用了overlay。

模版11


const std::string CAT_RENDERER11 = R"(
{
        "schema-version" : 1,
        "template" : {
            "category-layout" : "vertical-journal",
            "card-layout": "horizontal",
            "card-size": "small",
            "collapsed-rows": 0
        },
        "components" : {
            "title":"title",
            "subtitle":"subtitle",
            "summary":"summary",
            "art":{
                "field": "art",
                "aspect-ratio": 2
            }
        }
})";





模版12


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




这是一个叫做“vertical-journal”的layout。看起来和Grid有些类似。

模版13


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

        auto cat = reply->register_category( "horizontal-list", "horizontal-list" ,
                                         "", sc::CategoryRenderer(CAT_RENDERER13) );

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






这是一个叫做“horizontal-list”的layout。我们可以左右滑动来移动list。

开发者可以通过本例程,添加任何多的模版来做做展示。


整个项目的源码在: git clone https://gitcafe.com/ubuntu/scopetemplates.git
作者:UbuntuTouch 发表于2015/6/19 10:43:59 原文链接
阅读:306 评论:0 查看评论

Read more
UbuntuTouch

在应用中有时我们希望在不中断应用界面导航的前提下,我们希望插入一个展示内容的窗口。我们可以是用DefaultSheetComposerSheet来显示我们所需要的内容。其实在以前我们的Dialog教程中,有类似的功能尽管展示有一点不同。


我们来做一个练习:


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Popups 0.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: "sheet.liu-xiao-guo"

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

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

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

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

        Component {
            id: defaultSheet
            DefaultSheet {
                id: sheet
                title: i18n.tr("Default sheet Title")
//                doneButton: true

                Label {
                    anchors.fill: parent
                    wrapMode: Text.WordWrap
                    text:
                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
                    "Integer et ante at erat malesuada accumsan in eget mauris. " +
                    "Nunc ultrices tristique laoreet. In non tristique lorem. " +
                    "Donec in libero ut libero pretium lacinia. Proin dictum faucibus viverra. "
                }
            }
        }

        Component {
            id: composerSheet
            ComposerSheet {
                id: sheet
                title: i18n.tr("Composer sheet")
                Label {
                    anchors.fill: parent
                    wrapMode: Text.WordWrap
                    text:
                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
                    "Integer et ante at erat malesuada accumsan in eget mauris. " +
                    "Nunc ultrices tristique laoreet. In non tristique lorem. " +
                    "Donec in libero ut libero pretium lacinia. Proin dictum faucibus viverra. "
                }
                onCancelClicked: PopupUtils.close(sheet)
                onConfirmClicked: PopupUtils.close(sheet)
            }
        }

        Row {
            anchors.centerIn: parent
            spacing: units.gu(2)

            Button {
                text: "Default sheet"
                onClicked: {
                    PopupUtils.open(defaultSheet, null)
                }
            }

            Button {
                text: "Composer Sheet"

                onClicked: {
                    PopupUtils.open(composerSheet, null)
                }
            }
        }
    }
}


运行我们的应用:


   


对于DefaultSheet来说,我们可以看见一个“Close”按钮,当然,我们也可以设置它的属性“doneButton”为真,这样就会有“Done”的按钮。当然,这两个按钮不可以同时出现:




对于ComposerSheet来说,它有两个按钮“Cancel”及“Confirm”。这样我们就可以利用这两个按钮来确认或放弃我们在Sheet中的修改(如果有edit及选项的情况)。


结合我们先前的Dialog来说,展示的有些类似的地方。但是Dialog显示的没有这么宽,并且背景的颜色显示的黑色的。


在我们先前的教程“从零开始创建一个Ubuntu应用--一个小的RSS阅读器”。在第一部分的练习中,我们可以甚至可以利用DefaultSheet来展示我们的RSS内容而不需要创建一个新的Component。


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



作者:UbuntuTouch 发表于2015/6/19 14:32:54 原文链接
阅读:189 评论:0 查看评论

Read more
UbuntuTouch

[原]开发工具集锦

在这篇文章中,我将主要列举我们常用的开发工具。

JSON viewer:

http://jsonviewer.stack.hu/


在Ubuntu手机中抓图片

把手机和电脑连接起来,打开电脑中的terminal,并键入如下的命令:

$phablet-screenshot a.png

这样,就可以在当前目录下把手机的屏幕存于一个叫做a.png的文件中。


解开click包

$dpkg -x myapp.click unpacked

解开myapp.click文件后,所有的文件将存在于目录“unpacked”中。
作者:UbuntuTouch 发表于2015/6/23 10:40:41 原文链接
阅读:117 评论:0 查看评论

Read more