poi-yelp: change to libqtappfw navigation interface
[apps/poi-yelp.git] / MainApp.cpp
1 #include <QNetworkReply>
2 #include <QNetworkRequest>
3 #include <QUrl>
4 #include <QUrlQuery>
5 #include <QNetworkProxy>
6 #include <QTreeWidget>
7 #include <iostream>
8 #include <error.h>
9 #include <json-c/json.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 #define __STDC_FORMAT_MACROS
13 #include <inttypes.h>
14
15 #include "MainApp.h"
16 #include "Business.h"
17 #include "InfoPanel.h"
18 #include "ClickableLabel.h"
19 #include "Keyboard.h"
20 #include "traces.h"
21
22 #include <navigation.h>
23
24 #define NAVI_CONFIG_FILEPATH    "/etc/naviconfig.ini"
25
26 #define DEFAULT_TEXT        "Select your destination with Yelp !"
27 #define URL_AUTH            "https://api.yelp.com/oauth2/token"
28 #define URL_AUTOCOMPLETE    "https://api.yelp.com/v3/autocomplete"
29 #define URL_SEARCH          "https://api.yelp.com/v3/businesses/search"
30
31 #define LEFT_OFFSET         28
32 #define FONT_SIZE_LINEDIT   20
33 #define FONT_SIZE_LIST      18
34 #define TEXT_INPUT_WIDTH    800
35 #define SEARCH_BTN_SIZE     105
36 #define SPACER              15
37 #define WIDGET_WIDTH        (SEARCH_BTN_SIZE + SPACER + TEXT_INPUT_WIDTH)
38 #define DISPLAY_WIDTH       TEXT_INPUT_WIDTH
39 #define DISPLAY_HEIGHT      480
40 #define COMPLETE_W_WITH_KB    1080
41 #define COMPLETE_H_WITH_KB    1488
42 #define RESULT_ITEM_HEIGHT  80
43 #define MARGINS             25
44 #define AGL_REFRESH_DELAY   75 /* milliseconds */
45
46 #define SCROLLBAR_STYLE \
47 "QScrollBar:vertical {" \
48 "    border: 2px solid grey;" \
49 "    background: gray;" \
50 "    width: 45px;" \
51 "}"
52
53 using namespace std;
54
55 MainApp::MainApp(Navigation *navigation):QMainWindow(Q_NULLPTR, Qt::FramelessWindowHint),
56     networkManager(this),searchBtn(QIcon(tr(":/images/loupe-90.png")), tr(""), this),
57     lineEdit(this),keyboard(QRect(0, 688, COMPLETE_W_WITH_KB, 720), this),
58     mutex(QMutex::Recursive),token(""),currentSearchingText(""),currentSearchedText(""),
59     pSearchReply(NULL),pInfoPanel(NULL),pResultList(NULL),currentLatitude(36.136261),currentLongitude(-115.151254),
60     navicoreSession(0),currentIndex(0),fontId(-1),isInfoScreen(false),
61     isInputDisplayed(false),isKeyboard(false),isAglNavi(false), naviapi(navigation)
62 {
63     //this->setAttribute(Qt::WA_TranslucentBackground);
64     this->setStyleSheet("border: none;");
65
66     searchBtn.setStyleSheet("border: none; color: #FFFFFF;");
67     searchBtn.setMinimumSize(QSize(SEARCH_BTN_SIZE, SEARCH_BTN_SIZE));
68     searchBtn.setIconSize(searchBtn.size());
69     searchBtn.setGeometry(QRect(LEFT_OFFSET, 0, searchBtn.width(), searchBtn.height()));
70
71     lineEdit.setStyleSheet("border: none; color: #FFFFFF;");
72     lineEdit.setMinimumSize(QSize(TEXT_INPUT_WIDTH, SEARCH_BTN_SIZE));
73
74     lineEdit.setPlaceholderText(QString(DEFAULT_TEXT));
75     font = lineEdit.font();
76     font.setPointSize(FONT_SIZE_LINEDIT);
77     lineEdit.setFont(font);
78     lineEdit.setTextMargins(MARGINS/2, 0, 0, 0);
79     lineEdit.installEventFilter(this);
80     lineEdit.setGeometry(QRect(LEFT_OFFSET + searchBtn.width() + SPACER, 0, lineEdit.width(), lineEdit.height()));
81     lineEdit.setVisible(false);
82
83
84     QFile file(NAVI_CONFIG_FILEPATH);
85     if (file.open(QIODevice::ReadOnly | QIODevice::Text))
86     {
87         QByteArray data = file.readAll();
88         QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
89         QJsonObject jsonObj(jsonDoc.object());
90
91         if (jsonObj.contains("latitude"))
92             currentLatitude = jsonObj["latitude"].toDouble();
93
94         if (jsonObj.contains("longitude"))
95             currentLongitude = jsonObj["longitude"].toDouble();
96     }
97
98     /* We might need a Japanese font: */
99     QFile fontFile(":/fonts/DroidSansJapanese.ttf");
100     if (!fontFile.open(QIODevice::ReadOnly))
101     {
102         TRACE_ERROR("failed to open font file");
103     }
104     else
105     {
106         QByteArray fontData = fontFile.readAll();
107         fontId = QFontDatabase::addApplicationFontFromData(fontData);
108         if (fontId < 0)
109         {
110             TRACE_ERROR("QFontDatabase::addApplicationFontFromData failed");
111         }
112     }
113
114     /* Check if "AGL_NAVI" env variable is set. If yes, we must notify
115      * AGL environment when surface needs to be resized */
116     if (getenv("AGL_NAVI"))
117         isAglNavi = true;
118
119     connect(this, SIGNAL(positionGotSignal()), this, SLOT(positionGot()));
120
121     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
122     this->setStyleSheet("background-image: url(:/images/AGL_POI_Background.png);");
123     this->show();
124 }
125
126 MainApp::~MainApp()
127 {
128     mutex.lock();
129     if (fontId >= 0)
130         QFontDatabase::removeApplicationFont(fontId);
131
132     searchBtn.disconnect();
133     lineEdit.disconnect();
134     networkManager.disconnect();
135     keyboard.disconnect();
136
137     delete pSearchReply;
138     delete pInfoPanel;
139     mutex.unlock();
140 }
141
142 void MainApp::searchBtnClicked()
143 {
144     isInputDisplayed = !isInputDisplayed;
145     TRACE_DEBUG("isInputDisplayed = %d", isInputDisplayed);
146     DisplayLineEdit(isInputDisplayed);
147 }
148
149 void MainApp::DisplayLineEdit(bool display)
150 {
151     mutex.lock();
152
153     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
154
155     if (display)
156     {
157         lineEdit.setVisible(true);
158         lineEdit.setFocus();
159     }
160     else
161     {
162         if (pResultList)
163         {
164             pResultList->removeEventFilter(this);
165             delete pResultList;
166             pResultList = NULL;
167         }
168         if (pInfoPanel)
169         {
170             delete pInfoPanel;
171             pInfoPanel = NULL;
172         }
173         lineEdit.setText(tr(""));
174         lineEdit.setVisible(false);
175     }
176     isInputDisplayed = display;
177
178     mutex.unlock();
179 }
180
181 void MainApp::UpdateAglSurfaces()
182 {
183     char cmd[1024];
184
185     TRACE_DEBUG("handle AGL demo surfaces (new surface is bigger)");
186     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT source region 0 0 %d %d",
187         this->width(), this->height());
188     TRACE_DEBUG("%s", cmd);
189     system(cmd);
190     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT destination region $CLIENT_X $CLIENT_Y %d %d",
191         this->width(), this->height());
192     TRACE_DEBUG("%s", cmd);
193     system(cmd);
194 }
195
196 void MainApp::DisplayResultList(bool display, bool RefreshDisplay)
197 {
198     mutex.lock();
199
200     if (display)
201     {
202         if (!pResultList)
203         {
204             pResultList = new QTreeWidget(this);
205             pResultList->setStyleSheet("border: none; color: #FFFFFF;");
206             pResultList->setRootIsDecorated(false);
207             pResultList->setEditTriggers(QTreeWidget::NoEditTriggers);
208             pResultList->setSelectionBehavior(QTreeWidget::SelectRows);
209             pResultList->setFrameStyle(QFrame::Box | QFrame::Plain);
210             pResultList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
211             //pResultList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
212             pResultList->setAttribute(Qt::WA_AcceptTouchEvents);
213             pResultList->verticalScrollBar()->setStyleSheet(SCROLLBAR_STYLE);
214             pResultList->header()->hide();
215             //font.setPointSize(FONT_SIZE_LIST);
216             //pResultList->setFont(font);
217             pResultList->installEventFilter(this);
218         }
219
220         pResultList->setGeometry(QRect(   LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
221                                         DISPLAY_WIDTH, DISPLAY_HEIGHT));
222         if (RefreshDisplay)
223         {
224             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
225         }
226         pResultList->setVisible(true);
227         pResultList->setFocus();
228     }
229     else
230     {
231         if (pResultList)
232         {
233             pResultList->removeEventFilter(this);
234             pResultList->deleteLater();
235             pResultList = NULL;
236         }
237
238         lineEdit.setFocus();
239
240         if (RefreshDisplay)
241         {
242             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
243         }
244     }
245
246     mutex.unlock();
247 }
248
249 void MainApp::textChanged(const QString & text)
250 {
251     TRACE_INFO("New text is: %s", qPrintable(text));
252
253     /* do not handle text input if info panel is displayed: */
254     if (pInfoPanel) return;
255
256     mutex.lock();
257
258     delete pSearchReply;    /* cancel current search */
259     pSearchReply = NULL;
260
261     if (text.length() == 0) /* if empty text -> no search */
262     {
263         DisplayResultList(false);
264         mutex.unlock();
265         return;
266     }
267
268     /* if text is the same as previous search -> no need to search again */
269     if (text == currentSearchedText)
270     {
271         DisplayResultList(true);
272         FillResultList(Businesses, currentIndex);
273         mutex.unlock();
274         return;
275     }
276     this->currentSearchingText = text;
277
278     emit positionGotSignal();
279
280     mutex.unlock();
281 }
282
283 void MainApp::textAdded(const QString & text)
284 {
285     mutex.lock();
286     lineEdit.setText(lineEdit.text() + text);
287
288     // be sure any text is visible on initial input
289     lineEdit.setVisible(true);
290
291     mutex.unlock();
292 }
293
294 void MainApp::keyPressed(int key)
295 {
296     mutex.lock();
297     if (key == '\b') /* backspace */
298     {
299         int len = lineEdit.text().length();
300         if (len > 0)
301             lineEdit.setText(lineEdit.text().remove(len-1, 1));
302     }
303     mutex.unlock();
304 }
305
306 void MainApp::itemClicked()
307 {
308     mutex.lock();
309     if (isInfoScreen)
310     {
311         DisplayInformation(true, false);
312     }
313     else
314     {
315         SetDestination();
316         DisplayLineEdit(false);
317     }
318     mutex.unlock();
319 }
320
321 void MainApp::ParseJsonBusinessList(const char* buf, std::vector<Business> & Output)
322 {
323     json_object *jobj = json_tokener_parse(buf);
324     if (!jobj)
325     {
326         TRACE_ERROR("json_tokener_parse failed");
327         cerr << "json_tokener_parse failed: " << buf << endl;
328         return;
329     }
330
331     json_object_object_foreach(jobj, key, val)
332     {
333         (void)key;
334         json_object *value;
335
336         if (json_object_get_type(val) == json_type_array)
337         {
338             TRACE_DEBUG_JSON("an array was found");
339
340             if(json_object_object_get_ex(jobj, "businesses", &value))
341             {
342                 TRACE_DEBUG_JSON("an business was found");
343
344                 int arraylen = json_object_array_length(value);
345
346                 for (int i = 0; i < arraylen; i++)
347                 {
348                     Business NewBusiness;
349
350                     json_object* medi_array_obj, *medi_array_obj_elem;
351                     medi_array_obj = json_object_array_get_idx(value, i);
352                     if (medi_array_obj)
353                     {
354                         if (json_object_object_get_ex(medi_array_obj, "rating", &medi_array_obj_elem))
355                         {
356                             NewBusiness.Rating = json_object_get_double(medi_array_obj_elem);
357                             TRACE_DEBUG_JSON("got Rating : %f", NewBusiness.Rating);
358                         }
359
360                         if (json_object_object_get_ex(medi_array_obj, "distance", &medi_array_obj_elem))
361                         {
362                             NewBusiness.Distance = json_object_get_double(medi_array_obj_elem);
363                             TRACE_DEBUG_JSON("got Distance : %f", NewBusiness.Distance);
364                         }
365
366                         if (json_object_object_get_ex(medi_array_obj, "review_count", &medi_array_obj_elem))
367                         {
368                             NewBusiness.ReviewCount = json_object_get_int(medi_array_obj_elem);
369                             TRACE_DEBUG_JSON("got ReviewCount : %u", NewBusiness.ReviewCount);
370                         }
371
372                         if (json_object_object_get_ex(medi_array_obj, "name", &medi_array_obj_elem))
373                         {
374                             NewBusiness.Name = QString(json_object_get_string(medi_array_obj_elem));
375                             TRACE_DEBUG_JSON("got Name : %s", qPrintable(NewBusiness.Name));
376                         }
377
378                         if (json_object_object_get_ex(medi_array_obj, "image_url", &medi_array_obj_elem))
379                         {
380                             NewBusiness.ImageUrl = QString(json_object_get_string(medi_array_obj_elem));
381                             TRACE_DEBUG_JSON("got ImageUrl : %s", qPrintable(NewBusiness.ImageUrl));
382                         }
383
384                         if (json_object_object_get_ex(medi_array_obj, "phone", &medi_array_obj_elem))
385                         {
386                             NewBusiness.Phone = QString(json_object_get_string(medi_array_obj_elem));
387                             TRACE_DEBUG_JSON("got Phone : %s", qPrintable(NewBusiness.Phone));
388                         }
389
390                         if (json_object_object_get_ex(medi_array_obj, "coordinates", &medi_array_obj_elem))
391                         {
392                             json_object *value2;
393
394                             TRACE_DEBUG_JSON("coordinates were found");
395
396                             if(json_object_object_get_ex(medi_array_obj_elem, "latitude", &value2))
397                             {
398                                 NewBusiness.Latitude = json_object_get_double(value2);
399                                 TRACE_DEBUG_JSON("got Latitude : %f", NewBusiness.Latitude);
400                             }
401
402                             if(json_object_object_get_ex(medi_array_obj_elem, "longitude", &value2))
403                             {
404                                 NewBusiness.Longitude = json_object_get_double(value2);
405                                 TRACE_DEBUG_JSON("got Longitude : %f", NewBusiness.Longitude);
406                             }
407                         }
408
409                         if (json_object_object_get_ex(medi_array_obj, "location", &medi_array_obj_elem))
410                         {
411                             json_object *value2;
412
413                             TRACE_DEBUG_JSON("a location was found");
414
415                             /* TODO: how do we deal with address2 and address3 ? */
416                             if(json_object_object_get_ex(medi_array_obj_elem, "address1", &value2))
417                             {
418                                 NewBusiness.Address = QString(json_object_get_string(value2));
419                                 TRACE_DEBUG_JSON("got Address : %s", qPrintable(NewBusiness.Address));
420                             }
421
422                             if(json_object_object_get_ex(medi_array_obj_elem, "city", &value2))
423                             {
424                                 NewBusiness.City = QString(json_object_get_string(value2));
425                                 TRACE_DEBUG_JSON("got City : %s", qPrintable(NewBusiness.City));
426                             }
427
428                             if(json_object_object_get_ex(medi_array_obj_elem, "state", &value2))
429                             {
430                                 NewBusiness.State = QString(json_object_get_string(value2));
431                                 TRACE_DEBUG_JSON("got State : %s", qPrintable(NewBusiness.State));
432                             }
433
434                             if(json_object_object_get_ex(medi_array_obj_elem, "zip_code", &value2))
435                             {
436                                 NewBusiness.ZipCode = QString(json_object_get_string(value2));
437                                 TRACE_DEBUG_JSON("got ZipCode : %s", qPrintable(NewBusiness.ZipCode));
438                             }
439
440                             if(json_object_object_get_ex(medi_array_obj_elem, "country", &value2))
441                             {
442                                 NewBusiness.Country = QString(json_object_get_string(value2));
443                                 TRACE_DEBUG_JSON("got Country : %s", qPrintable(NewBusiness.Country));
444                             }
445                         }
446
447                         /* TODO: parse categories */
448
449                         /* Add business in our list: */
450                         Businesses.push_back(NewBusiness);
451                     }
452                 }
453             }
454         }
455     }
456
457     json_object_put(jobj);
458 }
459
460 bool MainApp::eventFilter(QObject *obj, QEvent *ev)
461 {
462     bool ret = false;
463
464     mutex.lock();
465
466     if (obj == pResultList)
467     {
468         //TRACE_DEBUG("ev->type() = %d", (int)ev->type());
469
470         if (ev->type() == QEvent::KeyPress)
471         {
472             bool consumed = false;
473             int key = static_cast<QKeyEvent*>(ev)->key();
474             TRACE_DEBUG("key pressed (%d)", key);
475             switch (key) {
476                 case Qt::Key_Enter:
477                 case Qt::Key_Return:
478                     TRACE_DEBUG("enter or return");
479                     if (isInfoScreen)
480                     {
481                         DisplayInformation(true);
482                     }
483                     else
484                     {
485                         SetDestination();
486                         DisplayLineEdit(false);
487                     }
488                     consumed = true;
489                     break;
490
491                 case Qt::Key_Escape:
492                     TRACE_DEBUG("escape");
493                     DisplayResultList(false);
494                     consumed = true;
495                     break;
496
497                 case Qt::Key_Up:
498                 case Qt::Key_Down:
499                 case Qt::Key_Home:
500                 case Qt::Key_End:
501                 case Qt::Key_PageUp:
502                 case Qt::Key_PageDown:
503                     TRACE_DEBUG("arrows");
504                     break;
505
506                 default:
507                     TRACE_DEBUG("default");
508                     lineEdit.event(ev);
509                     break;
510             }
511
512             mutex.unlock();
513             return consumed;
514         }
515     }
516     else if (obj == &lineEdit)
517     {
518         if (pInfoPanel && ev->type() == QEvent::KeyPress)
519         {
520             switch(static_cast<QKeyEvent*>(ev)->key())
521             {
522                 case Qt::Key_Escape:
523                     TRACE_DEBUG("Escape !");
524                     DisplayInformation(false, false);
525                     DisplayResultList(true);
526                     FillResultList(Businesses, currentIndex);
527                     break;
528                 case Qt::Key_Enter:
529                 case Qt::Key_Return:
530                     TRACE_DEBUG("Go !");
531                     SetDestination(currentIndex);
532                     DisplayLineEdit(false);
533                     break;
534                 default: break;
535             }
536         }
537     }
538     else
539     {
540         ret = QMainWindow::eventFilter(obj, ev);
541     }
542     mutex.unlock();
543     return ret;
544 }
545
546 void MainApp::resizeEvent(QResizeEvent* event)
547 {
548     QMainWindow::resizeEvent(event);
549     if (isAglNavi)
550     {
551         QTimer::singleShot(AGL_REFRESH_DELAY, Qt::CoarseTimer, this, SLOT(UpdateAglSurfaces()));
552     }
553 }
554
555 void MainApp::SetDestination(int index)
556 {
557     mutex.lock();
558
559     /* if pResultList exists, take the selected index
560      * otherwise, take the index given as parameter */
561     if (pResultList)
562     {
563         QList<QTreeWidgetItem *> SelectedItems = pResultList->selectedItems();
564         if (SelectedItems.size() > 0)
565         {
566             /* select the first selected item : */
567             index = pResultList->indexOfTopLevelItem(*SelectedItems.begin());
568         }
569     }
570
571     TRACE_DEBUG("index is: %d", index);
572
573     /* retrieve the coordinates of this item : */
574     this->destinationLatitude = Businesses[index].Latitude;
575     this->destinationLongitude = Businesses[index].Longitude;
576
577     SetWayPoints(0);
578
579     mutex.unlock();
580 }
581
582 void MainApp::DisplayInformation(bool display, bool RefreshDisplay)
583 {
584     mutex.lock();
585     if (display)
586     {
587         /* pResultList must exist, so that we can retrieve the selected index: */
588         if (!pResultList)
589         {
590             TRACE_ERROR("pResultList is null");
591             mutex.unlock();
592             return;
593         }
594
595         QList<QTreeWidgetItem *> SelectedItems = pResultList->selectedItems();
596         if (SelectedItems.size() <= 0)
597         {
598             TRACE_ERROR("no item is selected");
599             mutex.unlock();
600             return;
601         }
602
603         /* select the first selected item : */
604         currentIndex = pResultList->indexOfTopLevelItem(*SelectedItems.begin());
605
606         /* Resize window: */
607         DisplayResultList(false, false);
608
609         /* Display info for the selected item: */
610         QRect rect( LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
611                     DISPLAY_WIDTH, DISPLAY_HEIGHT);
612         pInfoPanel = new InfoPanel(this, Businesses[currentIndex], rect);
613
614         if (RefreshDisplay)
615         {
616             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
617         }
618
619         connect(pInfoPanel->getGoButton(),      SIGNAL(clicked(bool)), this, SLOT(goClicked()));
620         connect(pInfoPanel->getCancelButton(),  SIGNAL(clicked(bool)), this, SLOT(cancelClicked()));
621     }
622     else
623     {
624         if (pInfoPanel)
625         {
626             pInfoPanel->getGoButton()->disconnect();
627             pInfoPanel->getCancelButton()->disconnect();
628             delete pInfoPanel;
629             pInfoPanel = NULL;
630         }
631         lineEdit.setFocus();
632
633         if (RefreshDisplay)
634         {
635             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
636         }
637     }
638
639     mutex.unlock();
640 }
641
642 void MainApp::networkReplySearch(QNetworkReply* reply)
643 {
644     QByteArray buf;
645
646     mutex.lock();
647
648     /* memorize the text which gave this result: */
649     currentSearchedText = lineEdit.text();
650
651     if (reply->error() == QNetworkReply::NoError)
652     {
653         // we only handle this callback if it matches the last search request:
654         if (reply != pSearchReply)
655         {
656             TRACE_INFO("this reply is already too late (or about a different network request)");
657             mutex.unlock();
658             return;
659         }
660
661         buf = reply->readAll();
662
663         if (buf.isEmpty())
664         {
665             mutex.unlock();
666             return;
667         }
668
669
670
671         currentIndex = 0;
672         Businesses.clear();
673         ParseJsonBusinessList(buf.data(), Businesses);
674         DisplayResultList(true);
675         FillResultList(Businesses);
676     }
677     else
678     {
679         fprintf(stderr,"POI: reply error network please check to poikey and system time (adjusted?)\n");
680     }
681
682     mutex.unlock();
683 }
684
685 /* pResultList must be allocated at this point ! */
686 int MainApp::FillResultList(vector<Business> & list, int focusIndex)
687 {
688     int nbElem = 0;
689
690     mutex.lock();
691
692     pResultList->setUpdatesEnabled(false);
693     pResultList->clear();
694
695     /* filling the dropdown menu: */
696     for (vector<Business>::iterator it = list.begin(); it != list.end(); it++)
697     {
698         /*  workaround to avoid entries with wrong coordinates returned by Yelp: */
699         if (IsCoordinatesConsistent(*it) == false)
700         {
701             list.erase(it--);
702             continue;
703         }
704
705         QTreeWidgetItem * item = new QTreeWidgetItem(pResultList);
706
707         ClickableLabel *label = new ClickableLabel("<b>"+(*it).Name+
708             "</b><br>"+(*it).Address+", "+(*it).City+", "+(*it).State+
709             " "+(*it).ZipCode+", "+(*it).Country, pResultList);
710         label->setTextFormat(Qt::RichText);
711         font.setPointSize(FONT_SIZE_LIST);
712         label->setFont(font);
713         label->setIndent(MARGINS);
714         label->setAttribute(Qt::WA_AcceptTouchEvents);
715         item->setSizeHint(0, QSize(TEXT_INPUT_WIDTH, RESULT_ITEM_HEIGHT));
716         pResultList->setItemWidget(item, 0, label);
717         connect(label, SIGNAL(clicked()), this, SLOT(itemClicked()));
718
719         //item->setText(0, (*it).Name);
720
721         if (nbElem == focusIndex)
722         {
723             pResultList->setCurrentItem(item);
724         }
725         nbElem++;
726     }
727
728     pResultList->setUpdatesEnabled(true);
729
730     mutex.unlock();
731     return nbElem;
732 }
733
734 /* Well... some of the POI returned by Yelp have coordinates which are
735  * completely inconsistent with the distance at which the POI is
736  * supposed to be.
737  * https://github.com/Yelp/yelp-fusion/issues/104
738  * Let's skip them for the moment: */
739 #define PI 3.14159265
740 #define EARTH_RADIUS 6371000
741 static inline double toRadians(double a) { return a * PI / 180.0; }
742 bool MainApp::IsCoordinatesConsistent(Business & business)
743 {
744     double lat1 = toRadians(currentLatitude);
745     double lon1 = toRadians(currentLongitude);
746     double lat2 = toRadians(business.Latitude);
747     double lon2 = toRadians(business.Longitude);
748     double x = (lon2 - lon1) * cos((lat1 + lat2)/2);
749     double y = lat2 - lat1;
750     double DistanceFromCoords = EARTH_RADIUS * sqrt(pow(x, 2) + pow(y, 2));
751
752     /* if calculated distance is not between +/- 10% of the announced
753      * distance -> skip this POI: */
754     if (DistanceFromCoords < business.Distance * 0.9 ||
755         DistanceFromCoords > business.Distance * 1.1)
756     {
757         TRACE_ERROR("Announced distance: %f, calculated distance: %f", business.Distance, DistanceFromCoords);
758         return false;
759     }
760
761     return true;
762 }
763 /* end of workaround */
764
765 int MainApp::AuthenticatePOI(const QString & CredentialsFile)
766 {
767     char buf[512];
768     QString AppId;
769     QString AppSecret;
770     QString ProxyHostName;
771     QString PortStr;
772     QString User;
773     QString Password;
774     int portnum;
775
776     /* First, read AppId and AppSecret from credentials file: */
777     FILE* filep = fopen(qPrintable(CredentialsFile), "r");
778     if (!filep)
779     {
780         fprintf(stderr,"Failed to open credentials file \"%s\": %m", qPrintable(CredentialsFile));
781         return -1;
782     }
783
784     if (!fgets(buf, 512, filep))
785     {
786         fprintf(stderr,"Failed to read AppId from credentials file \"%s\"", qPrintable(CredentialsFile));
787         fclose(filep);
788         return -1;
789     }
790     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
791         buf[strlen(buf)-1] = '\0';
792     AppId = QString(buf);
793
794     if (!fgets(buf, 512, filep))
795     {
796         fprintf(stderr,"Failed to read AppSecret from credentials file \"%s\"", qPrintable(CredentialsFile));
797         fclose(filep);
798         return -1;
799     }
800     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
801         buf[strlen(buf)-1] = '\0';
802     AppSecret = QString(buf);
803
804     QNetworkProxy proxy;
805
806     //ProxyHostName
807     if (!fgets(buf, 512, filep))
808     {
809         TRACE_INFO("Failed to read ProxyHostName from credentials file \"%s\"", qPrintable(CredentialsFile));
810     }
811     else
812     {
813         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
814             buf[strlen(buf)-1] = '\0';
815         ProxyHostName = QString(buf);
816         ProxyHostName.replace(0, 14, tr(""));
817
818         //Port
819         if (!fgets(buf, 512, filep))
820         {
821             TRACE_ERROR("Failed to read Port from credentials file \"%s\"", qPrintable(CredentialsFile));
822             fclose(filep);
823             return -1;
824         }
825         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
826             buf[strlen(buf)-1] = '\0';
827         PortStr = QString(buf);
828         PortStr.replace(0, 5, tr(""));
829         portnum = PortStr.toInt();
830
831         //User
832         if (!fgets(buf, 512, filep))
833         {
834             TRACE_ERROR("Failed to read User from credentials file \"%s\"", qPrintable(CredentialsFile));
835             fclose(filep);
836             return -1;
837         }
838         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
839             buf[strlen(buf)-1] = '\0';
840         User = QString(buf);
841         User.replace(0, 5, tr(""));
842
843         //Password
844         if (!fgets(buf, 512, filep))
845         {
846             TRACE_ERROR("Failed to read Password from credentials file \"%s\"", qPrintable(CredentialsFile));
847             fclose(filep);
848             return -1;
849         }
850         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
851             buf[strlen(buf)-1] = '\0';
852         Password = QString(buf);
853         Password.replace(0, 9, tr(""));
854
855         proxy.setType(QNetworkProxy::HttpProxy);
856         proxy.setHostName(qPrintable(ProxyHostName));
857         proxy.setPort(portnum);
858         proxy.setUser(qPrintable(User));
859         proxy.setPassword(qPrintable(Password));
860         QNetworkProxy::setApplicationProxy(proxy);
861     }
862
863     fclose(filep);
864
865     TRACE_INFO("Found credentials");
866
867     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
868
869     token = AppSecret;
870     return 0;
871 }
872
873 int MainApp::StartMonitoringUserInput()
874 {
875     connect(&searchBtn, SIGNAL(clicked(bool)), this, SLOT(searchBtnClicked()));
876     connect(&lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &)));
877     connect(&networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReplySearch(QNetworkReply*)));
878     connect(&keyboard, SIGNAL(keyClicked(const QString &)), this, SLOT(textAdded(const QString &)));
879     connect(&keyboard, SIGNAL(specialKeyClicked(int)), this, SLOT(keyPressed(int)));
880     return 1;
881 }
882
883 void MainApp::SetWayPoints(uint32_t myRoute)
884 {
885     /* set the destination : */
886     naviapi->sendWaypoint(this->destinationLatitude, this->destinationLongitude);
887
888     /* reset search: */
889     currentSearchingText = tr("");
890     currentSearchedText = tr("");
891     currentIndex = 0;
892     Businesses.clear();
893 }
894
895 void MainApp::goClicked()
896 {
897     TRACE_DEBUG("Go clicked !");
898     SetDestination(currentIndex);
899     DisplayLineEdit(false);
900 }
901
902 void MainApp::cancelClicked()
903 {
904     TRACE_DEBUG("Cancel clicked !");
905     DisplayInformation(false, false);
906     DisplayResultList(true, false);
907     FillResultList(Businesses, currentIndex);
908 }
909
910 void MainApp::getAllSessions_reply(const std::map< uint32_t, std::string >& allSessions)
911 {
912     mutex.lock();
913
914     if (allSessions.empty())
915     {
916         TRACE_ERROR("Error: could not find an instance of Navicore");
917         mutex.unlock();
918         return;
919     }
920
921     this->navicoreSession = allSessions.begin()->first;
922
923     TRACE_INFO("Current session: %d", this->navicoreSession);
924
925     mutex.unlock();
926 }
927
928 void MainApp::positionGot()
929 {
930     mutex.lock();
931
932     /* let's generate a search request : */
933     QString myUrlStr = URL_SEARCH + tr("?") + tr("term=") + currentSearchingText +
934         tr("&latitude=") + QString::number(currentLatitude) +
935         tr("&longitude=") + QString::number(currentLongitude);
936
937     TRACE_DEBUG("URL: %s", qPrintable(myUrlStr));
938
939     QUrl myUrl = QUrl(myUrlStr);
940     QNetworkRequest req(myUrl);
941     req.setRawHeader(QByteArray("Authorization"), (tr("bearer ") + token).toLocal8Bit());
942
943     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
944
945     pSearchReply = networkManager.get(req);
946
947     mutex.unlock();
948 }