summaryrefslogtreecommitdiff
path: root/core/settings/citytime/zonemap.cpp
Unidiff
Diffstat (limited to 'core/settings/citytime/zonemap.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--core/settings/citytime/zonemap.cpp670
1 files changed, 670 insertions, 0 deletions
diff --git a/core/settings/citytime/zonemap.cpp b/core/settings/citytime/zonemap.cpp
new file mode 100644
index 0000000..337f4d9
--- a/dev/null
+++ b/core/settings/citytime/zonemap.cpp
@@ -0,0 +1,670 @@
1/**********************************************************************
2** Copyright (C) 2000 Trolltech AS. All rights reserved.
3**
4** This file is part of Qtopia Environment.
5**
6** This file may be distributed and/or modified under the terms of the
7** GNU General Public License version 2 as published by the Free Software
8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file.
10**
11** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13**
14** See http://www.trolltech.com/gpl/ for GPL licensing information.
15**
16** Contact info@trolltech.com if any conditions of this licensing are
17** not clear to you.
18**
19**********************************************************************/
20
21#include "sun.h"
22#include "zonemap.h"
23
24#include <qpe/resource.h>
25#include <qpe/timestring.h>
26#include <qpe/qpeapplication.h>
27
28#include <qdatetime.h>
29#include <qfile.h>
30#include <qimage.h>
31#include <qlabel.h>
32#include <qlist.h>
33#include <qmessagebox.h>
34#include <qpixmap.h>
35#include <qpainter.h>
36#include <qregexp.h>
37#include <qtextstream.h>
38#include <qtimer.h>
39#include <qtoolbutton.h>
40
41#include <limits.h>
42
43// the map file...
44static const char strZONEINFO[] = "/usr/share/zoneinfo/zone.tab";
45static const char strMAP[] = "simple_grid_400";
46
47// the maximum distance we'll allow the pointer to be away from a city
48// and still show the city's time
49static const int iTHRESHOLD = 50000;
50
51// The label offset (how far away from pointer)
52static const int iLABELOFFSET = 8;
53
54// the size of the dot to draw, and where to start it
55static const int iCITYSIZE = 3;
56const int iCITYOFFSET = 2;
57
58// the darkening function
59static inline void darken( QImage *pImage, int start, int stop, int row );
60static void dayNight( QImage *pImage );
61
62ZoneField::ZoneField( const QString& strLine )
63{
64 // make a bunch of RegExp's to match the data from the line
65 QRegExp regCoord( "[-+][0-9]+" );// the latitude
66 QRegExp regCountry( "[A-Za-z]+/" ); // the country (not good enough)
67 QRegExp regCity( "[A-Za-z_-]*" ); // the city
68
69 int iStart,
70 iStop,
71 iLen,
72 tmp;
73 QString strTmp;
74 // we should be able to assume that the country code is always the first
75 // two chars, so just grap them and let it go...
76 strCountryCode = strLine.left( 2 );
77 iStart = regCoord.match( strLine, 0, &iLen );
78 if ( iStart >= 0 ) {
79 strTmp = strLine.mid( iStart, iLen );
80 tmp = strTmp.toInt();
81 // okay, there are two versions of the format, make a decision based on
82 // the size...
83 // Oh BTW, we are storing everything in seconds!
84 if ( iLen < 7 ) {
85 _y = tmp / 100;
86 _y *= 60;
87 _y += tmp % 100;
88 _y *= 60;
89 } else {
90 _y = tmp / 10000;
91 _y *= 60;
92 tmp %= 10000;
93 _y += tmp / 100;
94 _y *= 60;
95 tmp %= 100;
96 _y += tmp;
97 }
98 }
99 iStart = regCoord.match( strLine, iStart + iLen, &iLen );
100 if ( iStart >= 0 ) {
101 strTmp = strLine.mid( iStart, iLen );
102 tmp = strTmp.toInt();
103 if ( iLen < 8 ) {
104 _x = tmp / 100;
105 _x *= 60;
106 _x += tmp % 100;
107 _x *= 60;
108 } else {
109 _x = tmp / 10000;
110 _x *= 60;
111 tmp %= 10000;
112 _x += tmp / 100;
113 _x *= 60;
114 tmp %= 100;
115 _x += tmp;
116 }
117 }
118 iStart = regCountry.match( strLine, 0, &iLen );
119 // help with the shortcoming in 2.x regexp...
120 iStop = strLine.findRev( '/' );
121 if ( iStart >= 0 ) {
122 iLen = (iStop - iStart) + 1;
123 strCountry = strLine.mid( iStart, iLen );
124 }
125 // now match the city...
126 iStart = regCity.match( strLine, iStart + iLen, &iLen );
127 if ( iStart >= 0 ) {
128 strCity = strLine.mid( iStart, iLen );
129 }
130}
131
132void ZoneField::showStructure( void ) const
133{
134 qDebug( "Country: %s", strCountry.latin1() );
135 qDebug( "City: %s", strCity.latin1() );
136 qDebug( "x: %d", _x );
137 qDebug( "y: %d\n", _y );
138}
139
140ZoneMap::ZoneMap( QWidget *parent, const char* name )
141 : QScrollView( parent, name ),
142 pLast( 0 ),
143 pRepaint( 0 ),
144 ox( 0 ),
145 oy( 0 ),
146 drawableW( -1 ),
147 drawableH( -1 ),
148 bZoom( FALSE ),
149 bIllum( TRUE ),
150 cursor( 0 )
151{
152 viewport()->setFocusPolicy( StrongFocus );
153
154 // set mouse tracking so we can use the mouse move event
155 zones.setAutoDelete( true );
156 // get the map loaded
157 // just set the current image to point
158 pixCurr = new QPixmap();
159
160 QPixmap pixZoom = Resource::loadPixmap( "mag" );
161
162 cmdZoom = new QToolButton( this, "Zoom command" );
163 cmdZoom->setPixmap( pixZoom );
164 cmdZoom->setToggleButton( true );
165
166 cmdZoom->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0,
167 (QSizePolicy::SizeType)0,
168 cmdZoom->sizePolicy().hasHeightForWidth() ) );
169 cmdZoom->setMaximumSize( cmdZoom->sizeHint() );
170 // probably don't need this, but just in case...
171 cmdZoom->move( width() - cmdZoom->width(), height() - cmdZoom->height() );
172
173
174 lblCity = new QLabel( tr( "CITY" ), this, "City Label" );
175 lblCity->setMinimumSize( lblCity->sizeHint() );
176 lblCity->setFrameStyle( QFrame::Plain | QFrame::Box );
177 lblCity->setBackgroundColor( yellow );
178 lblCity->hide();
179
180 // A timer to make sure the label gets hidden
181 tHide = new QTimer( this, "Label Timer" );
182 QObject::connect( tHide, SIGNAL( timeout() ),
183 lblCity, SLOT( hide() ) );
184 QObject::connect( tHide, SIGNAL( timeout() ),
185 this, SLOT( slotRedraw() ) );
186 QTimer *tUpdate = new QTimer( this, "Update Timer" );
187 QObject::connect( tUpdate, SIGNAL( timeout() ),
188 this, SLOT( slotUpdate() ) );
189 QObject::connect( qApp, SIGNAL( timeChanged() ),
190 this, SLOT( slotUpdate() ) );
191 QObject::connect( cmdZoom, SIGNAL( toggled( bool ) ),
192 this, SLOT( slotZoom( bool ) ) );
193 QObject::connect( &norm, SIGNAL( signalNewPoint( const QPoint& ) ),
194 this, SLOT( slotFindCity( const QPoint& ) ) );
195 QObject::connect( qApp, SIGNAL( clockChanged( bool ) ),
196 this, SLOT( changeClock( bool ) ) );
197 // update the sun's movement every 5 minutes
198 tUpdate->start( 5 * 60 * 1000 );
199 // May as well read in the timezone information too...
200 readZones();
201}
202
203ZoneMap::~ZoneMap()
204{
205}
206
207void ZoneMap::readZones( void )
208{
209 QFile fZone( strZONEINFO );
210 if ( !fZone.open( IO_ReadOnly ) ) {
211 QMessageBox::warning (this,
212 tr( "Unable to Find Timezone Info" ),
213 tr( "<p>Unable to find any timezone information in %1" )
214 .arg( strZONEINFO ));
215 exit(-1);
216 } else {
217 QTextStream tZone( &fZone );
218 while ( !tZone.atEnd() ) {
219 QString strLine = tZone.readLine();
220 // only pass on lines that aren't comments
221 if ( strLine[0] != '#' ) {
222 zones.append( new ZoneField( strLine ) );
223 }
224 }
225 fZone.close();
226 }
227}
228
229void ZoneMap::viewportMousePressEvent( QMouseEvent* event )
230{
231 // add the mouse event into the normalizer, and get the average,
232 // pass it along
233 slotRedraw();
234 norm.start();
235 norm.addEvent( event->pos() );
236}
237
238void ZoneMap::viewportMouseMoveEvent( QMouseEvent* event )
239{
240 norm.addEvent( event->pos() );
241}
242
243void ZoneMap::viewportMouseReleaseEvent( QMouseEvent* )
244{
245 // get the averaged points in case a timeout hasn't occurred,
246 // more for "mouse clicks"
247 norm.stop();
248 if ( pLast != NULL ) {
249 emit signalTz( pLast->country(), pLast->city() );
250 pLast = NULL;
251 }
252 tHide->start( 2000, true );
253}
254
255void ZoneMap::keyPressEvent( QKeyEvent *ke )
256{
257 switch ( ke->key() ) {
258 case Key_Left:
259 case Key_Right:
260 case Key_Up:
261 case Key_Down: {
262 tHide->stop();
263 if ( !cursor )
264 slotFindCity( QPoint( contentsWidth(), contentsHeight() ) / 2 );
265 ZoneField *city = findCityNear( cursor, ke->key() );
266 if ( city ) {
267 cursor = city;
268 int tmpx, tmpy;
269 zoneToWin( cursor->x(), cursor->y(), tmpx, tmpy );
270 ensureVisible( tmpx, tmpy );
271 showCity( cursor );
272 tHide->start( 3000, true );
273 }
274 }
275 break;
276
277 case Key_Space:
278 case Key_Enter:
279 case Key_Return:
280 if ( cursor ) {
281 emit signalTz( cursor->country(), cursor->city() );
282 tHide->start( 0, true );
283 }
284 break;
285 }
286}
287
288ZoneField *ZoneMap::findCityNear( ZoneField *city, int key )
289{
290 ZoneField *pZone;
291 ZoneField *pClosest = 0;
292 long ddist = LONG_MAX;
293
294 QListIterator<ZoneField> it( zones );
295 for (; it.current(); ++it) {
296 pZone = it.current();
297 long dx = (pZone->x() - city->x())/100;
298 long dy = (pZone->y() - city->y())/100;
299 switch ( key ) {
300 case Key_Right:
301 case Key_Left:
302 if ( key == Key_Left )
303 dx = -dx;
304 if ( dx > 0 ) {
305 long dist = QABS(dy)*4 + dx;
306 if ( dist < ddist ) {
307 ddist = dist;
308 pClosest = pZone;
309 }
310 }
311 break;
312 case Key_Down:
313 case Key_Up:
314 if ( key == Key_Down )
315 dy = -dy;
316 if ( dy > 0 ) {
317 long dist = QABS(dx)*4 + dy;
318 if ( dist < ddist ) {
319 ddist = dist;
320 pClosest = pZone;
321 }
322 }
323 break;
324 }
325 }
326
327 return pClosest;
328}
329
330void ZoneMap::slotFindCity( const QPoint &pos )
331{
332 lblCity->hide();
333 // given coordinates on the screen find the closest city and display the
334 // label close to it
335 int tmpx, tmpy, x, y;
336 long lDistance,
337 lClosest;
338 ZoneField *pZone,
339 *pClosest;
340
341 if ( tHide->isActive() ) {
342 tHide->stop();
343 }
344 viewportToContents(pos.x(), pos.y(), tmpx, tmpy);
345 winToZone( tmpx, tmpy, x, y );
346 // Find city alogorithim: start out at an (near) infinite distance away and
347 // then find the closest city, (similar to the Z-buffer technique, I guess)
348 // the only problem is that this is all done with doubles, but I don't know
349 // another way to do it at the moment. Another problem is a linked list is
350 // used obviously something indexed would help
351 QListIterator<ZoneField> it( zones );
352 pClosest = 0;
353 lClosest = LONG_MAX;
354 for (; it.current(); ++it) {
355 pZone = it.current();
356 // use the manhattenLength, a good enough of an appoximation here
357 lDistance = QABS( x - pZone->x() ) + QABS( y - pZone->y() );
358 // first to zero wins!
359 if ( lDistance < lClosest ) {
360 lClosest = lDistance;
361 pClosest = pZone;
362 }
363 }
364
365 // Okay, we found the closest city, but it might still be too far away.
366 if ( lClosest <= iTHRESHOLD ) {
367 showCity( pClosest );
368 cursor = pClosest;
369 }
370}
371
372void ZoneMap::showCity( ZoneField *city )
373{
374 pLast = city;
375 // we'll use city and country a couple of times, get them to save some
376 // time
377 QString strCity = pLast->city();
378 QString strCountry = pLast->country();
379 // Display the time at this location by setting the environment timezone
380 // getting the current time [there] and then swapping back the variable
381 // so no one notices...
382 QString strSave;
383 char *p = getenv( "TZ" );
384 if ( p ) {
385 strSave = p;
386 }
387 // set the timezone :)
388 setenv( "TZ", strCountry + strCity, true );
389 lblCity->setText( strCity.replace( QRegExp("_"), " ") + "\n" +
390 TimeString::shortTime( ampm ) );
391 lblCity->setMinimumSize( lblCity->sizeHint() );
392 // undue our damage...
393 unsetenv( "TZ" );
394 if ( p )
395 setenv( "TZ", strSave, true );
396 // Now decide where to move the label, x & y can be reused
397 int tmpx, tmpy, x, y;
398 zoneToWin( pLast->x(), pLast->y(), tmpx, tmpy );
399 contentsToViewport(tmpx, tmpy, x, y);
400 if ( lblCity->width() > drawableW - x ) {
401 // oops... try putting it on the right
402 x = x - lblCity->width() - iLABELOFFSET;
403 } else {
404 // the default...
405 x += iLABELOFFSET;
406 }
407 if ( lblCity->height() > drawableH - y ) {
408 // move it up...
409 y = y - lblCity->height() - iLABELOFFSET;
410 } else if ( y < 0 ) {
411 // the city is actually off the screen...
412 // this only happens on the a zoom when you are near the top,
413 // a quick workaround..
414 y = iLABELOFFSET;
415 } else {
416 // the default
417 y += iLABELOFFSET;
418 }
419
420 // draw in the city and the label
421 if ( pRepaint ) {
422 int repx,
423 repy;
424 zoneToWin( pRepaint->x(), pRepaint->y(), repx, repy );
425 updateContents( repx - iCITYOFFSET, repy - iCITYOFFSET,
426 iCITYSIZE, iCITYSIZE );
427 }
428 updateContents( tmpx - iCITYOFFSET, tmpy - iCITYOFFSET, iCITYSIZE,
429 iCITYSIZE );
430 pRepaint = pLast;
431
432 lblCity->move( x, y );
433 lblCity->show();
434}
435
436void ZoneMap::resizeEvent( QResizeEvent *e )
437{
438 // keep the zoom button down in the corner
439 QSize _size = e->size();
440 cmdZoom->move( _size.width() - cmdZoom->width(),
441 _size.height() - cmdZoom->height() );
442 if ( !bZoom ) {
443 drawableW = width() - 2 * frameWidth();
444 drawableH = height() - 2 * frameWidth();
445 makeMap( drawableW, drawableH );
446 resizeContents( drawableW, drawableH );
447 }
448}
449
450void ZoneMap::showZones( void ) const
451{
452 // go through the zones in the list and just display the values...
453 QListIterator<ZoneField> itZone( zones );
454 for ( itZone.toFirst(); itZone.current(); ++itZone ) {
455 ZoneField *pZone = itZone.current();
456 pZone->showStructure();
457 }
458}
459
460void ZoneMap::drawCities( QPainter *p )
461{
462 int x,
463 y,
464 j;
465 // draw in the cities
466 // for testing only as when you put it
467 // on the small screen it looks awful and not to mention useless
468 p->setPen( red );
469 QListIterator<ZoneField> itZone( zones );
470 for ( itZone.toFirst(), j = 0; itZone.current(); ++itZone, j++ ) {
471 ZoneField *pZone = itZone.current();
472 zoneToWin( pZone->x(), pZone->y(), x, y );
473 if ( x > wImg )
474 x = x - wImg;
475 p->drawRect( x - iCITYOFFSET, y - iCITYOFFSET, iCITYSIZE, iCITYSIZE);
476 }
477}
478
479static void dayNight(QImage *pImage)
480{
481 // create a mask the functions from sun.h
482 double dJulian,
483 dSunRad,
484 dSunDecl,
485 dSunRadius,
486 dSunLong;
487 int wImage = pImage->width(),
488 hImage = pImage->height(),
489 iStart,
490 iStop,
491 iMid,
492 relw,
493 i;
494 short wtab[ wImage ];
495 time_t tCurrent;
496 struct tm *pTm;
497
498 // get the position of the sun bassed on our current time...
499 tCurrent = time( NULL );
500 pTm = gmtime( &tCurrent );
501 dJulian = jtime( pTm );
502 sunpos( dJulian, 0, &dSunRad, &dSunDecl, &dSunRadius, &dSunLong );
503
504 // now get the projected illumination
505 projillum( wtab, wImage, hImage, dSunDecl );
506 relw = wImage - int( wImage * 0.0275 );
507
508 // draw the map, keeping in mind that we may go too far off the map...
509 iMid = ( relw * ( 24*60 - pTm->tm_hour * 60 - pTm->tm_min ) ) / ( 24*60 );
510
511 for ( i = 0; i < hImage; i++ ) {
512 if ( wtab[i] > 0 ) {
513 iStart = iMid - wtab[i];
514 iStop = iMid + wtab[i];
515 if ( iStart < 0 ) {
516 darken( pImage, iStop, wImage + iStart, i );
517 } else if ( iStop > wImage ) {
518 darken( pImage, iStop - wImage, iStart, i );
519 } else {
520 darken( pImage, 0, iStart, i );
521 darken( pImage, iStop, wImage, i );
522 }
523 } else {
524 darken( pImage, 0, wImage, i );
525 }
526 }
527}
528
529static inline void darken( QImage *pImage, int start, int stop, int row )
530{
531 int colors,
532 j;
533 uchar *p;
534
535 // assume that the image is similar to the one we have...
536 colors = pImage->numColors() / 2;
537
538 p = pImage->scanLine( row );
539 for ( j = start; j <= stop; j++ ) {
540 if ( p[j] < colors )
541 p[j] += colors;
542 }
543}
544
545void ZoneMap::makeMap( int w, int h )
546{
547 QImage imgOrig = Resource::loadImage( strMAP );
548 if ( imgOrig.isNull() ) {
549 QMessageBox::warning( this,
550 tr( "Couldn't Find Map" ),
551 tr( "<p>Couldn't load map: %1, exiting")
552 .arg( strMAP ) );
553 exit(-1);
554 }
555
556 // set up the color table for darkening...
557 imgOrig = imgOrig.convertDepth( 8 );
558 int numColors = imgOrig.numColors();
559 // double the colors
560 imgOrig.setNumColors( 2 * numColors );
561 // darken the new ones...
562 for ( int i = 0; i < numColors; i++ ) {
563 QRgb rgb = imgOrig.color( i );
564 imgOrig.setColor ( i + numColors, qRgb( 2 * qRed( rgb ) / 3,
565 2 * qGreen( rgb ) / 3, 2 * qBlue( rgb ) / 3 ) );
566 }
567
568 // else go one with making the map...
569 if ( bIllum ) {
570 // do a daylight mask
571 dayNight(&imgOrig);
572 }
573 // redo the width and height
574 wImg = w;
575 hImg = h;
576 ox = ( wImg / 2 ) - int( wImg * 0.0275 );
577 oy = hImg / 2;
578 pixCurr->convertFromImage( imgOrig.smoothScale(w, h),
579 QPixmap::ThresholdDither );
580}
581
582void ZoneMap::drawCity( QPainter *p, const ZoneField *pCity )
583{
584 int x,
585 y;
586
587 p->setPen( red );
588 zoneToWin( pCity->x(), pCity->y(), x, y );
589 p->drawRect( x - iCITYOFFSET, y - iCITYOFFSET, iCITYSIZE, iCITYSIZE );
590}
591
592void ZoneMap::drawContents( QPainter *p, int cx, int cy, int cw, int ch )
593{
594 // if there is a need to resize, then do it...
595 // get our drawable area
596 drawableW = width() - 2 * frameWidth();
597 drawableH = height() - 2 * frameWidth();
598
599 int pixmapW = pixCurr->width(),
600 pixmapH = pixCurr->height();
601 if ( !bZoom && ( ( pixmapW != drawableW ) ||
602 ( pixmapH != drawableH) ) ) {
603 makeMap( drawableW, drawableH );
604 }
605
606 // taken from the scrollview example...
607 int rowheight = pixCurr->height();
608 int toprow = cy / rowheight;
609 int bottomrow = ( cy + ch + rowheight - 1 ) / rowheight;
610 int colwidth = pixCurr->width();
611 int leftcol= cx / colwidth;
612 int rightcol= ( cx + cw + colwidth - 1 ) / colwidth;
613 for ( int r = toprow; r <= bottomrow; r++ ) {
614 int py = r * rowheight;
615 for ( int c = leftcol; c <= rightcol; c++ ) {
616 int px = c * colwidth;
617 p->drawPixmap( px, py, *pixCurr );
618 }
619 }
620
621 // Draw that city!
622 if ( pLast )
623 drawCity( p, pLast );
624}
625
626void ZoneMap::slotZoom( bool setZoom )
627{
628 bZoom = setZoom;
629 if ( bZoom ) {
630 makeMap( 2 * wImg , 2 * hImg );
631 resizeContents( wImg, hImg );
632 } else {
633 makeMap( drawableW, drawableH );
634 resizeContents( drawableW, drawableH );
635 }
636}
637
638void ZoneMap::slotIllum( bool setIllum )
639{
640 bIllum = !setIllum;
641 // make the map...
642 makeMap( pixCurr->width(), pixCurr->height() );
643 updateContents( 0, 0, wImg, hImg );
644}
645
646void ZoneMap::slotUpdate( void )
647{
648 // recalculate the light, most people will never see this,
649 // but it is good to be complete
650 makeMap ( pixCurr->width(), pixCurr->height() );
651 updateContents( contentsX(), contentsY(), drawableW, drawableH );
652}
653
654void ZoneMap::slotRedraw( void )
655{
656 // paint over that pesky city...
657 int x,
658 y;
659 if ( pRepaint ) {
660 pLast = 0;
661 zoneToWin(pRepaint->x(), pRepaint->y(), x, y);
662 updateContents( x - iCITYOFFSET, y - iCITYOFFSET, iCITYSIZE, iCITYSIZE);
663 pRepaint = 0;
664 }
665}
666
667void ZoneMap::changeClock( bool whichClock )
668{
669 ampm = whichClock;
670}