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