fix crash problems and update the openweathermap URLs
[apps/thermostat-demo.git] / mainwindow.cpp
1 #include "mainwindow.h"\r
2 #include "forecastdata.h"\r
3 #include "weatherwidget.h"\r
4 #include "thermostatwidget.h"\r
5 #include "optionswidget.h"\r
6 #include "remoteaccessmanager.h"\r
7 #include "settingscreen.h"\r
8 #include "globalsettings.h"\r
9 #include "weatherdata.h"\r
10 #include "utilities.h"\r
11 \r
12 #include <QtGui>\r
13 \r
14 #include <QtDebug>\r
15 \r
16 /***********************************************************************************************************\r
17 * MainWindow\r
18 *   Main Widget that holds all the child widgets and handles their layouts. Also coordinates loading of\r
19 *   data from the web for the weather widget from the webdata class.\r
20 ************************************************************************************************************/\r
21 \r
22 MainWindow::MainWindow(QWidget *parent) :\r
23     QWidget(parent)\r
24 {\r
25 \r
26     QString http_proxy = QString::fromLatin1(qgetenv("http_proxy"));\r
27 \r
28     // the MainWindow provides the container for the ThermostatWidget, WeatherWidget, and OptionsWidget\r
29     // it is also provides much of the interface between WebData and the widgets that require web updates\r
30     // it is set to initial hard-coded values first, and only updated from the web if internet options\r
31     // have been allowed\r
32     setObjectName("MainWindow");\r
33 \r
34     //get a handle to the global settings singleton\r
35     m_globalSettings = GlobalSettings::getInstance();\r
36 \r
37     // Initialize the proxy setting from http_proxy if defined\r
38     if(!http_proxy.isEmpty())\r
39     {\r
40         QUrl url(http_proxy);\r
41         m_globalSettings->setProxyInfo(url.host(), url.port());\r
42     }\r
43 \r
44     // sets initial weather background\r
45     setStyleSheet("mainwindow {border-image: url(:/Images/background-sunny-very-few-clouds.JPG)}");\r
46 \r
47     // remove cursor (i.e. mouse pointer) throughout application\r
48     //qApp->setOverrideCursor( QCursor( Qt::BlankCursor ) );\r
49 \r
50     //set a default temp for the thermostat\r
51     m_currentThermostatTemp = 72;\r
52 \r
53     // create web data object\r
54     webData = new WebData;\r
55 \r
56     //connect the signals for asynchronous data transfer\r
57     connect(webData, SIGNAL(dataAvailable(WeatherData*)), this, SLOT(setWebData(WeatherData*)));\r
58     connect(webData, SIGNAL(networkTimeout()), this, SLOT(webDataFailed()));\r
59 \r
60     // create weather widget\r
61     weatherWidget = new WeatherWidget(this);\r
62     connect(weatherWidget, SIGNAL(webReloadRequested()), this, SLOT(loadWebData()));\r
63 \r
64     // create thermostat widget\r
65     thermostatWidget = new ThermostatWidget;\r
66     thermostatWidget->setCurrentTempPtr(&m_currentThermostatTemp);\r
67 \r
68     // create options widget\r
69     optionsWidget = new OptionsWidget;\r
70     optionsWidget->awayScreen->setCurrentTempPtr(&m_currentThermostatTemp);\r
71     optionsWidget->settingScreen->setLicenseString(webData->licenseString());\r
72 \r
73     // connect 10 second timer that causes current temp to follow setpoint to pass signal through options widget\r
74     connect(thermostatWidget, SIGNAL(timeout()), optionsWidget, SIGNAL(currentTempChanged()));\r
75     // connect new city name signal to the MainWindow function changeCity()\r
76     connect(optionsWidget, SIGNAL(cityChanged()), this, SLOT(loadWebData()));\r
77     connect(optionsWidget, SIGNAL(valueChanged()), webData, SLOT(configureProxy()));\r
78 \r
79     // connect Celsius/Fahrenheit change signals\r
80     connect(optionsWidget, SIGNAL(valueChanged()), thermostatWidget, SLOT(updateUnit()));\r
81     connect(optionsWidget, SIGNAL(valueChanged()), weatherWidget, SIGNAL(valueChanged()));\r
82     connect(optionsWidget, SIGNAL(valueChanged()), this, SLOT(updateClock()));\r
83 \r
84     // make energy button change when setpoint is reached\r
85     connect(thermostatWidget, SIGNAL(setpointIsReached(bool)),this,SLOT(energySaving(bool)));\r
86 \r
87     // create energy button\r
88     energyButton = new QPushButton("48kW");\r
89     energyButton->setObjectName("energyButton");\r
90     energyButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);\r
91     energyButton->setAttribute(Qt::WA_StyledBackground,true);\r
92     energyButton->setFocusPolicy(Qt::NoFocus);\r
93 \r
94     // create time button\r
95     timeButton = new QPushButton("4:18PM");\r
96     timeButton->setObjectName("timeButton");\r
97     timeButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);\r
98     timeButton->setAttribute(Qt::WA_StyledBackground);\r
99     timeButton->setFocusPolicy(Qt::NoFocus);\r
100 \r
101     // create date button\r
102     dateButton = new QPushButton("Mon, Jun 17");\r
103     dateButton->setObjectName("dateButton");\r
104     dateButton->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed);\r
105     dateButton->setAttribute(Qt::WA_StyledBackground);\r
106     dateButton->setFocusPolicy(Qt::NoFocus);\r
107 \r
108     // create close button\r
109     closeButton = new QPushButton();\r
110     closeButton->setIcon(QIcon(":/Images/simple-red-square-icon-x.png"));\r
111     closeButton->setIconSize(QSize(20,20));\r
112     closeButton->setObjectName("closeButton");\r
113     closeButton->setFocusPolicy(Qt::NoFocus);\r
114     connect(closeButton,SIGNAL(clicked()),this, SLOT(close()));\r
115 \r
116     //layout all of the widgets we just instatiated\r
117     createScreenLayout();\r
118 \r
119     //set clock to  local system time\r
120     dateTime = QDateTime::currentDateTime();\r
121 \r
122     //start a timer to attempt to update the clock every second\r
123     clockTimer = new QTimer(this);\r
124     connect(clockTimer,SIGNAL(timeout()),this,SLOT(updateClock()));\r
125     clockTimer->start(1000);\r
126 \r
127 \r
128     //create and start the remote access manager\r
129     RemoteAccessManager *m_remoteAccessManager = new RemoteAccessManager(this);\r
130     m_remoteAccessManager->start();\r
131     //connect(m_remoteAccessManager, SIGNAL(remoteChangeReceived(QHash<QString,QVariant>)), this, SLOT(processCommand(QHash<QString,QVariant>)));\r
132 \r
133     //first set based on local cache so user sees something!\r
134     webData->loadLocalData();\r
135 \r
136     //call the changecity function which dispatches a network request for api weather data.\r
137     loadWebData();\r
138 }\r
139 \r
140 //FUNCTION: createScreenLayout\r
141 //\r
142 //  all code to generate the main layout for the MainWindow is here.\r
143 //\r
144 \r
145 void MainWindow::createScreenLayout()\r
146 {\r
147     // create left portion of the layout holding the options buttons and thermostat control\r
148     QVBoxLayout *leftLayout = new QVBoxLayout;\r
149     leftLayout->addWidget(optionsWidget);\r
150     leftLayout->addWidget(thermostatWidget);\r
151     leftLayout->setStretchFactor(optionsWidget, 1);\r
152     leftLayout->setStretchFactor(thermostatWidget, 3);\r
153 \r
154     // combine the previous layout with the weatherwidget and the close button\r
155     QHBoxLayout *middleLayout = new QHBoxLayout;\r
156     middleLayout->addSpacing(6);\r
157     middleLayout->addLayout(leftLayout);\r
158     middleLayout->addSpacing(6);\r
159     middleLayout->addWidget(weatherWidget);\r
160     middleLayout->addWidget(closeButton);\r
161     middleLayout->addSpacing(6);\r
162     middleLayout->setStretchFactor(leftLayout, 1);\r
163     middleLayout->setStretchFactor(weatherWidget, 1);\r
164     middleLayout->setAlignment(closeButton,Qt::AlignTop);\r
165 \r
166     //combine the previous nested layout with the date time and energy buttons\r
167     QHBoxLayout *bottomLayout = new QHBoxLayout;\r
168     bottomLayout->addWidget(energyButton);\r
169     bottomLayout->addWidget(dateButton);\r
170     bottomLayout->addWidget(timeButton);\r
171     bottomLayout->setSpacing(0);\r
172     bottomLayout->setMargin(0);\r
173 \r
174     //it is easier to define size constraints for a widget so\r
175     //we will encapsulate everything in sizeLimiterWidget\r
176 \r
177     QWidget *sizeLimiterWidget = new QWidget;\r
178     sizeLimiterWidget->setMaximumSize(800, 450);\r
179     sizeLimiterWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);\r
180     sizeLimiterWidget->setLayout(middleLayout);\r
181     QHBoxLayout* contentsBox = new QHBoxLayout;\r
182     contentsBox->addWidget(sizeLimiterWidget);\r
183 \r
184     //build the main layout\r
185     QVBoxLayout *mainLayout = new QVBoxLayout;\r
186     mainLayout->addStretch(0);\r
187     mainLayout->addLayout(contentsBox);\r
188     mainLayout->setStretchFactor(contentsBox, 1);\r
189     mainLayout->addStretch(0);\r
190     mainLayout->addLayout(bottomLayout);\r
191     mainLayout->setContentsMargins(0,0,0,0);\r
192     mainLayout->setAlignment(sizeLimiterWidget,Qt::AlignCenter);\r
193     mainLayout->setAlignment(bottomLayout,Qt::AlignBottom);\r
194 \r
195     // show layout\r
196     setLayout(mainLayout);\r
197 }\r
198 \r
199 //FUNCTION: closeEvent\r
200 //\r
201 //  Reimplemented closeEvent handler just to allow the app to save the settings before exiting\r
202 //\r
203 \r
204 void MainWindow::closeEvent(QCloseEvent *e)\r
205 {\r
206     m_globalSettings->save();\r
207     delete m_globalSettings;\r
208     e->accept();\r
209     qApp->quit();\r
210 }\r
211 \r
212 void MainWindow::paintEvent(QPaintEvent *)\r
213 {\r
214     /* Applying CSS styles to custom widgets inherited from QWidget\r
215     requires reimplementing paintEvent(). Without doing it, custom\r
216     widgets will support only the background, background-clip,\r
217     and background-origin properties.  */\r
218 \r
219     QStyleOption opt;\r
220     opt.init(this);\r
221     QPainter p(this);\r
222     style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);\r
223 }\r
224 \r
225 void MainWindow::updateClock()\r
226 {\r
227     // make clock tick\r
228     dateTime = QDateTime::currentDateTime();\r
229     timeButton->setText(formatTimeString(dateTime.time(), m_globalSettings->timeFormat()));\r
230     dateButton->setText(dateTime.date().toString("ddd, MMM d"));\r
231 \r
232 }\r
233 \r
234 void MainWindow::energySaving(bool setpointReached)\r
235 {\r
236     // change energyButton based on whether or not setpoint is reached\r
237     if(setpointReached == true) {\r
238         energyButton->setStyleSheet("background: green;");\r
239         energyButton->setText("48kW");\r
240     }\r
241     else if(setpointReached == false) {\r
242         energyButton->setStyleSheet("background: red;");\r
243         energyButton->setText("64kW");\r
244     }\r
245 \r
246 }\r
247 \r
248 //FUNCTION: changeCity\r
249 //\r
250 //  sends a web request through the webdata class to get weather data for the city\r
251 //  passed. returns immediately. the webdata class invokes setWebData slot on success\r
252 //  or webDataFailed otherwise.\r
253 \r
254 void MainWindow::loadWebData()\r
255 {\r
256     // get new city information and pass it to WebData to send\r
257     // a new request and get updated information for that city\r
258     weatherWidget->setStatusUpdating();\r
259     webData->changeCity(m_globalSettings->currentCity());\r
260 \r
261 }\r
262 \r
263 //FUNCTION: setWebData\r
264 //\r
265 //  slot invoked by the webdata class on transfer of data across the network that handles\r
266 //  setting an appropriate background for the mainwidget and passing the parsed data along\r
267 //  to the weatherwidget\r
268 \r
269 void MainWindow::setWebData(WeatherData* weatherData)\r
270 {\r
271 \r
272     if(!weatherData)\r
273     {\r
274         webDataFailed();\r
275         return;\r
276     }\r
277     setBackground(weatherData->icon(), weatherData->localTime().time());\r
278     weatherWidget->setWeatherData(weatherData);\r
279     if(weatherData->cachedData())\r
280         weatherWidget->setStatusFailed();\r
281     else\r
282         weatherWidget->setStatusUpdated();\r
283 }\r
284 \r
285 //FUNCTION: webDataFailed\r
286 //\r
287 //  slot invoked by the webdata class on an unsuccessful attempt at fetching weather data\r
288 //  from the network. tells the weather widget to alert the user of the failure.\r
289 \r
290 void MainWindow::webDataFailed()\r
291 {\r
292     WeatherData *weatherData = weatherWidget->weatherData();\r
293 \r
294     //if the update fails, generate random data so things look nice!\r
295     //easiest way to tell if the update failed and we have no cached data is\r
296     //to compare the current configured city to the data's city\r
297 \r
298     if(weatherData->currentCity() != m_globalSettings->currentCity())\r
299     {\r
300         //generate a quick lookup for the icons\r
301         QStringList icons;\r
302         icons << "sunny" << "partlysunny" << "cloudy" << "rain" << "tstorms";\r
303 \r
304         //update the city to the current configured city\r
305         weatherData->setCurrentCity(m_globalSettings->currentCity());\r
306 \r
307         //generate temperature numbers +/- 75\r
308         weatherData->setCurrentTemp(75+(qrand()%10*qrand()%2*(-1)));\r
309 \r
310         //mod a random number by the number of icons then index into the array to pick one\r
311         weatherData->setIcon(icons[qrand()%icons.size()]);\r
312 \r
313         //set the main page background to selected icon\r
314         setBackground(weatherData->icon(), weatherData->localTime().time());\r
315 \r
316         //now iterate through the forecast days and do the same thing\r
317         int i;\r
318         for(i = 0; i < weatherData->forecastData().size(); i++)\r
319         {\r
320             ForecastData* forecast = weatherData->forecastData().at(i);\r
321 \r
322             forecast->setLowTemp(75+(qrand()%10*qrand()%2*(-1)));\r
323             forecast->setHighTemp(forecast->lowTemp()+qrand()%10);\r
324             forecast->setIcon(icons[qrand()%icons.size()]);\r
325         }\r
326 \r
327         //set the data just to trigger an update of the widgets\r
328         weatherWidget->setWeatherData(weatherData);\r
329     }\r
330 \r
331     weatherWidget->setStatusFailed();\r
332 }\r
333 \r
334 void MainWindow::setBackground(QString icon, QTime localTime)\r
335 {\r
336     // set the MainWindow background based on the current conditions\r
337 \r
338     if (icon == "partlysunny" || icon == "mostlycloudy" ) {\r
339         if(localTime.hour() < 5 || localTime.hour() > 20) {\r
340             setStyleSheet("MainWindow {border-image: url(:/Images/clear_night_sky.jpeg)}");\r
341         } else {\r
342             setStyleSheet("MainWindow {border-image: url(:/Images/mostlycloudy.jpg)}");\r
343         }\r
344     }\r
345     else if (icon == "fog") {\r
346         setStyleSheet("MainWindow {border-image: url(:/Images/fog.jpg)}");\r
347     }\r
348     else if (icon == "hazy") {\r
349         if(localTime.hour() < 5 || localTime.hour() > 20) {\r
350             setStyleSheet("MainWindow {border-image: url(:/Images/clear_night_sky.jpeg)}");\r
351         } else {\r
352             setStyleSheet("MainWindow {border-image: url(:/Images/hazy.jpg)}");\r
353         }\r
354     }\r
355     else if (icon == "cloudy") {\r
356         setStyleSheet("MainWindow {border-image: url(:/Images/overcast.jpg)}");\r
357     }\r
358     else if (icon == "rain" || icon == "chancerain") {\r
359         setStyleSheet("MainWindow {border-image: url(:/Images/rain.jpg)}");\r
360     }\r
361     else if (icon == "sleet" || icon == "chancesleet") {\r
362         setStyleSheet("MainWindow {border-image: url(:/Images/sleet.jpg)}");\r
363     }\r
364     else if (icon == "flurries" || icon == "snow" ||\r
365              icon == "chanceflurries" || icon == "chancesnow") {\r
366         setStyleSheet("MainWindow {border-image: url(:/Images/snow.jpg)}");\r
367     }\r
368     else if (icon == "clear" || icon == "sunny") {\r
369         if(localTime.hour() < 5 || localTime.hour() > 20) {\r
370             setStyleSheet("MainWindow {border-image: url(:/Images/clear_night_sky.jpeg)}");\r
371         } else {\r
372             setStyleSheet("MainWindow {border-image: url(:/Images/clear_sky.jpg)}");\r
373         }\r
374     }\r
375     else if (icon == "mostlysunny" || icon == "partlycloudy" ||\r
376              icon == "unknown") {\r
377         if(localTime.hour() < 5 || localTime.hour() > 20) {\r
378             setStyleSheet("MainWindow {border-image: url(:/Images/clear_night_sky.jpeg)}");\r
379         } else {\r
380             setStyleSheet("MainWindow {border-image: url(:/Images/background-sunny-very-few-clouds.JPG)}");\r
381         }\r
382     }\r
383     else if (icon == "tstorms" || icon == "chancetstorms") {\r
384         setStyleSheet("MainWindow {border-image: url(:/Images/storm.jpg)}");\r
385 \r
386     }\r
387 \r
388 }\r
389 \r
390 QHash<QString, QVariant> MainWindow::processCommand(QHash<QString, QVariant> command)\r
391 {\r
392     if(command["command"] == "update" || command["command"] == "update_forced")\r
393     {\r
394         QVariantHash data;\r
395         data.unite(weatherWidget->getCurrentData());\r
396         data.unite(thermostatWidget->getCurrentData());\r
397         return data;\r
398 \r
399     }\r
400     else if(command["command"] == "change_temp")\r
401     {\r
402         if(command["dt"].toInt()>0)\r
403             thermostatWidget->increaseTemp();\r
404         else if(command["dt"].toInt()<0)\r
405             thermostatWidget->decreaseTemp();\r
406     }\r
407     return QHash<QString, QVariant>();\r
408 }\r