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