在前面的一些文章中,我们已经介绍了一些怎么利用Qt和C++ API来创建一个Scope。它们都是一些基本的Scope。在这篇文章中,我们将介绍department Scope,并掌握开发它的方法。Department Scope将会在许多的Scope中进行分类搜寻。更多关于Scope的介绍可以在网址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”目录下有两个目录: api及 scope。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有更多的认识。更多的详细描述,开发者可以在 developer.ubuntu.com/api/scopes/…查看。\
\
在上一节中,我们已经基本上完成了对它的改造。对大多数的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
- std::string CR_GRID = R"(
- {
- "schema-version" : 1,
- "template" : {
- "category-layout" : "grid",
- "card-size": "small"
- },
- "components" : {
- "title" : "title",
- "art" : {
- "field": "art",
- "aspect-ratio": 1.6,
- "fill-mode": "fit"
- }
- }
- }
- )";
\
更多关于 CategoryRenderer 类的介绍可以在 docs找到。
我们为每个JSON Object创建了一个CategoryRenderer,并同时向reply object注册:
[cpp] view plain copy
- CategoryRenderer rdrGrid(CR_GRID);
- CategoryRenderer rdrCarousel(CR_CAROUSEL);
- QString title = queryString + "美味";
- auto carousel = reply->register_category("dianpingcarousel", title.toStdString(), "", rdrCarousel);
- 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"中实现。跟多关于这个类的介绍可以在developer.ubuntu.com/api/scopes/…找到。
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
- 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的运行情况。
\
\
大家如果有什么建议,请告诉我。
\
\
\