Canonical Voices

UbuntuTouch

在今天的教程中,我们来显示一个QML的所有的属性。这个应用也上传到商店里去了。希望对所有的开发者有所帮助。我现在直接把我的代码贴出来:


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtQuick.Layouts 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: "propertyviewer.xiaoguo"

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

    property var obj: null
    property var datalist: []

    function createQmlObject(objName) {
        var objStr = "import QtQuick 2.4;
                      import QtQuick.XmlListModel 2.0;
                      import Qt.labs.folderlistmodel 2.1;
                      import Qt.labs.settings 1.0;
                      import QtQuick.Particles 2.0;
                      import QtQuick.Window 2.2;
//                      import QtTest 1.1;
                      import Ubuntu.Components 1.2;
                      import Ubuntu.Components.ListItems 1.0;
                      import Ubuntu.Components.Pickers 1.0;
                      import Ubuntu.Components.Popups 1.0;
                      import Ubuntu.Components.Styles 1.2;
                      import Ubuntu.Layouts 1.0;
                      import Ubuntu.PerformanceMetrics 1.0;
//                      import Ubuntu.Test 1.0;
                      import Ubuntu.Web 0.2;
                      import QtContacts 5.0;
                      import QtLocation 5.3;
                      import QtOrganizer 5.0;
                      import Ubuntu.Content 1.1;
                      import Ubuntu.DownloadManager 0.1;
                      import Ubuntu.OnlineAccounts 0.1;
                      import QtSensors 5.0;
//                      import QtAudioEngine 1.0;
                      import QtMultimedia 5.4;
                      import QtQml 2.2;"

        if ( pack.text != "" ) {
            objStr += "import " + pack.text + ";"
        }

        objStr += objName + "{ id: myid"
        objStr += "}";
        console.log("objStr: " + objStr);
        var obj = Qt.createQmlObject(objStr, page, "myobj");
        return obj
    }

    function getProperties() {
        console.log("it is clicked")
        if ( obj != undefined ) {
            obj.destroy();
        }

        obj = createQmlObject(type.text);

        var list = [];

        list.push(obj.toString());

        // Now get the properties of the obj
        var keys = Object.keys(obj);
        for( var i = 0; i < keys.length; i++ ) {
            var key = keys[ i ];
            var data = key + ' : ' + obj[ key ];
            list.push( data  );
        }

        console.log("Let's dump the data");
        datalist = list;

        for ( key in datalist) {
            console.log( datalist[key] );
        }
    }

    Page {
        id: page
        title: i18n.tr("PropertyViewer")

        property var datalist: ["good"]

        ListModel {
            id: mymodel
        }

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

            RowLayout {
                id: search
                spacing: units.gu(1)
                width: parent.width

                TextField {
                    id: type
                    width: parent.width
                    Layout.preferredWidth: parent.width*0.7
                    placeholderText: "A QML type (eg. Rectangle)"
                    hasClearButton: true

                    onAccepted: {
                        getProperties()
                    }
                }

                Button {
                    text: "Get"

                    onClicked: {
                        getProperties()
                    }
                }
            }

            TextField {
                id: pack
                width: parent.width
                placeholderText: "Package name if not detected (eg. QtSensors 5.0) "
                hasClearButton: true
            }

            ListView {
                clip:true
                width: parent.width
                height: parent.height - search.height
                model: datalist;

                delegate: Label {
                    text: modelData
                    fontSize: "large"
                }
            }
        }
    }
}

在这里,我们通过Qt.createQmlObject来动态地创建一个QML的object,并使用这个Object来得到它所有的属性。

运行这个应用:

  


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





作者:UbuntuTouch 发表于2015/8/5 16:45:29 原文链接
阅读:41 评论:0 查看评论

Read more
UbuntuTouch

[原]如何在QML中使用camera API来拍照

在先前的例程中“如何使用Ubuntu手机平台中的照相机API来存储照片”,我们已经展示了如何使用Item的属性来存储我们的照片。在这篇文章中,我们将使用Camera API来完成同样的功能。


我们来直接贴自己的代码:


main.qml

import QtQuick 2.0
import Ubuntu.Components 1.1
import QtMultimedia 5.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: "cameraapp.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)

    property var resolution

    // This function is used to get the writable private directory of this app
    function getPriateDirectory() {
        var sharepath = "/home/phablet/.local/share/";
        var path = sharepath + applicationName;
        console.log("path: " + path);
        return path;
    }

    Page {
        id: page
        title: i18n.tr("cameraapp")

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

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

            flash.mode: Camera.FlashRedEyeReduction

            imageCapture {
                onImageCaptured: {
                    console.log("image captured! reqId: " + requestId)
                    image.source = preview  // Show the preview in an Image
                }

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

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
            }
        }

        Row {
            id: container
            VideoOutput {
                id: video
                source: camera
                width: page.width
                height: page.height
                focus : visible // to receive focus and capture key events when visible
                orientation: -90

                //                Image {
                //                    id: photoPreview
                //                    anchors.fill: parent
                //                    rotation: -90
                //                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "left":
                            page.state = "image";
                            break
                        }
                    }
                }
            }

            Item {
                id: view
                width: page.width
                height: page.height

                Image {
//                    anchors.fill: parent
                    anchors.horizontalCenter: parent.horizontalCenter
                    anchors.verticalCenter: parent.verticalCenter
                    width: parent.height
                    height: parent.width
                    id: image
                    rotation: 90
                    fillMode: Image.PreserveAspectFit
                }

                Text {
                    text: image.source
                    color:"red"
                    font.pixelSize: units.gu(2.5)
                    width: page.width
                    wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "right":
                            page.state = "";
                            break
                        }
                    }
                }
            }
        }

        states: [
            State {
                name: "image"
                PropertyChanges {
                    target: container
                    x:-page.width
                }
                PropertyChanges {
                    target: capture
                    opacity:0
                }
            }
        ]

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

        Button {
            id: capture
            anchors.bottom: parent.bottom
            anchors.bottomMargin: units.gu(1)
            anchors.horizontalCenter: parent.horizontalCenter
            text: "Capture"

            onClicked: {
                console.log("capture path: " + getPriateDirectory());
                camera.imageCapture.captureToLocation(getPriateDirectory());
                page.state = "image"
            }
        }
    }
}



在这里我们必须指出的是我们可以使用:

camera.imageCapture.captureToLocation(getPriateDirectory());

来把照片存储到我们指定的位置。这个位置可以是手机应用自己的私有的目录或它下面的子目录。

我们可以在如下的代码中得到照相机存储的文件信息:

           imageCapture {
                onImageCaptured: {
                    console.log("image captured! reqId: " + requestId)
                    image.source = preview  // Show the preview in an Image
                }

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

通过得到的path信息,我们可以得到照片的位置,并显示它。

在我们的设计中,我们可以使用swipe向左或向右来切换viewFinder及拍照的相片。

运行我们的应用,我们可以看到如下的图片:

  

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

作者:UbuntuTouch 发表于2015/7/30 11:23:15 原文链接
阅读:248 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章“如何在QML中使用camera API来拍照”中,我们介绍了如何使用Camera API来进行拍照。今天我们在这篇文章中来介绍如何使用Camera API来进行录像。


首先,还是和以前一样,我直接把自己的代码贴出来:


main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtMultimedia 5.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: "videoapp.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)

    property var resolution

    // This function is used to get the writable private directory of this app
    function getPriateDirectory() {
        var sharepath = "/home/phablet/.local/share/";
        var path = sharepath + applicationName;
        console.log("path: " + path);
        return path;
    }

    Page {
        id: page
        title: i18n.tr("videoapp")

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

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

            flash.mode: Camera.FlashRedEyeReduction

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

            videoRecorder.audioEncodingMode: videoRecorder.ConstantBitrateEncoding;
            videoRecorder.audioBitRate: 128000
            videoRecorder.mediaContainer: "mp4"
            videoRecorder.outputLocation: getPriateDirectory()

            captureMode: Camera.CaptureVideo

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
                console.log("deviceId: " + camera.deviceId)
            }
        }

        Row {
            id: container

            Item {
                width: page.width
                height: page.height

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

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "left":
                            page.state = "image";
                            break
                        }
                    }
                }
            }

            Item {
                id: view
                width: page.width
                height: page.height

                Video {
                    id: myvideo
                    anchors.fill: parent
                    autoPlay: true
                    focus: true

                    Component.onCompleted: {
                        myvideo.play();
                    }
                }

                Text {
                    text: myvideo.source
                    color:"red"
                    font.pixelSize: units.gu(2.5)
                    width: page.width
                    wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                }

                SwipeArea {
                    anchors.fill: parent
                    onSwipe: {
                        console.log("swipe happened!: " + direction)
                        switch (direction) {
                        case "right":
                            page.state = "";
                            break
                        }
                    }
                }
            }
        }

        states: [
            State {
                name: "playvideo"
                PropertyChanges {
                    target: container
                    x:-page.width
                }
                PropertyChanges {
                    target: capture
                    opacity:0
                }
                PropertyChanges {
                    target: stop
                    opacity:0
                }
            }
        ]

        transitions: [
            Transition {
                NumberAnimation { target: container; property: "x"; duration: 500
                    easing.type:Easing.OutSine}
                //                NumberAnimation { target: inputcontainer; property: "opacity"; duration: 200}
                NumberAnimation { target: capture; property: "opacity"; duration: 200}
                NumberAnimation { target: stop; property: "opacity"; duration: 200}
            }
        ]

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

            Button {
                id: capture
                text: "Record"

                onClicked: {
                    console.log("capture path: " + getPriateDirectory());
                    camera.videoRecorder.record();
                }
            }

            Button {
                id: stop
                text: "Stop"

                onClicked: {
                    console.log("stop is clicked!");
                    camera.videoRecorder.stop();
                    page.state = "playvideo"
                }
            }

            Button {
                id: play
                text: "Play video"

                onClicked: {
                    console.log("filepath: " + myvideo.source);
                    console.log( "actual: " +  camera.videoRecorder.actualLocation);
                    myvideo.play();
                }
            }
        }

    }
}


在这里,QML Camera是被如下的方式使用的:

        Camera {
            id: camera

            imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceFlash

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

            flash.mode: Camera.FlashRedEyeReduction

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

            videoRecorder.audioEncodingMode: videoRecorder.ConstantBitrateEncoding;
            videoRecorder.audioBitRate: 128000
            videoRecorder.mediaContainer: "mp4"
            videoRecorder.outputLocation: getPriateDirectory()

            captureMode: Camera.CaptureVideo

            Component.onCompleted: {
                resolution = camera.viewfinder.resolution;
                console.log("resolution: " + resolution.width + " " + resolution.height);
                console.log("deviceId: " + camera.deviceId)
            }
        }

我们定义了capureMode为Camera.CaptureVideo。同时我们也指定了录像文件的路径:

videoRecorder.outputLocation: getPriateDirectory()

当我们使用CameraRecorder来进行如下的方式录像时:

 camera.videoRecorder.record();

我们可以通过:

            videoRecorder {
                onRecorderStateChanged: {
                    console.log("onRecorderStateChanged: " + videoRecorder.recorderState);
                    if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
                        console.log("actualLocation: " + videoRecorder.actualLocation);
                        myvideo.source =  videoRecorder.actualLocation;
                    }
                }
            }

来获取所录像的文件的实际路径。

我们可以通过:

               Video {
                    id: myvideo
                    anchors.fill: parent
                    autoPlay: true
                    focus: true

                    Component.onCompleted: {
                        myvideo.play();
                    }
                }

来播放已经录下的文件。

运行为我们的应用:

 

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



作者:UbuntuTouch 发表于2015/7/31 15:54:58 原文链接
阅读:100 评论:0 查看评论

Read more
UbuntuTouch

我们可以在QML应用中直接使用QML WebView来装载我们的HTML应用,但是如果我们的HTML文件中使用到alert及confirm,我们的应用可能并不能弹出我们需要的alert及confirm。这时,我们需要对WebView中的alertDialog及confirmDialog进行设计才可以起到作用。


我们在下面来展示如何去做:


index.html


<html>
   <head>
   
      <script type="text/javascript">
         <!--
            function getConfirmation(){
               var retVal = confirm("Do you want to continue ?");
               if( retVal == true ){
                  return true;
               }
               else{
                  return false;
               }
            }
         //-->

         <!--
            function getAlert(){
               alert("This is cool!");
            }
         //-->

        <!--
            function getPrompt(){
               var retVal = prompt("Enter your name : ", "your name here");
               // console.log("retVal: + " + retVal);
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following button to see the result: </p>
      
      <form>
         <input type="button" value="Alert Dialog" onclick="getAlert();" /> <br>
         <input type="button" value="Confirm Dialog" onclick="getConfirmation();" /> <br>
         <input type="button" value="Prompt Dialog" onclick="getPrompt();" />
      </form>
      
   </body>
</html>


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Web 0.2
import Ubuntu.Components.Popups 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: "webviewdialog.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("webviewdialog")

        Component {
            id: confirmDlg

            Dialog {
                title: i18n.tr("JavaScript Confirmation")

                Button {
                    text: i18n.tr("OK")
                    onClicked: model.accept()
                }

                Button {
                    text: i18n.tr("Cancel")
                    onClicked: model.reject()
                }

                Component.onCompleted: show()
            }
        }

        Component {
            id: alertDlg

            Dialog {
                title: model.message

                Button {
                    text: i18n.tr("OK")
                    onClicked: model.accept()
                }

                Component.onCompleted: show()
            }
        }

        Component {
            id: promptDlg

            Dialog {
                TextField {
                    id: input
                    text: model.defaultValue
                    onAccepted: model.accept(input.text)
                }

                Button {
                    text: i18n.tr("OK")
                    color: "green"
                    onClicked: model.accept(input.text)
                }

                Button {
                    text: i18n.tr("Cancel")
                    color: UbuntuColors.coolGrey
                    onClicked: model.reject()
                }

                Binding {
                    target: model
                    property: "currentValue"
                    value: input.text
                }

                Component.onCompleted: show()
            }
        }

        WebView {
            anchors.fill: parent
            url: "www/index.html"
            confirmDialog: confirmDlg
            alertDialog: alertDlg
            promptDialog: promptDlg
        }
    }
}



在这里,我们对alertDialog及confirmDialog进行了初始化,这样当我们在index.html中使用alert及confirm时,我们可以得到我们需要的Dialog:


   




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


作者:UbuntuTouch 发表于2015/8/3 10:31:52 原文链接
阅读:65 评论:0 查看评论

Read more
UbuntuTouch

[原]为QML动态生成Tab

在QML设计中,Tabs是构成QML应用的一种UI架构。在今天的这篇文章中,我们来尝试来动态创建一些Tab。


我们先把代码贴出来:


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtQuick.LocalStorage 2.0

/*!
    \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: "dynamictab.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)

    Component.onCompleted: {
        mainView.initializeDB();
        mainView.saveFeed("BBC News","http://feeds.bbci.co.uk/news/rss.xml");
        mainView.saveFeed("Jono Bacon","http://www.jonobacon.org/?feed=rss2");
        mainView.saveFeed("The Register", "http://www.theregister.co.uk/headlines.atom");
        fillTabs();
    }

    Tabs {
        id: initialtabs
        anchors.fill: parent


//        // First tab begins here
//        Tab {
//            id: tabFrontPage
//            objectName: "tabFrontPage"

//            title: i18n.tr("Front Page")

//            // Tab content begins here
//            page: Page {
//                Column {
//                     anchors.centerIn: parent
//                    Label {
//                        id: labelFrontPage
//                        text: i18n.tr("This will be the front page \n An aggregation of the top stories from each feed")
//                    }
//                }
//            }
//        }
    }


    function fillTabs() {
        initialtabs.destroy();
        var objStr = "import QtQuick 2.0; import Ubuntu.Components 1.1; import QtQuick.XmlListModel 2.0; Tabs{ id:tabs; anchors.fill:parent;"
        var db = getDatabase();
        db.transaction(function(tx) {
            var rs = tx.executeSql('SELECT * FROM feeds;');
            if (rs.rows.length > 0) {
                for(var i = 0; i < rs.rows.length; i++) {
                    objStr += "Tab { id:tab" + i + ";anchors.fill:parent;title:'" + rs.rows.item(i).feedName + "';
                               property string source: '" + rs.rows.item(i).feedURL + "';
                               page: Page { anchors.margins: units.gu(2);Column {anchors.centerIn: parent;
                               Label{text:tab" + i + ".source;}}}}";
                }
                objStr += "}";

                console.log("objStr: " + objStr );
                var cmpTabs = Qt.createQmlObject(objStr, mainView, "tabsfile");
            } else {
                res = "Unknown";
            }
        })
    }

    //Create tabs for each feed
    function createTabs() {
        var feeds = getFeeds();
        for (var i = 0; i < feeds.length; i++){
            //Add tab for each feed.
            // Cannot be done with existing API

        }
    }

    //Storage API
    function getDatabase() {
        return LocalStorage.openDatabaseSync("news-feed","1.0","StorageDatabase",10000)
    }

    //Initialise DB tables if not already existing
    function initializeDB() {
        var db = getDatabase();
        db.transaction(function(tx) {
            //Create settings table if not existing
            tx.executeSql('CREATE TABLE IF NOT EXISTS settings(setting TEXT UNIQUE, value TEXT)');
            tx.executeSql('CREATE TABLE IF NOT EXISTS feeds(feedName TEXT UNIQUE, feedURL TEXT UNIQUE)')
        });
    }

    //Write setting to DB
    function setSetting(setting,value){
        //setting: string - setting name (key)
        //value: string - value
        var db = getDatabase();
        var res = "";
        db.transaction(function(tx) {
            var rs = tx.executeSql('INSERT OR REPLACE INTO settings VALUES (?,?);',[setting,value]);
            //console.log(rs.rowsAffected)
            if(rs.rowsAffected > 0) {
                res = "OK";
            } else {
                res = "Error";
            }
        })
        return res;
    }

    //Read setting from DB
    function getSetting(setting) {
        var db = getDatabase();
        var res="";
        db.transaction(function(tx) {
            var rs = tx.executeSql('SELECT value FROM settings WHERE setting=?;', [setting]);
            if (rs.rows.length > 0) {
                res = rs.rows.item(0).value;
            } else {
                res = "Unknown";
            }
        })
        // The function returns “Unknown” if the setting was not found in the database
        // For more advanced projects, this should probably be handled through error codes
        return res;
    }

    function saveFeed(feedName, feedURL) {
        var db = getDatabase();
        var res = "";
        db.transaction(function(tx){
            var rs = tx.executeSql('INSERT OR REPLACE INTO feeds VALUES (?,?)',[feedName,feedURL]);
            //console.log(rs.rowsAffected)
            if (rs.rowsAffected > 0) {
                res = "OK";
            } else {
                res = "Error";
            }
        })
        return res;
    }

    //Return a single feed
    function getFeed(feedName) {
        var db = getDatabase();
        var res = "";
        db.transaction(function(tx) {
            var rs = tx.executeSql('SELECT feedURL FROM feeds WHERE feedName=?;', [feedName]);
            if (rs.rows.length > 0) {
                res = rs.rows.item(0).feedURL;
            } else {
                res = "Unknown";
            }

        })
        return res;
    }

    //Return all feeds and urls
    function getFeeds() {
        var db = getDatabase();
        var res = "";
        db.transaction(function(tx) {
            var rs = tx.executeSql('SELECT * FROM feeds;');
            if (rs.rows.length > 0) {
                return rs;
            } else {
                res = "Unknown";
            }
        })

        return res;
    }
}

这里,我们们先创建一个自己的数据库来存储一些RSS feed的值:


   Component.onCompleted: {
        mainView.initializeDB();
        mainView.saveFeed("BBC News","http://feeds.bbci.co.uk/news/rss.xml");
        mainView.saveFeed("Jono Bacon","http://www.jonobacon.org/?feed=rss2");
        mainView.saveFeed("The Register", "http://www.theregister.co.uk/headlines.atom");
        fillTabs();
    }

在上面的最后部分,我们调用fillTabs来动态创建一些我们想要的Tab:


    function fillTabs() {
        initialtabs.destroy();
        var objStr = "import QtQuick 2.0; import Ubuntu.Components 1.1; import QtQuick.XmlListModel 2.0; Tabs{ id:tabs; anchors.fill:parent;"
        var db = getDatabase();
        db.transaction(function(tx) {
            var rs = tx.executeSql('SELECT * FROM feeds;');
            if (rs.rows.length > 0) {
                for(var i = 0; i < rs.rows.length; i++) {
                    objStr += "Tab { id:tab" + i + ";anchors.fill:parent;title:'" + rs.rows.item(i).feedName + "';
                               property string source: '" + rs.rows.item(i).feedURL + "';
                               page: Page { anchors.margins: units.gu(2);Column {anchors.centerIn: parent;
                               Label{text:tab" + i + ".source;}}}}";
                }
                objStr += "}";

                console.log("objStr: " + objStr );
                var cmpTabs = Qt.createQmlObject(objStr, mainView, "tabsfile");
            } else {
                res = "Unknown";
            }
        })
    }


这里我们使用了Qt.createQmlObject来创建不同的Tab。这些tab的具体的内容来自我们的数据库。


运行我们的应用:


  


当然我们也可以利用我们存储的rss feed的值来创建一个完全新的RSS reader。具体下面的开发就留给开发者吧。我们也可以使用Qt.createComponent从一个文件中来创建一个我们所需要的tab。


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

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

Read more
UbuntuTouch

[原]在QML中使用alarm

我们可以QML应用中设置alarm来完成一些提醒的工作。为了使得它能够工作,我们必须加入calendar policy。由于calendar policy目前还是处于reserved状态,所以我们目前只能做测试使用。等将来正式发布后,就可以使用了。


import QtQuick 2.4
import Ubuntu.Components 1.2

/*!
    \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: "alarm.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("alarm")

        Alarm{
            id: alarm
        }

        Column {
            spacing: units.gu(1)

            Row {
                spacing: units.gu(1)

                Label {
                    id: date
                    text: "Date:"
                    anchors.verticalCenter: parent.verticalCenter
                }
                TextField {
                    text: alarm.date.toString()
                    onAccepted: {
                        console.log("this is cool!");
                        onAccepted: alarm.date = new Date(text)
                    }
                }
            }

            Row {
                spacing: units.gu(1)
                Label {
                    id: msg
                    text: "Message:"
                    anchors.verticalCenter: parent.verticalCenter
                }
                TextField {
                    text: alarm.message
                    onAccepted: {
                        console.log("Setting the alarm message!")
                        alarm.message = text
                    }
                }
            }

            Button {
                text: "Save"
                onClicked: {
                    var now = new Date();
                    var newdate = new Date(now.getTime() + 10*1000);
                    console.log("now: " + newdate.toLocaleDateString())
                    alarm.date = newdate;

                    alarm.save();
                    if (alarm.error !== Alarm.NoError) {
                        print("Error saving alarm, code: " + alarm.error);
                    } else {
                        print("There is no error!")
                    }

                }
            }
        }
    }
}


在我们的应用中,当我们设置alarm时,我们把当前的时间加上10秒,然后设置时间:


            Button {
                text: "Save"
                onClicked: {
                    var now = new Date();
                    var newdate = new Date(now.getTime() + 10*1000);
                    console.log("now: " + newdate.toLocaleDateString())
                    alarm.date = newdate;

                    alarm.save();
                    if (alarm.error !== Alarm.NoError) {
                        print("Error saving alarm, code: " + alarm.error);
                    } else {
                        print("There is no error!")
                    }

                }

这样,当10秒钟过后,就会有我们的alarm信息。


  


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






作者:UbuntuTouch 发表于2015/8/4 14:21:26 原文链接
阅读:32 评论:0 查看评论

Read more
UbuntuTouch

VisualItemModel可以让我们把QML Item变为我们的ListView的Model成为可能。这个Model可以既含有数据(data)也可以含有delegate。VisualItemModel含有的Item提供可以用来画数据内容的delete。这个Model不提供任何roles,也就是说我们不可以使用任何“model.xxxx”来引用我们的model数据。VisualItemModel适合于ListView中的每个delegate显示的情况都不太一样的情况,但是,我们可以照样使用ListView来显示我们的数据,并使用ListView的特性来展示并scroll数据。更多阅读ObjectModel。我们可以使用ObjectModel来代替VisualItemModel来实现同样的事情。


一个简单的例程如下:


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

        VisualItemModel {
            id: itemModel
            Rectangle { height: view.height/3; width: view.width; color: "red" }
            Rectangle { height: view.height/3; width: view.width; color: "green" }
            Rectangle { height: view.height/3; width: view.width; color: "blue" }
        }

        ListView {
            id: view
            anchors.fill: parent
            model: itemModel
        }

    }




代码:git clone https://gitcafe.com/ubuntu/visualitemmodel1.git


我们的另外一个例程代码展示如下:


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: "visualitemmodel.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("visualitemmodel")

        Rectangle {
            color: "lightgray"
            anchors.fill: parent

            VisualItemModel {
                id: itemModel

                Rectangle {
                    width: view.width; height: view.height - footer.height
                    color: "#FFFEF0"
                    Text { text: "Page 1"; font.bold: true; anchors.centerIn: parent }
                }

                Rectangle {
                    width: view.width; height: view.height - footer.height
                    color: "#F0FFF7"
                    Text { text: "Page 2"; font.bold: true; anchors.centerIn: parent }
                }

                Rectangle {
                    width: view.width; height: view.height - footer.height
                    color: "#F4F0FF"
                    Text { text: "Page 3"; font.bold: true; anchors.centerIn: parent }
                }
            }

            UbuntuListView {
                id: view
                anchors { fill: parent; bottomMargin: 30 }
                model: itemModel
                preferredHighlightBegin: 0; preferredHighlightEnd: 0
                highlightRangeMode: ListView.StrictlyEnforceRange
                orientation: ListView.Horizontal
                highlightMoveVelocity : 5000
                snapMode: ListView.SnapOneItem
                highlightMoveDuration: 1000
                flickDeceleration: 2000
                Component.onCompleted: {
                    console.log("velocity: " + view.horizontalVelocity)
                    console.log("highlightMoveDuration: " + view.highlightMoveDuration)
                }
            }
        }

        Row {
            id: footer
            anchors { bottom: parent.bottom }
            anchors.bottomMargin: units.gu(1)
            // anchors.centerIn: parent
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: units.gu(4)
            height: units.gu(3)

            Repeater {
                model: itemModel.count

                Rectangle {
                    width: units.gu(3); height: width
                    radius: units.gu(1.5)
                    color: view.currentIndex == index ? "blue" : "yellow"

                    MouseArea {
                        width: units.gu(3); height: width
                        anchors.centerIn: parent
                        onClicked: view.currentIndex = index
                    }
                }
            }
        }
    }
}


  

在上面的例程中,我们可以点击下面的圆点来切换我们的page。我们也可以利用ListView的特性来左右swipe我们的画面来改变当前的Page号码。这种情况适合于展示不同页面,而且每个页面也不同的情况。

我们的源码在: git clone https://gitcafe.com/ubuntu/visualitemmodel.git



作者:UbuntuTouch 发表于2015/8/5 11:27:33 原文链接
阅读:59 评论:0 查看评论

Read more
UbuntuTouch

我们知道,在Ubuntu 手机平台中,我们使用了全新的安全机制。任何一个应用只能访问自己的私有空间,但是它不能访问不属于它的任何其它的空间。具体来说,如果我们想直接通过系统文件目录的方式来使用照相机所拥有的Pictures目录的话,那是不可以的。原因是那个目录只属于创建它的那个应用。那么我们该如何才能访问那个目录中的文件呢?答案就是ContentHub API。我们在先前的一个例子“利用ContentHub API来import图片”中也展示了如何这么做。


下面我们来通过一个例子来展示如何来导入一个从其它应用产生的图片:


Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.2
import Ubuntu.Components.ListItems 0.1 as ListItem
import Ubuntu.Components.Popups 0.1
import Ubuntu.Content 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: "contenthub-picker.liu-xiao-guo"

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

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

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

        Page {
            id: root
            title: i18n.tr("Peer Picker Example")
            visible: false

            property list<ContentItem> importItems
            property var activeTransfer

            Column {
                anchors.fill: parent

                ListItem.Standard {
                    id: peerListHeader
                    anchors {
                        left: parent.left
                        right: parent.right
                    }
                    text: i18n.tr("Sources")
                    control: Button {
                        text: i18n.tr("Select source")
                        onClicked: {
                            pageStack.push(picker);
                        }
                    }
                }

                ListItem.Header {
                    id: titleItem
                    anchors {
                        left: parent.left
                        right: parent.right
                    }
                    text: i18n.tr("Results")
                }

                GridView {
                    id: resultList
                    anchors {
                        left: parent.left
                        right: parent.right
                    }
                    height: childrenRect.height
                    cellWidth: units.gu(20)
                    cellHeight: cellWidth

                    model: root.importItems
                    delegate: Item {
                        id: result
                        height: units.gu(19)
                        width: height
                        UbuntuShape {
                            width: parent.width
                            height: width
                            source: Image {
                                id: image
                                source: url
                                sourceSize.width: width
                                sourceSize.height: height
                                height: parent.height
                                width: height
                                fillMode: Image.PreserveAspectFit
                                smooth: true
                            }
                        }
                    }
                }
            }

            ContentTransferHint {
                anchors.fill: root
                activeTransfer: root.activeTransfer
            }

            Connections {
                target: root.activeTransfer
                onStateChanged: {
                    console.log("StateChanged: " + root.activeTransfer.state);
                    switch (root.activeTransfer.state ) {
                    case ContentTransfer.Created:
                        console.log("StateChanged: Created")
                        break;

                    case ContentTransfer.Initiated:
                        console.log("StateChanged: Initiated")
                        break;

                    case ContentTransfer.InProgress:
                        console.log("StateChanged: InProgress")
                        break;

                    case ContentTransfer.Downloading:
                        console.log("StateChanged: Downloading")
                        break;

                    case ContentTransfer.Downloaded:
                        console.log("StateChanged: Downloaded")
                        break;

                    case ContentTransfer.Charged:
                        console.log("StateChanged: Charged")
                        break;

                    case ContentTransfer.Collected:
                        console.log("StateChanged: Collected")
                        break;

                    case ContentTransfer.Aborted:
                        console.log("StateChanged: Aborted")
                        break;

                    case ContentTransfer.Finalized:
                        console.log("StateChanged: Finalized")
                        break;
                    }

                    if (root.activeTransfer.state === ContentTransfer.Charged) {
                        root.importItems = root.activeTransfer.items;

                        for ( var key in root.importItems ) {
                            console.log("url: " + root.importItems[key].url)
                        }
                    }
                }
            }

        }

        Page {
            id: picker
            visible: false

            ContentPeerPicker {
                visible: parent.visible

                // Type of handler: Source, Destination, or Share
                handler: ContentHandler.Source
                // well know content type
                contentType: ContentType.Pictures

                onPeerSelected: {
                    console.log("onPeerSelected-----------------------------!")
                    root.activeTransfer = peer.request();
                    pageStack.pop();
                }

                onCancelPressed: {
                    console.log("onCancelPressed!---------------------------!")
                    pageStack.pop();
                }
            }
        }
    }
}


在上面的代码中,我们使用了ContentPeerPicker来进行选择我们所需要的图片的Peer。当我们一旦选择好Peer后,我们通过


 Connections {
                target: root.activeTransfer
                onStateChanged: {
                 ...
                 }
 }


来得到目前activeTransfer的具体的状态。当它的状态变为ContentTransfer.Charged时,我们就可以得到所选文件的url。下面是我们运行我们的应用时的截图。记住我们需要添加content_exchange policy来完成我们的操作:


  


   


在图片被import过来后,我们会发现照片所处的位置在:


qml: url: file:///home/phablet/.cache/contenthub-picker.liu-xiao-guo/HubIncoming/3/image20150806_080715636.jpg

显然这个位置是我们的应用可以访问的位置。我们可以对它进行我们所需要的任何操作。


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



作者:UbuntuTouch 发表于2015/8/6 16:37:19 原文链接
阅读:61 评论:0 查看评论

Read more
UbuntuTouch

我们在QML应用中有时需要调用系统设置(system settings)来完成我们的一些设置。比如,我们在使用GPS来定位时,可能GPS并没有打开,如果在我们的应用中直接打开系统中的GPS设置页面,这样我们就可以直接打开系统的GPS而不用单独设计一个页面。我们可以通过使用URL dispatcher的方法来打开另外一个应用。在先前的我们的文章中,我们已经讲述了很多关于URL dispatcher方面的东西:

  1. 怎么在Ubuntu手机上发送短信及拨打电话
  2. 使用URL dispatcher的范例

关于系统设置(system-settings)的源码,我们可以在地址找到。

如何查看系统的url dispatcher


通常情况下,我们可能很想知道系统中到底有那些的URL dispatcher。我们可以通过如下的命名来查看我们的手机系统的URL dispatcher:

 


上面列举了我们手机系统中已经有的URL dispatcher。比如我们可以查看system-settings的url dispatcher的protocol:


[
        {
                "protocol": "settings"
        }
]


这样我们在我们的QML应用中使用如下的方法来启动系统设置中的页面:

 Qt.openUrlExternally("settings:///system/about");

上面的方法就可以打开系统设置中的“关于”页面。

基于上面的理解,我设计了如下的例程来启动系统设置中的不同的页面:

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: "urldispatcher.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)

    property var plugins: ["about", "phone", "battery", "bluetooth", "brightness",
        "cellular", "language", "background", "flight-mode",
        "notifications", "orientation-lock", "reset", "security-privacy",
        "sound", "system-update", "time-date", "wifi"]

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

        Flickable {
            clip: true
            width: parent.width
            height: parent.height
            contentHeight: content.childrenRect.height

            Column {
                id: content
                anchors.centerIn: parent
                spacing: units.gu(1)

                Repeater {
                    model: plugins
                    delegate: Button {
                        text: modelData
                        onClicked: {
                            Qt.openUrlExternally("settings:///system/" + modelData);
                        }
                    }
                }
            }

        }
    }
}

运行我们的应用:

  

应用的源码在: git clone https://gitcafe.com/ubuntu/urldispatcher.git

作者:UbuntuTouch 发表于2015/8/17 13:23:24 原文链接
阅读:186 评论:0 查看评论

Read more
UbuntuTouch

我们知道在Ubuntu手机中一个应用不能直接访问另外一个应用的空间。我们有时需要这么做。比如我们想把我们自己使用照相机API照下一个照片,并放入到我们自己应用的自己可以访问的空间。但是我们不能直接把我们所照的照片直接放入到Gallery应用所拥有的目录中。如果是这样做,直接就违反系统的平台安全性。更多关于平台安全性,可以阅读文章“Ubuntu OS应用Runtime Enviroment”。我们该如何把自己的照片存入到Gallery应用所拥有的目录并让Gallery应用来显示呢?答案是使用ContentHub API


下面我们来通过一个例程来完成展示如何完成这个功能。


首先,我们来完成一个Dialog:


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Popups 1.0
import Ubuntu.Content 1.1

PopupBase {
    id: downloadDialog
    anchors.fill: parent
    property var activeTransfer
    property var downloadId
    property alias contentType: peerPicker.contentType

    Rectangle {
        anchors.fill: parent
        ContentPeerPicker {
            id: peerPicker
            handler: ContentHandler.Destination
            visible: parent.visible

            onPeerSelected: {
                activeTransfer = peer.request()
                activeTransfer.downloadId = downloadDialog.downloadId
                activeTransfer.state = ContentTransfer.Downloading
                PopupUtils.close(downloadDialog)
            }

            onCancelPressed: {
                PopupUtils.close(downloadDialog)
            }
        }
    }
}

这个是用来显示一个Dialog。它是让我们选择我们想要放到什么地方去。这里的contentType是让我们选择我们所需要的内容的类型。


import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Popups 1.0
import Ubuntu.DownloadManager 0.1
import Ubuntu.Content 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: "contenthub-savefile.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: page
        title: i18n.tr("contenthub-savefile")

        Component {
            id: downloadDialog
            ContentSaveDialog { }
        }

        SingleDownload {
            id: downloader
            autoStart: false
            onDownloadIdChanged: {
                PopupUtils.open( downloadDialog, page, {"contentType" : ContentType.Pictures,
                                    "downloadId" : downloadId } )
            }

            onFinished: {
                print("download finished! saved path: " + path);
            }
        }


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

            Image {
                id: image
                source: "images/sample.jpg"
                width: page.width/2
                height: page.height/2
            }

            Button {
                anchors.horizontalCenter: parent.horizontalCenter
                text: "Save"
                onClicked: {
                    console.log("image source: " + image.source);
                    downloader.download(image.source)
                }
            }
        }
    }
}



在这里,我们使用了DownloadManger里的SingleDownload来把我们所想要存的照片image存到我们想要存的地方。这个地方有上面的Dialog所提供。


为了使得这个应用能够正常运行,我们必须添加content_exchange及content_exchange_source到apparmor文件中去。否则我们将收到security的一些错误信息。

contenthub-savefile.apparmor

{
    "policy_groups": [
        "networking",
        "webview",
        "content_exchange",
        "content_exchange_source"
    ],
    "policy_version": 1.3
}


运行我们的应用:


  


  


我们从上面可以看出,我们把我们自己images目录下的sample.jpg传到我们的Gallery应用拥有的目录里了。


qml: download finished! saved path: /home/phablet/.cache/com.ubuntu.gallery/HubIncoming/12/sample.jpg


所有源码在:git clone https://gitcafe.com/ubuntu/contenthub-savefile.git





作者:UbuntuTouch 发表于2015/8/17 17:15:54 原文链接
阅读:169 评论:0 查看评论

Read more
UbuntuTouch

我们在先前的例程中已经通过一些方法得到我们应用的一些环境变量值。这些值有的非常有用,比如我们可以得到我们应用所只能访问的目录。在今天的例程中,我们来展示一种方法可以得到应用所有的环境变量。在我们的实际应用中,我们可以通过这些环境变量来做一些事情。另外,在这个例程中,我们也展示了如何在Qt C++的代码中构造我们的ListView中的model。我们在先前的例程“Ubuntu OS应用Runtime Enviroment”已经展示了和我们平台安全相关的一些环境变量。


首先,我们来展示我们所使用的model dataobject:


dataobject.h


#ifndef DATAOBJECT_H
#define DATAOBJECT_H

#include <QObject>

class DataObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString key READ key WRITE setKey NOTIFY keyChanged)
    Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)

public:
    DataObject(QObject *parent=0);
    DataObject(const QString &key, const QString &value, QObject *parent=0);

    QString key() const;
    void setKey(const QString &key);

    QString value() const;
    void setValue(const QString &value);

signals:
    void keyChanged();
    void valueChanged();

private:
    QString m_key;
    QString m_value;
};

#endif // DATAOBJECT_H


dataobject.cpp


#include <QDebug>
#include "dataobject.h"

DataObject::DataObject(QObject *parent)
    : QObject(parent)
{
}

DataObject::DataObject(const QString &key, const QString &value, QObject *parent)
    : QObject(parent), m_key(key), m_value(value)
{
}

QString DataObject::key() const
{
    return m_key;
}

void DataObject::setKey(const QString &key)
{
    if (key != m_key) {
        m_key = key;
        emit keyChanged();
    }
}

QString DataObject::value() const
{
    return m_value;
}

void DataObject::setValue(const QString &value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged();
    }
}


这个model dataobject是为了我们能够在ListView中显示我们的key及value。


main.cpp


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

#include "dataobject.h"

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

    // Try to get all of the env variables here
    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();

    QStringList keys = environment.keys();

    QList<QObject*> dataList;

    foreach (QString key, keys) {
        qDebug() << "key: " << key;

        QString value = environment.value(key);
        qDebug() << "value: " << value;

        dataList.append(new DataObject(key, value));
    }

    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:///Main.qml")));
    view.setResizeMode(QQuickView::SizeRootObjectToView);

    QQmlContext *ctxt = view.rootContext();
    ctxt->setContextProperty("varModel", QVariant::fromValue(dataList));

    view.show();
    return app.exec();
}


在上面,我们通过QProcessEnvironment来得到我们所有的环境变量,并把它们装在我们的datalist变量中。这个datalist将在我们的QML中以varModel的形式访问。

Main.qml


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

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

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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "runtime.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("Run time variables")

        Column {
            anchors.fill: parent

            ListItem.Base {
                id: header
                height: ubuntuLabel.height + runtime.height + units.gu(6)

                Column {
                    anchors.left: parent.left
                    anchors.right: parent.right
                    anchors.centerIn: parent
                    spacing: units.gu(2)
                    Label {
                        id: ubuntuLabel
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: ""
                        fontSize: "x-large"
                    }
                    Label {
                        id: runtime
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: "Runtime Environment"
                    }
                }
            }

            ListView {
                height: parent.height - header.height
                width: parent.width
                model: varModel
                clip: true

                delegate:  ListItem.Subtitled {
                    text: key
                    subText: value
                }
            }

        }
    }
}


这里的显示非常简单。我们直接使用一个ListView来显示我们的数据。

运行我们的应用:

    


所有的源码在:git clone https://gitcafe.com/ubuntu/runtime.git

作者:UbuntuTouch 发表于2015/8/18 12:05:13 原文链接
阅读:140 评论:0 查看评论

Read more
UbuntuTouch

我们在很多的系统中看见可以在屏幕的一个地方长按,然后就可以根据当前显示的上下文弹出一个菜单。菜单中可以有一些选项,比如删除,修改该项。这种一般在ListViewGridView中常见。今天,我们就在这个例程中详细介绍如何实现这个功能。


对ListView来说,我们只需要对它的delegate做一些修改:


        Component {
            id: listDelegate

            ListItem {
                id: delegateItem
                width: listView.width; height: units.gu(10)
                onPressAndHold: ListView.view.ViewItems.dragMode =
                                !ListView.view.ViewItems.dragMode

                Image {
                    id: pic
                    height: parent.height - units.gu(1)
                    width: height
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
                    anchors.leftMargin: units.gu(0.5)
                    source: image
                }

                Column {
                    id: content
                    anchors.top: parent.top
                    anchors.left: pic.right
                    anchors.leftMargin: units.gu(2)
                    anchors.topMargin: units.gu(1)
                    width: parent.width - pic.width - units.gu(1)
                    height: parent.height
                    spacing: units.gu(1)

                    Label {
                        text: name
                    }

                    Label { text: description }

                    Label {
                        text: '$' + Number(cost).toFixed(2)
                        font.bold: true
                    }
                }


                ListView.onAdd: SequentialAnimation {
                    PropertyAction { target: delegateItem; property: "height"; value: 0 }
                    NumberAnimation { target: delegateItem; property: "height"; to: delegateItem.height; duration: 250; easing.type: Easing.InOutQuad }
                }

                ListView.onRemove: SequentialAnimation {
                    PropertyAction { target: delegateItem; property: "ListView.delayRemove"; value: true }
                    NumberAnimation { target: delegateItem; property: "height"; to: 0; duration: 250; easing.type: Easing.InOutQuad }

                    // Make sure delayRemove is set back to false so that the item can be destroyed
                    PropertyAction { target: delegateItem; property: "ListView.delayRemove"; value: false }
                }

                /* create an empty item centered in the image to align the popover to */
                Item {
                    id: emptyItemForCaller
                    anchors.centerIn: parent
                    z: 100

                }

                Component {
                    id: actPopComp

                    ActionSelectionPopover {
                        id: actPop
                        delegate: ListItems.Standard {
                            text: action.text
                        }

                        actions: ActionList {
                            Action {
                                text: "Add 1 dollar"
                                iconName: "add"
                                onTriggered: {
                                    PopupUtils.close(actPop);
                                    console.log("Add 1 dollar");
                                    fruitModel.setProperty(index, "cost", cost + 1.0);
                                }
                            }
                            Action {
                                text: "Deduct 1 dollar"
                                iconName: "remove"
                                onTriggered: {
                                    PopupUtils.close(actPop);
                                    console.log("Deduct 1 dollar");
                                    fruitModel.setProperty(index, "cost", Math.max(0,cost-1.0));
                                }
                            }
                            Action {
                                text: "delete"
                                iconName: "delete"

                                onTriggered: {
                                    console.log("delete the item!");
                                    fruitModel.remove(index)
                                }
                            }
                        }
                    }
                }

                MouseArea {
                    anchors.fill: parent
                    onPressAndHold: {
                        PopupUtils.open(actPopComp, emptyItemForCaller);
                    }

                    onClicked: {
                        console.log("we can do something else!");
                    }
                }
            }
        }


从上面的代码中可以看出:


  /* create an empty item centered in the image to align the popover to */
                Item {
                    id: emptyItemForCaller
                    anchors.centerIn: parent
                    z: 100

                }

我们使用了一个空的Item来作为一个placeholder。这个是为了给我们在长按ListView中项时,来提供一个位置显示我们的Popup menu。

当我们长按我们的ListView中的项时,我们可以通过如下的方法来得到事件并弹出我们所需要的Popup:


               MouseArea {
                    anchors.fill: parent
                    onPressAndHold: {
                        PopupUtils.open(actPopComp, emptyItemForCaller);
                    }

                    onClicked: {
                        console.log("we can do something else!");
                    }
                }

这里,我们的actPopComp的设计为:


                Component {
                    id: actPopComp

                    ActionSelectionPopover {
                        id: actPop
                        delegate: ListItems.Standard {
                            text: action.text
                        }

                        actions: ActionList {
                            Action {
                                text: "Add 1 dollar"
                                iconName: "add"
                                onTriggered: {
                                    PopupUtils.close(actPop);
                                    console.log("Add 1 dollar");
                                    fruitModel.setProperty(index, "cost", cost + 1.0);
                                }
                            }
                            Action {
                                text: "Deduct 1 dollar"
                                iconName: "remove"
                                onTriggered: {
                                    PopupUtils.close(actPop);
                                    console.log("Deduct 1 dollar");
                                    fruitModel.setProperty(index, "cost", Math.max(0,cost-1.0));
                                }
                            }
                            Action {
                                text: "delete"
                                iconName: "delete"

                                onTriggered: {
                                    console.log("delete the item!");
                                    fruitModel.remove(index)
                                }
                            }
                        }
                    }
                }

在这里,我们有三个Action的菜单。


运行我们的应用:


  


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


作者:UbuntuTouch 发表于2015/8/19 12:32:44 原文链接
阅读:185 评论:0 查看评论

Read more
UbuntuTouch

[原]如何创建一个ContentHub exporter

在以前的例子“利用ContentHub API来import图片”及“使用ContentHub来导入我们需要的照片”中,我们已经讲述了如何从别的应用中提取一个图片到我们的应用中。我们也在我们的另外一个文章“如何通过ContentHub把内容从一个地方传到另外一个地方”介绍了如何把我们应用的照片存放到我们想要的目录中。这一切都是通过ContentHub API来实现的。在今天的文章中,我们将着重介绍如何使自己的应用变成一个exporter,从而别的应用可以从我们的应用中import图片等到它的应用中。同时我们也可以把我们的照片分享或传人到其它的应用中去。


我们可以用我们常用的方法来创建一个叫做“contenhub-exporter”的qmlproject项目:




我们想把我们的应用变为一个可以export图片的应用。更多的类型可以在地址找到。


Type Description
ContentType.Uknown Unknown type
ContentType.Documents Documents
ContentType.Pictures Pictures
ContentType.Music Music
ContentType.Contacts Contacts
ContentType.Videos Videos
ContentType.Links Links
ContentType.EBooks EBooks
ContentType.All Any of the above content types
这样每当别的应用想import一个图片时,我们的应用将会被列出来供选择提供图片来给别的应用使用。


我们在我们项目的根目录里,同时创建一个叫做“content-hub”的目录,同时也创建一个叫做“hub-exporter.json”的文件,它的内容为:

hub-exporter.json

{
    "source": [
        "pictures"
    ]
}

注意这里,我们定义的是pictures,表明我们的应用将提供图片给别的应用使用。


为了应用这个文件,我们必须同时也修改我们mainifest.json文件:

manifest.json

{
    "name": "contenthub-exporter.liu-xiao-guo",
    "description": "description of contenthub-exporter",
    "architecture": "all",
    "title": "contenthub-exporter",
    "hooks": {
        "contenthub-exporter": {
            "apparmor": "contenthub-exporter.apparmor",
            "desktop": "contenthub-exporter.desktop",
            "content-hub": "content-hub/hub-exporter.json"
        }
    },
    "version": "0.1",
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "framework": "ubuntu-sdk-15.04"
}


注意这上面的“content-hub”一项。


好了,我们来设计我们的应用界面。我们的界面如下:




显然,这是一个GridView的界面。它里面显示了一些图片。我们为此定义了如下的ListModel以便使用。


            ListModel {
                id: images

                // Text
                ListElement {
                    src: "Some text to share"
                    selected: false
                    contentType: ContentType.Text
                }
                // Images
                ListElement {
                    src: "images/1.jpg"
                    selected: false
                    contentType: ContentType.Pictures
                }
                ListElement {
                    src: "images/2.jpg"
                    selected: false
                    contentType: ContentType.Pictures
                }

            ...

          }


在ListModel中,我们定义了一些相应的ListElement。每个ListElement中定义了相应的roles供我们来显示。


为了能够显示我们的GridView,我们必须有相应的delegate:


            Component {
                id: imageDelegate
                Item {
                    id: curItem
                    width: units.gu(19)
                    height: width

                    property string url: contentType === ContentType.Text ? src : Qt.resolvedUrl(src)
                    property string preview: root.__getImageFromItem(images.get(index))
                    property bool selected: selected

                    UbuntuShape {
                        height: parent.width
                        width: height
                        image: Image {
                            id: image
                            source: preview
                            height: parent.width
                            width: height
                            sourceSize.height: height
                            sourceSize.width: width
                            fillMode: Image.PreserveAspectFit
                            smooth: true
                        }
                        Image {
                            id: selectionTick
                            anchors.right: parent.right
                            anchors.top: parent.top
                            width: units.gu(5)
                            height: units.gu(5)
                            visible: curItem.selected && root.pickMode
                            source: "photo-preview-selected-overlay.png"
                        }

                    }

                    MouseArea {
                        anchors.fill: parent
                        enabled: root.pickMode
                        onClicked: {
                            selected = !selected;
                            images.setProperty(index, "selected", selected);
                        }
                    }

                    MouseArea {
                        anchors.fill: parent
                        enabled: !root.pickMode
                        onPressAndHold: {
                            PopupUtils.open(actPopComp, emptyItemForCaller);
                        }
                        onClicked: {
                            if (!actPopComp.visible)
                            {
                                // PopupUtils.open(actPopComp, emptyItemForCaller);
                                pageStack.push(imageView, {"url": url,
                                                   "preview": preview,
                                                   "contentType": contentType});

                            }
                        }
                    }

                    /* create an empty item centered in the image to align the popover to */
                    Item {
                        id: emptyItemForCaller
                        anchors.centerIn: parent
                        z: 100

                    }

                    Component {
                        id: actPopComp
                        ActionSelectionPopover {
                            id: actPop
                            delegate: ListItem.Standard {
                                text: action.text
                            }

                            actions: ActionList {
                                Action {
                                    text: "Open with..."
                                    onTriggered: {
                                        PopupUtils.close(actPop);
                                        if (contentType === ContentType.Text) {
                                            pageStack.push(picker, {"url": src,
                                                               "handler": ContentHandler.Destination,
                                                               "contentType": contentType});
                                        } else {
                                            pageStack.push(picker, {"url": url,
                                                               "handler": ContentHandler.Destination,
                                                               "contentType": contentType});
                                        }
                                    }
                                }
                                Action {
                                    text: "Share"
                                    onTriggered: {
                                        PopupUtils.close(actPop);
                                        if (contentType === ContentType.Text) {
                                            pageStack.push(picker, {"url": src,
                                                               "handler": ContentHandler.Share,
                                                               "contentType": contentType});
                                        } else {
                                            pageStack.push(picker, {"url": url,
                                                               "handler": ContentHandler.Share,
                                                               "contentType": contentType});
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }


这里的delegate代码相比较来说,有一点多。但是,它的核心是显示一个UbuntuShape的图片:


                    UbuntuShape {
                        height: parent.width
                        width: height
                        image: Image {
                            id: image
                            source: preview
                            height: parent.width
                            width: height
                            sourceSize.height: height
                            sourceSize.width: width
                            fillMode: Image.PreserveAspectFit
                            smooth: true
                        }
                        Image {
                            id: selectionTick
                            anchors.right: parent.right
                            anchors.top: parent.top
                            width: units.gu(5)
                            height: units.gu(5)
                            visible: curItem.selected && root.pickMode
                            source: "photo-preview-selected-overlay.png"
                        }

                    }



当我们点击它时,在作为exporter时,也即pickMode为真时:


  property bool pickMode: (root.activeTransfer.state === ContentTransfer.InProgress &&
                           root.activeTransfer.direction === ContentTransfer.Import)

                    MouseArea {
                        anchors.fill: parent
                        enabled: root.pickMode
                        onClicked: {
                            selected = !selected;
                            images.setProperty(index, "selected", selected);
                        }
                    }


UbuntuShape中显示的图片为:




右上角出现一个折起来的角,表明被选中。我们可以选择“Select”按钮,并实现把图片传人到importer的应用中。


当我们的应不处于pickMode时,我们点击图片:


                    MouseArea {
                        anchors.fill: parent
                        enabled: !root.pickMode
                        onPressAndHold: {
                            PopupUtils.open(actPopComp, emptyItemForCaller);
                        }
                        onClicked: {
                            if (!actPopComp.visible)
                            {
                                // PopupUtils.open(actPopComp, emptyItemForCaller);
                                pageStack.push(imageView, {"url": url,
                                                   "preview": preview,
                                                   "contentType": contentType});

                            }
                        }
                    }

如果长按图片,就会出现一个Context Menu的菜单,并传人照片到指定的应用目录中或分享。




如果只是点击,就会出现如下的画面:




照片被打开并显示于屏幕上,同时,在应用的标题上,我们可以点击“+”来选择应用来传人照片及一个分享的图标。这是通过如下的imageView来实现的:


        Page {
            id: imageView
            visible: false
            property var url
            property var contentType
            title: i18n.tr("Image detais")
            property alias preview: image.source

            tools: ToolbarItems {
                ToolbarButton {
                    action: Action {
                        text: "Open with..."
                        iconName: "add"
                        onTriggered: {
                            pageStack.push(picker, {"url": imageView.url, "handler": ContentHandler.Destination, "contentType": imageView.contentType });
                        }
                    }
                }
                ToolbarButton {
                    action: Action {
                        text: "Share"
                        iconName: "contact-group"
                        onTriggered: {
                            pageStack.push(picker, {"url": imageView.url, "handler": ContentHandler.Share, "contentType": imageView.contentType });
                        }
                    }
                }
            }

            Image {
                id: image
                anchors.fill: parent
                sourceSize.height: height
                sourceSize.width: width
            }
        }


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


同时,为了配合我们的测试,我也设计了我们的contenthub-importer应用:

git clone https://gitcafe.com/ubuntu/contenthub-importer.git


contenthub importer 的应用界面:




我们通过点击位于contenthub-importer位置中的“import”按钮,就可以把我们想要的图片导入到我们的应用(contenthub-importer)中了。



作者:UbuntuTouch 发表于2015/8/19 16:59:33 原文链接
阅读:142 评论:0 查看评论

Read more
UbuntuTouch

在前面的文章中,我们实现了如何使用Content Hub来import我们的图片等。在今天的例程中,我们来介绍如何使用ContentHub来import我们所需要的电话本中的contact。电话本Contacts的许多的API可以在地址http://developer.ubuntu.com/api/apps/qml/sdk-15.04/QtContacts/找到。


为了说明问题,在今天的应用中,我们也使用了特殊的policy contacts。这个policy是为了能够在我们的应用中显示已有的电话本信息,虽然我们在使用ContentHub import时并不需要。contacts目前是受限制的policy。在运行我们的应用时,我们可以强制让它在手机上执行。


        ContactModel {
            id: model
            autoUpdate: true
        }

        Component.onCompleted:  {
            var list = model.availableManagers;

            console.log("The " + list.length + " ContactModel managers are: ");
            for (var item in list ) {
                console.log("manager[ " + item + " ]: " + list[item] );
            }
        }

我们可以通过上面的方法来得到所有已经在手机中有的manager。


qml: The 3 ContactModel managers are: 
qml: manager[ 0 ]: invalid
qml: manager[ 1 ]: galera
qml: manager[ 2 ]: memory

目前在我们的手机中,我们有以上的显示的managers。这里的galera是我们的手机电话本所需要的manager。如果需要读取手机中的电话本,我们可以需要用到这个。memory是存在于手机内存中的。我们可以通过它来import一个.vcf格式的电话本。这样,我们可以轻松地通过ContactModel来翻译解析我们的数据。


为了显示galera中的数据,我们使用如下的代码:


        ContactModel {
            id: phoneContact
            autoUpdate: true

            manager: "galera"
        }

       ...

        ListView {
            id: contactView
            height: parent.height *.45

            anchors {
                left: parent.left
                right: parent.right
                top: parent.top
            }

            model: phoneContact

            delegate: ListItem.Subtitled {
                text: contact.name.firstName
                subText: contact.phoneNumber.number
            }
        }


为了显示我们我们import过来的.vcf格式的contact,我们使用如下的方法:


        ContactModel {
            id: contactModel
            autoUpdate: true

            manager: "memory"
        }

        ...

        ListView {
            id: importedView
            height: parent.height - contactView.height -
                    buttons.height - importedText.height

            anchors {
                left: parent.left
                right: parent.right
                top: importedText.bottom
                bottom: buttons.bottom
            }

            delegate: ListItem.Subtitled {
                text: contact.name.firstName
                subText: contact.phoneNumber.number
            }
        }

       ...

        Connections {
            target: root.activeTransfer ? root.activeTransfer : null
            onStateChanged: {
                if (root.activeTransfer.state === ContentTransfer.Charged) {
                    importItems = root.activeTransfer.items;

                    console.log("length: " + importItems.length);

                    for ( var i = 0; i < importItems.length; i ++ ) {
                        console.log("imported url: " + importItems[i].url);
                        console.log("imported text: " + importItems[i].text);
                    }

                    contactModel.importContacts(importItems[0].url, ["Sync"])

                    // Now dump the data
                    var contacts = contactModel.contacts;

                    console.log("length: " + contacts.length );
                    for ( var contact in contacts ) {
                        console.log("contact[ " + contact + "]: " + contacts[contact].name);
                    }

                    importedView.model = contactModel;

                }
            }
        }


我们可以通过动态设置我们的ListView model的方法来显示我们从ContentHub import过来的contacts。


运行我们的代码:


  


  


所有的源码在: git clone https://gitcafe.com/ubuntu/contact-importer.git

作者:UbuntuTouch 发表于2015/8/25 11:57:56 原文链接
阅读:124 评论:0 查看评论

Read more
UbuntuTouch

对于一些应用场景来说,TCP/IP连接是唯一的一种通讯的协议。对于我们的QML应用来说,我们可以使用WebSocket来建立一个双工的(full-duplex)的TCP/IP连接。在今天的例程中,我们将来介绍如何使用WebSocket来建立这种连接,并实现通信。


首先,我们得import我们需要的模块:

import Qt.WebSockets 1.0

然后,我们使用它:


import QtQuick 2.0
import Ubuntu.Components 1.1
import QtQuick.Layouts 1.1
import Qt.WebSockets 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: "websocket.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)

    function appendMessage(msg) {
        var length = output.length;
        output.insert(length, msg + "\r\n");
    }

    Page {
        id: page
        title: i18n.tr("websocket")

        WebSocket {
            id: socket
            url: input.text
            onTextMessageReceived: {
                console.log("something is received!");
                appendMessage("received: " + message);
            }

            onStatusChanged: if (socket.status == WebSocket.Error) {
                                 console.log("Error: " + socket.errorString)
                             } else if (socket.status == WebSocket.Open) {
                                 appendMessage("sending \"Hello world\"");
                                 socket.sendTextMessage("Hello World")
                             } else if (socket.status == WebSocket.Closed) {
                                 appendMessage("Socket closed");
                             }
            active: true
        }

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

            RowLayout {
                id: top
                width: parent.width

                TextField {
                    id: input
                     Layout.minimumWidth: page.width *.7
                    text: "ws://echo.websocket.org"
                }

                Button {
                    id: get
                    text: "Get"
                    onClicked: {
                        socket.active = true
                        socket.sendTextMessage("Nice to meet you!")
                    }
                }
            }

            TextArea {
                id: output
                width: parent.width
                height: page.height - top.height - units.gu(1)
            }
        }
    }
}

在上面的代码中:


       WebSocket {
            id: socket
            url: input.text
            onTextMessageReceived: {
                console.log("something is received!");
                appendMessage("received: " + message);
            }

            onStatusChanged: if (socket.status == WebSocket.Error) {
                                 console.log("Error: " + socket.errorString)
                             } else if (socket.status == WebSocket.Open) {
                                 appendMessage("sending \"Hello world\"");
                                 socket.sendTextMessage("Hello World")
                             } else if (socket.status == WebSocket.Closed) {
                                 appendMessage("Socket closed");
                             }
            active: true
        }

我们从input.text中得到url。当active为真时,建立起Socket的连接。我们可以在onStatusChhanged中得到这个变化。当我们把active设为假时,安全套接字将被自动断开。在例程中,我们使用了一个公共的网站


ws://echo.websocket.org


每当我们向这个地址发送信息时,就会得到和发送信息一模一样的信息(是一个echo服务器)。我们可以通过onTextMessageReceived来得到这个信息。


运行我们的应用:




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

作者:UbuntuTouch 发表于2015/8/26 13:58:13 原文链接
阅读:148 评论:0 查看评论

Read more
UbuntuTouch

[原]如何得到Ubuntu手机上的IP地址

在这篇文章中,我们介绍如何在Ubuntu QML应用中得到手机上的IP地址。


我们在我们的main.cpp中加入一些代码来得到IP地址:


main.cpp


#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QNetworkInterface>
#include <QQmlContext>

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

    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    QStringList datalist;

    for(int nIter = 0; nIter < list.count(); nIter++) {
        qDebug() << list[ nIter ].toString();
        datalist.append(list[ nIter ].toString());
    }

    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:///Main.qml")));
    view.setResizeMode(QQuickView::SizeRootObjectToView);

    QQmlContext *ctxt = view.rootContext();
    ctxt->setContextProperty("myModel", QVariant::fromValue(datalist));

    view.show();
    return app.exec();
}


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: "ipaddress.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("ipaddress")

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

            Label {
                id: label
                objectName: "label"
                fontSize: "large"

                text: i18n.tr("IP addresses: ")
            }

            ListView {
                width: parent.width
                height: parent.height - label.height
                model: myModel
                delegate: Text {
                    text: modelData
                }
            }

        }
    }
}


运行我们的应用:



这是我们在利用wifi时显示的IP地址。

我们的源码在: git clone https://gitcafe.com/ubuntu/ipaddress.git


作者:UbuntuTouch 发表于2015/8/26 15:05:55 原文链接
阅读:202 评论:0 查看评论

Read more
UbuntuTouch

[原]如何在Ubuntu平台上使用Bluetooth

截止到目前为止,在Ubuntu平台上,Bluetooth还没有被正式支持。对于一些hacker级的开发者来说,可能等不急。我们在这里提供一个方法让大家来尝尝鲜。最终的平台会支持Bluetooth。而且相应的库也将会安装到平台之中去。


为了能够在手机中运行Bluetooth的应用,我们必须做如下的动做:




上面的句子:

$ sudo mount -o rw,remount /

是为了让我们的image变为可读写的状态,以便我们在下面安装我们的Qt Bluetooth库

我们通过命令:


$ sudo apt-get install qml-module-qtbluetooth


来安装相应的Qt Bluetooth的库。


由于目前Bluetooth没有被完全支持,所以没有相应的security policy和它对应。在我们的测试应用中,我们必须适应“unconfined” template:


bluetooth.apparmor

{
    "policy_groups": [
        "networking",
        "webview",
        "connectivity"
    ],
    "policy_version": 1.3,
    "template": "unconfined"
}


Scanner.qml


import QtQuick 2.0
import QtBluetooth 5.2

Item {
    id: top

    property BluetoothService currentService

    BluetoothDiscoveryModel {
        id: btModel
        running: true
        discoveryMode: BluetoothDiscoveryModel.DeviceDiscovery
        onDiscoveryModeChanged: console.log("Discovery mode: " + discoveryMode)
        onServiceDiscovered: console.log("Found new service " + service.deviceAddress + " " + service.deviceName + " " + service.serviceName);
        onDeviceDiscovered: console.log("New device: " + device)
        onErrorChanged: {
                switch (btModel.error) {
                case BluetoothDiscoveryModel.PoweredOffError:
                    console.log("Error: Bluetooth device not turned on"); break;
                case BluetoothDiscoveryModel.InputOutputError:
                    console.log("Error: Bluetooth I/O Error"); break;
                case BluetoothDiscoveryModel.InvalidBluetoothAdapterError:
                    console.log("Error: Invalid Bluetooth Adapter Error"); break;
                case BluetoothDiscoveryModel.NoError:
                    break;
                default:
                    console.log("Error: Unknown Error"); break;
                }
        }
   }

    Rectangle {
        id: busy

        width: top.width * 0.7;
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: top.top;
        height: text.height*1.2;
        radius: 5
        color: "#1c56f3"
        visible: btModel.running

        Text {
            id: text
            text: "Scanning"
            font.bold: true
            font.pointSize: 20
            anchors.centerIn: parent
        }

        SequentialAnimation on color {
            id: busyThrobber
            ColorAnimation { easing.type: Easing.InOutSine; from: "#1c56f3"; to: "white"; duration: 1000; }
            ColorAnimation { easing.type: Easing.InOutSine; to: "#1c56f3"; from: "white"; duration: 1000 }
            loops: Animation.Infinite
        }
    }

    ListView {
        id: mainList
        width: top.width
        anchors.top: busy.bottom
        anchors.bottom: buttonGroup.top
        anchors.bottomMargin: 10
        anchors.topMargin: 10
        clip: true

        model: btModel
        delegate: Rectangle {
            id: btDelegate
            width: parent.width
            height: column.height + 10

            property bool expended: false;
            clip: true
            Image {
                id: bticon
                source: "images/default.png";
                width: bttext.height;
                height: bttext.height;
                anchors.top: parent.top
                anchors.left: parent.left
                anchors.margins: 5
            }

            Column {
                id: column
                anchors.left: bticon.right
                anchors.leftMargin: 5
                Text {
                    id: bttext
                    text: deviceName ? deviceName : name
                    font.family: "FreeSerif"
                    font.pointSize: 16
                }

                Text {
                    id: details
                    function get_details(s) {
                        if (btModel.discoveryMode == BluetoothDiscoveryModel.DeviceDiscovery) {
                            //We are doing a device discovery
                            var str = "Address: " + remoteAddress;
                            return str;
                        } else {
                            var str = "Address: " + s.deviceAddress;
                            if (s.serviceName) { str += "<br>Service: " + s.serviceName; }
                            if (s.serviceDescription) { str += "<br>Description: " + s.serviceDescription; }
                            if (s.serviceProtocol) { str += "<br>Protocol: " + s.serviceProtocol; }
                            return str;
                        }
                    }
                    visible: opacity !== 0
                    opacity: btDelegate.expended ? 1 : 0.0
                    text: get_details(service)
                    font.family: "FreeSerif"
                    font.pointSize: 14
                    Behavior on opacity {
                        NumberAnimation { duration: 200}
                    }
                }
            }
            Behavior on height { NumberAnimation { duration: 200} }

            MouseArea {
                anchors.fill: parent
                onClicked: btDelegate.expended = !btDelegate.expended
            }
        }
        focus: true
    }

    Row {
        id: buttonGroup
        property var activeButton: devButton

        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottomMargin: 5
        spacing: 10

        Button {
            id: fdButton
            width: top.width/3*0.9
            //mdButton has longest text
            height: mdButton.height
            text: "Full Discovery"
            onClicked: btModel.discoveryMode = BluetoothDiscoveryModel.FullServiceDiscovery
        }
        Button {
            id: mdButton
            width: top.width/3*0.9
            text: "Minimal Discovery"
            onClicked: btModel.discoveryMode = BluetoothDiscoveryModel.MinimalServiceDiscovery
        }
        Button {
            id: devButton
            width: top.width/3*0.9
            //mdButton has longest text
            height: mdButton.height
            text: "Device Discovery"
            onClicked: btModel.discoveryMode = BluetoothDiscoveryModel.DeviceDiscovery
        }
    }

}


关于BluetoothDiscoveryModel的介绍,开发者可以直接参阅Qt文档做更一步的认识。


运行我们的应用:


  


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

作者:UbuntuTouch 发表于2015/8/27 10:40:20 原文链接
阅读:39 评论:0 查看评论

Read more
UbuntuTouch

[原]如何固定你的Ubuntu应用的方向

在这篇文章中,我们将介绍如何固定一个Ubuntu应用的方向。固定应用的方向对有些游戏应用来说,非常有用。这样可以让游戏专注于一个方向的布局,比如开车的游戏!


在Ubuntu应用中,我们可以通过如下的flag:


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

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "usermetrics.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)
   ...
}

这里有一个automaticOrientation标志位。由于一些原因,目前还是不能正常工作,虽然我们可以设置它为false。


目前我发现一个更加简单的办法,就是直接修改项目的.desktop文件:


[Desktop Entry]
Name=fixedorientationapp
Exec=qmlscene $@ Main.qml
Icon=fixedorientationapp.png
Terminal=false
Type=Application
X-Ubuntu-Touch=true
X-Ubuntu-Supported-Orientations=landscape


我们在上面添加了X-Ubuntu-Supported-Orientations=landscape。这样我们的应用就只有在landscape模式下。


当我们把项目的.desktop文件修改为:


[Desktop Entry]
Name=fixedorientationapp
Exec=qmlscene $@ Main.qml
Icon=fixedorientationapp.png
Terminal=false
Type=Application
X-Ubuntu-Touch=true
X-Ubuntu-Supported-Orientations=portrait

当我们运行我们的应用时,我们的应用只有在portrait模式下运行。不可以更改。



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



作者:UbuntuTouch 发表于2015/8/28 16:20:53 原文链接
阅读:121 评论:0 查看评论

Read more
UbuntuTouch

我们可以通过UserMetric的API发布消息到我们手机的欢迎页面(手机的锁屏界面)。在锁屏界面中,我们可以双击中间的圆圈,来循环播放我们手机发布的消息。如下图所示,

我们发布了“Useermetric messages received: 4”个消息。




我们需要使用的模块是:

import UserMetrics 0.1

我们可以使用如下的方法得到它所有的API:


liuxg@liuxg:~$ qmlplugindump import UserMetrics 0.1
QQmlComponent: Component is not ready
liuxg@liuxg:~$ qmlplugindump  UserMetrics 0.1
import QtQuick.tooling 1.1

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

Module {
    Component {
        name: "Metric"
        prototype: "QObject"
        exports: ["Metric 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "name"; type: "string" }
        Property { name: "format"; type: "string" }
        Property { name: "emptyFormat"; type: "string" }
        Property { name: "domain"; type: "string" }
        Property { name: "minimum"; type: "double" }
        Property { name: "maximum"; type: "double" }
        Method {
            name: "increment"
            Parameter { name: "amount"; type: "double" }
        }
        Method { name: "increment" }
        Method {
            name: "update"
            Parameter { name: "value"; type: "double" }
        }
    }
}


我们的应用非常简单:


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1
import UserMetrics 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: "usermetrics.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("usermetrics")

        Metric {
            id: incomingMessagesMetric
            name: "usermetric"
            format: i18n.tr("Usermetric messages received today: %1")
            emptyFormat: i18n.tr("No Usermetric messages received today.")
            domain: applicationName
        }

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

            Button {
                text: i18n.tr("Increase")

                onClicked: {
                    console.log("Going to increase the metric!");
                    incomingMessagesMetric.increment()
                }
            }

            Button {
                text: i18n.tr("Decrease")

                onClicked: {
                    console.log("Going to increase the metric bye 2!");
                    incomingMessagesMetric.increment(-2)
                }
            }

            Button {
                text: i18n.tr("Decrease")

                onClicked: {
                    console.log("Going to update to 3 to the metric!");
                    incomingMessagesMetric.update(3)
                }
            }

        }
    }
}


为了使用UserMetric,我们必须在我们的应用中使用usermetrics security policy:

usermetrics.apparmor


{
    "policy_groups": [
        "networking",
        "webview",
        "usermetrics"
    ],
    "policy_version": 1.3
}


运行我们的应用:




每当我们点击increase按钮后,我们的欢迎界面数据将增加一个:




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


作者:UbuntuTouch 发表于2015/8/28 16:51:41 原文链接
阅读:140 评论:0 查看评论

Read more
UbuntuTouch

[原]利用Qt.locale显示本地化数据

我们知道对于一些应用来说,我们可以根据语言的选择来显示不同的数据格式,比如时间,金钱等。在今天的例程中,我们来展示如何Qt.locale根据不同的语言选择来帮助我们显示不同格式的数据。


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: "locale.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("Locale")

        Rectangle {
            id: root
            anchors.fill: parent
            color: "lightgray"

            property string locale: view.currentItem.locale

            Text {
                id: title
                text: "Select locale:"
            }

            Rectangle {
                id: chooser
                anchors.top: title.bottom
                anchors.topMargin: 5
                width: parent.width-10
                x: 5
                height: parent.height/2 - 10
                color: "#40300030"
                ListView {
                    id: view
                    clip: true
                    focus: true
                    anchors.fill: parent
                    model: [
                        "en_US",
                        "en_GB",
                        "fi_FI",
                        "de_DE",
                        "ar_SA",
                        "hi_IN",
                        "zh_CN",
                        "th_TH",
                        "fr_FR",
                        "nb_NO",
                        "sv_SE"
                    ]
                    delegate: Text {
                        property string locale: modelData
                        height: units.gu(3)
                        width: view.width
                        text: Qt.locale(modelData).name + " ("+ Qt.locale(modelData).nativeCountryName + "/" + Qt.locale(modelData).nativeLanguageName + ")"
                        MouseArea {
                            anchors.fill: parent
                            onClicked: view.currentIndex = index

                        }
                    }
                    highlight: Rectangle {
                        height: 30
                        color: "#60300030"
                    }
                }
            }

            Rectangle {
                color: "white"
                anchors.top: chooser.bottom
                anchors.topMargin: 5
                anchors.bottom: parent.bottom
                anchors.bottomMargin: 5
                x: 5; width: parent.width - 10

                Column {
                    anchors.fill: parent
                    spacing: 5
                    Text {
                        property var date: new Date()
                        text: "Date: " + date.toLocaleDateString(Qt.locale(root.locale))
                    }
                    Text {
                        property var date: new Date()
                        text: "Time: " + date.toLocaleTimeString(Qt.locale(root.locale))
                    }
                    Text {
                        property var dow: Qt.locale(root.locale).firstDayOfWeek
                        text: "First day of week: " + Qt.locale(root.locale).standaloneDayName(dow)
                    }
                    Text {
                        property var num: 10023823
                        text: "Number: " + num.toLocaleString(Qt.locale(root.locale))
                    }
                    Text {
                        property var num: 10023823
                        text: "Currency: " + num.toLocaleCurrencyString(Qt.locale(root.locale))
                    }
                }
            }
        }
    }
}


在例程中,注意下面的写法:


delegate: Text {
                        property string locale: modelData
                        height: units.gu(3)
                        width: view.width
                        text: Qt.locale(modelData).name + " ("+ Qt.locale(modelData).nativeCountryName + "/" + Qt.locale(modelData).nativeLanguageName + ")"
                        MouseArea {
                            anchors.fill: parent
                            onClicked: view.currentIndex = index

                        }
                    }

我们可以通过定义一个property locale来得到当前的Item的modelData。这样在delegate的外面,我们通过


 property string locale: view.currentItem.locale

来得到当前列表项中的modelData,进而在程序的其它部分进行引用!

  


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



作者:UbuntuTouch 发表于2015/8/31 15:12:33 原文链接
阅读:185 评论:0 查看评论

Read more