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