Canonical Voices

Daniel Holbach

Ubuntu 16.04 LTS is out of the door, we started work on the Yakkety Yak already, now it’s time for the Ubuntu Online Summit. It’s all happening 3-5 May 14-20 UTC. This is where we discuss upcoming features, get feedback and demo all the good work which happened recently.

If you want to join the event, just head to the registration page and check out the UOS 16.05 schedule afterwards. You can star (☆) sessions and mark them as important to you and thus plan your attendance for the event.

Now let’s take a look on the bits which are in one way or another related to Ubuntu Core at UOS:

  • Snappy Ubuntu 16 – what’s new
    16.04 has landed and with it came big changes in the world of snapd and friends. Some of them are still in the process of landing, so you’re in for more goodness coming down the pipe for Ubuntu 16.04 LTS.
  • The Snapcraft roadmap
    Publishing software through snaps is super easy and snapcraft is the tool to use for this. Let’s take a look at the roadmap together and see which exciting features are going to come up next.
  • Snappy interfaces
    Interfaces in Ubuntu Core allow snaps to communicate or share resources, so it’s important we figure out how interfaces work, which ones we’d like to implement next and which open questions there are.
  • Playpen – Snapping software together
    Some weeks ago the Community team set up a small branch in which we collaborated on snapping software. It was good fun, we worked on things together, learnt from each other and quickly worked out common issues. We’d like to extend the project and get more people involved. Let’s discuss the project and workflow together.
  • How to snap your software
    If you wanted to start snapping software (yours or somebody else’s) and wanted to see a presentation of snapcraft and a few demos, this is exactly the session you’ve been looking for.
  • Snappy docs – next steps
    Snappy and snapcraft docs are luckily being written by the developers as part of the development process, but we should take a look at the docs together again and see what we’re missing, no matter if it’s updates, more coherence, more examples or whatever else.
  • Demo: Snaps on the desktop
    Here’s the demo on how to get yourself set up as a user or developer of snaps on your regular Ubuntu desktop.

I’m looking forward to see you in all these sessions!

Read more
Prakash

Tripping valuations of unicorns might have set a dull tone for startups this year, but many industry experts believe that companies that are doing things “the right way” will continue to secure funding. Companies like Big Basket, Practo, Car Dekho, MSwipe and Urban Ladder have continued to show progress in their respective business models in spite of a weak environment.

Prakash AdvaniCanonical’s Regional Director, Sales & Alliances – India & South East Asia said that companies should raise capital as the last resort. If they can quickly convert their ideas into a profitable business then they don’t need to give up on equity.

Read More: https://www.entrepreneur.com/article/274986

Read more
UbuntuTouch

在最新的Scope Widget中,有一个新的Content Sharing Widget.我们可以利用这个Widget来分享我们的图片到信息,Facebook,Twitter等渠道.比如,在我们的Scope Preview中,点击图片上的分享按钮,就可以把我们的内容分享出去.


  


 

它的设计也非常简单明了.

我们还是从我们已经做过的练习中下载代码:

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

1)添加新的图片资源

query.cpp

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

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

    r["comment_icon"] = icons_[3].toStdString();
    r["share_icon"] = icons_[4].toStdString();
    r["share_pic"] = icons_[5].toStdString();

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

    r["musicSource"] = "http://qqmp3.djwma.com/mp3/魔音神据极品私货这锯子拉的耳膜都要碎了.mp3";
    r["videoSource"] = "http://techslides.com/demos/sample-videos/small.mp4";
    r["screenshot"] = icons_[2].toStdString();

    // add an array to show the gallary of it
    sc::VariantArray arr;

    for(const auto &datum : icons_) {
        arr.push_back(Variant(datum.toStdString()));
    }

    r["array"] = sc::Variant(arr);

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

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

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

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

    r["comment_icon"] = icons_[3].toStdString();
    r["share_icon"] = icons_[4].toStdString();
    r["share_pic"] = icons_[5].toStdString();

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

    r["musicSource"] = "";
    r["videoSource"] = "";
    r["screenshot"] = "";
    // This is to ensure that the preview can well for the grid/carousel/lists...
    VariantArray arr;
    r["array"] = sc::Variant(arr);;

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

在上面我们添加了两个新的图片:

    r["share_icon"] = icons_[4].toStdString();
    r["share_pic"] = icons_[5].toStdString();

注意这里的"share_icon"是在Preview窗口中显示的图片.它可以和我们实际分享的照片是不一样的.在"share_pic"中真正定义的是我们想要分享的图片.在实际的应用中,被分享的图片也可以是一个数组.请开发者自己参考我们的文档来实践.


2)在Preview中添加我们的Widget

preview.cpp

void Preview::run(sc::PreviewReplyProxy const& reply) {
    Result result = PreviewQueryBase::result();

    ColumnLayout layout1col(1);
    std::vector<std::string> ids = { "image", "header", "summary", "tracks",
                                    "videos", "gallery_header", "gallerys", "reviews", "exp",
                                     "review_input", "rating_input", "inputId", "img" };
//    std::vector<std::string> ids = { "inputId", "img" };

    PreviewWidgetList widgets;

    ColumnLayout layout2col(2);
    layout2col.add_column(ids);
    layout2col.add_column({});

    ColumnLayout layout3col(3);
    layout3col.add_column(ids);
    layout3col.add_column({});
    layout3col.add_column({});

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

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

    // Define the summary section
    sc::PreviewWidget description("summary", "text");
    // It has a text property, mapped to the result's description property
    description.add_attribute_mapping("text", "description");
    widgets.emplace_back(description);

    PreviewWidget listen("tracks", "audio");
    {
        VariantBuilder builder;
        builder.add_tuple({
            {"title", Variant("This is the song title")},
            {"source", Variant(result["musicSource"].get_string().c_str())}
        });
        listen.add_attribute_value("tracks", builder.end());
    }

    PreviewWidget video("videos", "video");
    video.add_attribute_value("source", Variant(result["videoSource"].get_string().c_str()));
    video.add_attribute_value("screenshot", Variant(result["screenshot"].get_string().c_str()));

    PreviewWidget header_gal("gallery_header", "header");
    header_gal.add_attribute_value("title", Variant("Gallery files are:"));

    PreviewWidget gallery("gallerys", "gallery");
    gallery.add_attribute_mapping("sources", "array");

    if ( result["musicSource"].get_string().length() != 0 ) {
        widgets.emplace_back(listen);
    }

    if( result["videoSource"].get_string().length() != 0 ) {
        widgets.emplace_back(video);
    }

    if( result["array"].get_array().size() != 0 ) {
        widgets.emplace_back(header_gal);
        widgets.emplace_back(gallery);
    }

    // The following shows the review
    PreviewWidget review("reviews", "reviews");
    VariantBuilder builder;
    builder.add_tuple({
                          {"author", Variant("John Doe")},
                          {"review", Variant("very good")},
                          {"rating", Variant(3.5)}
                      });
    builder.add_tuple({
                          {"author", Variant("Mr. Smith")},
                          {"review", Variant("very poor")},
                          {"rating", Variant(5)}
                      });
    review.add_attribute_value("reviews", builder.end());
    widgets.emplace_back(review);

    // The following shows the expandable
    PreviewWidget expandable("exp", "expandable");
    expandable.add_attribute_value("title", Variant("This is an expandable widget"));
    expandable.add_attribute_value("collapsed-widgets", Variant(1));
    PreviewWidget w1("w1", "text");
    w1.add_attribute_value("title", Variant("Subwidget 1"));
    w1.add_attribute_value("text", Variant("A text"));
    PreviewWidget w2("w2", "text");
    w2.add_attribute_value("title", Variant("Subwidget 2"));
    w2.add_attribute_value("text", Variant("A text"));
    expandable.add_widget(w1);
    expandable.add_widget(w2);
    widgets.emplace_back(expandable);

    // The following shows a review rating-input
    PreviewWidget w_review("review_input", "rating-input");
    w_review.add_attribute_value("submit-label", Variant("Send"));
    w_review.add_attribute_value("visible", Variant("review"));
    w_review.add_attribute_value("required", Variant("review"));
    std::string reply_label = "Reply";
    std::string max_chars_label = "140 characters max";
    w_review.add_attribute_value("review-label", Variant(reply_label + ": " + max_chars_label));
    widgets.emplace_back(w_review);

    // The follwing shows a rating rating-input
    PreviewWidget w_rating("rating_input", "rating-input");
    w_rating.add_attribute_value("visible", Variant("rating"));
    w_rating.add_attribute_value("required", Variant("rating"));
    w_rating.add_attribute_value("rating-label", Variant("Please rate this"));
    widgets.emplace_back(w_rating);

    PreviewWidget w_image("img", "image");
    w_image.add_attribute_value("source", Variant(result["share_icon"].get_string().c_str()));
    VariantMap share_data;
//    share_data["uri"] = Variant("http://img2.imgtn.bdimg.com/it/u=442803940,143587648&fm=21&gp=0.jpg");
    share_data["uri"] = Variant(result["share_pic"].get_string().c_str());
    share_data["content-type"] = Variant("pictures");
    w_image.add_attribute_value("share-data",  sc::Variant(share_data));
    widgets.emplace_back(w_image);

    PreviewWidget w_commentInput("inputId", "comment-input");
    w_commentInput.add_attribute_value("submit-label", Variant("Post"));
    widgets.emplace_back(w_commentInput);

    // In the following, fake some comments data
    QList<Comment> comment_list;
    std::string comment_str = "Comment ";
    for(int i = 0; i < 3;  i++) {
        Comment comment;

        comment.id         = 1.0;
        comment.publishTime = "2015-3-18";
        comment.text = comment_str;
        comment.text += std::to_string(i+1);
        comment_list.append(comment);
    }

    int index = 0;
    Q_FOREACH(const auto & comment, comment_list) {
        std::string id = "commentId_" + std::to_string(index++);
        ids.emplace_back(id);

        PreviewWidget w_comment(id, "comment");
        w_comment.add_attribute_value("comment", Variant(comment.text));
        w_comment.add_attribute_value("author", Variant("Author"));
        w_comment.add_attribute_value("source", Variant(result["comment_icon"].get_string().c_str()));
        w_comment.add_attribute_value("subtitle", Variant(comment.publishTime));
        widgets.emplace_back(w_comment);
    }

    layout1col.add_column(ids);
    reply->register_layout({layout1col, layout2col, layout3col});
    reply->push( widgets );
}

在上面的代码中,我们添加了

    PreviewWidget w_image("img", "image");
    w_image.add_attribute_value("source", Variant(result["share_icon"].get_string().c_str()));
    VariantMap share_data;
//    share_data["uri"] = Variant("http://img2.imgtn.bdimg.com/it/u=442803940,143587648&fm=21&gp=0.jpg");
    share_data["uri"] = Variant(result["share_pic"].get_string().c_str());
    share_data["content-type"] = Variant("pictures");
    w_image.add_attribute_value("share-data",  sc::Variant(share_data));
    widgets.emplace_back(w_image);

同时,我们也在下面的Ids中添加了新增加的img ID:

    std::vector<std::string> ids = { "image", "header", "summary", "tracks",
                                    "videos", "gallery_header", "gallerys", "reviews", "exp",
                                     "review_input", "rating_input", "inputId", "img" };

通过这样的方法,当我们的照片被显示时,在图片的左下方就会出现一个分享的按钮.



点击我们的按钮就可以把我们在上面"share_pic"中定义的照片分享出去.这个照片也可以是网路上的一张照片.


作者:UbuntuTouch 发表于2016/3/30 8:25:59 原文链接
阅读:551 评论:0 查看评论

Read more
UbuntuTouch

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

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

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


1)创建一个最小的shared库


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

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

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

Student.h

#include<string>

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

student.cpp

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

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

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

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

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

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

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

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

 

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


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


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



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

mytype.h


#ifndef MYTYPE_H
#define MYTYPE_H

#include <QObject>

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

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

Q_SIGNALS:
    void helloWorldChanged();

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

    QString m_message;
};

#endif // MYTYPE_H

mytype.cpp

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

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

}

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

MyType::~MyType() {

}

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

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

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

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

libs.pro

TEMPLATE = lib

load(ubuntu-click)

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

OTHER_FILES = filetypes

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

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

INSTALLS += other_files

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

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

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

SUBDIRS += testplugin \
           backend/Testplugin \
           libs

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

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

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

Main.qml

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

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

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



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

$ adb shell

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

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

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




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



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

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

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


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

Read more
UbuntuTouch

我刚看了一下AbstractButton.这个API提供了一个最基本的供我们写我们的自己按钮的功能,比如它有clicked, pressAndHold信号供我们使用,同时也提供了一个最基本的一下属性,比如hover及触觉功能等.在最新的API中,它也提供了一个最新的功能:


sensingMargins
sensingMargins.left : real
sensingMargins.right : real
sensingMargins.top : real
sensingMargins.bottom : real
sensingMargins.all : real


这个属性是为了能够使得我们定义我们按钮的触发区域,比如,我们想在延伸我们的按钮的区域使得我们可以在更大的区域对我们的按钮产生按钮事件.在下面的例程中,我们来做一个简单的实验:


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

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

        Rectangle {
            id: red
            anchors.centerIn: parent
            width: units.gu(10)
            height: units.gu(10)
            border.color: "red"

            AbstractButton {
                anchors.fill: parent
                sensingMargins {
                    left: units.gu(1)
                    top: units.gu(1)
                    bottom: units.gu(1)
                    right: units.gu(10)
                }

                onClicked: {
                    console.log("it is clicked in red")
                }
            }
        }

        Rectangle {
            id: blue
            anchors.left: red.right
            anchors.top: red.top
            border.color: "blue"
            width: units.gu(10)
            height: units.gu(10)

//            MouseArea {
//                anchors.fill: parent
//                onClicked: {
//                    console.log("it is clicked in blue")
//                }
//            }
        }
    }
}

在上面,我们定义了一个红色区域的按钮,同时我们也定义了一个蓝色的区域.在红色的按钮中,我们定义了:

 sensingMargins {
                    left: units.gu(1)
                    top: units.gu(1)
                    bottom: units.gu(1)
                    right: units.gu(10)
                }

qml: it is clicked in red
qml: it is clicked in red
qml: it is clicked in red
qml: it is clicked in red

这是因为我们在红色按钮中把它可以触碰的区域延伸到了蓝色的方框中.当然如果,我们把我们上面的代码修改为如下:

        Rectangle {
            id: blue
            anchors.left: red.right
            anchors.top: red.top
            border.color: "blue"
            width: units.gu(10)
            height: units.gu(10)

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    console.log("it is clicked in blue")
                }
            }
        }

那么当我们在蓝色区域触屏时,我们再也看不到上面的输出了.



值得指出的是,由于这个sensingMargins比较新,所以它需要最新的软件版本才能够支持.具体要求可以参阅bug

作者:UbuntuTouch 发表于2016/3/21 11:51:23 原文链接
阅读:250 评论:0 查看评论

Read more
UbuntuTouch

我们知道一个好的工具可以帮我们更好更快地开发我们的应用.在今天的文章中,我来介绍一下我最新开发的一个文件浏览器工具.这个工具可以很方便地帮我们打开我们所需要的文件,并快速地查看我们应用/Scope输出的信息,以便更方便地调试我们的应用.由于该应用使用了"unconfined"安全策略,所有不能上传到应用商店.如果开发者感兴趣的话,可以直接从我的github中下载,并安装到自己的手机中.目前应用还在完善中.如果大家有什么建议的话,欢迎提出来.我会在下一个版本中考虑进去的.


应用截图:


    


   


  




具体使用方法:


1)在目录的图片上向右滑动,进入到下一个目录中

2)在窗口中向左滑动,退回到上一个目录中

3)双击当前的文件,查看文件的内容.目前只支持.txt,.log,.conf,jpg/jpeg/png文件的查看

4)点击右上的"h"图标来查看hidden文件

5)点击右上"log"图标,只显示log文件.用于快速定位log文件.如果想看到其它格式的文件,就需要取消只显示log文件图标.

6)点击"syslog"图标,显示/var/log/syslog中的内容,供调试


对于我们开发者开发Scope来说,我们可以查看/home/phablet/.cache/upstart/scope_registry.log文件来查看我们的输出以帮助我们来更好地调试我们的Scope.


具体安装步骤,请参阅github中的"readme.txt"文件.

项目文件:https://github.com/liu-xiao-guo/filebrowser

更多想下载有趣的"unconfined"应用的开发者可以参阅网址https://open.uappexplorer.com/apps.里面也有一个文件浏览器,但是它打不开log文件等.操作起来应该没有我的这个方便 :)

作者:UbuntuTouch 发表于2016/3/21 13:29:53 原文链接
阅读:326 评论:0 查看评论

Read more
UbuntuTouch

由于一些原因,在Ubuntu手机上,只支持一个前台运行的应用.每当应用被推到后台后,就会被自动挂起(suspended),该应用将不再被运行.这也是Ubuntu手机和安卓系统不一样的地方.更确切地说,Ubuntu手机应用的设计更类似于iPhone的应用.只允许一个前台运行的应用.当然如果把手机接到一个无线鼠标和键盘,那么我们的手机会自动进入到Desktop模式,也就自动具有多任务的功能.

对于很多开发者来说,没有了多任务,就好像没有了一些创新的应用,比如,我们不可能把导航的应用推到后台就不运行了.在实际的一些应用中,能够在后台运行,也是一个非常有用的功能.在今天的文章中,我们将介绍如何实现能够在后台运行的应用.在该应用中,我们使用了"unconfined"安全策略.必须注意的一点是,这个样的应用将不能进入到Ubuntu Store.如果有可能的话,可以上传到https://open.uappexplorer.com/apps商店供一些特别需要的用户使用.


1)使用unconfined安全策略


就像我们上面提到的,为了能够访问我们一般应用访问不到的文件地址,我们必须在我们的apparmor文件中定义一些特别的安全策略:

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

有了上面unconfined定义,我们的应用就可以访问到一些我们不能访问到的资源.从某种意义上讲,已经打破了我们在Ubuntu手机上所设置的安全机制.必须注意的是这样的不明来源的应用可能对我们的手机造成伤害


2)修改应用的lifecycleException标示



一旦一个应用的这个lifecycleException标志被修改,我们的应用在被推到后台时,将不被系统所挂起(suspended).那么我们怎么修该我们的应用的这个标示呢?我们可以仿照在launchpad.net上的https://launchpad.net/tweakgeek项目.我们可以通过上面的方法来获取我们当前应用的信息,并做适当的修改.

Main.cpp

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

#include "applicationmodel.h"

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

    qmlRegisterType<ApplicationModel>("TweakGeek", 1, 0, "ApplicationModel");
    qmlRegisterUncreatableType<ApplicationItem>("TweakGeek", 1, 0, "ApplicationItem", "bla");

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

在上面我们把ApplicationModel及ApplicationItem数据类型进行登记,这样我们可以在我们的QML中运用它们.

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import TweakGeek 1.0
import QtQuick.Layouts 1.1

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

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

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

    property int index: 0
    property var myapp

    ApplicationModel {
        id: applicationModel
    }

    Timer {
        interval: 500; running: true; repeat: true
        onTriggered: {
            index ++
            console.log("index: " + index)
        }
    }

    function findMyApp() {
        var count = applicationModel.count();
        for ( var i = 0 ; i < count; i++ ) {
            var app = applicationModel.get(i)
            // console.log("app name: " + app.name)
            if ( app.name === "keeprunningapp" ) {
                // if our own app is found, make it not die
                console.log("My app setLifecycleException: " + app.lifecycleException)
                myapp = app
            }
        }
    }

    Page {
        header: PageHeader {
            title: "keeprunningapp"
        }

        Connections {
            target: Qt.application
            onActiveChanged: {
                console.log("Qt.application.active: " + Qt.application.active);
            }
        }

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

            Label {
                text: "current app is: " + Qt.application.active
                fontSize: "large"
            }

            Label {
                text: "index: " + index
                fontSize: "large"
            }

            RowLayout {
                spacing: units.gu(5)

                Label {
                    text: "Prevent suspending"
                    Layout.fillWidth: true
                    fontSize: "large"
                }

                Switch {
                    checked: myapp.lifecycleException
                    onClicked: myapp.lifecycleException = checked
                }
            }
        }
    }

    Component.onCompleted: {
        findMyApp();
    }
}

我们的实现也非常地简单.我们首先通过findMyApp来寻找到我们自己应用的实例,并在Switch中:
                Switch {
                    checked: myapp.lifecycleException
                    onClicked: myapp.lifecycleException = checked
                }
进行绑定.

在我们的应用,为了显示我们的应用是否正在运行,我们特别设计了一个定时器,每隔500毫秒,输出一行字.

    Timer {
        interval: 500; running: true; repeat: true
        onTriggered: {
            index ++
            console.log("index: " + index)
        }
    }

我们可以运行我们的应用:



当我们的应用的"Prevent suspending"开关设为"关"时,每当应用被推到后台,上面的定时器的输出就会停止,表明应用被suspended.而当我们的开关设为"开"时,每当应用被推到后台时,在我们的应用窗口还是可以看到连绵不断地输出:

qml: index: 418
qml: index: 419
qml: index: 420
qml: index: 421
qml: index: 422
qml: index: 423
qml: index: 424
qml: index: 425
qml: index: 426
qml: index: 427

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




作者:UbuntuTouch 发表于2016/3/22 14:13:17 原文链接
阅读:381 评论:0 查看评论

Read more
UbuntuTouch

我们知道对于一些手机应用来说,能够得到屏幕的尺寸信息已经屏幕的方向,那么对我们的应用的布局非常中.我们可以通过对这个事件的感知,进一步来对我们的应用的布局来重新调整.


下面我们来通过一个简单的例程来显示在Ubuntu Screen API中有那些信息.


Main.qml

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

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

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

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

    property var orientations: ["PrimaryOrientation", "PortraitOrientation",
                    "LandscapeOrientation","", "InvertedPortraitOrientation","", "","",
                    "InvertedLandscapeOrientation" ]


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

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

            Label {
                text: "desktopAvailableHeight: " + Screen.desktopAvailableHeight
            }

            Label {
                text: "devicePixelRatio: " + Screen.devicePixelRatio
            }

            Label {
                text: "desktopAvailableWidth: " + Screen.desktopAvailableWidth
            }

            Label {
                text: "height: " + Screen.height
            }

            Label {
                text: "width: " + Screen.width
            }

            Label {
                text: "name: " + Screen.name
            }

            Label {
                text: "orientation: " + orientations[Screen.orientation]
            }

            Label {
                text: "primaryOrientation: " + orientations[Screen.primaryOrientation]
            }

            Label {
                text: "orientationUpdateMask: " + Screen.orientationUpdateMask
            }

            Label {
                text: "pixelDensity: " + Screen.pixelDensity
            }
        }
    }
}

在我们的MX4手机上运行我们的应用:

    

从上面的信息中,我们可以看出来,我们的Screen API给出的所有的信息.细心的开发者将会发现这里得到的信息和我们之前在文章"如何得到屏幕和可用显示区域的大小尺寸及运用分辨率无关的编程"得到的屏幕尺寸是一样的.


作者:UbuntuTouch 发表于2016/3/22 17:08:29 原文链接
阅读:242 评论:0 查看评论

Read more
UbuntuTouch

地图在我们的生活中扮演非常重要的角色.我们可以利用Ubuntu手机中提供的位置服务得到位置信息并显示自己的位置在百度地图上.在显示位置信息时,我们必须记住一点,我们必须要进行必要的坐标转换,否则我们所显示的地图信息不是精确的.具体更多的信息可以访问"坐标转换API Web服务API".


下面我们来通过一个例程来显示如何进行坐标转换,并显示正确的地图信息.


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3
import "WebApi.js" as API
import QtPositioning 5.0

MainView {
    id:  mainScreen

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

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

    property string longitude: "116.3883"
    property string latitude: "39.9289"

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

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

            mainScreen.longitude = position.coordinate.longitude;
            mainScreen.latitude = position.coordinate.latitude;

            before.source = API.getStaticMap(longitude, latitude)

            // Do the conversion here
            API.convertCoordinates(longitude, latitude, gotConverted)
        }

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


    function gotConverted(o) {
        after.source = API.getStaticMap(o.longitude, o.latitude)
    }

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

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

            Image {
                id: before
                width: parent.width
                height: parent.height/2                

                Label {
                    text: "Before conversion"
                    fontSize: "large"
                }
            }

            Image {
                id: after
                width: parent.width
                height: parent.height/2

                Label {
                    text: "After conversion"
                    fontSize: "large"
                }
            }
        }
    }
}

在上面的代码中,我们使用:

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

            mainScreen.longitude = position.coordinate.longitude;
            mainScreen.latitude = position.coordinate.latitude;

            before.source = API.getStaticMap(longitude, latitude)

            // Do the conversion here
            API.convertCoordinates(longitude, latitude, gotConverted)
        }

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

来得到我们的当前的位置信息:经度,维度.我们可以通过百度的staticmap API接口进行显示地图信息.
在上面的代码中,我们显示两个图片,一个是没有经过转换的图片,另外一个是经过坐标转换后的地图图片.经过比对,我们会发现,经过转换过的地图信息更贴近我们的实际的位置.
运行我们的项目:



不怕大家知道,上面显示的正是我家的地址.非常精确.
整个项目的源码在:https://github.com/liu-xiao-guo/baidumap


作者:UbuntuTouch 发表于2016/3/23 14:12:58 原文链接
阅读:315 评论:0 查看评论

Read more
UbuntuTouch

在有些设计中,比如,我们想在鼠标所在的区域以外响应我们的鼠标按钮事件来消除一个popup,那么我们可以利用Ubuntu SDK中所提供的InverseMouseArea来实现这个功能.

下面我们来通过一个例子来展示如何利用InverseMouseArea来实现这个功能.

首先,我们来创建一个Popup.qml文件

Popup.qml

import QtQuick 2.4
import Ubuntu.Components 1.3

Rectangle {
    anchors.centerIn: parent
    width: parent.width/2
    height: width
    color: "darkgray"
    radius: 10

    InverseMouseArea {
       anchors.fill: parent
       acceptedButtons: Qt.LeftButton
       onPressed: parent.destroy()
    }
}


在上面的Popup定义了一个InverseMouseArea.当我们在这个Popup以外的地方进行点击的时候,那么它将自动销毁.

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

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

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

        Rectangle {
            width: parent.width
            height: parent.height/2
            border.color: "red"

            Button {
                id: button
                text: "Press me"
                onClicked: {
                    var component = Qt.createComponent("Popup.qml");
                    var obj = component.createObject(parent);
                    obj.visible = true;
                }
            }
        }
    }
}

在我们的Main.qml中,我们利用一个按钮"Press me".当它被点击的时候,动态地生产一个Popup的控件.当我们在控件上点鼠标时,没有任何事情发生,但是如果,我们在它之外的任何一个地方点击的时候,那么这个Popup就会被销毁.

运行我们的应用:

  

当然如果我们想在我们的Popup中得到鼠标的按钮信息的话,我们也可以在我们的Popup中加入MouseArea来进行捕获.

Popup.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

Rectangle {
    anchors.centerIn: parent
    width: parent.width/2
    height: width
    color: "darkgray"
    radius: 10

    MouseArea {
        anchors.fill: parent

        onClicked: {
            console.log("it is clicked inside popup")
        }
    }

    InverseMouseArea {
       anchors.fill: parent
       acceptedButtons: Qt.LeftButton
       onPressed: parent.destroy()
    }
}

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

另外如果我们想对鼠标的点击区域进行限制的话,我们可以通过利用:

sensingArea : Item

来做限制,比如下面的代码:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

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

        Rectangle {
            width: units.gu(40)
            height: units.gu(71)
            border.color: "yellow"
            border.width: units.gu(1)

            MouseArea {
                anchors.fill: parent
                onClicked: console.log("clicked on the root component")
            }

            Rectangle {
                id: blueRect
                width: units.gu(30)
                height: units.gu(51)
                anchors.centerIn: parent
                color: "blue"

                Rectangle {
                    width: units.gu(20)
                    height: units.gu(20)
                    anchors.centerIn: parent
                    color: "red"

                    InverseMouseArea {
                        anchors.fill: parent
                        sensingArea: blueRect
                        onClicked: console.log("clicked on the blue rect")
                    }
                }
            }
        }
    }
}

在上面的代码中,我们通过blueRect来限制InverseMouseArea的作用范围.也就是在蓝色和红色直接的范围才会显示:

qml: clicked on the blue rect

而在其它的任何地方点击时都会显示:

qml: clicked on the root component

我们的应用的显示如下:




更多关于这个API的阅读,请参阅连接InverseMouseArea


作者:UbuntuTouch 发表于2016/3/24 11:01:02 原文链接
阅读:269 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu屏幕尺寸及字体大小

在今天的这篇文章中,我们们来做一个应用来显示Ubuntu字体及屏幕大小.这篇文章的内容在以前的一些文章中也有提及.在这里,我把所有的内容综合到一起,做成一个应用.这样大家可以一目了然.


先贴我们的应用:


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

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

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

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

    property string fontsize: listview.currentItem.fontsize
    property var orientations: ["PrimaryOrientation", "PortraitOrientation",
        "LandscapeOrientation","", "InvertedPortraitOrientation","", "","",
        "InvertedLandscapeOrientation" ]

    Component {
        id: highlightBar
        Rectangle {
            width: 200; height: 50
            color: "#FFFF88"
            y: listview.currentItem.y;
            Behavior on y { SpringAnimation { spring: 2; damping: 0.1 } }
        }
    }

    Page {
        title: i18n.tr("Ubuntu Sizes")

        Rectangle {
            id: rect
            anchors.fill: parent

            Component.onCompleted: {
                client.text = "client area width: " + rect.width/units.gu(1) + "gu height: " + rect.height/units.gu(1) + "gu"
            }

            onWidthChanged: {
                clientgu.text = "client area width: " + Math.round(rect.width/units.gu(1)) + "gu height: " + Math.round(rect.height/units.gu(1)) + "gu"
                client.text = "client area (" + rect.width + ", " + height + ")"
            }

            onHeightChanged: {
                client.text = "client area (" + rect.width + ", " + height + ")"
                clientgu.text = "client area width: " + Math.round(rect.width/units.gu(1)) + "gu height: " + Math.round(rect.height/units.gu(1)) + "gu"
            }
        }

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

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

                Text {
                    id: unitsgu
                    text: "1 units.gu = " + units.gu(1) + " pixels"
                }

                Label {
                    text: "desktopAvailableHeight: " + Screen.desktopAvailableHeight
                }

                Label {
                    text: "devicePixelRatio: " + Screen.devicePixelRatio
                }

                Label {
                    text: "desktopAvailableWidth: " + Screen.desktopAvailableWidth
                }

                Label {
                    text: "Screen height: " + Screen.height
                }

                Label {
                    text: "Sreen width: " + Screen.width
                }

                Label {
                    text: "orientation: " + orientations[Screen.orientation]
                }

                Label {
                    text: "primaryOrientation: " + orientations[Screen.primaryOrientation]
                }

                Label {
                    text: "orientationUpdateMask: " + Screen.orientationUpdateMask
                }

                Label {
                    text: "pixelDensity: " + Screen.pixelDensity
                }

                Label {
                    id: info
                    text: "screen width: " + Math.round(Screen.width/units.gu(1)) + "gu  height: " + Math.round(Screen.height/units.gu(1)) + "gu"
                }

                Label {
                    id: clientgu
                }

                Label {
                    id: client
                }

                Label {
                    text: "1 dp = " + units.dp(1) + " pixels"
                }

                Row {
                    spacing: units.gu(1)

                    Label {
                        id: mylabel
                        anchors.verticalCenter: parent.verticalCenter
                        text: "I love"
                        fontSize: fontsize
                    }

                    Text {
                        anchors.verticalCenter: parent.verticalCenter
                        text: mylabel.fontSize
                    }

                    Text {
                        anchors.verticalCenter: parent.verticalCenter
                        text: mylabel.font.pixelSize + " pixels"
                    }

                    Text {
                        anchors.verticalCenter: parent.verticalCenter
                        text: (mylabel.font.pixelSize/units.gu(1)).toFixed(2)+ " units.gu"
                    }
                }

                Row {
                    width: parent.width

                    Label {
                        width: parent.width/2
                        text: "Font Size"
                        font.bold: true
                        fontSize: "large"

                    }

                    Label {
                        width: parent.width/2
                        text: "ModularScale"
                        font.bold: true
                        fontSize: "large"
                    }

                }

                ListView {
                    id: listview
                    anchors.horizontalCenter: parent.horizontalCenter
                    width: parent.width
                    // height: parent.height - mylabel.height
                    height: units.gu(20)
                    highlight: highlightBar
                    model: ["xx-small","x-small", "small", "medium", "large", "x-large" ]
                    delegate: Item {
                        width: listview.width
                        height: layout.height
                        property string fontsize: modelData

                        Row {
                            id: layout
                            width: parent.width

                            Label {
                                width: parent.width/2
                                text: modelData
                                fontSize: "large"
                            }

                            Label {
                                width: parent.width/2
                                text: (FontUtils.modularScale(modelData)).toFixed(2)
                                fontSize: "large"
                            }

                        }

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

运行我们的应用:


   


大家可以在Ubuntu商店中下载UbuntuSizes应用来测试自己的应.

项目源码:https://github.com/liu-xiao-guo/ubuntusizes

作者:UbuntuTouch 发表于2016/3/28 9:42:40 原文链接
阅读:314 评论:0 查看评论

Read more
UbuntuTouch

ViewItems是依附于一个ListView或ListItem中的属性.我们可以利用它来管理我们的ListView或ListItem中项的drag-and-drop.下面我们来通过一个简单的例程来展示如何利用它来实现一些我们需要的功能:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

    Page {
        id: page
        header: PageHeader {
            title: "viewitems"
        }

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

        ListView {
            width: parent.width
            height: parent.height - page.header.height
            anchors.top: page.header.bottom

            model: mymodel
            delegate: ListItem {
                Image {
                    anchors.verticalCenter: parent.verticalCenter
                    source: name
                    height: units.gu(8)
                    width: height
                }

                Label {
                    text: index
                    anchors.horizontalCenter: parent.horizontalCenter
                }

                dragMode: false

                color: dragMode ? "white" : "lightgray"
                onPressAndHold: ListView.view.ViewItems.dragMode =
                                !ListView.view.ViewItems.dragMode

                onDragModeChanged: {
                    console.log("dragMode: " + dragMode)
                }
            }

            ViewItems.dragMode: true
            ViewItems.onDragUpdated: {
                if (event.status === ListItemDrag.Started) {
                    console.log("event.from: " + event.from + " " + "event.to: " + event.to)
                    if (event.from < 5) {
                        // deny dragging on the first 5 element
                        event.accept = false;
                    } else if (event.from >= 5 && event.from <= 10 ) {
                        // specify the interval
                        event.minimumIndex = 5;
                        event.maximumIndex = 10;
                    } else if (event.from > 10) {
                        // prevent dragging to the first 11 items area
                        event.minimumIndex = 11;
                    }
                } else {
                    console.log("moving ....")
                    model.move(event.from, event.to, 1);
                }
            }
        }
    }
}

我们来运行我们的代码.



在上面的实现中,我们可以通过对event.status === ListItemDrag.Started的检测来限制那些项是可以移动的,或定义它们的移动的范围.

            ViewItems.onDragUpdated: {
                if (event.status === ListItemDrag.Started) {
                    console.log("event.from: " + event.from + " " + "event.to: " + event.to)
                    if (event.from < 5) {
                        // deny dragging on the first 5 element
                        event.accept = false;
                    } else if (event.from >= 5 && event.from <= 10 ) {
                        // specify the interval
                        event.minimumIndex = 5;
                        event.maximumIndex = 10;
                    } else if (event.from > 10) {
                        // prevent dragging to the first 11 items area
                        event.minimumIndex = 11;
                    }
                } else {
                    console.log("moving ....")
                    model.move(event.from, event.to, 1);
                }
            }

在上面的例子中,当我们在最前面的5个列表项中去移动时,它根本没有反应.当为我们在5-10的范围去移动我们的项时,如果在我们drop时,落下的项不在5-10之间,也不起任何的作用.也就是说5-10之间的项只能在它们之间进行交换.如果是10以外的项,它们只能在10以外的地方进行交换.



作者:UbuntuTouch 发表于2016/3/28 10:40:26 原文链接
阅读:292 评论:0 查看评论

Read more
UbuntuTouch

对于大多数的网页开发者来说,我们可以充分利用我们的QML UI框架来实现一些比较炫的界面,我们也可以利用HTML5现有的应用架构来实现我们基于Web的应用.在今天的例程中,我们来展示如何把这两个方面充分结合起来.这样可以使得我们的应用更加炫,更加充分利用现有的Ubuntu QML APIs来实现我们的一些功能.


1)设计我们的HTML文件


<html>
<head>
<script>
    document.addEventListener("QMLmessage", function (event) {
        document.body.innerHTML += "<p><font size='20' color='red'>Message received.  You said " + event.detail.greeting + "</font></p>";
    });
</script>
</head>

<body>
<h1>Hello</h1>
</body>
</html>

为了说明文档的方便,我设计了一个简单的界面.


在上面的界面中,html显示的内容不包括上面的按钮部分.只显示一个"Hello".同时我们定义了一个如下的一个事件响应器:

 document.addEventListener("QMLmessage", function (event) {
        document.body.innerHTML += "<p><font size='20' color='red'>Message received.  You said " + event.detail.greeting + "</font></p>";
    });

每当有一个叫做QMLmessage的实际响应的时候,我们会在界面上自动加入我们想要的一行字.比如:




2)触发QMLmessage事件


在下面的Main.qml代码中:

import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Web 0.2
import com.canonical.Oxide 1.9 as Oxide

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

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

    property string usContext: "messaging://"

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

    Page {
        title: i18n.tr("webview-oxide")

        Rectangle {
            anchors.fill: parent

            // Both the UserScript and the call to sendMessage need to share the same
            // context, which should be in the form of a URL.  It doesn't seem to matter
            // what it is, though.
            property string usContext: "messaging://"

            WebView {
                id: webview
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                    bottom: button.top
                }
                context: webcontext
                url: {
                    console.log("address: " + Qt.resolvedUrl("index.html"))
                    return Qt.resolvedUrl("index.html")
                }
            }

            WebContext {
                id: webcontext
                userScripts: [
                    Oxide.UserScript {
                        context: usContext
                        url: Qt.resolvedUrl("oxide-user.js")
                    }
                ]
            }

            Button {
                id: button
                anchors {
                    bottom: parent.bottom
                    left: parent.left
                    right: parent.right
                }
                text: "Press Me"
                onClicked: {
                    var req = webview.rootFrame.sendMessage(usContext, "MESSAGE", {greeting: "Hello"})
                    req.onreply = function (msg) {
                        console.log("Got reply: " + msg.str);
                    }
                    req.onerror = function (code, explanation) {
                        console.log("Error " + code + ": " + explanation)
                    }
                }
            }
        }
    }
}

我们通过按钮"button"来向我们的WebView发送我们需要的QMLmessage事件.在这里我们使用了:

            WebContext {
                id: webcontext
                userScripts: [
                    Oxide.UserScript {
                        context: usContext
                        url: Qt.resolvedUrl("oxide-user.js")
                    }
                ]
            }

上面的oxide-user的内容为:

oxide.addMessageHandler("MESSAGE", function (msg) {
    var event = new CustomEvent("QMLmessage", {detail: msg.args});
    document.dispatchEvent(event);
    msg.reply({str: "OK"});
});

也就是在我们发送完一个信息后,我们同时也得到一个返回的信息"OK".这个可以在为我们的应用输出中可以看出:



这样我们实现了从QML到HTML文件的交互.这对于我们设计一些基于地图定位或导航的应用来说是非常中要的手段.



作者:UbuntuTouch 发表于2016/3/29 12:10:45 原文链接
阅读:294 评论:0 查看评论

Read more
UbuntuTouch

在新的Scope设计中,有一个新的Comment-input PreviewWidget.这个可以帮我用来列举我们收到评论,比如在点评中,对一个餐馆的评论文字.


在上面的显示中,我们显示了Author,时间,及Comments.在今天的实验中,我们将介绍如何实现这样的功能.


我们首先还是从我们以前做过的练习中来讲述.下载我们的代码:

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

在我们的代码中,我们添加如下的东西:

query.cpp


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

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

    r["comment_icon"] = icons_[3].toStdString();

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

    r["musicSource"] = "http://qqmp3.djwma.com/mp3/魔音神据极品私货这锯子拉的耳膜都要碎了.mp3";
    r["videoSource"] = "http://techslides.com/demos/sample-videos/small.mp4";
    r["screenshot"] = icons_[2].toStdString();

    // add an array to show the gallary of it
    sc::VariantArray arr;

    for(const auto &datum : icons_) {
        arr.push_back(Variant(datum.toStdString()));
    }

    r["array"] = sc::Variant(arr);

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

在上面,我们加入了:

    r["comment_icon"] = icons_[3].toStdString();

这是我们需要显示Comment时最左边的图标.

preview.cpp

void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts

    ColumnLayout layout1col(1);
    std::vector<std::string> ids = { "image", "header", "summary", "tracks",
                                    "videos", "gallery_header", "gallerys", "reviews", "exp",
                                     "review_input", "rating_input", "inputId" };

    ColumnLayout layout2col(2);
    layout2col.add_column(ids);
    layout2col.add_column({});

    ColumnLayout layout3col(3);
    layout3col.add_column(ids);
    layout3col.add_column({});
    layout3col.add_column({});

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

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

    // Define the summary section
    sc::PreviewWidget description("summary", "text");
    // It has a text property, mapped to the result's description property
    description.add_attribute_mapping("text", "description");

    Result result = PreviewQueryBase::result();
    PreviewWidget listen("tracks", "audio");
    {
        VariantBuilder builder;
        builder.add_tuple({
            {"title", Variant("This is the song title")},
            {"source", Variant(result["musicSource"].get_string().c_str())}
        });
        listen.add_attribute_value("tracks", builder.end());
    }

    PreviewWidget video("videos", "video");
    video.add_attribute_value("source", Variant(result["videoSource"].get_string().c_str()));
    video.add_attribute_value("screenshot", Variant(result["screenshot"].get_string().c_str()));

    sc::PreviewWidget header_gal("gallery_header", "header");
    header_gal.add_attribute_value("title", Variant("Gallery files are:"));

    PreviewWidget gallery("gallerys", "gallery");
//    gallery.add_attribute_value("sources", Variant(result["array"]));
    gallery.add_attribute_mapping("sources", "array");

    PreviewWidgetList widgets({ image, header, description });

    if ( result["musicSource"].get_string().length() != 0 ) {
        widgets.emplace_back(listen);
    }

    if( result["videoSource"].get_string().length() != 0 ) {
        widgets.emplace_back(video);
    }

    if( result["array"].get_array().size() != 0 ) {
        widgets.emplace_back(header_gal);
        widgets.emplace_back(gallery);
    }

    // The following shows the review
    PreviewWidget review("reviews", "reviews");
    VariantBuilder builder;
    builder.add_tuple({
                          {"author", Variant("John Doe")},
                          {"review", Variant("very good")},
                          {"rating", Variant(3.5)}
                      });
    builder.add_tuple({
                          {"author", Variant("Mr. Smith")},
                          {"review", Variant("very poor")},
                          {"rating", Variant(5)}
                      });
    review.add_attribute_value("reviews", builder.end());
    widgets.emplace_back(review);

    // The following shows the expandable
    PreviewWidget expandable("exp", "expandable");
    expandable.add_attribute_value("title", Variant("This is an expandable widget"));
    expandable.add_attribute_value("collapsed-widgets", Variant(1));
    PreviewWidget w1("w1", "text");
    w1.add_attribute_value("title", Variant("Subwidget 1"));
    w1.add_attribute_value("text", Variant("A text"));
    PreviewWidget w2("w2", "text");
    w2.add_attribute_value("title", Variant("Subwidget 2"));
    w2.add_attribute_value("text", Variant("A text"));
    expandable.add_widget(w1);
    expandable.add_widget(w2);
    widgets.emplace_back(expandable);

    // The following shows a review rating-input
    PreviewWidget w_review("review_input", "rating-input");
    w_review.add_attribute_value("submit-label", Variant("Send"));
    w_review.add_attribute_value("visible", Variant("review"));
    w_review.add_attribute_value("required", Variant("review"));
    std::string reply_label = "Reply";
    std::string max_chars_label = "140 characters max";
    w_review.add_attribute_value("review-label", Variant(reply_label + ": " + max_chars_label));
    widgets.emplace_back(w_review);

    // The follwing shows a rating rating-input
    PreviewWidget w_rating("rating_input", "rating-input");
    w_rating.add_attribute_value("visible", Variant("rating"));
    w_rating.add_attribute_value("required", Variant("rating"));
    w_rating.add_attribute_value("rating-label", Variant("Please rate this"));
    widgets.emplace_back(w_rating);

    PreviewWidget w_commentInput("inputId", "comment-input");
    w_commentInput.add_attribute_value("submit-label", Variant("Post"));
    widgets.emplace_back(w_commentInput);

    // In the following, fake some comments data
    QList<Comment> comment_list;
    std::string comment_str = "Comment ";
    for(int i = 0; i < 3;  i++) {
        Comment comment;

        comment.id         = 1.0;
        comment.publishTime = "2015-3-18";
        comment.text = comment_str;
        comment.text += std::to_string(i+1);
        comment_list.append(comment);
    }

    int index = 0;
    Q_FOREACH(const auto & comment, comment_list) {
        std::string id = "commentId_" + std::to_string(index++);
        ids.emplace_back(id);

        PreviewWidget w_comment(id, "comment");
        w_comment.add_attribute_value("comment", Variant(comment.text));
        w_comment.add_attribute_value("author", Variant("Author"));
        w_comment.add_attribute_value("source", Variant(result["comment_icon"].get_string().c_str()));
        w_comment.add_attribute_value("subtitle", Variant(comment.publishTime));
        widgets.emplace_back(w_comment);
    }

    layout1col.add_column(ids);
    reply->register_layout({layout1col, layout2col, layout3col});
    reply->push( widgets );
}


在上面的函数中,我们加入了如下的代码:

    PreviewWidget w_commentInput("inputId", "comment-input");
    w_commentInput.add_attribute_value("submit-label", Variant("Post"));
    widgets.emplace_back(w_commentInput);

    // In the following, fake some comments data
    QList<Comment> comment_list;
    std::string comment_str = "Comment ";
    for(int i = 0; i < 3;  i++) {
        Comment comment;

        comment.id         = 1.0;
        comment.publishTime = "2015-3-18";
        comment.text = comment_str;
        comment.text += std::to_string(i+1);
        comment_list.append(comment);
    }

    int index = 0;
    Q_FOREACH(const auto & comment, comment_list) {
        std::string id = "commentId_" + std::to_string(index++);
        ids.emplace_back(id);

        PreviewWidget w_comment(id, "comment");
        w_comment.add_attribute_value("comment", Variant(comment.text));
        w_comment.add_attribute_value("author", Variant("Author"));
        w_comment.add_attribute_value("source", Variant(result["comment_icon"].get_string().c_str()));
        w_comment.add_attribute_value("subtitle", Variant(comment.publishTime));
        widgets.emplace_back(w_comment);
    }

    layout1col.add_column(ids);

在上面我们创建了一个叫做comment-input的PreviewWidget.同时,我们在显示每项comment时,使用了一个叫做comment的PreviewWidget.由于没有online数据,我们自己创建了一下fake的数据.运行我们的Scope,我们可以看到结果就像我们上面图显示的那样.

注意在上面的实现中,我们必须动态地添加每个创建的comment PreviewWidget,所以,有上面的语句:

layout1col.add_column(ids);

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



作者:UbuntuTouch 发表于2016/3/29 15:10:08 原文链接
阅读:276 评论:0 查看评论

Read more
UbuntuTouch

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


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

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

    // anchorToKeyboard: true

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

        Flickable {
            id: sampleFlickable

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

            Column {
                id: mainColumn

                spacing: units.gu(2)

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

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

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

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

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

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

    anchorToKeyboard: true

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


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

    anchorToKeyboard: true

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



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

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

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

Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

  //  anchorToKeyboard: true

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

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

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

    anchorToKeyboard: true

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

  

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

  anchorToKeyboard: true

在重新运行我们的应用:

  



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

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



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

Read more
UbuntuTouch

在我们许多的项目中,我们需要用到一些资源.这些资源我们想分别放入到自己分别的目录中,比如images, videos, audios或assets.当我们打包我们的qmake应用时,我们需要把这些目录也打入到我们的中,并保证我们的应用所引用的文件保持相应的文件架构(相对文件路径).否则我们的应用不会真确运行.也有很多的web应用,我们希望把所有的文件一起打入到我们的应用中,这样我们可以可以使用WebView来访问.


我们还是拿我们先前的一个例子:

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

在那个例子中,我们的.pro文件如下:


TEMPLATE = aux
TARGET = audio

RESOURCES += audio.qrc

QML_FILES += $$files(*.qml,true) \
             $$files(*.js,true)

QML_SOUNDS += $$files(sounds/*.mp3,true) \
             $$files(sounds/*.wav,true)
CONF_FILES +=  audio.apparmor \
               audio.desktop \
               audio.png
OTHER_FILES += $${CONF_FILES} \
               $${QML_FILES}
#specify where the qml/js files are installed to
qml_files.path = /audio
qml_files.files += $${QML_FILES}
qm_sounds.path = /audio/sounds
qm_sounds.files += $${QML_SOUNDS}
#specify where the config files are installed to
config_files.path = /audio
config_files.files += $${CONF_FILES}
INSTALLS+=config_files qml_files qm_sounds

在这里我们使用了:

QML_SOUNDS += $$files(sounds/*.mp3,true) \
             $$files(sounds/*.wav,true)

qm_sounds.path = /audio/sounds
qm_sounds.files += $${QML_SOUNDS}


来把我们的文件打包到我们需要的文件目录中.整个的过程非常麻烦.

在今天的例程中,我们来直接把我们的.pro文件修改为:


TEMPLATE = aux
TARGET = audio

#SUBDIRS += sounds

RESOURCES += audio.qrc

QML_FILES += $$files(*.qml,true) \
             $$files(*.js,true) \
             sounds

#QML_SOUNDS += $$files(sounds/*.mp3,true) \
#             $$files(sounds/*.wav,true)

CONF_FILES +=  audio.apparmor \
               audio.desktop \
               audio.png

OTHER_FILES += $${CONF_FILES} \
               $${QML_FILES}

#specify where the qml/js files are installed to
qml_files.path = /audio
qml_files.files += $${QML_FILES}

#qm_sounds.path = /audio/sounds
#qm_sounds.files += $${QML_SOUNDS}

#specify where the config files are installed to
config_files.path = /audio
config_files.files += $${CONF_FILES}

INSTALLS+=config_files 

phablet@ubuntu-phablet:/opt/click.ubuntu.com/audio.liu-xiao-guo/current/audio$ ls
total 64
drwxrwxr-x 3 clickpkg clickpkg  4096 Mar 31 15:31 .
drwxr-xr-x 4 clickpkg clickpkg  4096 Mar 31 15:31 ..
-rw-r--r-- 1 clickpkg clickpkg   114 Mar 31 15:31 audio.apparmor
-rw-r--r-- 1 clickpkg clickpkg   167 Mar 31 15:31 audio.desktop
-rw-r--r-- 1 clickpkg clickpkg 38968 May  7  2015 audio.png
-rw-r--r-- 1 clickpkg clickpkg  1291 Aug 23  2015 Main.qml
drwxrwxr-x 2 clickpkg clickpkg  4096 Mar 31 15:31 sounds

在这里,我们只在上面的QML_FILES中加入"sounds"即可.这样整个"sounds"将会整个打包入我们的应用中.

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

如果大家感兴趣的话,大家可以把一个西班牙开发的项目uNav变为一个Qt Creator的项目.我已经帮他做了:




呵呵,这是我家的位置哦!


作者:UbuntuTouch 发表于2016/3/31 15:34:33 原文链接
阅读:257 评论:0 查看评论

Read more
UbuntuTouch

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



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


Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.3

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

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

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

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

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

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

                ProgressionSlot {}
            }
        }
    }
}

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



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


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

Read more
UbuntuTouch

[原]如何快速地安装Ubuntu SDK

我在先前的文章"Ubuntu SDK 安装"中已经详细地介绍了如何安装Ubuntu SDK.但是很多的开发者可能在最后安装SDK所需要的chroots时候会失败.这里面的原因是SDK在安装chroots时,它不支持断点续传.也就是说在安装chroots时,由于网路的原因或某种原因,造成chroots的安装失败时,我们需要再次重新安装它.一般我们需要删除现有的已经安装失败的chroots.每个chroot(armhf及i386)在安装后的大小约为1.6G.这篇文章详细介绍如何快速地安装我们的Ubuntu SDK.


1)安装Ubuntu SDK


首先,我们可以安装好我们的Ubuntu桌面系统.我们一般推荐安装最新的Ubuntu桌面系统,比如目前即将面世的16.04 LTS版本.如果已经有一个Ubuntu桌面系统,我们可以它通过如下的命令来升级我们的桌面系统到最新的系统:

$ update-manager -d

依照现有的文章"Ubuntu SDK 安装"中介绍的那样,安装最新的Ubuntu SDK.

$sudo apt-get update
$sudo apt-get upgrade
$sudo apt-get dist-upgrade
$sudo add-apt-repository ppa:ubuntu-sdk-team/ppa
$sudo apt-get install ubuntu-sdk
$sudo udo apt-get install ubuntu-sdk-dev ubuntu-sdk-ide

在上面的先开始的命令中,我们先把我们的Ubuntu桌面系统更新到最新的状态,这样使得我们的最新的SDK依赖的包都被安装到系统中以使得后面的SDK的安装能够顺利进行.否侧在我们的实际安装中,如果有的包不在系统中或是最新的,那么后面SDK的安装可能失败.

在这个步骤中,由于我们使用了ppa,所有它可以支持断点续传.如果失败了,它可以在下次安装时再次从上次中断的地方继续下载安装.一般来说我们并不担心这一步的失败.对于有些开发者来说,可以尝试使用VPN的方法来提高安装的速度.成功安装后,我们可以在dash中找到我们所需要的Ubuntu SDK:



对于网路情况不是很好的开发者来说,请直接跳到下面的第三节下载已经成功安装过的chroots来安装而不需要下面的第二步.


2)安装chroots


在这个步骤中,由于它不支持断点续传,所有它是最容易导致安装失败的环节.如果我们的网路情况好的话,我们可以直接在我们的命令行中打入下面的命令来安装我们的chroots.在这个步骤中,我们可以来安装我们所需要版本的chroots.我们可以通过如下的方式找到我们手机所有支持的framework:
liuxg@liuxg:~$ adb shell
phablet@ubuntu-phablet:~$ click framework list



一般来说在我们开发时,会选择我们所需要的framework(相当于iOS的版本).如果我们所定义的framework在手机中不存在,那么我的应用在手机中将不能被运行.目前我们建议使用15.04的chroots.

- armhf chroot的安装


我们可以通过如下的命令来安装我们的armhf chroot:
$sudo click chroot -aarmhf -f ubuntu-sdk-15.04 create  
如果安装失败,我们必须使用如下的命令删除已经安装的半成品,然后再用上面的命令来安装我们的chroot.
$sudo click chroot -a armhf -f ubuntu-sdk-15.04  destroy   

- i386 chroot的安装


我们可以通过如下的命令来安装我们的armhf chroot:
$sudo click chroot -ai386 -f ubuntu-sdk-15.04 create   
如果安装失败,我们必须使用如下的命令删除已经安装的半成品,然后再用上面的命令来安装我们的chroot.
$sudo click chroot -a i386 -f ubuntu-sdk-15.04  destroy  

3)直接下载已有的chroots进行安装


在实际的安装中,我发现有很多的开发者在进行上面的安装时由于网路的原因而导致上面的安装不能成功.基于这个原因,我们把我已经成功安装过的chroots上传到我们的百度网盘供大家下载.等下载完后,我们再把它们解压到我们所需要的路径中.这样的安装好处是,我们可以使用各种方法进行断点下载我们打包过的chroots,并成功拷贝到相应的目录中.这个方法的缺点是:chroots可能不是最新的.开发者在以后的SDK中可以进行自动更新或手动更新我们的chroots.不过一般来说,这个chroots已经够用即使在不更新的情况下.

删除任何已经安装或安装不成功的chroots


我们可以通过上一节中介绍的方法来删除曾经没有安装成功的chroots以保证我们有干净的安装环境:
$sudo click chroot -a armhf -f ubuntu-sdk-15.04  destroy 
$sudo click chroot -a i386 -f ubuntu-sdk-15.04  destroy  
通过上面的方法,我们确保在我们的桌面系统中没有任何我们曾经安装失败后残存的chroots文件.

下载我们上传的chroots


开发者可以到我们的网址下载我们已经上传的chroots.这个chroots是到上传时间最新的chroot.对于绝大多数的应用开发应该是没有任何问题的.当然开发者可以在以后的SDK中进行更新.



就如同我们上面显示的那样.在我们上次的chroots中,有两部分文件.

- chroot.d:


 这个是需要安装到/etc/schroot/chroot.d目录中的文件.安装后的文件架构是:
liuxg@liuxg:/etc/schroot/chroot.d$ tree
.
├── click-ubuntu-sdk-15.04-armhf
└── click-ubuntu-sdk-15.04-i386
在实际的拷贝中,我们需要使用sudo来拷贝的方法这两个文件.这里的liuxg是我自己的电脑liuxg上用户名.在你们自己安装时,这个名字应该是你们自己的用户名.打开这个两个文件,同样我们需要使用sudo来编辑这两个文件,比如click-ubuntu-sdk-15.04-armhf:
[click-ubuntu-sdk-15.04-armhf]
description=Build chroot for click packages on armhf
users=root,liuxg
root-users=root,liuxg
source-root-users=root,liuxg
type=directory
profile=default
setup.fstab=click/fstab
# Not protocols or services see
# debian bug 557730
setup.nssdatabases=sbuild/nssdatabases
union-type=overlayfs
directory=/var/lib/schroot/chroots/click-ubuntu-sdk-15.04-armhf

我们可以使用vi或gedit来编辑上面的文件.替换上面的"liuxg"为自己电脑上的用户名.然后存下来.我们使用同样的方法来对click-ubuntu-sdk-15.04-i386进行同样的操作.

- chroots.tar.gz


我们把这个文件拷贝到/var/lib/schroot/目录,然后我们通过如下的方法来解压缩:
liuxg@liuxg:/var/lib/schroot/chroots$ sudo tar -xvf chroots.tar.gz
解药缩后的文件架构为:
liuxg@liuxg:/var/lib/schroot/chroots$ tree -L 1
.
├── click-ubuntu-sdk-15.04-armhf
└── click-ubuntu-sdk-15.04-i386

同样上面的"liuxg"是我自己的用户名.在实际应用中,它应该是你自己电脑上的用户名.

- 检查我们已经安装好的chroots


我们可以通过如下的方法来检查我们的chroots的安装是否已经成功:
liuxg@liuxg:~$ schroot -l
chroot:click-ubuntu-sdk-15.04-armhf
chroot:click-ubuntu-sdk-15.04-i386
source:click-ubuntu-sdk-15.04-armhf
source:click-ubuntu-sdk-15.04-i386

如果我们已经看到上面的显示,表明我们的安装已经是成功的.

- 打开我们的Ubuntu SDK来检查我们的chroots


在SDK中,我们怎么来检查我们已经安装过的chroots是否已经成功呢?我们首先打开我们的SDK,然后点击菜单:
Tools ==> Options ==> Ubuntu ==>Click


如果我们已经看到我们的chroot已经在上面所示的图片中,表明我们的chroot是已经安装成功的.我们可以点击上面的Update来更新我们的chroots到最新状态.当然我们也可以点击Maintain来添加或删除我们所需要的模块或库.


4)最后一招


如果上面的所有方法都已经试过了,还是不可以的话,可以在地址下载debian文件进行安装.需要安装的文件在页面的最下面.目前使用于16.04 LTS的desktop系统.


在安装我们上面的文件时,我们必须按照上面的方法先删除我们先前安装过的文件。我们可以使用如下的命令:

$sudo click chroot -a armhf -f ubuntu-sdk-15.04  destroy 
$sudo click chroot -a i386 -f ubuntu-sdk-15.04  destroy  

- 检验我们已经安装的Ubuntu SDK


我们可以通过文章"创建第一个Ubuntu for phone应用"来检查我们的SDK的安装是否成功.如果一切顺利,我们可以把我们的应用部署到我们的设备或模拟器中.具体安装模拟器的方法,可以参阅文章"Ubuntu SDK 安装".这里将不再重复.
作者:UbuntuTouch 发表于2016/4/10 8:52:46 原文链接
阅读:1905 评论:1 查看评论

Read more
UbuntuTouch

最近我看了一篇关于在Scope中link query的文章.坦率地说,不是特别理解,如果没有具体的例程也不知道它的作用.简单地说,就是在我们的Scope设计中,我们可以点击一个Object,他可以让我们发起另外一个不同的query.

首先,我们可以下载我们已经设计好的例程:

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

在query.cpp中,

void Query::run(sc::SearchReplyProxy const& reply) {
    try {
        // The default is vertical
        pushResult(reply, CAT_RENDERER1, 1);
        pushResult(reply, CAT_RENDERER101, 101);
        pushResult(reply, CAT_RENDERER2, 2);
        pushResult(reply, CAT_RENDERER3, 3);
        pushResult(reply, CAT_RENDERER4, 4);
        pushResult(reply, CAT_RENDERER5, 5);
        pushResult(reply, CAT_RENDERER6, 6);
        pushResult(reply, CAT_RENDERER7, 7);
        pushResult(reply, CAT_RENDERER10, 10);
        pushResult(reply, CAT_RENDERER11, 11);

        int count = images_.count();

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

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

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

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

        cat = reply->register_category( "vertical-journal", "vertical-journal" ,
                                         "", sc::CategoryRenderer(CAT_RENDERER12) );

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

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

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

        cat = reply->register_category( "Linking queries", "Linking queries" ,
                                         "", sc::CategoryRenderer(CAT_RENDERER1) );

        // The link icon's index is 8
        pushResult( reply, &cat, 8, true);

        pushResult(reply, CR_CAROUSEL, 12);


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

我们加入了如下的语句:

        cat = reply->register_category( "Linking queries", "Linking queries" ,
                                         "", sc::CategoryRenderer(CAT_RENDERER1) );

        // The link icon's index is 8
        pushResult( reply, &cat, 8, true);

在下面的函数中:

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

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

    sc::CategorisedResult r(*cat);

    // If linkquery is true, we need to redirect it to another scope or
    // a scope department id locally in this scope
    if ( linkquery ) {
        sc::CannedQuery second_query("com.canonical.scopes.news_unity-scope-news");
        second_query.set_department_id("dept-finance");
        r.set_uri(second_query.to_uri());
    } else {
        r.set_uri( URI.toStdString() );
    }
   ...
}

当linkquery为真时,我们把它重新定向为发起另外一个叫做com.canonical.scopes.news_unity-scope-news Scope中的dept-finance department.

运行我们的Scope:

 


当我们点击link queries下面的图片时,在news中的dept-finance将被自动query并显示结果.





作者:UbuntuTouch 发表于2016/4/12 16:02:36 原文链接
阅读:160 评论:0 查看评论

Read more
UbuntuTouch

在我们的许多的应用中,我们希望键盘的布局只允许数字或字母的输入.对密码的输入,我们希望通过隐藏输入以免别人看见.那么怎么在Ubuntu的手机设计中实现这个功能呢?我们可以使用TextArea中的inputMethodHints来实现这个功能.


下面我们还是通过一个短小的例程来展示:

Main.qml


import QtQuick 2.4
import Ubuntu.Components 1.2

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

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

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

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

        Connections {
            target: Qt.inputMethod
            onVisibleChanged: console.log("OSK visible: " + visible)
        }

        property var model: [
            "Qt.ImhHiddenText",
            "Qt.ImhSensitiveData",
            "Qt.ImhNoAutoUppercase",
            "Qt.ImhPreferNumbers",
            "Qt.ImhPreferUppercase",
            "Qt.ImhPreferLowercase",
            "Qt.ImhNoPredictiveText",
            "Qt.ImhDate",
            "Qt.ImhTime",
            "Qt.ImhDigitsOnly",
            "Qt.ImhFormattedNumbersOnly",
            "Qt.ImhUppercaseOnly",
            "Qt.ImhLowercaseOnly",
            "Qt.ImhDialableCharactersOnly",
            "Qt.ImhEmailCharactersOnly",
            "Qt.ImhUrlCharactersOnly"
        ]

        property var inputMethods: [
            Qt.ImhHiddenText,
            Qt.ImhSensitiveData,
            Qt.ImhNoAutoUppercase,
            Qt.ImhPreferNumbers,
            Qt.ImhPreferUppercase,
            Qt.ImhPreferLowercase,
            Qt.ImhNoPredictiveText,
            Qt.ImhDate,
            Qt.ImhTime,
            Qt.ImhDigitsOnly,
            Qt.ImhFormattedNumbersOnly,
            Qt.ImhUppercaseOnly,
            Qt.ImhLowercaseOnly,
            Qt.ImhDialableCharactersOnly,
            Qt.ImhEmailCharactersOnly,
            Qt.ImhUrlCharactersOnly
        ]

        Flickable {
            anchors.fill: parent
            contentHeight: layout.childrenRect.height + units.gu(2)

            Column {
                id: layout
                width: parent.width

                OptionSelector {
                    id: option
                    text: page.model[selectedIndex]
                    model: page.model

                    // update the text
                    onSelectedIndexChanged: {
                        console.log("selectedIndex: " + selectedIndex )
                        text = page.model[selectedIndex]
                        textField.inputMethodHints = page.inputMethods[selectedIndex]
                    }
                }

                TextField {
                    id: textField
                    width:page.width*.7
                    placeholderText: "Please input something"
                }

                TextField {
                    id: textField1
                    width:page.width*.7
                    placeholderText: "Please input a password"
                    echoMode: TextInput.Password
                }
            }
        }
    }
}

运行上面的应用:

  

  


从上面的例程中可以看出来,我们可以通过选择不同的inputMethodHints值来实现不同的键盘布局.

作者:UbuntuTouch 发表于2016/4/14 9:46:52 原文链接
阅读:150 评论:0 查看评论

Read more