Earlier we learned how to make a Scope application on Ubuntu Touch. Scope is also a very important application for Ubuntu that differentiates it from other platforms. It does a good job of integrating Web services into the mobile platform as part of the system. Scope is also important to handset makers. It allows them to deeply customize their services into the system.

\

It’s worth pointing out: all current Scope development had to be on top of Ubuntu OS Utopic (14.10) for some reason. This is not possible on Ubuntu OS 14.04. \

1) Create a basic Scope application

\

First, open up our Ubuntu SDK. Select the “Unity Scope” template.

\

\

Then choose the path of the project, and choose their own project name “dianping”. We select the “Empty Scope” template.

\

\

\

\

\

Next, we complete the remaining steps to complete a basic Scope application. We can run it directly on the computer. Of course, we can also run it on mobile phones.

\

\

\

If you can get to this point, your installation environment is fine. If you have problems, please refer to my Ubuntu SDK installation article. This most basic application has no content. In the following sections we add a few things to this to implement some of the things we need.

\

2) Add support for Qt

\

We can see that under the project’s “SRC” directory there are two directories: API and Scope. The code in the API directory is mainly for accessing our Web Service to get a JSON or XML data. In this project, we are not going to use the Client class in this directory. Interested developers can try to separate their client and Scope code. We first open the cmakelists. TXT file in “SRC” and add the following sentence: \

\

find_package(Qt5Network REQUIRED) 
find_package(Qt5Core REQUIRED)      

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

....

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

qt5_use_modules(scope Core Network)

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

\

As you can see, we added calls to Qt Core, XML, and Network libraries. At the same time, we are open “tests/unit/CMakeLists. TXT” file, and add “qt5_use_modules (scope – the unit – tests the Core Xml Network)” :

\

\

# Our test executable. # It includes the object code from the scope add_executable( scope-unit-tests scope/test-scope.cpp $<TARGET_OBJECTS:scope-static> ) # Link against the scope, and all of our test lib dependencies target_link_libraries( scope-unit-tests ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ${SCOPE_LDFLAGS} ${TEST_LDFLAGS} ${Boost_LIBRARIES} ) qt5_use_modules(scope-unit-tests Core Network) # Register the test  with CTest add_test( scope-unit-tests scope-unit-tests )Copy the code

\

The code for our first step can be found at the following url:

\

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

3) Code explanation

\

src/dianping-scope.cpp

This file defines a Unity ::scopes::ScopeBase class. It provides the starting interface that the client uses to interact with the Scope.

  • This class defines “start”, “stop”, and “run” to run the scope. Most developers don’t need to change most of the implementation of this class. In our routine, we will make simple changes as needed
  • It also implements two other methods: Search and Preview. We generally do not need to change the implementation of these two methods. But the functions they call must be implemented in a concrete file

Note: We can learn more about the Scope API by examining its header file. A more detailed description, developers can developer.ubuntu.com/api/scopes/… Look at it.

\

Let’s recompile our application, and if we have no errors, our Scope can run directly under the Desktop. Here we add a “QCoreApplication” variable. This is mainly so that we can use the signal/slot mechanism and build a Qt application. Let’s modify the scope.h file and add the QoreApplication variable app and forward declaration. We must also include a method “run”. \

\

class QCoreApplication; // added namespace scope { class Scope: public unity::scopes::ScopeBase { public: void start(std::string const&) override; void stop() override; void run(); // added unity::scopes::PreviewQueryBase::UPtr preview(const unity::scopes::Result&, const unity::scopes::ActionMetadata&) override; unity::scopes::SearchQueryBase::UPtr search( unity::scopes::CannedQuery const& q, unity::scopes::SearchMetadata const&) override; protected: api::Config::Ptr config_; QCoreApplication * app; // added };Copy the code

\

We also open scope.cpp and make the following changes:

\

#include <QCoreApplication> // added.void Scope::stop(a) {
    /* The stop method should release any resources, such as network connections where applicable */
    delete app;
}

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

src/dianping-query.cpp

\

This document defines a unity: : scopes: : SearchQueryBase class. \

This class is used to produce query results produced by user-supplied query strings. The result could be JSON or XML based. This class can be used to process and display the returned results.

  • Gets the query string entered by the user
  • Send the request to the Web service
  • Generate the results of the search (different for each scope)
  • Create search result categories (such as different layouts — Grid/Carousel)
  • Bind different categories based on different search results to display the UI we need
  • Push different categories to display to end users

Basically all the code is concentrated in the “run” method. Here we add a “QCoreApplication” variable. This is mainly so that we can use the signal/slot mechanism.

\

When we use Dianping to make a request, we need to sign our request to make a request. For more details, see “API Request Signature Generation Documentation” on LinkedReview. To do this, we have designed the following helper method getQueryString. The input parameter to this class is a search term, and the final output is a URL that can be accessed. The appKey and Secret here can be found in the code below. With this method, we can get a URL and open the link in the browser to see the JSON data we need to parse.

QString Query::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "Food";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = "20";
    map["platform"] = "2";

    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

    qDebug() < <"UTF-8: " << temp.toUtf8(a); generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper(a);// qDebug() << sign;

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

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

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

    qDebug() < <"Final url: " << url;
    return url;
}
Copy the code

The entire Query class is designed as follows:

\

#include <boost/algorithm/string/trim.hpp>

#include <scope/query.h>

#include <unity/scopes/Annotation.h>
#include <unity/scopes/CategorisedResult.h>
#include <unity/scopes/CategoryRenderer.h>
#include <unity/scopes/QueryBase.h>
#include <unity/scopes/SearchReply.h>

#include <iomanip>
#include <sstream>

// The following headers are added
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QCoreApplication>

namespace sc = unity::scopes;
namespace alg = boost::algorithm;

using namespace std;
using namespace api;
using namespace scope;
using namespace unity::scopes; // added

const QString appkey = "3562917596";
const QString secret = "091bf584e9d24edbbf48971d65307be3";
const QString BASE_URI = "http://api.dianping.com/v1/business/find_businesses?";

//Create a JSON string to be used tro create a category renderer - uses grid layout
std::string CR_GRID = R"( { "schema-version" : 1, "template" : { "category-layout" : "grid", "card-size": "small" }, "components" : {" title ":" title ", "art" : {" field ":" art ", "the aspect - thewire" : 1.6, "the fill - mode", "fit"}}})";

//Create a JSON string to be used tro create a category renderer - uses carousel layout
std::string CR_CAROUSEL = R"( { "schema-version" : 1, "template" : { "category-layout" : "carousel", "card-size": "small", "overlay" : True}, "components" : {" title ":" title ", "art" : {" field ":" art ", "the aspect - thewire" : 1.6, "the fill - mode", "fit"}}})";


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

void Query::cancelled(a) {
    client_.cancel(a); }void Query::run(sc::SearchReplyProxy const& reply) {
    // Firstly, we get the query string here.
    CannedQuery query = SearchQueryBase::query(a); QString queryString = QString::fromStdString(query.query_string());

    QString uri;
    if ( queryString.isEmpty() ) {
        queryString = QString("Beijing");
        uri = getQueryString(queryString);
    } else  {
        uri = getQueryString(queryString);
    }

    qDebug() < <"queryString: " << queryString;

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

    QString title = queryString + "Delicious";

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

    QEventLoop loop;

    QNetworkAccessManager manager;

    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));

    QObject::connect(&manager, &QNetworkAccessManager::finished,
            [reply, carousel, grid, this](QNetworkReply *msg){
                QByteArray data = msg->readAll(a); QString json = data;// qDebug() << "Data:" << json;
                QJsonParseError err;
                QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &err);

                if(err.error ! = QJsonParseError::NoError) {qCritical() < <"Failed to parse server data: " << err.errorString(a); }else {
                    // Find the "payload" array of results
                    QJsonObject obj = doc.object(a); QJsonArray results = obj["businesses"].toArray(a);// for each result
                    const std::shared_ptr<const Category> * top;

                    bool isgrid = false;
                    //loop through results of our web query with each result called 'result'
                    for(const auto &result : results) {

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

                        //create our categorized result using our pointer, which is either to out
                        //grid or our carousel Category
                        CategorisedResult catres((*top));

                        //treat result as Q JSON
                        QJsonObject resJ = result.toObject(a);// load up vars with from result
                        auto name = resJ["name"].toString(a);// qDebug() << "name: " << name;
                        name = removeTestInfo( name );

                        auto business_uri = resJ["business_url"].toString(a);// qDebug() << "business_uri: " << business_uri;

                        auto s_photo_uri = resJ["s_photo_url"].toString(a);// qDebug() << "s_photo_url: " << s_photo_uri;

                        auto photo_uri = resJ["photo_url"].toString(a);// qDebug() << "photo_url: " << photo_uri;

                        auto rating_s_img_uri = resJ["rating_s_img_url"].toString(a);// qDebug() << "rating_s_img_uri: " << rating_s_img_uri;

                        auto address = resJ["address"].toString(a);auto telephone = resJ["telephone"].toString(a);//set our CateogroisedResult object with out searchresults values
                        catres.set_uri(business_uri.toStdString());
                        catres.set_dnd_uri(business_uri.toStdString());
                        catres.set_title(name.toStdString());
                        catres.set_art(photo_uri.toStdString());

                        catres["address"] = Variant(address.toStdString());
                        catres["telephone"] = Variant(telephone.toStdString());

                        //push the categorized result to the client
                        if(! reply->push(catres)) {
                            break; // false from push() means search waas cancelled}}}});qDebug() < <"Uri:" << uri ;
    manager.get(QNetworkRequest(QUrl(uri)));
    loop.exec(a); }QString Query::getQueryString(QString query) {
    QMap<QString, QString> map;

    map["category"] = "Food";
    map["city"] = query;
    map["sort"] = "2";
    map["limit"] = "20";
    map["platform"] = "2";

    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

    qDebug() < <"UTF-8: " << temp.toUtf8(a); generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper(a);// qDebug() << sign;

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

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

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

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

// The following method is used to remove the
// "This is a test merchant data, only for test development, after the completion of development please apply for official data..." string
const QString TEST_STRING = "(This is a test merchant data, only for test development, after the completion of development please apply for official data...) ";
QString Query::removeTestInfo(QString name)
{
    if ( name.contains(TEST_STRING) ) {
        int index = name.indexOf(TEST_STRING);
        QString newName = name.left(index);
        // qDebug() << "newName: " << newName;
        return newName;
    } else {
        qDebug() < <"it does not contain the string";
        returnname; }}Copy the code

We can go to developer.dianping.com/ to register as a developer of dianping. In the url developer.dianping.com/app/tutoria… The development guide is available. You can use the getQueryString() method to get the REQUESTED URI.

Create and register CategoryRenderers

In this example, we create two JSON objects. These are primitive strings, as shown below, with two fields: template and Components. Template is used to define what layout to use to display the search results. Here we chose “grid” and the small card-size. The components TAB allows us to select a predefined field to display the results we want. Here we add “title” and “art”.

\

std::string CR_GRID = R"( { "schema-version" : 1, "template" : { "category-layout" : "grid", "card-size": "Small"}, "components" : {" title ":" title ", "art" : {" field ":" art ", "the aspect - thewire" : 1.6, "the fill - mode" : "fit" } } } )";Copy the code

\

More information about the CategoryRenderer class can be found in Docs.

We create a CategoryRenderer for each JSON Object and register it with the Reply Object:

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

    QString title = queryString + "Delicious";

    auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
    auto grid = reply->register_category("dianpinggrid".""."", rdrGrid);
Copy the code


\

We can run the program we got and see what we get. We can enter other cities in the “Search Query”, such as “Shanghai” or “Guangzhou”, and we will see different results.

\

\

src/dianping-preview.cpp

This document defines a unity: : scopes: : PreviewQueryBase class. \

This class defines a widget and a Layout to display our search results. This is a Preview result, as its name suggests.

  • Define the widgets needed during Preview
  • Map the widget to the data field found in the search
  • Define a different number of Layout columns (depending on the screen size)
  • Assign different widgets to different columns in a layout
  • Display the Reply instance in a Layout widget

Most of the code is in “run”. In the implementation. More about the introduction of this class can be in developer.ubuntu.com/api/scopes/… To find it.

Preview

Preview is needed to generate widgets and connect their fields to data items defined by CategorisedResult. It is also used to generate different layouts for different display environments, such as screen sizes. Generate a different number of columns depending on the display environment.

Preview Widgets

This is a set of predefined widgets. Each has a type. And according to this type we can generate them. You can find a list of Preview Widgets and the field types they provide here.

This example uses the following widgets

  • Header: It has a title and subtitle field
  • Image: It has a source field to show you where to get this art from
  • Text: It has a text field
  • Action: Displays a button with “Open” on it. When the user clicks, the contained URI is opened

Here is an example that defines a PreviewWidget called “headerId”. The second argument is its type “header”. \

 PreviewWidget w_header("headerId"."header");
Copy the code

The final procedure is as follows:

#include <scope/preview.h>

#include <unity/scopes/ColumnLayout.h>
#include <unity/scopes/PreviewWidget.h>
#include <unity/scopes/PreviewReply.h>
#include <unity/scopes/Result.h>
#include <unity/scopes/VariantBuilder.h>

#include <iostream>

#include <QString>

namespace sc = unity::scopes;

using namespace std;
using namespace scope;
using namespace unity::scopes;

Preview::Preview(const sc::Result &result, const sc::ActionMetadata &metadata) :
    sc::PreviewQueryBase(result, metadata) {
}

void Preview::cancelled(a) {}void Preview::run(sc::PreviewReplyProxy const& reply) {
    // Support three different column layouts
    // Client can display Previews differently depending on the context
    // By creates two layouts (one with one column, one with two) and then
    // adding widgets to them differently, Unity can pick the layout the
    // scope developer thinks is best for the mode
    ColumnLayout layout1col(1).layout2col(2);

    // add columns and widgets (by id) to layouts.
    // The single column layout gets one column and all widets
    layout1col.add_column({"headerId"."artId"."infoId"."telId"."actionsId"});
    // The two column layout gets two columns.
    // The first column gets the art and header widgets (by id)
    layout2col.add_column({"artId"."headerId"});
    // The second column gets the info and actions widgets
    layout2col.add_column({"infoId"."telId"."actionsId"});

    // Push the layouts into the PreviewReplyProxy intance, thus making them
    // available for use in Preview diplay
    reply->register_layout({layout1col, layout2col});

    //Create some widgets
    // header type first. note 'headerId' used in layouts
    // second field ('header) is a standard preview widget type
    PreviewWidget w_header("headerId"."header");
    // This maps the title field of the header widget (first param) to the
    // title field in the result to be displayed in this preview, thus providing
    // the result-specific data to the preview for display
    w_header.add_attribute_mapping("title"."title");

    // Standard subtitle field here gets our 'artist' key value
    // w_header.add_attribute_mapping("subtitle", "artist");

    PreviewWidget w_art("artId"."image");
    w_art.add_attribute_mapping("source"."art");

    PreviewWidget w_info("infoId"."text");
    w_info.add_attribute_mapping("text"."address");

    PreviewWidget w_tel("telId"."text");
    w_tel.add_attribute_mapping("text"."telephone");

    Result result = PreviewQueryBase::result(a);QString urlString(result["uri"].get_string().c_str());
    // qDebug() << "[Details] GET " << urlString;
   // QUrl url = QUrl(urlString);

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

    // Bundle out widgets as required into a PreviewWidgetList
    PreviewWidgetList widgets({w_header, w_art, w_info, w_tel, w_actions});
    // And push them to the PreviewReplyProxy as needed for use in the preview
    reply->push(widgets);
}
Copy the code

The operation effect is as follows:

  

\

\

\

Here’s how it works on the phone:

\

     \

\

The complete code can be seen at the following url:

bzr branch lp:~liu-xiao-guo/debiantrial/dianpingqtjson\

\

A more complete department Scope can be found at the url “Create a Department review Scope on Ubuntu OS (Qt XML)”.