Skip to content

04 Searching

Overview

This tutorial will teach you how to search for locations with adresses or parts of it.

Base

You can use your results from Tutorial03 as a base for our new project.

Prerequisites

We add two new dialogs to our project. A search overview and a result dialog.

Add the following files from the Tutorial04 directory to your Tutorial folder:

  • SearchDialog.cpp
  • SearchDialog.h
  • searchdialog.ui
  • ResultListDialog.cpp
  • ResultListDialog.h
  • resultlistdialog.ui
  • SearchModel.cpp
  • SearchModel.h

Also, replace the following files with those of Tutorial04:

  • MapWidget.cpp
  • MapWidget.h
  • mainwindow.ui

Note

When replacing an .ui file, the build system sometimes doesn't recognize the file has change. As a workaround simply modify the file (for example add and remove a space) and save it so that its time stamp changes.

Edit the CMakelists.txt file and add the new files:

set(Tutorial_RES
    ...
    searchdialog.ui
    resultlistdialog.ui
    ...
)
set(Tutorial_SRCS
    ...
    SearchDialog.cpp
    ResultListDialog.cpp
    SearchModel.cpp
)

set(Tutorial_HEADER    
    ...
    SearchDialog.h
    ResultListDialog.h
    SearchModel.h
    ...
)

Call cmake ../ in the build directory or build the ALL_BUILD project of the solution to update it. Also, always trigger a rebuild after copying to ensure that replaced files will be rebuilt.

Search basics

Important

The search functions use the country code to determine the right search map on all platforms (except Windows CE), manually switching the map is not needed (and the current map will not be switched by the search functions). Search requests with a country code of 0 will search in the current map. POI searches take place in a given layer which specifies the POI category. They are also independent of the current map.

The NavigationSDK provides multiple possibilities for searching towns, streets in towns, house numbers in streets, point of interests and much more:

Search method Description Depends on
SDK_SearchTown() Search a town by a given name or name fragment -
SDK_SearchStreet() Search a street in a given town or postcode result by a name or name fragment of the street town result
SDK_SearchHouseNr() Search a house number in a given town and street result town, street result
SDK_SearchCrossing() Search crossings in a given street result street result
SDK_SearchPOI() Search POIs with a given name around a position or globally -
SDK_SearchPostcode() Search a town by postcode (also 6 digit NL and 7 digit UK postcodes) -
SDK_GeoCode() Search a complete given address and return its position -
SDK_InverseGeoCode() Search an address of a given position -

As you can see, some of the searches like street search depend on previous search results. You can for example only search a street in a previous found town and you can only search house numbers in a known street of a known town.

Every search is built up by the following steps:

First, let's add a menu entry to our MainWindow. For now, we use it to start the search and output the search result to the console. Later on, we will use it to switch to a dialog where we can search a town, street and houser number.

We deine a new action called "actionSearchAddress".

As you can see, we connected the action to the slot actionSearchAddressTriggered(), which we will implement in our MainWindow:

MainWindow.h:

protected slots: 
...
    void actionSearchAddressTriggered(bool triggered);

MainWindow.cpp:

 MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{   
    ...
    connect(ui.actionSearchAddress, SIGNAL(triggered(bool)), this, SLOT(actionSearchAddressTriggered(bool)));
}

void MainWindow::actionSearchAddressTriggered(bool triggered)
{
    disableNavigationLoopTimer();

    SDK_SearchRequest request;
    SDK_InitSearchRequest(&request);

    request.cc = 0;
    request.kind = SDK_SK_TOWNBYNAME_SORTED;
    request.request = L"karls";
    request.context = 0;

    SDK_UINT4 requestIndex = 0;
    SDK_INT4 searchResultCount = -1;

    SDK_ERROR rc = SDK_SearchTown(&request, 100, &requestIndex, &searchResultCount, SDK_TRUE, 0);

    SDK_SearchResult result;
    SDK_InitSearchResult(&result);

    for (SDK_INT4 i = 0; i < searchResultCount; i++)
    {           
        rc = SDK_GetResult(requestIndex, i, &result, SDK_FALSE);
        QString resultName = QString::fromWCharArray(result.name);
        SDK_INT4 latitude, longitude;
        SDK_TransformCoordinates(0, SDK_cf_MERCATOR, result.pos.x, result.pos.y, SDK_cf_GEODEZ, &longitude, &latitude);
        std::string temp = resultName.toStdString();
        qDebug("Result %i: %s, Longitude: %f, Latitude: %f", i, temp.c_str(), (float)longitude / SDK_GEOCONVERSION_FACTOR, (float)latitude / SDK_GEOCONVERSION_FACTOR); 


    }

    enableNavigationLoopTimer();
}

We also implemented two new functions disableNavigationLoopTimer and enableNavigationLoopTimer() setting the flag mTimerEnabled which is checked in the updateTimer() function. This avoids calling the run() function of the runnable in the timer loop. This is useful for example, when showing the search dialog and we do not want the map of the MainWindow to update.

Add/Change the following code in the MainWindow class:

MainWindow.h:

...
protected:
    ...
    void enableNavigationLoopTimer();
    void disableNavigationLoopTimer();
...
private:
    bool mTimerEnabled;

MainWindow.cpp:

void MainWindow::enableNavigationLoopTimer()
{
    mTimerEnabled = true;
}

void MainWindow::disableNavigationLoopTimer()
{
    mTimerEnabled = false;
}

void MainWindow::updateTimer()
{
    if(mTimerEnabled)
        mNavigationLoop->run();
}

Start the program, open the menu, select Search->Address and take a look at the "Output" window. We get a bunch of cities which all have a name beginning with "karls" and their geodecimal positions.

Now we take a more detailed view of the previous code:

First, we fill out the SDK_SearchRequest "request" with the needed parameters in MainWindow::actionSearchAddressTriggered():

    SDK_SearchRequest request;
    SDK_InitSearchRequest(&request);

    request.cc = 0;
    request.kind = SDK_SK_TOWNBYNAME_SORTED;
    request.request = L"karls";

The struct SDK_SearchRequest has the following members:

Type Name Description
SDK_HDRINFO size Size of struct - for controlling
SDK_HDRINFO version Version of struct - for controlling. Use SDK_SearchRequestVERSION
const SDK_WCHAR_T* request String representation of the item to search for
SDK_Position Pos Position for a nearest or circle search
const SDK_SearchResult* context Reserved for future implementations. Set to NULL
SDK_INT2 cc Numerical country code. Available for SDK_SearchTown & SDK_SearchPostCode. Set to 0 to ignore
SDK_INT4 kind Kind of search request (see SearchKinds)

We set the cc to 0. This has no effect in our example map, because this map only contains one country. In a multi country map, an optional cc will tell the SDK to return only results that matches this country code. If set to 0, the result will not be filtered by the cc, all towns that matches the search request will be returned. We like to search for towns and the results should be ordered in a reasonable way (biggest towns first, then districts of towns), so we set the kind to SDK_SK_TOWNBYNAME_SORTED. We want to search for all towns with their names begin with "karls", so we set the request string appropriately.

Attention

The search request type must match the search function, you cannot call SDK_SearchTown()with a request type of SDK_SK_HNR. This will result in an error.

Valid search kinds for the various searches are as following:

Search function Valid types
SDK_SearchTown() SDK_SK_TOWNBYNAME, SDK_SK_TOWNBYNAME_SORTED, SDK_SK_NEARESTTOWN
SDK_SearchStreet() SDK_SK_STREETBYNAME, SDK_SK_STREETBYNAME_SORTED
SDK_SearchHouseNr() SDK_SK_HNR, SDK_SK_HNR_FRAGMENTED
SDK_SearchCrossing() SDK_SK_CROSSING
SDK_SearchPOI() SDK_SA_NEAREST, SDK_SA_CIRCUIT, SDK_SA_NAME, SDK_SA_GLOBAL
SDK_SearchPostcode() SDK_SK_TOWNBYPOSTCODE, SDK_SK_NL6POSTCODE, SDK_SK_UK7POSTCODE
SDK_GeoCode() not used
SDK_InverseGeoCode() not used

The search itself is called by:

    SDK_UINT4 requestIndex = 0;
    SDK_INT4 searchResultCount = -1;

    SDK_ERROR rc = SDK_SearchTown(&request, 100, &requestIndex, &searchResultCount, SDK_TRUE, 0);

The signature of the search function is:

SDK_SearchTown(const SDK_SearchRequest * pSearchRequest, SDK_INT4 MaxSearchResult, SDK_UINT4 *pSearchRequestIndex, SDK_INT4 * pFoundSearchResult, SDK_BOOL bOneEntryPerTown, SDK_ProgressCBFuncT pCallBackFunc);

The parameters are:

Type Parameter Description
const SDK_SearchRequest* pSearchRequest The above filled request
SDK_INT4 MaxSearchResult The maxium number of results that should be returned. We set this to 100
SDK_UINT4* pSearchRequestIndex An index set by the search to identify this particular search. It will be needed to retrieve the results from this search later on
SDK_INT4* pFoundSearchResult Will be set by the search to the result count
SDK_BOOL bOneEntryPerTown When set, towns which have the same name but different postcodes will be filtered out
SDK_ProgressCBFuncT pCallBackFunc An optional callback function (we do not use it for now, so we set it to 0)

To get a single result, we call:

    rc = SDK_GetResult(requestIndex, i, &result, SDK_TRUE);

The parameters are:

Type Parameter Description
SDK_UINT4 SearchRequestIndex Index of the search result
SDK_UINT4 MaxSearchResult Row index of the wanted result
SDK_SearchResult* SearchResult The referenced address will be set to pointer to the matching result
SDK_BOOL generalizePLZ Flag whether the post code should be generalized and the name should be preprocessed by SDK_ParseTownName(). Only used for town searches.

The result output can be formatted and manipulated with the flags bOneEntryPerTown of the method SDK_SearchTown() and generalizePLZ from the SDK_GetResult() function. The flag bOneEntryPerTown in SDK_SearchTown() will cause the SDK to show only one entry for a town. If not set, all districts will also be shown.

The following is an example for a town result with the flag "bOneEntryPerTown" set:

And the without the flag set:

The generalizePLZ flag is used for preprocessing the name, cc and postcode of a town. Generalization means, that the trailing one to three digits of the postcode will be replaced with dots dependent on the category of the town. Towns with category 1 get the last three digits replaced, towns with cat 2 get the last two digits replaced and finally towns with cat 3 get the last digit replaced. Also, the town name will be preprocessed automatically with the functionality given in SDK_ParseTownName(). Karlsruhe[76131] with category 3 will be returned as D 7613. Karlsruhe and Berlin[10115] with category 1 will be returned as D 10... Berlin

Here are two example outputs, one with "generalizePLZ" set:

And one without:

To let the SDK know what search result to use for further searches, we have to provide the requestIndex parameter we previously got from our search to identify the result set. The second parameter is the index of the result in the result set you want to retrieve, the third will be filled with the result and the fourth is for changing the postcode string in a town search.

The struct SDK_SearchResult has the following members:

Type Name Description
SDK_HDRINFO size Size of struct - for controlling
SDK_HDRINFO version Version of struct - for controlling. Use SDK_SearchResultVERSION
SDK_INT4 kind Kind of search request (see SearchKinds)
SDK_WCHAR_T name[256] Name of the item
SDK_UINT4 addrOffset Internal use only
SDK_UINT2 cc Numerical country code of the item
SDK_UINT1 cat Category. For towns: [1..6] Lower value means more important
SDK_UINT1 type Type of returned item, mostly the same as the kind in SDK_SearchRequest
SDK_INT4 tabOffset Internal use only
SDK_UINT4 usrOffset Internal use only
SDK_Position pos Position of the search result
SDK_UINT4 searchRequestIndex The request index of the found result. Only used internally

The output is generated by:

rc = SDK_GetResult(requestIndex, i, &result, SDK_FALSE);
        QString resultName = QString::fromWCharArray(result.name);
        SDK_INT4 latitude, longitude;
        SDK_TransformCoordinates(0, SDK_cf_MERCATOR, result.pos.x, result.pos.y, SDK_cf_GEODEZ, &longitude, &latitude);
        qDebug("Result %i: %s, Longitude: %f, Latitude: %f", i, resultName.toLatin1(), (float)longitude / SDK_GEOCONVERSION_FACTOR, (float)latitude / SDK_GEOCONVERSION_FACTOR);

We print out the result name and the geodecimal position of the result.
Because the geodecimal position is hold internally as an integer value multiplied by the factor SDK_GEOCONVERSION_FACTOR, we have to cast the latitude and longitude values to float and divide it by this factor.

As you can see in the code above, we call SDK_GetResult() with the searchRequestIndex value that we got by calling SDK_SearchTown(). Every single search executed by one of the search functions has it's own search result index to allow access to multiple search results at a time.

As an example, you can do the following:

Search a town -> SDK_SearchTown returns searchRequestIndex = 1
Search another town -> SDK_SearchTown returns searchRequestIndex = 2
Search a street -> SDK_SearchStreet returns searchRequestIndex = 3

Important

As long as you do not destroy a search result, you can access the result vectors of the different searches you performed at any time. Please have in mind, that every search consumes memory. To avoid memory leaks, call SDK_DestroySearchResult() on results you do not need anymore.

As mentioned at the beginning of this tutorial, the map used for a search is the current set map. If you have searched a town in map A and switched to another map B, a consecutive search - for instance for a street in the result found in map A - will also take place in map A. The SDK takes care to always use the correct map for dependent searches.

Note

Searches that are not dependent on a former found result (like SDK_SearchTown()) will always search in the current set map. Searches that are dependent on a former search result will use the map of the former search result.

The next thing we do is to implement an interactive address search. For this, we have to add and change some code in our project:

Change the actionSearchAddressTriggered() method in the MainWindow

To be able to open a search dialog, we remove the sample search code from the actionSearchAddressTriggered() method and replace it. So this method should look like this:

Add/Change the MainWindow class:

MainWindow.cpp:

..
include "SearchDialog.h"
...
void MainWindow::actionSearchAddressTriggered(bool triggered)
{
    disableNavigationLoopTimer();

    SearchDialog dialog(this);
    int retVal = dialog.exec();

    // get the result from the search dialog
    SDK_SearchResult result;
    SDK_InitSearchResult(&result);

    SDK_ERROR rc = dialog.getCurrentSearchResult(result);

    if (retVal == QDialog::Accepted && rc == SDK_ERROR_Ok)
    {
        ui.map->setCenter(result.pos);
        ui.map->setScale(2500);
    }

    enableNavigationLoopTimer();
}

First we disable the timer to avoid updating the map when the new dialog is shown. When the dialog returns with Qt::Accepted, meaning that either the user clicked the "Ok" button or a search result entry in the listview, we try to get the clicked result from the dialog. If succesful we set the center of our map to the search result position and set a nice scale. After that, we enable the timer loop again.

For showing a pin on the map at the position of a search result, we added a function to add and to remove a pin on the MapWidget:

MapWidget.cpp:

MapWidget::MapWidget(QWidget *parent)
    : QWidget(parent)
{   
    ...
    mPinId = -1;
    ...
}

void MapWidget::setPin(const SDK_Position& pos)
{
    if (mPinId < 0)
        SDK_AddImage(L"pin.png", L"", &pos, &mPinId);
    else
        SDK_PositionImage(mPinId, &pos);
}

void MapWidget::removePin()
{
    if (mPinId >= 0)
    {
        SDK_DeleteImage(mPinId);
    mPinId = -1;
    }
}

The SDK_AddImage() function is the right starting point for adding images to a position on the map. Only the PNG image format is supported.

The SDK_AddImage() function has the following parameters:

Type Parameter Description
const SDK_WCHAR_T* pBitmapName Path to a bitmap (only PNG)
const SDK_WCHAR_T* pText Text centered on the image
const SDK_Position* Position The coordinate where to place the image (Mercator)
SDK_INT4* pImageId The id of the added image. Will be filled by the SDK

If the pBitmapName is given with a full path, the image at this path is used. If we omit a path, the SDK will search in the data directory for the image (which was set in the SDK_Initialize() function). Here we set the pBitmapName to L"pin.png", because our image resides in our data folder. We do not want text on the image, so we set this to L"". The returned pin id in pImageId is used later for repositioning or deleting the image.
SDK_PositionImage() is straight forward, it will reposition the formerly created image with the given id to a new position.
SDK_DeleteImage() will remove the image from the map.

Compile the example. When you click the menu button "Search->Address", the SearchDialog will appear:

This dialog is the starting point for all the searches we implement in this chapter. When you click on the town button, the ResultListDialog will be shown where you can start a search for a town:

SearchDialog

Open the searchdialog.ui file in the Qt designer:

The dialog consists of three buttons and a MapWidget. Each of the three buttons triggers a different search, namely a town, a street and a house number search. The buttons are labeled with the search type.

When opening this dialog, only the town search button is activated. After clicking, the ResultListDialog will show up where you can search for a town. We use the ResultListDialog for all three search types, we configure it at startup to search for the correct type.

When clicking on one of the found towns in the listview, the ResultListDialog closes, the town button text of the SearchDialog will be set to the name of the selected result, the position of the result will be shown on the MapWidget and the street button will be activated. The same will happen when searching for a street or a house number.

Now we will take a deeper look into the used classes:

SearchDialog.cpp:

#include "../navigationsdk//SDKInterface.h"
#include "SearchDialog.h"
#include "ResultListDialog.h"

SearchDialog::SearchDialog(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);

    ui.pushTown->setEnabled(true);
    ui.pushStreet->setEnabled(false);
    ui.pushHNR->setEnabled(false);

    ui.map->initialize();

    SDK_Position pos;
    SDK_InitPosition(&pos);

    pos.x = 934790;
    pos.y = 6269440; // this should be around karlsruhe...
    ui.map->setCenter(pos);

    mCurrentSearchType = 0;

    connect(ui.pushTown, SIGNAL(clicked()), this, SLOT(townButtonClicked()));
    connect(ui.pushStreet, SIGNAL(clicked()), this, SLOT(streetButtonClicked()));
    connect(ui.pushHNR, SIGNAL(clicked()), this, SLOT(hnrButtonClicked()));

}
In the constructor, we initalize the buttons so that only the town button is active on startup. We also initialize the map and set the map center to Karlsruhe. Finally, we connect the buttons to the corresponding slots.

void SearchDialog::townButtonClicked()
{
    ResultListDialog dialog(this);
    dialog.setSearchKind(SDK_SK_TOWNBYNAME_SORTED);
    int retVal = dialog.exec();
    if (retVal == QDialog::Accepted)
    {
        SDK_ERROR rc = dialog.getSearchResult(mCurrentTownResult);
        if (rc == SDK_ERROR_Ok)
        {
            mCurrentSearchType = SDK_SK_TOWNBYNAME;
            updateUi(SDK_SK_TOWNBYNAME);
        }
    }
}

void SearchDialog::streetButtonClicked()
{
    ResultListDialog dialog(this);
    dialog.setSearchKind(SDK_SK_STREETBYNAME);
    dialog.setFormerTownResult(mCurrentTownResult);
    int retVal = dialog.exec();
    if (retVal == QDialog::Accepted)
    {
        SDK_ERROR rc = dialog.getSearchResult(mCurrentStreetResult);
        if (rc == SDK_ERROR_Ok)
        {
            mCurrentSearchType = SDK_SK_STREETBYNAME;
            updateUi(SDK_SK_STREETBYNAME);
        }
    }
}

void SearchDialog::hnrButtonClicked()
{
    ResultListDialog dialog(this);
    dialog.setSearchKind(SDK_SK_HNR);
    dialog.setFormerTownResult(mCurrentTownResult);
    dialog.setFormerStreetResult(mCurrentStreetResult);
    int retVal = dialog.exec();
    if (retVal == QDialog::Accepted)
    {
        SDK_ERROR rc = dialog.getSearchResult(mCurrentHNRResult);
        if (rc == SDK_ERROR_Ok)
        {
            mCurrentSearchType = SDK_SK_HNR;
            updateUi(SDK_SK_HNR);
        }
    }
}

When clicking on one of the buttons, a new ResultListDialog will be created in the appropriate search mode (town, street or house number). When a street or house number search was triggered, we also set the town or town and street results to the ResultListDialog, so it can call the search functions appropriately. When the ResultListDialog returns with QDialog::Accepted, the user has clicked a search result item in the listview or the "Ok" button. We try to get the search result by calling:

SDK_ERROR rc = dialog.getSearchResult();
If the error code is SDK_ERROR_Ok (the user has clicked an item in the listview and not the "Ok" button), we update the buttons and the map by calling updateUi():
void SearchDialog::updateUi(SDK_INT4 kind)
{
    SDK_Position pos;
    SDK_InitPosition(&pos);

    SDK_INT4 mapScale = 2500;

    switch (kind)
    {
        case SDK_SK_TOWNBYNAME:
            ui.pushTown->setText(QString::fromWCharArray(mCurrentTownResult.name));
            ui.pushStreet->setEnabled(true);
            ui.pushStreet->setText("Street");
            ui.pushHNR->setText("House Number");
            ui.pushHNR->setEnabled(false);
            mapScale = 2500;
            pos = mCurrentTownResult.pos;
            break;
        case SDK_SK_STREETBYNAME:
            ui.pushStreet->setText(QString::fromWCharArray(mCurrentStreetResult.name));
            ui.pushHNR->setEnabled(true);
            mapScale = 200;
            pos = mCurrentStreetResult.pos;
            break;
        case SDK_SK_HNR:
            ui.pushHNR->setText(QString::fromWCharArray(mCurrentHNRResult.name));
            mapScale = 100;
            pos = mCurrentHNRResult.pos;
            break;
    }

    ui.map->setPin(pos);
    ui.map->setCenter(pos);
    ui.map->setScale(mapScale);
    update();
}
We set a nice scale dependent on the search type, set the text of the buttons and a pin on the map at the position of the search result.

To get the found result also in our MainWindow, we add the following:

SDK_ERROR SearchDialog::getCurrentSearchResult(SDK_SearchResult& result)
{
    SDK_InitSearchResult(&result);

    SDK_ERROR retVal = SDK_ERROR_Ok;

    if (mCurrentSearchType == 0)
        return SDK_ERROR_InvalidParameter;

    switch (mCurrentSearchType)
    {
        case SDK_SK_TOWNBYNAME:
            result = mCurrentTownResult;
            break;
        case SDK_SK_STREETBYNAME:
            result = mCurrentStreetResult;
            break;
        case SDK_SK_HNR:
            result = mCurrentHNRResult;
            break;
        default:
            retVal = SDK_ERROR_InvalidParameter;
    }

    return retVal;
}

The SearchDialog looks like this after a town search:

ResultListDialog

Open the resultlistdialog.ui file with the Qt designer. It should look like this:

This dialog has a text input field at the top and will show search results in a listview.

ResultListDialog.cpp:

#include "../navigationsdk//SDKInterface.h"
#include "ResultListDialog.h"

ResultListDialog::ResultListDialog(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);
    mResult = 0;
    ui.listView->setModel(&mModel);
    connect(ui.lineEditSearch, SIGNAL(textEdited(const QString&)), this, SLOT(onSearch(const QString&)));
    connect(ui.listView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(itemCLicked(const QModelIndex&)));
}

In the constructor, we connect the signal textEdited() of the edit field with the function onSearch(), so every time the text changes, we trigger a new search. We also connect the listview clicked signal to the slot itemClicked(), so we get informaed when the user selects an item in the listview.

Because we use the ResultListDialog for all searches, we have to set the search type externally by calling:

void ResultListDialog::setSearchKind(SDK_INT4 type)
{
    mType = type;
}

onSearch() is triggered when the text of the edit field has changed. In this case we will call the appropriate search function from SearchModel according to the set search type:

void ResultListDialog::onSearch(const QString& text)
{
    if (mType == SDK_SK_TOWNBYNAME || mType == SDK_SK_TOWNBYNAME_SORTED)
    {
        mModel.searchTown(text);
    }
    else if (mType == SDK_SK_STREETBYNAME)
    {
        mModel.searchStreet(text, mFormerTownResult);
    }
    else if (mType == SDK_SK_HNR)
    {
        mModel.searchHNR(text, mFormerTownResult, mFormerStreetResult);
    }
}

When an item in the listview was clicked, itemClicked() will be called and the asocciated result will be copied to mResult, which can later be retrieved from outside the class. After this, we call accept() to close the dialog:

void ResultListDialog::itemCLicked(const QModelIndex &index)
{
    if (mResult)
        delete mResult;

    mResult = new SDK_SearchResult;
    SDK_InitSearchResult(mResult);

    SDK_ERROR rc = mModel.getResult(index.row(), *mResult);
    accept();
}

The ResultListDialog in action:

To feed the ResultListDialog with data, we use the class SearchModel which we provide with this tutorial. The model will execute the current search and provide the result to the ListView in the ResultListDialog.

SearchModel

The SearchModel provides methods to search towns, streets and house numbers, to get the number of results and the results itself. It is subclassed from QAbstractListModel, so we can set it directly as a data source for our ResultListDialog listview.

The SearchModel class has three search functions:

  • searchTown()
  • searchStreet()
  • searchHNR()

Take a look at them:

void SearchModel::searchTown(QString name)
{
    mKind = SDK_SK_TOWNBYNAME_SORTED;
    resetTownSearch();
    SDK_SearchRequest request;
    SDK_InitSearchRequest(&request);
    request.cc = 0;
    request.kind = mKind;
    wchar_t nameW[255] = { 0 };
    name.toWCharArray(nameW);
    request.request = nameW;
    SDK_ERROR rc = SDK_SearchTown(&request, MAXTOWNSEARCHRESULTCOUNT, &mTownResultIndex, &mTownResultCount, SDK_TRUE, 0);
    QModelIndex topLeft = createIndex(0, 0);
    QModelIndex topBottomRight = createIndex(mTownResultCount-1, 0);
    emit dataChanged(topLeft, topBottomRight);
}

void SearchModel::searchStreet(QString name, const SDK_SearchResult& formerTownResult)
{
    mKind = SDK_SK_STREETBYNAME;
    resetStreetSearch();
    SDK_SearchRequest request;
    SDK_InitSearchRequest(&request);
    request.cc = 0;
    request.kind = mKind;
    wchar_t nameW[255] = { 0 };
    name.toWCharArray(nameW);
    request.request = nameW;
    SDK_ERROR rc = SDK_SearchStreet(&request, MAXSTREETSEARCHRESULTCOUNT, &mStreetResultIndex, &formerTownResult, &mStreetResultCount, 0);
    QModelIndex topLeft = createIndex(0, 0);
    QModelIndex topBottomRight = createIndex(mStreetResultCount - 1, 0);
    emit dataChanged(topLeft, topBottomRight);
}

void SearchModel::searchHNR(QString name, const SDK_SearchResult& formerTownResult, const SDK_SearchResult& formerStreetResult)
{
    mKind = SDK_SK_HNR;
    resetHNRSearch();
    SDK_SearchRequest request;
    SDK_InitSearchRequest(&request);
    request.cc = 0;
    request.kind = mKind;
    wchar_t nameW[255] = { 0 };
    name.toWCharArray(nameW);
    request.request = nameW;
    SDK_ERROR rc = SDK_SearchHouseNr(&request, MAXHNRSEARCHRESULTCOUNT, &mHNRResultIndex, &formerTownResult, &formerStreetResult, &mHNRResultCount, 0);
    QModelIndex topLeft = createIndex(0, 0);
    QModelIndex topBottomRight = createIndex(mHNRResultCount - 1, 0);
    emit dataChanged(topLeft, topBottomRight);
}

The search methods look very similar to the first search we created in the MainWindow. We fill a request and start the appropriate search. The street and house number searches are a bit different. A street can only be searched in a former found town and a house number search needs both, a town and a street result. So the street search will be fed with a former town search result, and the house number search needs a town and a street search result.

To inform the listview that its data has changed, we call:

    QModelIndex topLeft = createIndex(0, 0);
    QModelIndex topBottomRight = createIndex(mTownResultCount-1, 0);
    emit dataChanged(topLeft, topBottomRight);

When the listview of ResultListDialog gets updated, it calls the data() method of the SearchModel for getting information about what to show in a listview row. We call getResult() to retrieve the appropriate result and simply return the name of it. The getResult() method takes care about which result should be returned by checking the variable mKind.

QVariant SearchModel::data(const QModelIndex &modelIndex, int role) const
{
    SDK_SearchResult result;
    SDK_InitSearchResult(&result);

    int index = modelIndex.row();

    if (role == Qt::DisplayRole)
    {
        SDK_ERROR rc = getResult(index, result);
        return QString::fromWCharArray(result.name);
    }

    return QVariant();
}

SDK_ERROR SearchModel::getResult(SDK_INT4 index, SDK_SearchResult& result) const
{
    SDK_ERROR rc = SDK_ERROR_NoResultFound;
    if (mTownResultIndex != -1 && (mKind == SDK_SK_TOWNBYNAME || mKind == SDK_SK_TOWNBYNAME_SORTED))
    {
        if (index >= 0 && index < mTownResultCount)
        {
            rc = SDK_GetResult(mTownResultIndex, index, &result, SDK_TRUE);
        }
    }
    else if (mStreetResultIndex != -1 && (mKind == SDK_SK_STREETBYNAME))
    {
        if (index >= 0 && index < mStreetResultCount)
        {
            rc = SDK_GetResult(mStreetResultIndex, index, &result, SDK_TRUE);
        }
    }
    else if (mHNRResultIndex != -1 && (mKind == SDK_SK_HNR))
    {
        if (index >= 0 && index < mHNRResultCount)
        {
            rc = SDK_GetResult(mHNRResultIndex, index, &result, SDK_TRUE);
        }
    }
    return rc;
}

This is also similar to the former example of the search. We check which search was executed and call the SDK_GetResult() function with the correct search result index (which identifies the search) and get the result by its index (which is in our case the row number of the clicked listview row).