1 | // Copyright 2008 Henry de Valence <hdevalence@gmail.com> |
2 | // |
3 | // This library is free software; you can redistribute it and/or |
4 | // modify it under the terms of the GNU Lesser General Public |
5 | // License as published by the Free Software Foundation; either |
6 | // version 2.1 of the License, or (at your option) any later version. |
7 | // |
8 | // This library is distributed in the hope that it will be useful, |
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | // Lesser General Public License for more details. |
12 | // |
13 | // You should have received a copy of the GNU Lesser General Public |
14 | // License along with this library. If not, see <http://www.gnu.org/licenses/>. |
15 | |
16 | |
17 | //Mine |
18 | #include "worldclock.h" |
19 | |
20 | //Qt |
21 | #include <QPainter> |
22 | #include <QRadialGradient> |
23 | #include <QBrush> |
24 | #include <QGraphicsSceneHoverEvent> |
25 | #include <QList> |
26 | #include <QSize> |
27 | #include <QRect> |
28 | #include <QTime> |
29 | #include <QDate> |
30 | #include <QDateTime> |
31 | |
32 | //KDE |
33 | #include <KDebug> |
34 | #include <KLocale> |
35 | #include <KConfigDialog> |
36 | #include <KConfigGroup> |
37 | #include <KTimeZone> |
38 | #include <KTimeZoneWidget> |
39 | #include <KSystemTimeZone> |
40 | |
41 | //Plasma |
42 | #include <Plasma/Applet> |
43 | #include <Plasma/DataEngine> |
44 | |
45 | //Marble |
46 | #include "MarbleGlobal.h" |
47 | #include "MarbleMap.h" |
48 | #include "MarbleModel.h" |
49 | #include "AbstractFloatItem.h" |
50 | #include "SunLocator.h" |
51 | #include "GeoPainter.h" |
52 | #include "LatLonEdit.h" |
53 | #include "ViewportParams.h" |
54 | |
55 | namespace Marble |
56 | { |
57 | |
58 | WorldClock::WorldClock(QObject *parent, const QVariantList &args) |
59 | : Plasma::Applet(parent, args), |
60 | m_showDate( false ), |
61 | m_customTz( false ), |
62 | m_map(0), |
63 | m_isHovered( false ), |
64 | m_timeEngine( 0 ) |
65 | { |
66 | KGlobal::locale()->insertCatalog("marble" ); |
67 | KGlobal::locale()->insertCatalog("marble_qt" ); |
68 | KGlobal::locale()->insertCatalog("timezones4" ); |
69 | setHasConfigurationInterface(true); |
70 | setAcceptHoverEvents(true); |
71 | //The applet needs a 2:1 ratio |
72 | //so that the map fits properly |
73 | resize(QSize(400, 200)); |
74 | } |
75 | |
76 | void WorldClock::init() |
77 | { |
78 | KConfigGroup cg = config(); |
79 | m_map = new MarbleMap(); |
80 | |
81 | //Set how we want the map to look |
82 | m_map->centerOn( cg.readEntry("rotation" , -20), 0 ); |
83 | m_map->setMapThemeId( "earth/bluemarble/bluemarble.dgml" ); |
84 | m_map->setShowCompass ( false ); |
85 | m_map->setShowClouds ( false ); |
86 | m_map->setShowScaleBar ( false ); |
87 | m_map->setShowGrid ( false ); |
88 | m_map->setShowPlaces ( false ); |
89 | m_map->setShowCities ( false ); |
90 | m_map->setShowTerrain ( false ); |
91 | m_map->setShowOtherPlaces( false ); |
92 | // set the date time of the marble model otherwise the sun will not show up correctly |
93 | m_map->model()->setClockDateTime(QDateTime::currentDateTimeUtc()); |
94 | |
95 | if(cg.readEntry("projection" , static_cast<int>(Equirectangular)) == Mercator) |
96 | m_map->setProjection(Mercator); |
97 | else |
98 | m_map->setProjection(Equirectangular); |
99 | |
100 | foreach( RenderPlugin* item, m_map->renderPlugins() ) |
101 | item->setVisible( false ); |
102 | |
103 | //Set up the Sun to draw night/day shadow |
104 | m_map->setShowSunShading(true); |
105 | m_map->setShowCityLights(true); |
106 | m_map->setLockToSubSolarPoint(cg.readEntry("centersun" , false )); |
107 | m_map->setSubSolarPointIconVisible(true); |
108 | |
109 | m_customTz = cg.readEntry("customtz" , false ); |
110 | m_locationkey = KSystemTimeZones::local().name(); |
111 | if(m_customTz) { |
112 | QStringList tzlist = cg.readEntry("tzlist" , QStringList()); |
113 | m_locations = QMap<QString, KTimeZone>(); |
114 | foreach( const QString& tzname, tzlist ) { |
115 | m_locations.insert(tzname, KSystemTimeZones::zone(tzname)); |
116 | } |
117 | if(!m_locations.contains(m_locationkey)) |
118 | m_locationkey = m_locations.keys().first(); |
119 | } else { |
120 | m_locations = KSystemTimeZones::zones(); |
121 | QList<QString> zones = m_locations.keys(); |
122 | for (int i = 0; i < zones.size(); ++i ) { |
123 | KTimeZone curzone = m_locations.value( zones.at( i ) ); |
124 | if ( curzone.latitude() == KTimeZone::UNKNOWN || |
125 | curzone.longitude() == KTimeZone::UNKNOWN ) { |
126 | m_locations.remove( zones.at(i) ); |
127 | } |
128 | } |
129 | } |
130 | |
131 | //Font sizes will change before painting |
132 | m_timeFont = QFont( "Helvetica" , 12, QFont::Bold); |
133 | m_locationFont = QFont( "Helvetica" , 12, QFont::Bold); |
134 | m_points = QHash<QString, QPoint>(); |
135 | m_lastRect = QRect(0,0,0,0); |
136 | m_showDate = cg.readEntry("showdate" , false); |
137 | |
138 | setTz( getZone() ); |
139 | |
140 | Plasma::DataEngine *m_timeEngine = dataEngine("time" ); |
141 | m_timeEngine->connectSource( "Local" , this, 6000, Plasma::AlignToMinute); |
142 | |
143 | connect(m_map, SIGNAL(repaintNeeded(QRegion)), this, SLOT(slotRepaint())); |
144 | } |
145 | |
146 | WorldClock::~WorldClock() |
147 | { |
148 | delete m_map; |
149 | } |
150 | |
151 | void WorldClock::resizeMap(bool changeAspect) |
152 | { |
153 | int width = 0; |
154 | int height = 0; |
155 | int radius = 0; |
156 | double ratio = static_cast<double>(m_lastRect.width()) / |
157 | static_cast<double>(m_lastRect.height()); |
158 | if( m_map->projection() == Equirectangular ) { |
159 | kDebug() << "equirectangular with rect" << m_lastRect; |
160 | kDebug() << "w/h ratio:" << ratio; |
161 | if( ratio > 2 ) { |
162 | height = m_lastRect.height(); |
163 | width = height*2; |
164 | radius = static_cast<int>(height/2); |
165 | } else { |
166 | width = m_lastRect.width(); |
167 | height = static_cast<int>(width/2); |
168 | radius = static_cast<int>(width/4); |
169 | } |
170 | } else if( m_map->projection() == Mercator ) { |
171 | kDebug() << "mercator with rect" << m_lastRect; |
172 | kDebug() << "w/h ratio:" << ratio; |
173 | if( ratio > 1 ) { |
174 | height = m_lastRect.height(); |
175 | width = height; |
176 | radius = static_cast<int>(width/4); |
177 | } else { |
178 | width = m_lastRect.width(); |
179 | height = width; |
180 | radius = static_cast<int>(width/4); |
181 | } |
182 | } |
183 | kDebug() << "width, height, radius:" << width << height << radius; |
184 | |
185 | m_map->setSize(width, height); |
186 | m_map->setRadius( radius ); |
187 | update(); |
188 | if(changeAspect) { |
189 | QRectF curGeo = geometry(); |
190 | setGeometry( curGeo.x(), curGeo.y(), static_cast<double>(width), |
191 | static_cast<double>(height) ); |
192 | } |
193 | } |
194 | |
195 | void WorldClock::slotRepaint() |
196 | { |
197 | update(); |
198 | } |
199 | |
200 | void WorldClock::dataUpdated(const QString &source, |
201 | const Plasma::DataEngine::Data &data) |
202 | { |
203 | Q_UNUSED(source) |
204 | //kDebug() << "Time = " << data["Time"].toTime(); |
205 | m_localtime = QDateTime( QDate::currentDate(), data["Time" ].toTime() ); |
206 | m_time = KSystemTimeZones::local().convert(m_locations.value(m_locationkey), |
207 | m_localtime ); |
208 | //kDebug() << "Adjusted Time = " << m_time; |
209 | update(); |
210 | } |
211 | |
212 | void WorldClock::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) |
213 | { |
214 | m_isHovered = false; |
215 | Applet::hoverLeaveEvent(event); |
216 | update(); |
217 | } |
218 | void WorldClock::hoverEnterEvent(QGraphicsSceneHoverEvent *event) |
219 | { |
220 | m_isHovered = true; |
221 | m_hover = event->pos() - m_t; |
222 | Applet::hoverEnterEvent(event); |
223 | setTz( getZone() ); |
224 | update(); |
225 | } |
226 | void WorldClock::hoverMoveEvent(QGraphicsSceneHoverEvent *event) |
227 | { |
228 | m_hover = event->pos() - m_t; |
229 | Applet::hoverMoveEvent(event); |
230 | setTz( getZone() ); |
231 | update(); |
232 | } |
233 | |
234 | QString WorldClock::getZone() |
235 | { |
236 | qreal lat, lon; |
237 | // get the hover zone only if the hove point exists |
238 | bool ok = !m_hover.isNull() && |
239 | m_map->viewport()->geoCoordinates( m_hover.x(), m_hover.y(), lon, lat ); |
240 | |
241 | if( !ok ) { |
242 | return KSystemTimeZones::local().name(); |
243 | } |
244 | QList<QString> zones = m_locations.keys(); |
245 | |
246 | QString closest; |
247 | qreal mindist = 99999999999999999.9; |
248 | |
249 | for (int i = 0; i < zones.size(); ++i ) { |
250 | KTimeZone cz = m_locations.value( zones.at( i ) ); |
251 | qreal dist = sqrt( pow(lat-cz.latitude(), 2) + pow(lon-cz.longitude(), 2) ); |
252 | if ( dist < mindist ) { |
253 | mindist = dist; |
254 | closest = zones.at( i ); |
255 | } |
256 | } |
257 | return m_locations.value( closest ).name(); |
258 | } |
259 | |
260 | void WorldClock::setTz( QString newtz ) |
261 | { |
262 | if ( newtz == m_locationkey ) { return; } |
263 | m_locationkey = newtz; |
264 | m_time = KSystemTimeZones::local().convert(m_locations.value(m_locationkey), |
265 | m_localtime ); |
266 | recalculateFonts(); |
267 | } |
268 | |
269 | void WorldClock::recalculatePoints() |
270 | { |
271 | int x = m_map->width(); |
272 | int y = m_map->height(); |
273 | m_points.insert( "topright" , QPoint( ( x*0.666 ), ( y*0.25 ) ) ); |
274 | m_points.insert( "topleft" , QPoint( ( x*0.333 ), ( y*0.25 ) ) ); |
275 | m_points.insert( "middleright" , QPoint( ( x*0.666 ), ( y*0.58333 ) ) ); |
276 | m_points.insert( "middleleft" , QPoint( ( x*0.333 ), ( y*0.58333 ) ) ); |
277 | m_points.insert( "bottomright" , QPoint( ( x*0.666 ), ( y*0.75 ) ) ); |
278 | m_points.insert( "bottomleft" , QPoint( ( x*0.333 ), ( y*0.75 ) ) ); |
279 | return; |
280 | |
281 | } |
282 | |
283 | void WorldClock::recalculateFonts( ) |
284 | { |
285 | QString timestr; |
286 | if(m_showDate) |
287 | timestr = KGlobal::locale()->formatDateTime( m_time ); |
288 | else |
289 | timestr = KGlobal::locale()->formatTime( m_time.time() ); |
290 | |
291 | QString locstr = i18n( m_locationkey.toUtf8().data() ); |
292 | locstr.remove( 0, locstr.lastIndexOf( '/' ) + 1 ).replace( '_', ' ' ); |
293 | QRect timeRect( m_points.value( "topleft" ), m_points.value( "middleright" ) ); |
294 | QRect locationRect( m_points.value( "middleleft" ), m_points.value( "bottomright" ) ); |
295 | |
296 | m_locationFont = calculateFont(locstr, locationRect); |
297 | m_timeFont = calculateFont(timestr, timeRect); |
298 | } |
299 | |
300 | QFont WorldClock::calculateFont(const QString &text, const QRect &boundingBox) |
301 | { |
302 | QFont resultFont( "Helvetica" , 3, QFont::Bold); |
303 | |
304 | int unscaled = 0; // Avoid infinite loops, bug 189633 |
305 | QRect lastBox; |
306 | |
307 | //we set very small defaults and then increase them |
308 | for ( int curSize = resultFont.pointSize()+1; unscaled<100; ++curSize ) { |
309 | resultFont.setPointSize(curSize); |
310 | QFontMetrics metrics( resultFont ); |
311 | QRect rect = metrics.boundingRect( text ); |
312 | if ( rect.width() > boundingBox.width() || |
313 | rect.height() > boundingBox.height() ) { |
314 | break; |
315 | } |
316 | |
317 | if ( rect.width() > lastBox.width() || |
318 | rect.height() > lastBox.height() ) { |
319 | unscaled = 0; |
320 | } |
321 | else { |
322 | ++unscaled; |
323 | } |
324 | |
325 | lastBox = rect; |
326 | } |
327 | |
328 | resultFont.setPointSize(resultFont.pointSize()-1); |
329 | return resultFont; |
330 | } |
331 | |
332 | void WorldClock::recalculateTranslation() |
333 | { |
334 | m_t = QPoint(static_cast<int>( (m_lastRect.width()/2) - (m_map->width()/2) ), |
335 | static_cast<int>( (m_lastRect.height()/2) - (m_map->height()/2) )); |
336 | m_t += m_lastRect.topLeft(); |
337 | } |
338 | |
339 | void WorldClock::paintInterface(QPainter *p, |
340 | const QStyleOptionGraphicsItem *option, |
341 | const QRect &contentsRect) |
342 | { |
343 | Q_UNUSED(option) |
344 | if ( contentsRect != m_lastRect ) { |
345 | m_lastRect = contentsRect; |
346 | resizeMap(); |
347 | recalculateTranslation(); |
348 | recalculatePoints(); |
349 | recalculateFonts(); |
350 | } |
351 | p->setRenderHint( QPainter::TextAntialiasing , true ); |
352 | p->setRenderHint( QPainter::Antialiasing , true ); |
353 | p->setPen( Qt::NoPen ); |
354 | //p->setBrush( QBrush( QColor( 0x00, 0x00, 0x00, 0xFF ) ) ); |
355 | //p->drawRect( m_lastRect ); |
356 | QPixmap pixmap( m_map->width(), m_map->height() ); |
357 | pixmap.fill( Qt::transparent ); |
358 | GeoPainter gp( &pixmap, m_map->viewport(), |
359 | Marble::NormalQuality ); |
360 | QRect mapRect( 0, 0, m_map->width(), m_map->height() ); |
361 | m_map->paint(gp, mapRect ); |
362 | p->drawPixmap( m_t, pixmap ); |
363 | |
364 | if ( !m_isHovered ) { |
365 | setTz( KSystemTimeZones::local().name() ); |
366 | } |
367 | |
368 | //Show the location on the map |
369 | qreal tzx = 0; |
370 | qreal tzy = 0; |
371 | qreal lon = m_locations.value(m_locationkey).longitude() * DEG2RAD; |
372 | qreal lat = m_locations.value(m_locationkey).latitude() * DEG2RAD; |
373 | bool ok = m_map->viewport()->screenCoordinates(lon, lat, tzx, tzy); |
374 | if ( ok /*&& m_isHovered*/ ) { |
375 | QPoint tz( tzx, tzy ); |
376 | tz += m_t; |
377 | int radius = m_lastRect.width() / 40; |
378 | QRadialGradient grad( tz, radius ); |
379 | grad.setColorAt( 0, QColor( 0xFF, 0xFF, 0x00, 0xFF ) ); |
380 | grad.setColorAt( 0.33, QColor( 0xFF, 0xFF, 0x00, 0x46 ) ); |
381 | grad.setColorAt( 0.66, QColor( 0xFF, 0xFF, 0x00, 0x14 ) ); |
382 | grad.setColorAt( 1, QColor( 0xFF, 0xFF, 0x00, 0x00 ) ); |
383 | p->setBrush( QBrush( grad ) ); |
384 | p->drawEllipse( tz, radius, radius ); |
385 | } |
386 | |
387 | p->setPen( QColor( 0xFF, 0xFF, 0xFF ) ); |
388 | |
389 | QString locstr = i18n( m_locationkey.toUtf8().data() ); |
390 | locstr.remove( 0, locstr.lastIndexOf( '/' ) + 1 ).replace( '_', ' ' ); |
391 | |
392 | QString timestr; |
393 | if(m_showDate) |
394 | timestr = KGlobal::locale()->formatDateTime( m_time ); |
395 | else |
396 | timestr = KGlobal::locale()->formatTime( m_time.time() ); |
397 | |
398 | p->setFont( m_timeFont ); |
399 | p->drawText( QRect( m_points.value( "topleft" ) + m_t, |
400 | m_points.value( "middleright" ) + m_t ), |
401 | Qt::AlignCenter, timestr ); |
402 | |
403 | p->setFont( m_locationFont ); |
404 | p->drawText( QRect( m_points.value( "middleleft" ) + m_t, |
405 | m_points.value( "bottomright" ) + m_t ), |
406 | Qt::AlignCenter, locstr ); |
407 | } |
408 | |
409 | void WorldClock::createConfigurationInterface(KConfigDialog *parent) |
410 | { |
411 | QWidget *widget = new QWidget(); |
412 | ui.setupUi(widget); |
413 | parent->setButtons(KDialog::Ok | KDialog::Apply | KDialog::Cancel); |
414 | |
415 | KConfigGroup cg = config(); |
416 | |
417 | ui.longitudeEdit->setValue(cg.readEntry("rotation" , -20)); |
418 | |
419 | if(cg.readEntry("projection" , static_cast<int>(Equirectangular)) == Mercator) |
420 | ui.projection->setCurrentIndex(1); |
421 | else //Equirectangular is the default projection |
422 | ui.projection->setCurrentIndex(0); |
423 | |
424 | if(cg.readEntry("daylight" , false )) |
425 | ui.daylightButton->setChecked(true); |
426 | |
427 | if(cg.readEntry("showdate" , false )) |
428 | ui.showdate->setChecked(true); |
429 | |
430 | if(cg.readEntry("customtz" , false )) |
431 | ui.customTz->setChecked(true); |
432 | |
433 | ui.tzWidget->setSelectionMode( QTreeView::MultiSelection ); |
434 | foreach(const QString& tz, cg.readEntry("tzlist" )) { |
435 | ui.tzWidget->setSelected(tz,true); |
436 | } |
437 | |
438 | connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted())); |
439 | connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted())); |
440 | parent->addPage(widget, parent->windowTitle(), icon()); |
441 | } |
442 | |
443 | void WorldClock::configAccepted() |
444 | { |
445 | KConfigGroup cg = config(); |
446 | |
447 | if( ui.daylightButton->isChecked() ) |
448 | m_map->setSubSolarPointIconVisible(true); |
449 | else { |
450 | m_map->centerOn(ui.longitudeEdit->value(), 0); |
451 | update(); |
452 | } |
453 | |
454 | m_showDate = ui.showdate->isChecked(); |
455 | m_customTz = ui.customTz->isChecked(); |
456 | |
457 | if(m_customTz) { |
458 | QStringList tzlist = ui.tzWidget->selection(); |
459 | kDebug() << "\tSetting TZLIST" ; |
460 | kDebug() << tzlist; |
461 | QMap<QString, KTimeZone> selectedZones; |
462 | selectedZones.insert(KSystemTimeZones::local().name(), |
463 | KSystemTimeZones::local()); |
464 | foreach( const QString& tzname, tzlist ) { |
465 | selectedZones.insert(tzname, KSystemTimeZones::zone(tzname)); |
466 | } |
467 | cg.writeEntry("tzlist" ,tzlist); |
468 | m_locations = selectedZones; |
469 | if(!m_locations.contains(m_locationkey)) |
470 | m_locationkey = m_locations.keys().first(); |
471 | } |
472 | |
473 | // What projection? note: +1 because the spherical projection is 0 |
474 | if((ui.projection->currentIndex() + 1) != cg.readEntry("projection" , |
475 | static_cast<int>(Equirectangular)) ) |
476 | { |
477 | switch ( ui.projection->currentIndex() ) { |
478 | case 1: |
479 | //kDebug() << "case 1, setting proj to mercator"; |
480 | m_map->setProjection(Mercator); |
481 | update(); |
482 | resizeMap(true); |
483 | cg.writeEntry("projection" , static_cast<int>(Mercator)); |
484 | break; |
485 | //case 0 (and anything else that pops up) |
486 | default: |
487 | //kDebug() << "case default, setting proj to Equirectangular"; |
488 | m_map->setProjection(Equirectangular); |
489 | update(); |
490 | resizeMap(true); |
491 | cg.writeEntry("projection" , static_cast<int>(Equirectangular)); |
492 | break; |
493 | } |
494 | } |
495 | |
496 | cg.writeEntry("rotation" , ui.longitudeEdit->value()); |
497 | cg.writeEntry("centersun" , ui.daylightButton->isChecked()); |
498 | cg.writeEntry("showdate" , ui.showdate->isChecked()); |
499 | cg.writeEntry("customtz" , ui.customTz->isChecked()); |
500 | |
501 | emit configNeedsSaving(); |
502 | } |
503 | |
504 | } //ns Marble |
505 | |
506 | #include "worldclock.moc" |
507 | |