Create a department review Scope (Qt XML) on Ubuntu OS

Create a department review Scope (Qt XML) on Ubuntu OS

In the previous articles, we have introduced some how to use Qt and C++ API to create a Scope. They are all basic scopes. In this article, we will introduce department Scope and master the methods of developing it. Department Scope will perform classification searches in many Scopes. More information about Scope can be found at developer.ubuntu.com/scopes/ . The interface of our final Scope is as follows:

\

      \

\

1) What is department Scope

\

First of all, frankly, it is difficult for us to find a very accurate Chinese word to describe it. Let's call it Scope for the time being. On the left picture above, we can see that on the right of " Food ", there is a downward drop-down arrow. After clicking it, you can see the menu as shown in the middle image. This means that we can search for each category of Dianping.com, instead of listing all the search results in different fields. For example, I want to find food. I only want to know information related to restaurants, not search results related to beauty and entertainment. Through our review of API interfaces :

\

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


For analysis, we can set "category" as our department, so that we can search for each field separately. We can also get the category of all reviews through the API interface:

\

http://api.dianping.com/v1/metadata/get_categories_with_businessesCopy code


About this interface specific API can be found in the link found.

\

2) Create a basic Scope

\

1. let's open our Ubuntu SDK to create a basic application. We select the menu "New file or Project" or use the hotkey "Ctrl+N". We choose the "Unity Scope" template./

\

\

\

We give our application a name "dianping". Our colleagues also choose the template type as "Empty scope":\

\

  \

In this way, we have created a most basic scope. We can click on it, there may not be too many functions. To ensure that we can run and see our scope, we click on "Projects" and set our "Run Configuration" in "Run" to make it "dianping".

\

\

\

2) Add support for Qt

\

\

We can see that there are two directories under the " src " directory of the project : 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 adopt 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:

\

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} ) Copy code

\

We can see that we have added calls to Qt Core, XML and Network libraries. At the same time, we also open the "tests/unit/CMakeLists.txt" file and add "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 ) Copy code

\

Recompile the project, if there are still compilation errors, please correct them.

\

We also need to modify scope.cpp. Here we have added a "QCoreApplication" variable. This is mainly for us to be able to use the signal/slot mechanism and generate a Qt application. Let's modify the scope.h file and add the forward declaration of the QoreApplication variable app and class. We must also add a method "run" at the same time.

\

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 code

\

We open scope.cpp at the same time and make the following changes:

\

# 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 ); } Copy code

\

In this way, each of our scopes is actually a Qt application running. Recompile our Scope and run it on the desktop. So far, we have basically added basic Qt support to our framework. In the following links, we will complete the other parts of mine step by step.

\

3) Code explanation

src/scope/scope.cpp

**
**

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

  • This class defines "start", "stop" and "run" to run the scope. Most developers do not need to modify most of the implementation of this class. In our routine, we will not make any changes
  • It also implements two other methods at the same time: search and preview. We generally do not need to modify the implementation of these two methods. But the functions they call must be implemented in specific files

Note: We can learn more about the API by studying the header file of the Scope API. For more detailed descriptions, developers can view it at developer.ubuntu.com/api/scopes/ .../

\

In the previous section, we have basically completed the transformation of it. For most scopes, basically we don't need to make many changes. For our Scope, we want to use cache to buffer our data, which can improve the fluency of our Scope. Here we make the following modifications to the search function:

\

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_)); } Copy code


At the same time, we have to modify the constructor in the Query class to be able to compile:

\

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; } Copy code

\

Of course, we must remember to add the data variables m_scopeDir and m_cacheDir to our Query header file:

\

class Query : public unity::scopes::SearchQueryBase { .... private : QString m_scopeDir; QString m_cacheDir; .... } Copy code


Recompile our scope. If you have any questions at this time, you can download my source code

\

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

\

You can do exercises on this basis.

\

src/scope/query.cpp

\

This file defines a unity::scopes::SearchQueryBase class.
This class is used to generate query results produced by the query string provided by the user. This result may be based on json or xml. This class can be used to process and display the returned results.
\

  • Get the query string entered by the user
  • Send request to web services
  • Generate search results (different according to each scope)
  • Create search result categories (such as different layouts-- grid/carousel)
  • Bind different categories according to 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 have added a "QCoreApplication" variable. This is mainly for us to be able to use the signal/slot mechanism.

Next, we modify the "run" to achieve the search purpose. For the interface of dianping API , we need to sign the URL entered. For convenience, I defined the following helper method.

\

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; } Copy code


The " appKey " and " secret " used here are two defined QString constants. Developers need to review the site to apply. The addr here is the front part of the requested url, such as http://api.dianping.com/v1/metadata/get_categories_with_businesses. The map here is actually a set of data like the following, which is used to store the requested parameters. We use this method to get the url of our department. as follows:

\

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; } Copy code

\

We can add the above code to the constructor of the Query class. DEPARTMENTS here is defined as follows:

\

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

\

We can print it out to the Application Output window by printing:

\

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

\

If we enter the obtained url into the browser, we will find:

\

\

\

\

Next, we need to obtain the above xml format data and parse it through a network request. In order to be able to get the departments we need, we make the following modifications to the "run" method:

\

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 (); } Copy code


This is actually very simple. We interpret the data in xml format by requesting m_urlRSS and passing the result to rssDepartments. Each department has a department_id to identify it. It is a unique difference from other Strings. The implementation of rss_Departments is as follows:

\

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; } Copy code

\

This method parses and produces the corresponding department. The complete code can be found in

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

\

Run our Scope. We can see the department produced.

\

\

\

Now obviously we can't see anything because we haven't searched our department. Next, we can follow the article " How to get location address information in Ubuntu Scope " to set up and get the location information we need. On mobile phones, we can obtain the location information we need through the Internet or GPS. There is currently no support on the computer. Through the obtained location information, we search for local locations through reviews.

\

We next further modify the "run" to query the department we get:

\

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 (); } Copy code


Above we can see that we have defined another QEventLoop. Here, we make a new request for the deptUrl we just got, and pass the obtained data to the rssImporter function for analysis.

\

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" ; } Copy code

\

Please note that we used the following code in the above code to match the search results of each department again according to the string entered in the search input box, so that the displayed content can be narrowed down:

\

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!" ; } } Copy code

\

\

Create and register CategoryRenderers

In this example, we created two JSON objects. They are the most primitive string, as shown below, it has two fields: template and components. The template is used to define what layout is used to display the results we searched for. Here we choose "grid" and a small card-size. The components item can be used to allow us to select a pre-defined field to display the results we need. Here we have added "title" and "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. )";  

\

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

We created a CategoryRenderer for each JSON Object and registered with the reply object at the same time:

[cpp]   view plain copy

  1. CategoryRenderer rdrGrid(CR_GRID);  
  2. CategoryRenderer rdrCarousel(CR_CAROUSEL);  
  3.   
  4. QString title = queryString + "delicious";  
  5.   
  6. auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);  
  7. auto grid = reply->register_category("dianpinggrid", "", "", rdrGrid);  

\

\

We can print and debug the data obtained by qDebug. There are a lot of codes added here, you can download my code at the following address:

\

\

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

\

Run our Scope, we can see the following screen:

\

  \

\

You can click on the picture. We haven't finished the preview part yet, we will add more information below to show the information we got. We can enter a string and make the list less content. For example, we can enter "Chaoyang District" to further narrow our displayed list.

\

src/dianping-preview.cpp

This file defines a unity::scopes::PreviewQueryBase class./

This class defines a widget and a layout to display the results we found. This is a preview result, as its name describes.

  • Define the widgets needed during preview
  • Let the widget and the searched data field one-to-one correspondence
  • Define different number of layout columns (determined by the size of the screen)
  • Assign different widgets to different columns in the layout
  • Display the reply instance in the widget of layout

Most of the code is implemented in "run". More information about this class can be found at developer.ubuntu.com/api/scopes/ ...

Preview

Preview needs to generate widgets and connect their fields to the data items defined by CategorisedResult . It is also used to generate different layouts for different display environments (such as screen sizes). According to different display environments, different numbers of columns are generated.

Preview Widgets

This is a set of pre-defined widgets. Each has a type. 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 title and subtitle fields
  • image: It has a source field to show where the art is obtained
  • text: it has a text field
  • action: Used to display a button with "Open". When the user clicks, the contained URI will be opened

The following is an example, which defines a PreviewWidget called "headerId". The second parameter is its type "header"./

[cpp]   view plain copy

  1. PreviewWidget w_header("headerId", "header");  
  2. \

Our code can be found at the following address:

\

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

\

\

Re-run our Scope, we can see the following screen. In the Preview screen, we can see more information, such as phone numbers. At the same time, we can click "Open" and enter the web page to see more information.

\

  /

\

  /

\

\

4) Join the setting

\

We want to make a setting for dianping Scope here. For example, I want to have more search results instead of only 20 at most each time. We can use the article " How to define and read setting variables in Ubuntu Scope " to set our limit. 1. add a function to the Query class:

\

//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; } Copy code


And call it at the beginning of "run":

\

void Query::run (sc::SearchReplyProxy const & reply) { qDebug () << "Run is started .......................... ...!" ; //Initialize the scopes initScope (); .... } Copy code

\

At the same time, don't forget to produce the corresponding .ini file (/dianping/data/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini) in the "data" directory. Its contents are as follows:

\

[limit] type = number defaultValue = 20 displayName = number of searches Copy code

\

We also need to modify the CMakeLists.txt file in the "data" directory. Add the following part to it:

\

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}" ) Copy code


We can run "Run CMake" so that we can see the newly added .ini file in the Project. Re-run our Scope, and try to change the limit value in the setting icon (like a sawtooth) in the upper right corner of the Scope to see what the effect is.

\

  /

\

We can also modify the logo and icon files in the "data" directory at the same time to make our Scope more like a branded Scope. Finally, all the source code can be downloaded at the following address:

\

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

\

Or at the address: github.com/liu-xiao-gu...

\

\

5) Debug Scope

\

\

When we are developing an application, we can view the result by outputting the result of the above "cerr" in the "Application Output". When running on a mobile phone, we can also view the operation of Scope by viewing the following files:

\

\

\

We can look at the file "~/.cache/upstart/scope-registry.log" in the mobile phone to see the latest Scope operation.

\

\

If you have any suggestions, please let me know.

\

\

\