poi-yelp: fix issue with text input on startup
[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 #define DEFAULT_TEXT        "Select your destination with Yelp !"
23 #define URL_AUTH            "https://api.yelp.com/oauth2/token"
24 #define URL_AUTOCOMPLETE    "https://api.yelp.com/v3/autocomplete"
25 #define URL_SEARCH          "https://api.yelp.com/v3/businesses/search"
26
27 #define BIG_BUFFER_SIZE     (1024*1024)
28 #define LEFT_OFFSET         28
29 #define FONT_SIZE_LINEDIT   20
30 #define FONT_SIZE_LIST      18
31 #define TEXT_INPUT_WIDTH    800
32 #define SEARCH_BTN_SIZE     105
33 #define SPACER              15
34 #define WIDGET_WIDTH        (SEARCH_BTN_SIZE + SPACER + TEXT_INPUT_WIDTH)
35 #define DISPLAY_WIDTH       TEXT_INPUT_WIDTH
36 #define DISPLAY_HEIGHT      480
37 #define COMPLETE_W_WITH_KB    1080
38 #define COMPLETE_H_WITH_KB    1488
39 #define RESULT_ITEM_HEIGHT  80
40 #define MARGINS             25
41 #define AGL_REFRESH_DELAY   75 /* milliseconds */
42
43 #define SCROLLBAR_STYLE \
44 "QScrollBar:vertical {" \
45 "    border: 2px solid grey;" \
46 "    background: gray;" \
47 "    width: 45px;" \
48 "}"
49
50 using namespace std;
51
52 MainApp::MainApp():QMainWindow(Q_NULLPTR, Qt::FramelessWindowHint),
53     networkManager(this),searchBtn(QIcon(tr(":/images/loupe-90.png")), tr(""), this),
54     lineEdit(this),keyboard(QRect(0, 688, COMPLETE_W_WITH_KB, 720), this),
55     mutex(QMutex::Recursive),token(""),currentSearchingText(""),currentSearchedText(""),
56     pSearchReply(NULL),pInfoPanel(NULL),pResultList(NULL),currentLatitude(0.0),currentLongitude(0.0),
57     navicoreSession(0),currentIndex(0),fontId(-1),isInfoScreen(false),
58     isInputDisplayed(false),isKeyboard(false),isAglNavi(false)
59 {
60     //this->setAttribute(Qt::WA_TranslucentBackground);
61     this->setStyleSheet("border: none;");
62
63     searchBtn.setStyleSheet("border: none; color: #FFFFFF;");
64     searchBtn.setMinimumSize(QSize(SEARCH_BTN_SIZE, SEARCH_BTN_SIZE));
65     searchBtn.setIconSize(searchBtn.size());
66     searchBtn.setGeometry(QRect(LEFT_OFFSET, 0, searchBtn.width(), searchBtn.height()));
67
68     lineEdit.setStyleSheet("border: none; color: #FFFFFF;");
69     lineEdit.setMinimumSize(QSize(TEXT_INPUT_WIDTH, SEARCH_BTN_SIZE));
70
71     lineEdit.setPlaceholderText(QString(DEFAULT_TEXT));
72     font = lineEdit.font();
73     font.setPointSize(FONT_SIZE_LINEDIT);
74     lineEdit.setFont(font);
75     lineEdit.setTextMargins(MARGINS/2, 0, 0, 0);
76     lineEdit.installEventFilter(this);
77     lineEdit.setGeometry(QRect(LEFT_OFFSET + searchBtn.width() + SPACER, 0, lineEdit.width(), lineEdit.height()));
78     lineEdit.setVisible(false);
79
80     /* We might need a Japanese font: */
81     QFile fontFile(":/fonts/DroidSansJapanese.ttf");
82     if (!fontFile.open(QIODevice::ReadOnly))
83     {
84         TRACE_ERROR("failed to open font file");
85     }
86     else
87     {
88         QByteArray fontData = fontFile.readAll();
89         fontId = QFontDatabase::addApplicationFontFromData(fontData);
90         if (fontId < 0)
91         {
92             TRACE_ERROR("QFontDatabase::addApplicationFontFromData failed");
93         }
94     }
95
96     /* Check if "AGL_NAVI" env variable is set. If yes, we must notify
97      * AGL environment when surface needs to be resized */
98     if (getenv("AGL_NAVI"))
99         isAglNavi = true;
100
101     connect(this, SIGNAL(allSessionsGotSignal()), this, SLOT(allSessionsGot()));
102     connect(this, SIGNAL(positionGotSignal()), this, SLOT(positionGot()));
103     connect(this, SIGNAL(allRoutesGotSignal()), this, SLOT(allRoutesGot()));
104     connect(this, SIGNAL(routeCreatedSignal()), this, SLOT(routeCreated()));
105
106     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
107     this->setStyleSheet("background-image: url(:/images/AGL_POI_Background.png);");
108     this->show();
109 }
110
111 MainApp::~MainApp()
112 {
113     mutex.lock();
114     if (fontId >= 0)
115         QFontDatabase::removeApplicationFont(fontId);
116
117     searchBtn.disconnect();
118     lineEdit.disconnect();
119     networkManager.disconnect();
120     keyboard.disconnect();
121
122     delete pSearchReply;
123     delete pInfoPanel;
124     mutex.unlock();
125 }
126
127 void MainApp::searchBtnClicked()
128 {
129     isInputDisplayed = !isInputDisplayed;
130     TRACE_DEBUG("isInputDisplayed = %d", isInputDisplayed);
131     DisplayLineEdit(isInputDisplayed);
132 }
133
134 void MainApp::DisplayLineEdit(bool display)
135 {
136     mutex.lock();
137
138     this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
139
140     if (display)
141     {
142         lineEdit.setVisible(true);
143         lineEdit.setFocus();
144     }
145     else
146     {
147         if (pResultList)
148         {
149             pResultList->removeEventFilter(this);
150             delete pResultList;
151             pResultList = NULL;
152         }
153         if (pInfoPanel)
154         {
155             delete pInfoPanel;
156             pInfoPanel = NULL;
157         }
158         lineEdit.setText(tr(""));
159         lineEdit.setVisible(false);
160     }
161     isInputDisplayed = display;
162
163     mutex.unlock();
164 }
165
166 void MainApp::UpdateAglSurfaces()
167 {
168     char cmd[1024];
169
170     TRACE_DEBUG("handle AGL demo surfaces (new surface is bigger)");
171     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT source region 0 0 %d %d",
172         this->width(), this->height());
173     TRACE_DEBUG("%s", cmd);
174     system(cmd);
175     snprintf(cmd, 1023, "/usr/bin/LayerManagerControl set surface $SURFACE_ID_CLIENT destination region $CLIENT_X $CLIENT_Y %d %d",
176         this->width(), this->height());
177     TRACE_DEBUG("%s", cmd);
178     system(cmd);
179 }
180
181 void MainApp::DisplayResultList(bool display, bool RefreshDisplay)
182 {
183     mutex.lock();
184
185     if (display)
186     {
187         if (!pResultList)
188         {
189             pResultList = new QTreeWidget(this);
190             pResultList->setStyleSheet("border: none; color: #FFFFFF;");
191             pResultList->setRootIsDecorated(false);
192             pResultList->setEditTriggers(QTreeWidget::NoEditTriggers);
193             pResultList->setSelectionBehavior(QTreeWidget::SelectRows);
194             pResultList->setFrameStyle(QFrame::Box | QFrame::Plain);
195             pResultList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
196             //pResultList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
197             pResultList->setAttribute(Qt::WA_AcceptTouchEvents);
198             pResultList->verticalScrollBar()->setStyleSheet(SCROLLBAR_STYLE);
199             pResultList->header()->hide();
200             //font.setPointSize(FONT_SIZE_LIST);
201             //pResultList->setFont(font);
202             pResultList->installEventFilter(this);
203         }
204
205         pResultList->setGeometry(QRect(   LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
206                                         DISPLAY_WIDTH, DISPLAY_HEIGHT));
207         if (RefreshDisplay)
208         {
209             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
210         }
211         pResultList->setVisible(true);
212         pResultList->setFocus();
213     }
214     else
215     {
216         if (pResultList)
217         {
218             pResultList->removeEventFilter(this);
219             pResultList->deleteLater();
220             pResultList = NULL;
221         }
222
223         lineEdit.setFocus();
224
225         if (RefreshDisplay)
226         {
227             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
228         }
229     }
230
231     mutex.unlock();
232 }
233
234 void MainApp::textChanged(const QString & text)
235 {
236     TRACE_INFO("New text is: %s", qPrintable(text));
237
238     /* do not handle text input if info panel is displayed: */
239     if (pInfoPanel) return;
240
241     mutex.lock();
242
243     delete pSearchReply;    /* cancel current search */
244     pSearchReply = NULL;
245
246     if (text.length() == 0) /* if empty text -> no search */
247     {
248         DisplayResultList(false);
249         mutex.unlock();
250         return;
251     }
252
253     /* if text is the same as previous search -> no need to search again */
254     if (text == currentSearchedText)
255     {
256         DisplayResultList(true);
257         FillResultList(Businesses, currentIndex);
258         mutex.unlock();
259         return;
260     }
261     this->currentSearchingText = text;
262
263     /* we need to know our current position */
264     std::vector<int32_t> Params;
265     Params.push_back(naviapi::NAVICORE_LONGITUDE);
266     Params.push_back(naviapi::NAVICORE_LATITUDE);
267     naviapi.getPosition(Params);
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, false);
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, 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     naviapi.getAllRoutes();
567
568     mutex.unlock();
569 }
570
571 void MainApp::DisplayInformation(bool display, bool RefreshDisplay)
572 {
573     mutex.lock();
574     if (display)
575     {
576         /* pResultList must exist, so that we can retrieve the selected index: */
577         if (!pResultList)
578         {
579             TRACE_ERROR("pResultList is null");
580             mutex.unlock();
581             return;
582         }
583
584         QList<QTreeWidgetItem *> SelectedItems = pResultList->selectedItems();
585         if (SelectedItems.size() <= 0)
586         {
587             TRACE_ERROR("no item is selected");
588             mutex.unlock();
589             return;
590         }
591
592         /* select the first selected item : */
593         currentIndex = pResultList->indexOfTopLevelItem(*SelectedItems.begin());
594
595         /* Resize window: */
596         DisplayResultList(false, false);
597
598         /* Display info for the selected item: */
599         QRect rect( LEFT_OFFSET+searchBtn.width()+SPACER, searchBtn.height()+SPACER,
600                     DISPLAY_WIDTH, DISPLAY_HEIGHT);
601         pInfoPanel = new InfoPanel(this, Businesses[currentIndex], rect);
602
603         if (RefreshDisplay)
604         {
605             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
606         }
607
608         connect(pInfoPanel->getGoButton(),      SIGNAL(clicked(bool)), this, SLOT(goClicked()));
609         connect(pInfoPanel->getCancelButton(),  SIGNAL(clicked(bool)), this, SLOT(cancelClicked()));
610     }
611     else
612     {
613         if (pInfoPanel)
614         {
615             pInfoPanel->getGoButton()->disconnect();
616             pInfoPanel->getCancelButton()->disconnect();
617             delete pInfoPanel;
618             pInfoPanel = NULL;
619         }
620         lineEdit.setFocus();
621
622         if (RefreshDisplay)
623         {
624             this->setGeometry(QRect(this->pos().x(), this->pos().y(), COMPLETE_W_WITH_KB, COMPLETE_H_WITH_KB));
625         }
626     }
627
628     mutex.unlock();
629 }
630
631 void MainApp::networkReplySearch(QNetworkReply* reply)
632 {
633     char buf[BIG_BUFFER_SIZE];
634     int buflen;
635
636     mutex.lock();
637
638     /* memorize the text which gave this result: */
639     currentSearchedText = lineEdit.text();
640
641         if (reply->error() == QNetworkReply::NoError)
642         {
643             // we only handle this callback if it matches the last search request:
644             if (reply != pSearchReply)
645             {
646                 TRACE_INFO("this reply is already too late (or about a different network request)");
647                 mutex.unlock();
648                 return;
649             }
650
651         buflen = reply->read(buf, BIG_BUFFER_SIZE-1);
652             buf[buflen] = '\0';
653
654             if (buflen == 0)
655             {
656                 mutex.unlock();
657                 return;
658             }
659
660
661
662             currentIndex = 0;
663             Businesses.clear();
664             ParseJsonBusinessList(buf, Businesses);
665             DisplayResultList(true);
666             FillResultList(Businesses);
667     }
668     else
669     {
670         fprintf(stderr,"POI: reply error network please check to poikey and system time (adjusted?)\n");
671     }
672
673     mutex.unlock();
674 }
675
676 /* pResultList must be allocated at this point ! */
677 int MainApp::FillResultList(vector<Business> & list, int focusIndex)
678 {
679     int nbElem = 0;
680
681     mutex.lock();
682
683     pResultList->setUpdatesEnabled(false);
684     pResultList->clear();
685
686     /* filling the dropdown menu: */
687     for (vector<Business>::iterator it = list.begin(); it != list.end(); it++)
688     {
689         /*  workaround to avoid entries with wrong coordinates returned by Yelp: */
690         if (IsCoordinatesConsistent(*it) == false)
691         {
692             list.erase(it--);
693             continue;
694         }
695
696         QTreeWidgetItem * item = new QTreeWidgetItem(pResultList);
697
698         ClickableLabel *label = new ClickableLabel("<b>"+(*it).Name+
699             "</b><br>"+(*it).Address+", "+(*it).City+", "+(*it).State+
700             " "+(*it).ZipCode+", "+(*it).Country, pResultList);
701         label->setTextFormat(Qt::RichText);
702         font.setPointSize(FONT_SIZE_LIST);
703         label->setFont(font);
704         label->setIndent(MARGINS);
705         label->setAttribute(Qt::WA_AcceptTouchEvents);
706         item->setSizeHint(0, QSize(TEXT_INPUT_WIDTH, RESULT_ITEM_HEIGHT));
707         pResultList->setItemWidget(item, 0, label);
708         connect(label, SIGNAL(clicked()), this, SLOT(itemClicked()));
709
710         //item->setText(0, (*it).Name);
711
712         if (nbElem == focusIndex)
713         {
714             pResultList->setCurrentItem(item);
715         }
716         nbElem++;
717     }
718
719     pResultList->setUpdatesEnabled(true);
720
721     mutex.unlock();
722     return nbElem;
723 }
724
725 /* Well... some of the POI returned by Yelp have coordinates which are
726  * completely inconsistent with the distance at which the POI is
727  * supposed to be.
728  * https://github.com/Yelp/yelp-fusion/issues/104
729  * Let's skip them for the moment: */
730 #define PI 3.14159265
731 #define EARTH_RADIUS 6371000
732 static inline double toRadians(double a) { return a * PI / 180.0; }
733 bool MainApp::IsCoordinatesConsistent(Business & business)
734 {
735     double lat1 = toRadians(currentLatitude);
736     double lon1 = toRadians(currentLongitude);
737     double lat2 = toRadians(business.Latitude);
738     double lon2 = toRadians(business.Longitude);
739     double x = (lon2 - lon1) * cos((lat1 + lat2)/2);
740     double y = lat2 - lat1;
741     double DistanceFromCoords = EARTH_RADIUS * sqrt(pow(x, 2) + pow(y, 2));
742
743     /* if calculated distance is not between +/- 10% of the announced
744      * distance -> skip this POI: */
745     if (DistanceFromCoords < business.Distance * 0.9 ||
746         DistanceFromCoords > business.Distance * 1.1)
747     {
748         TRACE_ERROR("Announced distance: %f, calculated distance: %f", business.Distance, DistanceFromCoords);
749         return false;
750     }
751
752     return true;
753 }
754 /* end of workaround */
755
756 bool MainApp::CheckNaviApi(int argc, char *argv[])
757 {
758     bool ret = naviapi.connect(argc, argv, this);
759
760     if (ret == true)
761     {
762         naviapi.getAllSessions();
763     }
764
765     return ret;
766 }
767
768 int MainApp::AuthenticatePOI(const QString & CredentialsFile)
769 {
770     char buf[512];
771     QString AppId;
772     QString AppSecret;
773     QString ProxyHostName;
774     QString PortStr;
775     QString User;
776     QString Password;
777     int portnum;
778
779     /* First, read AppId and AppSecret from credentials file: */
780     FILE* filep = fopen(qPrintable(CredentialsFile), "r");
781     if (!filep)
782     {
783         fprintf(stderr,"Failed to open credentials file \"%s\": %m", qPrintable(CredentialsFile));
784         return -1;
785     }
786
787     if (!fgets(buf, 512, filep))
788     {
789         fprintf(stderr,"Failed to read AppId from credentials file \"%s\"", qPrintable(CredentialsFile));
790         fclose(filep);
791         return -1;
792     }
793     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
794         buf[strlen(buf)-1] = '\0';
795     AppId = QString(buf);
796
797     if (!fgets(buf, 512, filep))
798     {
799         fprintf(stderr,"Failed to read AppSecret from credentials file \"%s\"", qPrintable(CredentialsFile));
800         fclose(filep);
801         return -1;
802     }
803     if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
804         buf[strlen(buf)-1] = '\0';
805     AppSecret = QString(buf);
806
807     QNetworkProxy proxy;
808
809     //ProxyHostName
810     if (!fgets(buf, 512, filep))
811     {
812         TRACE_INFO("Failed to read ProxyHostName from credentials file \"%s\"", qPrintable(CredentialsFile));
813     }
814     else
815     {
816         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
817             buf[strlen(buf)-1] = '\0';
818         ProxyHostName = QString(buf);
819         ProxyHostName.replace(0, 14, tr(""));
820
821         //Port
822         if (!fgets(buf, 512, filep))
823         {
824             TRACE_ERROR("Failed to read Port from credentials file \"%s\"", qPrintable(CredentialsFile));
825             fclose(filep);
826             return -1;
827         }
828         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
829             buf[strlen(buf)-1] = '\0';
830         PortStr = QString(buf);
831         PortStr.replace(0, 5, tr(""));
832         portnum = PortStr.toInt();
833
834         //User
835         if (!fgets(buf, 512, filep))
836         {
837             TRACE_ERROR("Failed to read User from credentials file \"%s\"", qPrintable(CredentialsFile));
838             fclose(filep);
839             return -1;
840         }
841         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
842             buf[strlen(buf)-1] = '\0';
843         User = QString(buf);
844         User.replace(0, 5, tr(""));
845
846         //Password
847         if (!fgets(buf, 512, filep))
848         {
849             TRACE_ERROR("Failed to read Password from credentials file \"%s\"", qPrintable(CredentialsFile));
850             fclose(filep);
851             return -1;
852         }
853         if (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
854             buf[strlen(buf)-1] = '\0';
855         Password = QString(buf);
856         Password.replace(0, 9, tr(""));
857
858         proxy.setType(QNetworkProxy::HttpProxy);
859         proxy.setHostName(qPrintable(ProxyHostName));
860         proxy.setPort(portnum);
861         proxy.setUser(qPrintable(User));
862         proxy.setPassword(qPrintable(Password));
863         QNetworkProxy::setApplicationProxy(proxy);
864     }
865
866     fclose(filep);
867
868     TRACE_INFO("Found credentials");
869
870     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
871
872         token = AppSecret;
873         return 0;
874 }
875
876 int MainApp::StartMonitoringUserInput()
877 {
878     connect(&searchBtn, SIGNAL(clicked(bool)), this, SLOT(searchBtnClicked()));
879     connect(&lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &)));
880     connect(&networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReplySearch(QNetworkReply*)));
881     connect(&keyboard, SIGNAL(keyClicked(const QString &)), this, SLOT(textAdded(const QString &)));
882     connect(&keyboard, SIGNAL(specialKeyClicked(int)), this, SLOT(keyPressed(int)));
883     return 1;
884 }
885
886 void MainApp::SetWayPoints(uint32_t myRoute)
887 {
888     /* set the destination : */
889     naviapi::Waypoint destWp(this->destinationLatitude, this->destinationLongitude);
890     std::vector<naviapi::Waypoint> myWayPoints;
891     myWayPoints.push_back(destWp);
892     naviapi.setWaypoints(navicoreSession, myRoute, true, myWayPoints);
893
894     naviapi.calculateRoute(navicoreSession, myRoute);
895
896     /* reset search: */
897     currentSearchingText = tr("");
898     currentSearchedText = tr("");
899     currentIndex = 0;
900     Businesses.clear();
901 }
902
903 void MainApp::goClicked()
904 {
905     TRACE_DEBUG("Go clicked !");
906     SetDestination(currentIndex);
907     DisplayLineEdit(false);
908 }
909
910 void MainApp::cancelClicked()
911 {
912     TRACE_DEBUG("Cancel clicked !");
913     DisplayInformation(false, false);
914     DisplayResultList(true, false);
915     FillResultList(Businesses, currentIndex);
916 }
917
918 void MainApp::getAllSessions_reply(const std::map< uint32_t, std::string >& allSessions)
919 {
920     mutex.lock();
921
922     if (allSessions.empty())
923     {
924         TRACE_ERROR("Error: could not find an instance of Navicore");
925         mutex.unlock();
926         return;
927     }
928
929     this->navicoreSession = allSessions.begin()->first;
930
931     TRACE_INFO("Current session: %d", this->navicoreSession);
932
933     mutex.unlock();
934
935     emit allSessionsGotSignal();
936 }
937
938
939 void MainApp::getPosition_reply(std::map< int32_t, naviapi::variant > position)
940 {
941     mutex.lock();
942
943     std::map< int32_t, naviapi::variant >::iterator it;
944     for (it = position.begin(); it != position.end(); it++)
945     {
946         if (it->first == naviapi::NAVICORE_LATITUDE)
947         {
948             currentLatitude = it->second._double;
949         }
950         else if (it->first == naviapi::NAVICORE_LONGITUDE)
951         {
952             currentLongitude = it->second._double;
953         }
954     }
955
956     TRACE_INFO("Current position: %f, %f", currentLatitude, currentLongitude);
957
958     mutex.unlock();
959
960     emit positionGotSignal();
961 }
962
963 void MainApp::getAllRoutes_reply(std::vector< uint32_t > allRoutes)
964 {
965     mutex.lock();
966
967     uint32_t routeHandle = 0;
968
969     if (allRoutes.size() != 0)
970     {
971         routeHandle = allRoutes[0];
972     }
973
974     this->currentRouteHandle = routeHandle;
975
976     mutex.unlock();
977
978     emit allRoutesGotSignal();
979 }
980
981 void MainApp::createRoute_reply(uint32_t routeHandle)
982 {
983     mutex.lock();
984
985     this->currentRouteHandle = routeHandle;
986
987     mutex.unlock();
988
989     emit routeCreatedSignal();
990 }
991
992 void MainApp::allSessionsGot()
993 {
994     mutex.lock();
995
996     // nothing to do
997
998     mutex.unlock();
999 }
1000
1001 void MainApp::positionGot()
1002 {
1003     mutex.lock();
1004
1005     /* let's generate a search request : */
1006     QString myUrlStr = URL_SEARCH + tr("?") + tr("term=") + currentSearchingText +
1007         tr("&latitude=") + QString::number(currentLatitude) +
1008         tr("&longitude=") + QString::number(currentLongitude);
1009
1010     TRACE_DEBUG("URL: %s", qPrintable(myUrlStr));
1011
1012     QUrl myUrl = QUrl(myUrlStr);
1013     QNetworkRequest req(myUrl);
1014     req.setRawHeader(QByteArray("Authorization"), (tr("bearer ") + token).toLocal8Bit());
1015
1016     /* Then, send a HTTP request to get the token and wait for answer (synchronously): */
1017
1018     pSearchReply = networkManager.get(req);
1019
1020     mutex.unlock();
1021 }
1022
1023 void MainApp::allRoutesGot()
1024 {
1025     mutex.lock();
1026
1027     /* check if a route already exists, if not create it : */
1028     if (this->currentRouteHandle == 0)
1029     {
1030         naviapi.createRoute(navicoreSession);
1031     }
1032     else
1033     {
1034         naviapi.pauseSimulation(navicoreSession);
1035         naviapi.setSimulationMode(navicoreSession, false);
1036         naviapi.cancelRouteCalculation(navicoreSession, this->currentRouteHandle);
1037         sleep(1);
1038
1039         SetWayPoints(this->currentRouteHandle);
1040     }
1041
1042     mutex.unlock();
1043 }
1044
1045 void MainApp::routeCreated()
1046 {
1047     mutex.lock();
1048
1049     SetWayPoints(this->currentRouteHandle);
1050
1051     mutex.unlock();
1052 }
1053