2013年1月5日土曜日

Ubuntu for Phoneのアプリケーション作成チュートリアル(その2)

なんだか生暖かく見守られてるらしい隠者です。
見守られてるらしいので、がんばりますか。

通貨の取得と変換

昨日のアプリに、通貨情報の取得と変換のロジックを実装していきます。
これから実装を追加していきますが、基本的にRectangleのブロックの内側に入れて行く必要がある事に注意して下さい。

とまぁ、チュートリアルでは追加行だけ書いてあるわけですが、つまるところこういう事です。

import QtQuick 2.0
import Ubuntu.Components 0.1

Rectangle {
    id: root
    width: units.gu(60)
    height: units.gu(80)
    color: "lightgray"

    property real margins: units.gu(2)

    property real buttonWidth: units.gu(9)

    Label {

       id: title
       ItemStyle.class: "title"
       text: i18n.tr("CurrencyConverter")
       height: contentHeight + root.margins
       anchors {
           left: parent.left
           right: parent.right
           top: parent.top
       }
    }

    ListModel {
        id: currencies
        ListElement {
            currency: "EUR"
            rate: 1.0
        }

        function getCurrency(idx) {
            return (idx >= 0 && idx < count) ? get(idx).currency: ""
        }

        function getRate(idx) {
            return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
        }
    }
}

ここでは、為替を通貨とレートのペアをアイテムとしてもつリストのListModelオブジェクトとして扱います。通貨のListModelは、データを表示するビューのソースとして使用されます。ここでは欧州中央銀行(ECB)からユーロ外国為替参照レートから実際のデータをフェッチします。このデータにはユーロ自体は定義されていないので、事前に、1.0のリファレンスレートとして登録しています。

この関数の記事は、QMLの非常に強力な機能の実例となります(ここ、テストにでます)。Javascriptの統合です。2つのJavaScript関数は、indexから通貨とレートを取り出すためのグルーコードとなります。これらは、通貨情報がロードされていない最初にコンポーネントのプロパティがバインディングされた時に必要となります。ただまぁ、これらの事については今はあまり気にしなくて良いです。今の所、あなたのQMLオブジェクトにJavaScriptのコードを透過的に統合できるという事を覚えておく事が重要なのです。

さて、XmlListModel - XMLデータをmodelへとロードするQtQuickオブジェクト - で実際のデータをフェッチしましょう。これを使うために、ファイルの先頭に以下のようなimportを追加します。

import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
続いて、Rectangle内のListModelのブロックの後に、実際の為替レートをフェッチするコードを追加します。

    XmlListModel {
        id: ratesFetcher
        source: "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml"
        namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
                               +"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
        query: "/gesmes:Envelope/Cube/Cube/Cube"
        onStatusChanged: {
            if (status === XmlListModel.Ready) {
                for (var i = 0; i < count; i++)
                    currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
            }
        }
        XmlRole { name: "currency"; query: "@currency/string()" }
        XmlRole { name: "rate"; query: "@rate/string()" }
    }


関連したプロパティはsourceで、これは、データをどこからフェッチするのかというURLを含んでいます。queryは、以下のXmlRoleからモデルのアイテムを生成するための基本クエリとして使用するため、絶対XPathクエリを定義しています。また、namespaceDeclarationsは、XPathクエリに使う名前空間の宣言です。

onStatusChangedシグナルハンドラは、JavaScriptでのシグナル・ハンドラの仕組みもまた、もうひとつの自由度の機能である事を実証します。

少しだけ補足しておくと、Qtには独自のシグナル・スロットという仕組みがあります。これはUnix系OSが提供するシグナルとは異なるものですが、まぁ、あるイベントに対して、あるSlotとよばれるハンドラの呼び出しを登録するという仕組みである事は同じです。まぁ、Qt系のイベントのコールバック的な仕組みだと覚えておけば良いでしょう。

すべてのQMLプロパティは、<property>Changed というシグナルと、それに対応するon<property>Changedというシグナルハンドラを持っています。この場合、StatusChangedシグナルは、プロパティのステータスの任意の変更を通知するため発信され、rateFetcherがデータの読み込みを終え次第、すべての通貨・レートデータを通貨ListModelに登録するためのハンドラを定義しています。

要するにrateFetcher[XmlListModel]が通貨レート情報を読み込み、currencies[ListModel]に登録しています。

多くの場合、XmlListModel単体のデータソースとして使う事ができます。ただ、今回のケースでは中間コンテナとして使っています。今回はデータを修正し、ユーロのデータを追加しておく必要があったため、currencies[ListModel]にデータを入れています。

開発者であるあなたが、どのようにネットワークアクセスをしているのか考えなくて良い程、透過的にネットワークアクセスをしていることにお気づきでしょうか。

XmlListModelのブロックの後(60行目近辺)に、レートをフェッチしているアクティビティを示すため、Ubuntu ActivityIndicatorコンポーネントを追加しましょう。

    ActivityIndicator {
        anchors.right: parent.right
        running: ratesFetcher.status === XmlListModel.Loading
    }

それを、親(root)の右側に固定して、レートデータがフェッチされるまで、Activityを表示します(回線速度にもよるでしょうが、わりと一瞬なのですぐに消えちゃいます)

最後に、ActivityIndicatorの後(65行目近辺)に、実際の通貨変換を実行するconvert - JavaScript関数を追加します。

    function convert(from, fromRateIndex, toRateIndex) {
        var fromRate = currencies.getRate(fromRateIndex);
        if (from.length <= 0 || fromRate <= 0.0)
            return "";
        return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
    }

とまぁ、色々追加したのですが、ここまでだと画面を表示させてもほとんど変化はありません。裏でデータを取ってきて蓄える部分ですので。

通貨の選択

ここまででバックエンドはすべて加えたので、ここからはユーザー・インタラクションの追加に移ります。これから、オブジェクトの他のコンポーネントを組み合わせて再利用可能なブロック-新しいコンポーネント-の作成をします。

まずは、import分の追加です。
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1
続いて、先ほどのコードに以下のコードを追加します。

    Component {
        id: currencySelector
        Popover {
            Column {
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                }
                height: pageLayout.height
                Header {
                    id: header
                    text: i18n.tr("Select currency")
                }
                ListView {
                    clip: true
                    width: parent.width
                    height: parent.height - header.height
                    model: currencies
                    delegate: Standard {
                        text: currency
                        onClicked: {
                            caller.currencyIndex = index
                            caller.input.update()
                            hide()
                        }
                    }
                }
            }
        }
    }

ここでは、Ubuntu PopvarとスタンダードなQtQuickのListViewを組み合わせた通貨セレクタを作成しました。ListViewは、currencies ListModelからデータを出力します。どのように列オブジェクトがUbuntu Headerにラップされ、リストビューに縦に配置され、そしてどのようにLits Viewのそれぞれのアイテムが、スタンダードリストコンポーネントとなるのかに着目して下さい。

popvarはcurrenciesの選択肢を表示します。選択されると同時にpopvarはhideになり(onClickedハンドラを確認して下さい)、そしてcallerのデータが更新されています。

callerはcurrencyIndexとinputプロパティーを有しており、そして、inputはupdate()関数をもつitemを所持していると見なされます。


とここまでが、このページの説明なのですが、いやぁ、これ知らない人には確かにちんぷんかんぷんかもしれません。隠者はすっかり読み飛ばしていましたが・・・。この説明でわかった人は挙手を・・・・と勉強会なら聞く所です。

実はこのページのコードまでだと、画面上には何もでません。このComponentをpopup表示させるコードが必要です。ですので、動かして理解する事も困難かもしれません。とりあえず、次のページはほとんどコードと画像だけで、たいした説明が無いので、そちらで詳しく解説します。

今はとりあえず、以下の構成に成っているという事を理解しておいて下さい。()の中身はID-つまり名前です。

Rectangle(root) +-- Label(title)
                          +-- ListModel(currencies)
                          +-- XmlListModel(ratesFetcher)
                          +-- ActivityIndicator
                          +-- Component(currencySelector)
                                +-- Popver
                                     +-- Column
                                         +-- Header(header)
                                         +-- ListView[model:currencies]
                                             +-- delegate : Standard

とりあえず、本日ここまでのコードを記載しておきましょう。

import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1

Rectangle {
    id: root
    width: units.gu(60)
    height: units.gu(80)
    color: "lightgray"

    property real margins: units.gu(2)
    property real buttonWidth: units.gu(9)

    Label {
       id: title
       ItemStyle.class: "title"
       text: i18n.tr("CurrencyConverter")
       height: contentHeight + root.margins
       anchors {
           left: parent.left
           right: parent.right
           top: parent.top
       }
    }

    ListModel {
        id: currencies
        ListElement {
            currency: "EUR"
            rate: 1.0
        }

        function getCurrency(idx) {
            return (idx >= 0 && idx < count) ? get(idx).currency: ""
        }

        function getRate(idx) {
            return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
        }
    }

    XmlListModel {
        id: ratesFetcher
        source: "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml"
        namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
                               +"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
        query: "/gesmes:Envelope/Cube/Cube/Cube"

        onStatusChanged: {
            if (status === XmlListModel.Ready) {
                for (var i = 0; i < count; i++)
                    currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
            }
        }

        XmlRole { name: "currency"; query: "@currency/string()" }
        XmlRole { name: "rate"; query: "@rate/string()" }
    }

    ActivityIndicator {
        anchors.right: parent.right
        running: ratesFetcher.status === XmlListModel.Loading
    }


    function convert(from, fromRateIndex, toRateIndex) {
        var fromRate = currencies.getRate(fromRateIndex);
        if (from.length <= 0 || fromRate <= 0.0)
            return "";
        return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
    }

    Component {
        id: currencySelector
        Popover {
            Column {
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                }
                height: pageLayout.height
                Header {
                    id: header
                    text: i18n.tr("Select currency")
                }
                ListView {
                    clip: true
                    width: parent.width
                    height: parent.height - header.height
                    model: currencies
                    delegate: Standard {
                        text: currency
                        onClicked: {
                            caller.currencyIndex = index
                            caller.input.update()
                            hide()
                        }
                    }
                }
            }
        }
    }
}

というわけで、微妙にすっきりしない2ページ目の終わりですが、続きは次のエントリーへ持ち越します。



関連記事へのリンク

Toolkitについてその1その3

0 件のコメント:

コメントを投稿