Skip to content

04 Searching

Overview

Searching Functions

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 the ones of Tutorial04:

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

!!! note: When replacing an .ui file, the build system sometimes doesn't recognize that the file has changed. So edit the file (for example add and remove a space) and save it so that it's time stamp changes.

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

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 newly built.

Search basics

!!! important: Every search besides the Point Of Interest (POI) search will only take place in the currently opened map. To search in another map, you have to switch it to the one you would like to search in!

The Navigation-SDK 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 former town or postcode result by a name or name fragment of the street town result
SDK_SearchHouseNr() Search a house number in a former found town and street result town, street result
SDK_SearchCrossing() Search crossings in a former found 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 it's position -
SDK_InverseGeoCode() Search an address of a given position -

As you can see, some of the searches like the street search need a former result. You can 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 us 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.

There is one new action defined, called "actionSearchAddress".

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

Add the following code to your MainWindow class:

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() These set the flag mTimerEnabled which is checked in the updateTimer() function. This gives us the possibility to not call the run() function of the runnable in the timer loop (for example, if we show the search dialog, we do not want the map of the MainWindow to update, so we will stop the execution of the timer and wherefore the NavigationLoop with this flag).

Add/Change the following 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, go to 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 there geodecimal position.

Now comes a step by step introduction of the previous code to get a more detailed view:

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 item to search for
SDK_Position Pos Position For 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 be ignored
SDK_INT4 kind Kind of search request (see SearchKinds)

We set the cc to 0. This has no effect in our example map because there is only one country in this map. In a multi country map, an optional set cc will cause 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 want 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 which 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 would result in an error.

The 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. Generalization means that the last 1 to 3 digits of the postcode of will be replaced with dots dependent on the category of the town. Towns with cat 1 get the last 3 digits replaced, towns with cat 2 get the 2 digits replaced and finally towns with cat 3 get 1 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

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 flag not set:

The generalizePLZ flag is used for preprocessing the name, cc and postcode of a town. The category of a town will be represented by replacing the last digits in the postcode by dots. In category 1 towns (the lower the category, the bigger the town) 3 digits will be replaced, in category 3 towns, one will be replaced. Towns with a category higher than 3 will not get a replacement in the postcode. Additionally, the name of the town will be parsed automatically in the same way SDK_ParseTownName() will do. So, "Berlin[10115]" with category 1 will be returned as "D 10... Berlin".

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

and one without:

As you can see, the SDK has many possibilites to preprocess the search output.

To let the SDK know in what search result to search a single result, we have to provide the requestIndex parameter we previously got from our search to identiy 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 plz 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 \ref SDK_SearchResultVERSION
SDK_INT4 kind Kind of search request (see \ref 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

To get a result from the first search, you have to call SDK_GetResult() with the requestIndex == 1. To get a town result from the second town search, call it with searchRequestIndex == 2. If you want to get a result of the street search, call it with the requestIndex == 3. !!! important: As long as you do not destroy a search result, you can at every time access the result vectors of the different searches you performed. But have in mind, that every search consumes memory. To avoid memory leaks, call SDK_DestroySearchResult() on results you do not need anymore.

As we stated 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 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 use every time 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 the actionSearchAddressTriggered() 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, that means 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 that succeeds, we set the center of our map to the search result position and set a nice scale. After that, we reenable the timer loop.

Because we want to show 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 (only the "PNG" image format is supported) to a position on the map.

The SDK_AddImage() function has the following parameters:

Type Parameter Description
const SDK_WCHAR_T* pBitmapName Path to a bitmap (only the format PNG is supported)
const SDK_WCHAR_T* pText text centered on image
const SDK_Position* Position The coordinate of the image to be shown (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). So 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 button triggers a different search. The first a town, the second a street and third a house number search. The buttons show as a default text the search type. When this dialog is newly opened, only the town search button is activated. After clicking on it, the ResultListDialog will appear where you can search for a town. We use the ResultListDialog for every three search types, we only configure it at startup to search for the correct type. When a found town was clicked 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 at start. We also initialize the map and set the map center to Karlsruhe. Finally, we connect the buttons to the correspondent 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 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 / 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 on it. 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 everytime 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 dependent on the 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 running ResultListDialog:

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 provides the result to the ListView in the ResultListDialog. Open the searchdialog.ui and the resultlistdialog.ui files in the designer:

SearchModel

The SearchModel provides methods to search towns, streets and house numbers, to get the count of the 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 his 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 will be updated, it calls the data() method of the SearchModel to get information about what to show in a listview row. We call getResult() to get the appropriate result and return simply the name of it. The getResult() method takes care 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 it's index (which is in our case the row number of the clicked listview row).