在Ubuntu OS上创建一个department 点评Scope (Qt XML)

159 篇文章 2 订阅
137 篇文章 1 订阅

在前面的一些文章中,我们已经介绍了一些怎么利用Qt和C++ API来创建一个Scope。它们都是一些基本的Scope。在这篇文章中,我们将介绍department Scope,并掌握开发它的方法。Department Scope将会在许多的Scope中进行分类搜寻。更多关于Scope的介绍可以在网址http://developer.ubuntu.com/scopes/找到。我们最终的Scope的界面如下:


      


1)什么是department Scope


首先,坦率地说,我们很难找到一个很确切的中文词来描述它。姑且叫它部门Scope吧。在上面的左图上,我们可以看到在“ 美食”的正右边,有一个向下的下拉箭头。点击它后,就可以看到如中间图所示的菜单。这也就是说,我们可以对点评网的每个category进行分别地搜索,而不是把不同领域的搜索结果都罗列出来。比方,我想找吃的,我只想知道和餐馆相关的信息,而不要和美容,娱乐相关的搜寻结果。通过我们对点评API的 接口

http://api.dianping.com/v1/business/find_businesses?appkey=3562917596&sign=16B7FAB0AE9C04F356C9B1BE3BB3B77829F83EDA&category=美食&city=上海&latitude=31.18268013000488&longitude=121.42769622802734&sort=1&limit=20&offset_type=1&out_offset_type=1&platform=2

进行分析,我们可以把“category”设置为我们的部门,这样我们就可以对每个领域进行分别的搜寻。我们也可以通API接口来得到所有点评的category:

http://api.dianping.com/v1/metadata/get_categories_with_businesses

关于这个API的接口具体可以在 链接找到。

2)创建一个基本的Scope


首先,我们来打开我们的Ubuntu SDK来创建一个最基本的应用。我们选择菜单“New file or Project”或使用热键“Ctrl+N”。我们选择“Unity Scope”模版。



我们给我们的应用一个名字“dianping”。我们同事也选择template的类型为“Empty scope”:

  
这样我们就创建了一个最基本的scope。我们可以点击它,可能没有什么太多的功能。为了确保我们能够运行并看到我们的scope,我们点击“Projects”,并在“Run”中设置我们的“Run Configuration”使之成为“dianping”。




2)加入对Qt的支持



我们可以看到在项目的“ src”目录下有两个目录: apiscope。api目录下的代码主要是为了来访问我们的web service来得到一个json或是xml的数据。在这个项目中,我们并不准备采用这个目录中的client类。有兴趣的开发者可以尝试把自己的client和scope的代码分开。

我们首先打开在“ src”中的CMakeLists.txt文件,并加入如下的句子:

add_definitions(-DQT_NO_KEYWORDS)
find_package(Qt5Network REQUIRED)
find_package(Qt5Core REQUIRED)     
find_package(Qt5Xml REQUIRED)      

include_directories(${Qt5Core_INCLUDE_DIRS})    
include_directories(${Qt5Network_INCLUDE_DIRS})
include_directories(${Qt5Xml_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 Xml Network) 

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

我们可以看到,我们加入了对Qt Core,XML及Network库的调用。同时,我们也打开"tests/unit/CMakeLists.txt"文件,并加入“qt5_use_modules(scope-unit-tests 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 Xml Network)

# Register the test with CTest
add_test(
  scope-unit-tests
  scope-unit-tests
)

重新编译项目,如果还有编译错误错误,请修正。

我们同时需要对scope.cpp进行修改。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制及生成一个Qt应用。我们来修改scope.h文件,并加QoreApplication的变量app及类的forward申明。我们也必须同时加入一个方法"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
};

我们同时打开scope.cpp,并做如下的修改:

#include <QCoreApplication> // added

...

void Scope::stop() {
    /* The stop method should release any resources, such as network connections where applicable */
    delete app;
}

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

这样我们的每个scope其实也是一个Qt应用在运行。重新编译我们的Scope,并在desktop上运行。至此,我们基本对我们的框架加入了基本的Qt支持。在下面的环节中,我们来一步一步地完成我的其它的部分。

3)代码讲解

src/scope/scope.cpp


这个文件定义了一个unity::scopes::ScopeBase的类。它提供了客户端用来和Scope交互的起始接口。
  • 这个类定义了“start", "stop"及"run"来运行scope。绝大多数开发者并不需要修改这个类的大部分实现。在我们的例程中,我们将不做任何的修改
  • 它也同时实现了另外的两个方法:search 和 preview。我们一般来说不需要修改这俩个方法的实现。但是他们所调用的函数在具体的文件中必须实现
注意:我们可以通过研究Scope API的头文件来对API有更多的认识。更多的详细描述,开发者可以在 http://developer.ubuntu.com/api/scopes/sdk-14.10/查看。

在上一节中,我们已经基本上完成了对它的改造。对大多数的Scope来说,基本上我们不需要做很多的改变。对于我们的这个Scope,我们想使用cache来缓冲我们的数据,这样可以提高我们Scope的流畅度。这里我们对search函数做如下的修改:

sc::SearchQueryBase::UPtr Scope::search(const sc::CannedQuery &query,
                                        const sc::SearchMetadata &metadata) {

    const QString scopePath = QString::fromStdString(scope_directory());
    const QString cachePath =QString::fromStdString(cache_directory());

    // Boilerplate construction of Query
    return sc::SearchQueryBase::UPtr(new Query(query, metadata, scopePath,cachePath, config_));
}

同时我们也要对Query类中的构造函数进行修改,以便能够进行编译:

Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata, QString const& scopeDir,
        QString const& cacheDir, Config::Ptr config) :
        sc::SearchQueryBase( query, metadata ),
        m_scopeDir( scopeDir ),
        m_cacheDir( cacheDir ),
        client_(config)
{
    qDebug() << "CacheDir: " << m_cacheDir;
    qDebug() << "ScopeDir " <<  m_scopeDir;

    qDebug() << m_urlRSS;
}

当然,我们要记得在我们的Query头文件中加入数据变量m_scopeDir及m_cacheDir:

class Query: public unity::scopes::SearchQueryBase {

....

private:
    QString m_scopeDir;
    QString m_cacheDir;
    ....
}

重新编译我们的Scope。如果大家此时还有任何的问题的话,可以下载我的源码

bzr branch  lp:~liu-xiao-guo/debiantrial/dianpingdept1。

大家可以此为基础向下做练习。

src/scope/query.cpp


这个文件定义了一个unity::scopes::SearchQueryBase类。
这个类用来产生由用户提供的查询字符串而生产的查询结果。这个结果可能是基于json或是xml的。这个类可以用来进行对返回的结果处理并显示。

  • 得到由用户输入的查询字符串
  • 向web services发送请求
  • 生成搜索的结果(根据每个scope不同而不同)
  • 创建搜索结果category(比如不同的layout-- grid/carousel)
  • 根据不同的搜寻结果来绑定不同的category以显示我们所需要的UI
  • 推送不同的category来显示给最终用户
  • 基本上所有的代码集中在"run"方法中。这里我们加入了一个”QCoreApplication”变量。这主要是为了我们能够使用signal/slot机制。
接下来我们对“run”进行修改来达到搜寻的目的。对 dianping API的接口来说,我们需要对其输入的URL进行签名。为了方便,我定义了如下的helper方法。

QString Query::getUrl(QString addr, QMap<QString, QString> map) {
    QCryptographicHash generator(QCryptographicHash::Sha1);

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

    temp.append(secret);

    qDebug() << temp;

    qDebug() << "UTF-8: " << temp.toUtf8();

    generator.addData(temp.toUtf8());
    QString sign = generator.result().toHex().toUpper();

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

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

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

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

这里用到的“ appKey”及“ secret”是两个定义的QString常量。开发者需要到点评的 网站进行申请。这里的addr就是请求的url的前面部分,比如http://api.dianping.com/v1/metadata/get_categories_with_businesses。这里的map实际上是像如下的一组数据,用来存储请求的参数的。我们利用这个方法来得到我们的department的url。如下:

Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata, QString const& scopeDir,
        QString const& cacheDir, Config::Ptr config) :
        sc::SearchQueryBase( query, metadata ),
        m_scopeDir( scopeDir ),
        m_cacheDir( cacheDir ),
        // m_limit( 0 ),
        client_(config)
{
    qDebug() << "CacheDir: " << m_cacheDir;
    qDebug() << "ScopeDir " <<  m_scopeDir;

    QMap<QString,QString> map;
    map["format"] = "xml";

    m_urlRSS = getUrl(DEPARTMENTS,  map);
    qDebug() << "m_urlRSS: " << m_urlRSS;
}

我们可以把上面的代码加到Query类的构造函数中。这里的DEPARTMENTS定义如下:

const QString DEPARTMENTS = "http://api.dianping.com/v1/metadata/get_categories_with_businesses?";

我们可以通过打印的方式打印出来到Application Output窗口中:

m_urlRSS:  "http://api.dianping.com/v1/metadata/get_categories_with_businesses?appkey=3562917596&sign=4BAF8DD42A36538E17207A1C10F819571B00BF6E&format=xml"

如果我们把得到的url输入到浏览器中,我们会发现:




接下来,我们需要通过网路请求的方式得到上面的xml格式的数据并对它进行解析。为了能够得到我们需要的departments,我们对“run”方法做如下的修改:

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Create an instance of disk cache and set cache directory
    m_diskCache = new QNetworkDiskCache();
    m_diskCache->setCacheDirectory(m_cacheDir);

    QEventLoop loop;

    QNetworkAccessManager managerDepts;
    QObject::connect(&managerDepts, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&managerDepts, &QNetworkAccessManager::finished,
                     [reply,this](QNetworkReply *msg){
        if( msg->error()!= QNetworkReply::NoError ){
            qWarning() << "failed to retrieve raw data, error:" << msg->error();
            rssError(reply,ERROR_Connection);
            return;
        }
        QByteArray data = msg->readAll();

        // qDebug() << "XML data is: " << data.data();

        QString deptUrl = rssDepartments( data, reply );

        CannedQuery cannedQuery = query();
        QString deptId = qstr(cannedQuery.department_id());
        qDebug() << "department id: " << deptId;

        if (!query().department_id().empty()){ // needs departments support
            qDebug() << "it is not empty xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!";
            deptUrl = m_depts[deptId];
            qDebug() << "depatUrl: " << deptUrl;
        } else {
            qDebug() << "It is empty ===================================!";
        }

        if ( deptUrl.isEmpty() )
            return;
   });
    managerDepts.setCache(m_diskCache);
    managerDepts.get(QNetworkRequest(QUrl(m_urlRSS)));
    loop.exec();
}

这里其实很简单,我们通过对m_urlRSS的请求,并把得到的结果传给rssDepartments来解释所得到的xml格式的数据。每一个department都有一个叫做department_id来识别。它是一个独有的区别其他的String。rss_Departments的实现如下:

QString Query::rssDepartments( QByteArray &data, unity::scopes::SearchReplyProxy const& reply ) {
    QDomElement docElem;
    QDomDocument xmldoc;
    DepartmentList rss_depts;
    QString firstname = "";

    CannedQuery myquery( SCOPE_NAME );
    myquery.set_department_id( TOP_DEPT_NAME );

    Department::SPtr topDept;

    if ( !xmldoc.setContent(data) ) {
        qWarning()<<"Error importing data";
        return firstname;
    }

    docElem = xmldoc.firstChildElement("results");
    if (docElem.isNull()) {
        qWarning() << "Error in data," << "results" << " not found";
        return firstname;
    }

    docElem = docElem.firstChildElement("categories");
    if ( docElem.isNull() ) {
        qWarning() << "Error in data," << "categories" << " not found";
        return firstname;
    }

    docElem = docElem.firstChildElement("category");

    // Clear the previous departments since the URL may change according to settings
    m_depts.clear();

    int index = 0;
    while ( !docElem.isNull() ) {

        QString category = docElem.attribute("name","");
        qDebug() << "category: " << category;

        if ( !category.isEmpty() ) {
            QString url = getDeptUrl(category);

            QString deptId = QString::number(index);

            if (firstname.isEmpty()) {
                //Create the url here
                firstname = url;
                topDept = move(unity::scopes::Department::create( "",
                                                                  myquery, category.toStdString()));
            } else {
                Department::SPtr aDept = move( unity::scopes::Department::create( deptId.toStdString(),
                                              myquery, category.toStdString() ) );
                rss_depts.insert( rss_depts.end(), aDept );
            }

            m_depts.insert( QString::number(index), url );
            index++;
        }

        docElem = docElem.nextSiblingElement("category");
    }

    // Dump the deparmemts
    QMapIterator<QString, QString> i(m_depts);
    while (i.hasNext()) {
        i.next();
         qDebug() << i.key() << ": " << i.value();
    }

    topDept->set_subdepartments( rss_depts );

     try {
        reply->register_departments( topDept );
    } catch (std::exception const& e) {
        qWarning() << "Error happened: " << e.what();
    }

    return firstname;
}

这个方法通过解析,并生产相应的department。完整的代码可以在

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

运行我们的Scope。我们可以看到所生产的department。



现在显然我们还看不到任何东西因为我们没有对我们的department进行搜寻。接下来,我们可以按照文章“ 怎么在Ubuntu Scope中获取location地址信息”来设置获得我们所需要的位置信息。在手机上,我们可以通过网路或GPS来获得我们所需要的位置信息。在电脑上目前还没有支持。通过获得的位置信息,我们通过点评对当地的位置进行搜索。

我们接下来对“run”更进一步地修改来对我们得到的department进行查询:

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Initialize the scopes
    initScope();

    // Get the current location of the search
    auto metadata = search_metadata();
    if ( metadata.has_location() ) {
        qDebug() << "Location is supported!";
        auto location = metadata.location();

        if ( location.has_altitude()) {
            cerr << "altitude: " << location.altitude() << endl;
            cerr << "longitude: " << location.longitude() << endl;
            cerr << "latitude: " << location.latitude() << endl;
            auto latitude = std::to_string(location.latitude());
            auto longitude = std::to_string(location.longitude());
            m_longitude = QString::fromStdString(longitude);
            m_latitude = QString::fromStdString(latitude);
        }

        if ( m_longitude.isEmpty() ) {
            m_longitude = DEFAULT_LONGITUDE;
        }
        if ( m_latitude.isEmpty() ) {
            m_latitude = DEFAULT_LATITUDE;
        }

        qDebug() << "m_longitude1: " << m_longitude;
        qDebug() << "m_latitude1: " << m_latitude;
    } else {
        qDebug() << "Location is not supported!";
        m_longitude = DEFAULT_LONGITUDE;
        m_latitude = DEFAULT_LATITUDE;
    }

    // Create an instance of disk cache and set cache directory
    m_diskCache = new QNetworkDiskCache();
    m_diskCache->setCacheDirectory(m_cacheDir);

    QEventLoop loop;

    QNetworkAccessManager managerDepts;
    QObject::connect(&managerDepts, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
    QObject::connect(&managerDepts, &QNetworkAccessManager::finished,
                     [reply,this](QNetworkReply *msg){
        if( msg->error()!= QNetworkReply::NoError ){
            qWarning() << "failed to retrieve raw data, error:" << msg->error();
            rssError(reply,ERROR_Connection);
            return;
        }
        QByteArray data = msg->readAll();

        // qDebug() << "XML data is: " << data.data();

        QString deptUrl = rssDepartments( data, reply );

        CannedQuery cannedQuery = query();
        QString deptId = qstr(cannedQuery.department_id());
        qDebug() << "department id: " << deptId;

        if (!query().department_id().empty()){ // needs departments support
            qDebug() << "it is not empty xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!";
            deptUrl = m_depts[deptId];
            qDebug() << "depatUrl: " << deptUrl;
        } else {
            qDebug() << "It is empty ===================================!";
        }

        if ( deptUrl.isEmpty() )
            return;

        QEventLoop loop;
        QNetworkAccessManager managerRSS;
        QObject::connect( &managerRSS, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
        QObject::connect( &managerRSS, &QNetworkAccessManager::finished,
                         [reply,this](QNetworkReply *msg ){
            if( msg->error() != QNetworkReply::NoError ){
                qWarning() << "failed to retrieve specific dept raw data, error:" <<msg->error();
                rssError( reply, ERROR_Connection );
                return;
            }

            QByteArray data = msg->readAll();
            if( query().query_string().empty() ){
                rssImporter( data, reply, CATEGORY_HEADER );
            } else {
                rssImporter( data, reply, CATEGORY_SEARCH );
            }

        });
        managerRSS.setCache( m_diskCache );
        managerRSS.get( QNetworkRequest( QUrl(deptUrl)) );
        loop.exec();

    });
    managerDepts.setCache(m_diskCache);
    managerDepts.get(QNetworkRequest(QUrl(m_urlRSS)));
    loop.exec();
}

上面我们可以看到我们定义了另外一个QEventLoop。在这里,我们通过对刚才所得到的deptUrl做一个新的请求,并把得到的数据传到rssImporter函数中进行解析。

void Query::rssImporter(QByteArray &data, unity::scopes::SearchReplyProxy const& reply, QString title) {
    QDomElement docElem;
    QDomDocument xmldoc;
    CannedQuery cannedQuery = query();
    QString query = qstr( cannedQuery.query_string() );

    if ( !xmldoc.setContent( data ) ) {
        qWarning()<<"Error importing data";
        return;
    }

    docElem = xmldoc.documentElement();
    //find result
    docElem = docElem.firstChildElement("businesses");
    if (docElem.isNull()) {
        qWarning()<<"Error in data,"<< "result" <<" not found";
        return;
    }

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

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

    docElem = docElem.firstChildElement("business");

    while (!docElem.isNull()) {
        QString business_id = docElem.firstChildElement("business_id").text();
        // qDebug() << "business_id: " << business_id;

        QString name = docElem.firstChildElement("name").text();
        // qDebug() << "name: "  << name;

        // Let's get rid of the test info in the string
        name = removeTestInfo(name);

        QString branch_name = docElem.firstChildElement("branch_name").text();
        // qDebug() << "branch_name: " << branch_name;

        QString address = docElem.firstChildElement("address").text();
        // qDebug() << "address: " << address;

        QString telephone = docElem.firstChildElement("telephone").text();
        // qDebug() << "telephone: " << telephone;

        QString city = docElem.firstChildElement("city").text();
        // qDebug() << "city: " << city;

        QString photo_url = docElem.firstChildElement("photo_url").text();
        // qDebug() << "photo_url: " << photo_url;

        QString s_photo_url = docElem.firstChildElement("s_photo_url").text();
        // qDebug() << "s_photo_url: " << s_photo_url;

        QString rating_s_img_uri = docElem.firstChildElement("rating_s_img_uri").text();
        // qDebug() << "rating_s_img_uri: " << rating_s_img_uri;

        QString business_url = docElem.firstChildElement("business_url").text();
        // qDebug() << "business_url: " << business_url;

        QDomElement deals = docElem.firstChildElement("deals");
        QDomElement deal = deals.firstChildElement("deal");
        QString summary = deal.firstChildElement("description").text();
        // qDebug() << "Summary: " << summary;

        if ( !query.isEmpty() ) {
            if ( !name.contains( query, Qt::CaseInsensitive ) &&
                 !summary.contains( query, Qt::CaseInsensitive ) &&
                 !address.contains( query, Qt::CaseInsensitive ) ) {
                qDebug() << "it is going to be skipped";
                docElem = docElem.nextSiblingElement("business");
                continue;
            } else {
                qDebug() << "it is going to be listed!";
            }
        }

        docElem = docElem.nextSiblingElement("business");

        // for each result
        const std::shared_ptr<const Category> * top;

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

        CategorisedResult catres((*top));

        catres.set_uri(business_url.toStdString());
        catres.set_dnd_uri(business_url.toStdString());
        catres.set_title(name.toStdString());
        catres["subtitle"] = address.toStdString();
        catres["summary"] = summary.toStdString();
        catres["fulldesc"] = summary.toStdString();
        catres.set_art(photo_url.toStdString());
        catres["art2"] = s_photo_url.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()<<"parsing ended";
}

请注意,我们在上面的代码中使用了如下的代码一对每个department所搜寻的结果再次根据在search输入框中所输入的字符串进行匹配,从而可以更加缩小所显示的内容:

        if ( !query.isEmpty() ) {
            if ( !name.contains( query, Qt::CaseInsensitive ) &&
                 !summary.contains( query, Qt::CaseInsensitive ) &&
                 !address.contains( query, Qt::CaseInsensitive ) ) {
                qDebug() << "it is going to be skipped";
                docElem = docElem.nextSiblingElement("business");
                continue;
            } else {
                qDebug() << "it is going to be listed!";
            }
        }


创建并注册CategoryRenderers

在本例中,我们创建了两个JSON objects. 它们是最原始的字符串,如下所示,它有两个field:template及components。template是用来定义是用什么layout来显示我们所搜索到的结果。这里我们选择的是”grid"及小的card-size。components项可以用来让我们选择预先定义好的field来显示我们所需要的结果。这里我们添加了"title"及“art"。

[html]  view plain copy
  1. std::string CR_GRID = R"(  
  2.     {  
  3.         "schema-version" : 1,  
  4.         "template" : {  
  5.             "category-layout" : "grid",  
  6.             "card-size": "small"  
  7.         },  
  8.         "components" : {  
  9.             "title" : "title",  
  10.             "art" : {  
  11.                 "field": "art",  
  12.                 "aspect-ratio": 1.6,  
  13.                 "fill-mode": "fit"  
  14.             }  
  15.         }  
  16.     }  
  17. )";  

更多关于 CategoryRenderer 类的介绍可以在 docs找到。

我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册:

[cpp]  view plain copy
  1. CategoryRenderer rdrGrid(CR_GRID);  
  2. CategoryRenderer rdrCarousel(CR_CAROUSEL);  
  3.   
  4. QString title = queryString + "美味";  
  5.   
  6. auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);  
  7. auto grid = reply->register_category("dianpinggrid""""", rdrGrid);  


我们可以把得到的数据通过qDebug的方式进行打印并调试。这里加入的代码很多,你可以在如下的地址下载我的代码:


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

运行我们的Scope, 我们可以看到如下的画面:

  

大家可以点击图片。我们其实还没完成preview的部分,在下面我们会加入更多的信息来显示我们所得到的信息。我们可以输入一个字符串并使得list的内容更少。比如,我们可以输入”朝阳区“来更进一步地缩小我们的显示的list。

src/dianping-preview.cpp

这个文件定义了一个unity::scopes::PreviewQueryBase类。

这个类定义了一个widget及一个layout来展示我们搜索到的结果。这是一个preview结i果,就像它的名字所描述的那样。

  • 定义在preview时所需要的widget
  • 让widget和搜索到的数据field一一对应起来
  • 定义不同数量的layout列(由屏幕的尺寸来定)
  • 把不同的widget分配到layout中的不同列中
  • 把reply实例显示到layout的widget中

大多数的代码在“run&quot;中实现。跟多关于这个类的介绍可以在http://developer.ubuntu.com/api/scopes/sdk-14.10/previewwidgets/找到。

Preview

Preview需要来生成widget并连接它们的field到CategorisedResult所定义的数据项中。它同时也用来为不同的显示环境(比如屏幕尺寸)生成不同的layout。根据不同的显示环境来生成不同数量的column。

Preview Widgets

这是一组预先定义好的widgets。每个都有一个类型。更据这个类型我们可以生成它们。你可以在这里找到Preview Widget列表及它们提供的的field类型。

这个例子使用了如下的widgets

  • header:它有title及subtitle field
  • image:它有source field有来显示从哪里得到这个art
  • text:它有text field
  • action:用来展示一个有"Open"的按钮。当用户点击时,所包含的URI将被打开

如下是一个例子,它定义了一个叫做“headerId"的PreviewWidget。第二个参数是它的类型"header"。

[cpp]  view plain copy
  1. PreviewWidget w_header("headerId""header");  

我们的代码在如下的地址可以找到:

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


重新运行我们的Scope,我们可以看到如下的画面。在Preview的画面中,我们可以看到更多的信息,比如电话号码。同时我们可以点击“Open”,并进入到网页看到更多的信息。

  

  


4)加入设置


我们在这里想对dianping Scope做一个设置。比如我想有更多的搜寻的结果,而不是每次只有最多20个。我们可以通过文章“ 如何在Ubuntu Scope中定义设置变量并读取”来多我们的limit进行设置。首先,在Query类中加入函数:

// The followoing function is used to retrieve the settings for the scope
void Query::initScope()
{
    qDebug() << "Going to retrieve the settings!";

    unity::scopes::VariantMap config = settings();  // The settings method is provided by the base class
    if (config.empty())
        qDebug() << "CONFIG EMPTY!";

    m_limit = config["limit"].get_double();
    cerr << "limit: " << m_limit << endl;
}

并在“run”的开始部分调用它:

void Query::run(sc::SearchReplyProxy const& reply) {
    qDebug() <<  "Run is started .............................!";

    // Initialize the scopes
    initScope();

 ....
}

同时不要忘记在“data”目录下生产相应的.ini文件(/dianping/data/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini)。其内容如下:

[limit]
type = number
defaultValue = 20
displayName = 搜寻条数

我们也同时需要对“data”目录下的CMakeLists.txt文件进行修改。添加如下的部分到其中:

configure_file(
  "com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
  "${CMAKE_BINARY_DIR}/src/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
)

INSTALL(
  FILES "${CMAKE_BINARY_DIR}/src/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini"
  DESTINATION "${SCOPE_INSTALL_DIR}"
)

我们可以运行一下“Run CMake”,这样,我们在Project中可以看到新添加的.ini文件。重新运行我们的Scope,并在Scope的右上角的设置图标(像有锯齿的 )去尝试改变limit的值,看看效果是什么样的。

  

我们也可以同时修改“data”目录下的logo及icon文件来使得我们的Scope更像一个branded Scope。最终所有的源码可以在如下的地址下载:

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



5)调试Scope



当我们在开发应用时,我们可以通过上面的“cerr”在“Application Output”输出结果来查看结果。当在手机运行时,我们也可以通过查看如下的文件来看Scope的运行情况:



我们可以通过查看在手机中的文件“~/.cache/upstart/scope-registry.log”来看最新的Scope的运行情况。


大家如果有什么建议,请告诉我。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值