8ff04888f4d3c2fcb7085d2ba5476a978cd2ee07
[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():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)
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     connect(this, SIGNAL(allRoutesGotSignal()), this, SLOT(allRoutesGot()));
121     connect(this, SIGNAL(routeCreatedSignal()), this, SLOT(routeCreated()));
122
123     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
124     this->setStyleSheet("background-image: url(:/images/AGL_POI_Background.png);");
125     this->show();
126 }
127
128 MainApp::~MainApp()
129 {
130     mutex.lock();
131     if (fontId >= 0)
132         QFontDatabase::removeApplicationFont(fontId);
133
134     searchBtn.disconnect();
135     lineEdit.disconnect();
136     networkManager.disconnect();
137     keyboard.disconnect();
138
139     delete pSearchReply;
140     delete pInfoPanel;
141     mutex.unlock();
142 }
143
144 void MainApp::searchBtnClicked()
145 {
146     isInputDisplayed = !isInputDisplayed;
147     TRACE_DEBUG("isInputDisplayed = %d", isInputDisplayed);
148     DisplayLineEdit(isInputDisplayed);
149 }
150
151 void MainApp::DisplayLineEdit(bool display)
152 {
153     mutex.lock();
154
155     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
156
157     if (display)
158     {
159         lineEdit.setVisible(true);
160         lineEdit.setFocus();
161     }
162     else
163     {
164         if (pResultList)
165         {
166             pResultList->removeEventFilter(this);
167             delete pResultList;
168             pResultList = NULL;
169         }
170         if (pInfoPanel)
171         {
172             delete pInfoPanel;
173             pInfoPanel = NULL;
174         }
175         lineEdit.setText(tr(""));
176         lineEdit.setVisible(false);
177     }
178     isInputDisplayed = display;
179
180     mutex.unlock();
181 }
182
183 void MainApp::UpdateAglSurfaces()
184 {
185     char cmd[1024];
186
187     TRACE_DEBUG("handle AGL demo surfaces (new surface is bigger)");
188     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT source region 0 0 %d %d",
189         this->width(), this->height());
190     TRACE_DEBUG("%s", cmd);
191     system(cmd);
192     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT destination region $CLIENT_X $CLIENT_Y %d %d",
193         this->width(), this->height());
194     TRACE_DEBUG("%s", cmd);
195     system(cmd);
196 }
197
198 void MainApp::DisplayResultList(bool display, bool RefreshDisplay)
199 {
200     mutex.lock();
201
202     if (display)
203     {
204         if (!pResultList)
205         {
206             pResultList = new QTreeWidget(this);
207             pResultList->setStyleSheet("border: none; color: #FFFFFF;");
208             pResultList->setRootIsDecorated(false);
209             pResultList->setEditTriggers(QTreeWidget::NoEditTriggers);
210             pResultList->setSelectionBehavior(QTreeWidget::SelectRows);
211             pResultList->setFrameStyle(QFrame::Box | QFrame::Plain);
212             pResultList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
213             //pResultList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
214             pResultList->setAttribute(Qt::WA_AcceptTouchEvents);
215             pResultList->verticalScrollBar()->setStyleSheet(SCROLLBAR_STYLE);
216             pResultList->header()->hide();
217             //font.setPointSize(FONT_SIZE_LIST);
218             //pResultList->setFont(font);
219             pResultList->installEventFilter(this);
220         }
221
222         pResultList->setGeometry(QRect(   LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
223                                         DISPLAY_WIDTH, DISPLAY_HEIGHT));
224         if (RefreshDisplay)
225         {
226             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
227         }
228         pResultList->setVisible(true);
229         pResultList->setFocus();
230     }
231     else
232     {
233         if (pResultList)
234         {
235             pResultList->removeEventFilter(this);
236             pResultList->deleteLater();
237             pResultList = NULL;
238         }
239
240         lineEdit.setFocus();
241
242         if (RefreshDisplay)
243         {
244             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
245         }
246     }
247
248     mutex.unlock();
249 }
250
251 void MainApp::textChanged(const QString & text)
252 {
253     TRACE_INFO("New text is: %s", qPrintable(text));
254
255     /* do not handle text input if info panel is displayed: */
256     if (pInfoPanel) return;
257
258     mutex.lock();
259
260     delete pSearchReply;    /* cancel current search */
261     pSearchReply = NULL;
262
263     if (text.length() == 0) /* if empty text -> no search */
264     {
265         DisplayResultList(false);
266         mutex.unlock();
267         return;
268     }
269
270     /* if text is the same as previous search -> no need to search again */
271     if (text == currentSearchedText)
272     {
273         DisplayResultList(true);
274         FillResultList(Businesses, currentIndex);
275         mutex.unlock();
276         return;
277     }
278     this->currentSearchingText = text;
279
280     /* we need to know our current position */
281     std::vector<int32_t> Params;
282     Params.push_back(naviapi::NAVICORE_LONGITUDE);
283     Params.push_back(naviapi::NAVICORE_LATITUDE);
284     naviapi.getPosition(Params);
285
286     mutex.unlock();
287 }
288
289 void MainApp::textAdded(const QString & text)
290 {
291     mutex.lock();
292     lineEdit.setText(lineEdit.text() + text);
293
294     // be sure any text is visible on initial input
295     lineEdit.setVisible(true);
296
297     mutex.unlock();
298 }
299
300 void MainApp::keyPressed(int key)
301 {
302     mutex.lock();
303     if (key == '\b') /* backspace */
304     {
305         int len = lineEdit.text().length();
306         if (len > 0)
307             lineEdit.setText(lineEdit.text().remove(len-1, 1));
308     }
309     mutex.unlock();
310 }
311
312 void MainApp::itemClicked()
313 {
314     mutex.lock();
315     if (isInfoScreen)
316     {
317         DisplayInformation(true, false);
318     }
319     else
320     {
321         SetDestination();
322         DisplayLineEdit(false);
323     }
324     mutex.unlock();
325 }
326
327 void MainApp::ParseJsonBusinessList(const char* buf, std::vector<Business> & Output)
328 {
329     json_object *jobj = json_tokener_parse(buf);
330     if (!jobj)
331     {
332         TRACE_ERROR("json_tokener_parse failed");
333         cerr << "json_tokener_parse failed: " << buf << endl;
334         return;
335     }
336
337     json_object_object_foreach(jobj, key, val)
338     {
339         (void)key;
340         json_object *value;
341
342         if (json_object_get_type(val) == json_type_array)
343         {
344             TRACE_DEBUG_JSON("an array was found");
345
346             if(json_object_object_get_ex(jobj, "businesses", &value))
347             {
348                 TRACE_DEBUG_JSON("an business was found");
349
350                 int arraylen = json_object_array_length(value);
351
352                 for (int i = 0; i < arraylen; i++)
353                 {
354                     Business NewBusiness;
355
356                     json_object* medi_array_obj, *medi_array_obj_elem;
357                     medi_array_obj = json_object_array_get_idx(value, i);
358                     if (medi_array_obj)
359                     {
360                         if (json_object_object_get_ex(medi_array_obj, "rating", &medi_array_obj_elem))
361                         {
362                             NewBusiness.Rating = json_object_get_double(medi_array_obj_elem);
363                             TRACE_DEBUG_JSON("got Rating : %f", NewBusiness.Rating);
364                         }
365
366                         if (json_object_object_get_ex(medi_array_obj, "distance", &medi_array_obj_elem))
367                         {
368                             NewBusiness.Distance = json_object_get_double(medi_array_obj_elem);
369                             TRACE_DEBUG_JSON("got Distance : %f", NewBusiness.Distance);
370                         }
371
372                         if (json_object_object_get_ex(medi_array_obj, "review_count", &medi_array_obj_elem))
373                         {
374                             NewBusiness.ReviewCount = json_object_get_int(medi_array_obj_elem);
375                             TRACE_DEBUG_JSON("got ReviewCount : %u", NewBusiness.ReviewCount);
376                         }
377
378                         if (json_object_object_get_ex(medi_array_obj, "name", &medi_array_obj_elem))
379                         {
380                             NewBusiness.Name = QString(json_object_get_string(medi_array_obj_elem));
381                             TRACE_DEBUG_JSON("got Name : %s", qPrintable(NewBusiness.Name));
382                         }
383
384                         if (json_object_object_get_ex(medi_array_obj, "image_url", &medi_array_obj_elem))
385                         {
386                             NewBusiness.ImageUrl = QString(json_object_get_string(medi_array_obj_elem));
387                             TRACE_DEBUG_JSON("got ImageUrl : %s", qPrintable(NewBusiness.ImageUrl));
388                         }
389
390                         if (json_object_object_get_ex(medi_array_obj, "phone", &medi_array_obj_elem))
391                         {
392                             NewBusiness.Phone = QString(json_object_get_string(medi_array_obj_elem));
393                             TRACE_DEBUG_JSON("got Phone : %s", qPrintable(NewBusiness.Phone));
394                         }
395
396                         if (json_object_object_get_ex(medi_array_obj, "coordinates", &medi_array_obj_elem))
397                         {
398                             json_object *value2;
399
400                             TRACE_DEBUG_JSON("coordinates were found");
401
402                             if(json_object_object_get_ex(medi_array_obj_elem, "latitude", &value2))
403                             {
404                                 NewBusiness.Latitude = json_object_get_double(value2);
405                                 TRACE_DEBUG_JSON("got Latitude : %f", NewBusiness.Latitude);
406                             }
407
408                             if(json_object_object_get_ex(medi_array_obj_elem, "longitude", &value2))
409                             {
410                                 NewBusiness.Longitude = json_object_get_double(value2);
411                                 TRACE_DEBUG_JSON("got Longitude : %f", NewBusiness.Longitude);
412                             }
413                         }
414
415                         if (json_object_object_get_ex(medi_array_obj, "location", &medi_array_obj_elem))
416                         {
417                             json_object *value2;
418
419                             TRACE_DEBUG_JSON("a location was found");
420
421                             /* TODO: how do we deal with address2 and address3 ? */
422                             if(json_object_object_get_ex(medi_array_obj_elem, "address1", &value2))
423                             {
424                                 NewBusiness.Address = QString(json_object_get_string(value2));
425                                 TRACE_DEBUG_JSON("got Address : %s", qPrintable(NewBusiness.Address));
426                             }
427
428                             if(json_object_object_get_ex(medi_array_obj_elem, "city", &value2))
429                             {
430                                 NewBusiness.City = QString(json_object_get_string(value2));
431                                 TRACE_DEBUG_JSON("got City : %s", qPrintable(NewBusiness.City));
432                             }
433
434                             if(json_object_object_get_ex(medi_array_obj_elem, "state", &value2))
435                             {
436                                 NewBusiness.State = QString(json_object_get_string(value2));
437                                 TRACE_DEBUG_JSON("got State : %s", qPrintable(NewBusiness.State));
438                             }
439
440                             if(json_object_object_get_ex(medi_array_obj_elem, "zip_code", &value2))
441                             {
442                                 NewBusiness.ZipCode = QString(json_object_get_string(value2));
443                                 TRACE_DEBUG_JSON("got ZipCode : %s", qPrintable(NewBusiness.ZipCode));
444                             }
445
446                             if(json_object_object_get_ex(medi_array_obj_elem, "country", &value2))
447                             {
448                                 NewBusiness.Country = QString(json_object_get_string(value2));
449                                 TRACE_DEBUG_JSON("got Country : %s", qPrintable(NewBusiness.Country));
450                             }
451                         }
452
453                         /* TODO: parse categories */
454
455                         /* Add business in our list: */
456                         Businesses.push_back(NewBusiness);
457                     }
458                 }
459             }
460         }
461     }
462
463     json_object_put(jobj);
464 }
465
466 bool MainApp::eventFilter(QObject *obj, QEvent *ev)
467 {
468     bool ret = false;
469
470     mutex.lock();
471
472     if (obj == pResultList)
473     {
474         //TRACE_DEBUG("ev->type() = %d", (int)ev->type());
475
476         if (ev->type() == QEvent::KeyPress)
477         {
478             bool consumed = false;
479             int key = static_cast<QKeyEvent*>(ev)->key();
480             TRACE_DEBUG("key pressed (%d)", key);
481             switch (key) {
482                 case Qt::Key_Enter:
483                 case Qt::Key_Return:
484                     TRACE_DEBUG("enter or return");
485                     if (isInfoScreen)
486                     {
487                         DisplayInformation(true);
488                     }
489                     else
490                     {
491                         SetDestination();
492                         DisplayLineEdit(false);
493                     }
494                     consumed = true;
495                     break;
496
497                 case Qt::Key_Escape:
498                     TRACE_DEBUG("escape");
499                     DisplayResultList(false);
500                     consumed = true;
501                     break;
502
503                 case Qt::Key_Up:
504                 case Qt::Key_Down:
505                 case Qt::Key_Home:
506                 case Qt::Key_End:
507                 case Qt::Key_PageUp:
508                 case Qt::Key_PageDown:
509                     TRACE_DEBUG("arrows");
510                     break;
511
512                 default:
513                     TRACE_DEBUG("default");
514                     lineEdit.event(ev);
515                     break;
516             }
517
518             mutex.unlock();
519             return consumed;
520         }
521     }
522     else if (obj == &lineEdit)
523     {
524         if (pInfoPanel && ev->type() == QEvent::KeyPress)
525         {
526             switch(static_cast<QKeyEvent*>(ev)->key())
527             {
528                 case Qt::Key_Escape:
529                     TRACE_DEBUG("Escape !");
530                     DisplayInformation(false, false);
531                     DisplayResultList(true);
532                     FillResultList(Businesses, currentIndex);
533                     break;
534                 case Qt::Key_Enter:
535                 case Qt::Key_Return:
536                     TRACE_DEBUG("Go !");
537                     SetDestination(currentIndex);
538                     DisplayLineEdit(false);
539                     break;
540                 default: break;
541             }
542         }
543     }
544     else
545     {
546         ret = QMainWindow::eventFilter(obj, ev);
547     }
548     mutex.unlock();
549     return ret;
550 }
551
552 void MainApp::resizeEvent(QResizeEvent* event)
553 {
554     QMainWindow::resizeEvent(event);
555     if (isAglNavi)
556     {
557         QTimer::singleShot(AGL_REFRESH_DELAY, Qt::CoarseTimer, this, SLOT(UpdateAglSurfaces()));
558     }
559 }
560
561 void MainApp::SetDestination(int index)
562 {
563     mutex.lock();
564
565     /* if pResultList exists, take the selected index
566      * otherwise, take the index given as parameter */
567     if (pResultList)
568     {
569         QList<QTreeWidgetItem *> SelectedItems = pResultList->selectedItems();
570         if (SelectedItems.size() > 0)
571         {
572             /* select the first selected item : */
573             index = pResultList->indexOfTopLevelItem(*SelectedItems.begin());
574         }
575     }
576
577     TRACE_DEBUG("index is: %d", index);
578
579     /* retrieve the coordinates of this item : */
580     this->destinationLatitude = Businesses[index].Latitude;
581     this->destinationLongitude = Businesses[index].Longitude;
582
583     naviapi.getAllRoutes();
584
585     mutex.unlock();
586 }
587
588 void MainApp::DisplayInformation(bool display, bool RefreshDisplay)
589 {
590     mutex.lock();
591     if (display)
592     {
593         /* pResultList must exist, so that we can retrieve the selected index: */
594         if (!pResultList)
595         {
596             TRACE_ERROR("pResultList is null");
597             mutex.unlock();
598             return;
599         }
600
601         QList<QTreeWidgetItem *> SelectedItems = pResultList->selectedItems();
602         if (SelectedItems.size() <= 0)
603         {
604             TRACE_ERROR("no item is selected");
605             mutex.unlock();
606             return;
607         }
608
609         /* select the first selected item : */
610         currentIndex = pResultList->indexOfTopLevelItem(*SelectedItems.begin());
611
612         /* Resize window: */
613         DisplayResultList(false, false);
614
615         /* Display info for the selected item: */
616         QRect rect( LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
617                     DISPLAY_WIDTH, DISPLAY_HEIGHT);
618         pInfoPanel = new InfoPanel(this, Businesses[currentIndex], rect);
619
620         if (RefreshDisplay)
621         {
622             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
623         }
624
625         connect(pInfoPanel->getGoButton(),      SIGNAL(clicked(bool)), this, SLOT(goClicked()));
626         connect(pInfoPanel->getCancelButton(),  SIGNAL(clicked(bool)), this, SLOT(cancelClicked()));
627     }
628     else
629     {
630         if (pInfoPanel)
631         {
632             pInfoPanel->getGoButton()->disconnect();
633             pInfoPanel->getCancelButton()->disconnect();
634             delete pInfoPanel;
635             pInfoPanel = NULL;
636         }
637         lineEdit.setFocus();
638
639         if (RefreshDisplay)
640         {
641             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
642         }
643     }
644
645     mutex.unlock();
646 }
647
648 void MainApp::networkReplySearch(QNetworkReply* reply)
649 {
650     QByteArray buf;
651
652     mutex.lock();
653
654     /* memorize the text which gave this result: */
655     currentSearchedText = lineEdit.text();
656
657     if (reply->error() == QNetworkReply::NoError)
658     {
659         // we only handle this callback if it matches the last search request:
660         if (reply != pSearchReply)
661         {
662             TRACE_INFO("this reply is already too late (or about a different network request)");
663             mutex.unlock();
664             return;
665         }
666
667         buf = reply->readAll();
668
669         if (buf.isEmpty())
670         {
671             mutex.unlock();
672             return;
673         }
674
675
676
677         currentIndex = 0;
678         Businesses.clear();
679         ParseJsonBusinessList(buf.data(), Businesses);
680         DisplayResultList(true);
681         FillResultList(Businesses);
682     }
683     else
684     {
685         fprintf(stderr,"POI: reply error network please check to poikey and system time (adjusted?)\n");
686     }
687
688     mutex.unlock();
689 }
690
691 /* pResultList must be allocated at this point ! */
692 int MainApp::FillResultList(vector<Business> & list, int focusIndex)
693 {
694     int nbElem = 0;
695
696     mutex.lock();
697
698     pResultList->setUpdatesEnabled(false);
699     pResultList->clear();
700
701     /* filling the dropdown menu: */
702     for (vector<Business>::iterator it = list.begin(); it != list.end(); it++)
703     {
704         /*  workaround to avoid entries with wrong coordinates returned by Yelp: */
705         if (IsCoordinatesConsistent(*it) == false)
706         {
707             list.erase(it--);
708             continue;
709         }
710
711         QTreeWidgetItem * item = new QTreeWidgetItem(pResultList);
712
713         ClickableLabel *label = new ClickableLabel("<b>"+(*it).Name+
714             "</b><br>"+(*it).Address+", "+(*it).City+", "+(*it).State+
715             " "+(*it).ZipCode+", "+(*it).Country, pResultList);
716         label->setTextFormat(Qt::RichText);
717         font.setPointSize(FONT_SIZE_LIST);
718         label->setFont(font);
719         label->setIndent(MARGINS);
720         label->setAttribute(Qt::WA_AcceptTouchEvents);
721         item->setSizeHint(0, QSize(TEXT_INPUT_WIDTH, RESULT_ITEM_HEIGHT));
722         pResultList->setItemWidget(item, 0, label);
723         connect(label, SIGNAL(clicked()), this, SLOT(itemClicked()));
724
725         //item->setText(0, (*it).Name);
726
727         if (nbElem == focusIndex)
728         {
729             pResultList->setCurrentItem(item);
730         }
731         nbElem++;
732     }
733
734     pResultList->setUpdatesEnabled(true);
735
736     mutex.unlock();
737     return nbElem;
738 }
739
740 /* Well... some of the POI returned by Yelp have coordinates which are
741  * completely inconsistent with the distance at which the POI is
742  * supposed to be.
743  * https://github.com/Yelp/yelp-fusion/issues/104
744  * Let's skip them for the moment: */
745 #define PI 3.14159265
746 #define EARTH_RADIUS 6371000
747 static inline double toRadians(double a) { return a * PI / 180.0; }
748 bool MainApp::IsCoordinatesConsistent(Business & business)
749 {
750     double lat1 = toRadians(currentLatitude);
751     double lon1 = toRadians(currentLongitude);
752     double lat2 = toRadians(business.Latitude);
753     double lon2 = toRadians(business.Longitude);
754     double x = (lon2 - lon1) * cos((lat1 + lat2)/2);
755     double y = lat2 - lat1;
756     double DistanceFromCoords = EARTH_RADIUS * sqrt(pow(x, 2) + pow(y, 2));
757
758     /* if calculated distance is not between +/- 10% of the announced
759      * distance -> skip this POI: */
760     if (DistanceFromCoords < business.Distance * 0.9 ||
761         DistanceFromCoords > business.Distance * 1.1)
762     {
763         TRACE_ERROR("Announced distance: %f, calculated distance: %f", business.Distance, DistanceFromCoords);
764         return false;
765     }
766
767     return true;
768 }
769 /* end of workaround */
770
771 bool MainApp::CheckNaviApi(int argc, char *argv[])
772 {
773     bool ret = naviapi.connect(argc, argv, this);
774
775     if (ret == true)
776     {
777         naviapi.getAllSessions();
778     }
779
780     return ret;
781 }
782
783 int MainApp::AuthenticatePOI(const QString & CredentialsFile)
784 {
785     char buf[512];
786     QString AppId;
787     QString AppSecret;
788     QString ProxyHostName;
789     QString PortStr;
790     QString User;
791     QString Password;
792     int portnum;
793
794     /* First, read AppId and AppSecret from credentials file: */
795     FILE* filep = fopen(qPrintable(CredentialsFile), "r");
796     if (!filep)
797     {
798         fprintf(stderr,"Failed to open credentials file \"%s\": %m", qPrintable(CredentialsFile));
799         return -1;
800     }
801
802     if (!fgets(buf, 512, filep))
803     {
804         fprintf(stderr,"Failed to read AppId from credentials file \"%s\"", qPrintable(CredentialsFile));
805         fclose(filep);
806         return -1;
807     }
808     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
809         buf[strlen(buf)-1] = '\0';
810     AppId = QString(buf);
811
812     if (!fgets(buf, 512, filep))
813     {
814         fprintf(stderr,"Failed to read AppSecret from credentials file \"%s\"", qPrintable(CredentialsFile));
815         fclose(filep);
816         return -1;
817     }
818     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
819         buf[strlen(buf)-1] = '\0';
820     AppSecret = QString(buf);
821
822     QNetworkProxy proxy;
823
824     //ProxyHostName
825     if (!fgets(buf, 512, filep))
826     {
827         TRACE_INFO("Failed to read ProxyHostName from credentials file \"%s\"", qPrintable(CredentialsFile));
828     }
829     else
830     {
831         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
832             buf[strlen(buf)-1] = '\0';
833         ProxyHostName = QString(buf);
834         ProxyHostName.replace(0, 14, tr(""));
835
836         //Port
837         if (!fgets(buf, 512, filep))
838         {
839             TRACE_ERROR("Failed to read Port from credentials file \"%s\"", qPrintable(CredentialsFile));
840             fclose(filep);
841             return -1;
842         }
843         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
844             buf[strlen(buf)-1] = '\0';
845         PortStr = QString(buf);
846         PortStr.replace(0, 5, tr(""));
847         portnum = PortStr.toInt();
848
849         //User
850         if (!fgets(buf, 512, filep))
851         {
852             TRACE_ERROR("Failed to read User from credentials file \"%s\"", qPrintable(CredentialsFile));
853             fclose(filep);
854             return -1;
855         }
856         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
857             buf[strlen(buf)-1] = '\0';
858         User = QString(buf);
859         User.replace(0, 5, tr(""));
860
861         //Password
862         if (!fgets(buf, 512, filep))
863         {
864             TRACE_ERROR("Failed to read Password from credentials file \"%s\"", qPrintable(CredentialsFile));
865             fclose(filep);
866             return -1;
867         }
868         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
869             buf[strlen(buf)-1] = '\0';
870         Password = QString(buf);
871         Password.replace(0, 9, tr(""));
872
873         proxy.setType(QNetworkProxy::HttpProxy);
874         proxy.setHostName(qPrintable(ProxyHostName));
875         proxy.setPort(portnum);
876         proxy.setUser(qPrintable(User));
877         proxy.setPassword(qPrintable(Password));
878         QNetworkProxy::setApplicationProxy(proxy);
879     }
880
881     fclose(filep);
882
883     TRACE_INFO("Found credentials");
884
885     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
886
887     token = AppSecret;
888     return 0;
889 }
890
891 int MainApp::StartMonitoringUserInput()
892 {
893     connect(&searchBtn, SIGNAL(clicked(bool)), this, SLOT(searchBtnClicked()));
894     connect(&lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &)));
895     connect(&networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReplySearch(QNetworkReply*)));
896     connect(&keyboard, SIGNAL(keyClicked(const QString &)), this, SLOT(textAdded(const QString &)));
897     connect(&keyboard, SIGNAL(specialKeyClicked(int)), this, SLOT(keyPressed(int)));
898     return 1;
899 }
900
901 void MainApp::SetWayPoints(uint32_t myRoute)
902 {
903     /* set the destination : */
904     naviapi::Waypoint destWp(this->destinationLatitude, this->destinationLongitude);
905     std::vector<naviapi::Waypoint> myWayPoints;
906     myWayPoints.push_back(destWp);
907     naviapi.setWaypoints(navicoreSession, myRoute, true, myWayPoints);
908
909     naviapi.calculateRoute(navicoreSession, myRoute);
910
911     /* reset search: */
912     currentSearchingText = tr("");
913     currentSearchedText = tr("");
914     currentIndex = 0;
915     Businesses.clear();
916 }
917
918 void MainApp::goClicked()
919 {
920     TRACE_DEBUG("Go clicked !");
921     SetDestination(currentIndex);
922     DisplayLineEdit(false);
923 }
924
925 void MainApp::cancelClicked()
926 {
927     TRACE_DEBUG("Cancel clicked !");
928     DisplayInformation(false, false);
929     DisplayResultList(true, false);
930     FillResultList(Businesses, currentIndex);
931 }
932
933 void MainApp::getAllSessions_reply(const std::map< uint32_t, std::string >& allSessions)
934 {
935     mutex.lock();
936
937     if (allSessions.empty())
938     {
939         TRACE_ERROR("Error: could not find an instance of Navicore");
940         mutex.unlock();
941         return;
942     }
943
944     this->navicoreSession = allSessions.begin()->first;
945
946     TRACE_INFO("Current session: %d", this->navicoreSession);
947
948     mutex.unlock();
949 }
950
951
952 void MainApp::getPosition_reply(std::map< int32_t, naviapi::variant > position)
953 {
954     mutex.lock();
955
956     std::map< int32_t, naviapi::variant >::iterator it;
957     for (it = position.begin(); it != position.end(); it++)
958     {
959         if (it->first == naviapi::NAVICORE_LATITUDE)
960         {
961             currentLatitude = it->second._double;
962         }
963         else if (it->first == naviapi::NAVICORE_LONGITUDE)
964         {
965             currentLongitude = it->second._double;
966         }
967     }
968
969     TRACE_INFO("Current position: %f, %f", currentLatitude, currentLongitude);
970
971     mutex.unlock();
972
973     emit positionGotSignal();
974 }
975
976 void MainApp::getAllRoutes_reply(std::vector< uint32_t > allRoutes)
977 {
978     mutex.lock();
979
980     uint32_t routeHandle = 0;
981
982     if (allRoutes.size() != 0)
983     {
984         routeHandle = allRoutes[0];
985     }
986
987     this->currentRouteHandle = routeHandle;
988
989     mutex.unlock();
990
991     emit allRoutesGotSignal();
992 }
993
994 void MainApp::createRoute_reply(uint32_t routeHandle)
995 {
996     mutex.lock();
997
998     this->currentRouteHandle = routeHandle;
999
1000     mutex.unlock();
1001
1002     emit routeCreatedSignal();
1003 }
1004
1005 void MainApp::positionGot()
1006 {
1007     mutex.lock();
1008
1009     /* let's generate a search request : */
1010     QString myUrlStr = URL_SEARCH + tr("?") + tr("term=") + currentSearchingText +
1011         tr("&latitude=") + QString::number(currentLatitude) +
1012         tr("&longitude=") + QString::number(currentLongitude);
1013
1014     TRACE_DEBUG("URL: %s", qPrintable(myUrlStr));
1015
1016     QUrl myUrl = QUrl(myUrlStr);
1017     QNetworkRequest req(myUrl);
1018     req.setRawHeader(QByteArray("Authorization"), (tr("bearer ") + token).toLocal8Bit());
1019
1020     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
1021
1022     pSearchReply = networkManager.get(req);
1023
1024     mutex.unlock();
1025 }
1026
1027 void MainApp::allRoutesGot()
1028 {
1029     mutex.lock();
1030
1031     /* check if a route already exists, if not create it : */
1032     if (this->currentRouteHandle == 0)
1033     {
1034         naviapi.createRoute(navicoreSession);
1035     }
1036     else
1037     {
1038         naviapi.pauseSimulation(navicoreSession);
1039         naviapi.setSimulationMode(navicoreSession, false);
1040         naviapi.cancelRouteCalculation(navicoreSession, this->currentRouteHandle);
1041         sleep(1);
1042
1043         SetWayPoints(this->currentRouteHandle);
1044     }
1045
1046     mutex.unlock();
1047 }
1048
1049 void MainApp::routeCreated()
1050 {
1051     mutex.lock();
1052
1053     SetWayPoints(this->currentRouteHandle);
1054
1055     mutex.unlock();
1056 }
1057