libyui-qt  2.50.2
YQTimezoneSelector.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: YQTimezoneSelector.cc
20 
21  Author: Stephan Kulow <coolo@suse.de>
22 
23 /-*/
24 
25 
26 #define YUILogComponent "qt-ui"
27 #include <yui/YUILog.h>
28 #include <math.h>
29 
30 #include <qdatetimeedit.h>
31 
32 #include "utf8.h"
33 #include "YQUI.h"
34 #include "YQTimezoneSelector.h"
35 #include "YQWidgetCaption.h"
36 #include <yui/YEvent.h>
37 #include <QVBoxLayout>
38 #include <QPainter>
39 #include <QMouseEvent>
40 #include <QDebug>
41 #include <QToolTip>
42 #include <QIcon>
43 #include <QtGlobal>
44 
46 {
47  QWidget *parent;
48 
49 public:
50 
51  YQTimezoneSelectorPrivate( QWidget *p ) {
52  parent = p;
53  blink = 0;
54  highlight = 0;
55  }
56  QImage _pix;
57  QPoint _zoom;
58 
59  struct Location
60  {
61  QString country;
62  double latitude;
63  double longitude;
64  QString zone;
65  QString comment;
66  QString tip;
67 
68  QPoint pix_pos;
69 
70  bool operator<(const Location& l2) const;
71  };
72 
73  Location _best;
74 
75  QList<Location> locations;
76 
77  Location findBest( const QPoint &pos ) const;
78 
79  QTimer *blink;
80 
81  int highlight;
82 
83  QPoint pixPosition( const Location &pos ) const;
84 
85  QPoint pixToWindow( const QPoint &pos ) const;
86 
87  QPixmap cachePix;
88 };
89 
90 
91 static float
92 convert_pos (const QString &pos, int digits)
93 {
94  if (pos.length() < 4 || digits > 9) return 0.0;
95 
96  QString whole = pos.left( digits + 1 );
97  QString fraction = pos.mid( digits + 1 );
98 
99  float t1 = whole.toFloat();
100  float t2 = fraction.toFloat();
101 
102  if (t1 >= 0.0)
103  return t1 + t2/pow (10.0, fraction.length() );
104  else
105  return t1 - t2/pow (10.0, fraction.length());
106 }
107 
108 
109 bool YQTimezoneSelectorPrivate::Location::operator<(const Location& l1 ) const
110 {
111  return l1.latitude < latitude;
112 }
113 
114 
115 YQTimezoneSelector::YQTimezoneSelector( YWidget * parent, const std::string & pixmap, const std::map<std::string,std::string> & timezones )
116  : QFrame( (QWidget *) parent->widgetRep() )
117  , YTimezoneSelector( parent, pixmap, timezones )
118 {
119  d = new YQTimezoneSelectorPrivate( this );
120 
121  setWidgetRep( this );
122  setMouseTracking(true);
123  d->_pix.load( fromUTF8( pixmap ) );
124 
125  setStretchable( YD_HORIZ, true );
126  setStretchable( YD_VERT, true );
127 
128  char buf[4096];
129  FILE *tzfile = fopen ("/usr/share/zoneinfo/zone.tab", "r");
130  while (fgets (buf, sizeof(buf), tzfile))
131  {
132  if (*buf == '#') continue;
133 
134  QString sbuf = buf;
135  QStringList arr = sbuf.trimmed().split( '\t' );
136 
137  int split_index = 1;
138  while ( split_index < arr[1].length() && arr[1][split_index] != '-' && arr[1][split_index] != '+' )
139  split_index++;
140 
142  loc.country = arr[0];
143  loc.zone = arr[2];
144  std::map<std::string, std::string>::const_iterator tooltip = timezones.find( loc.zone.toStdString() );
145  if (tooltip == timezones.end() )
146  continue;
147 
148  loc.tip = fromUTF8( tooltip->second );
149  if ( arr.size() > 3 )
150  loc.comment = arr[3];
151  loc.latitude = convert_pos ( arr[1].left( split_index ), 2);
152  loc.longitude = convert_pos ( arr[1].mid( split_index ), 3);
153 
154  loc.pix_pos = d->pixPosition( loc );
155 
156  d->locations.push_back( loc );
157  }
158 
159  fclose (tzfile);
160 
161  std::sort( d->locations.begin(), d->locations.end() );
162 
163  d->blink = new QTimer( this );
164  d->blink->setInterval( 200 );
165  connect( d->blink, &pclass(d->blink)::timeout,
166  this, &pclass(this)::slotBlink );
167 
168  d->highlight = 0;
169 }
170 
172 {
173  delete d;
174  // NOP
175 }
176 
177 
179 {
180  return 600;
181 }
182 
183 
185 {
186  return 300;
187 }
188 
189 
190 void YQTimezoneSelector::setSize( int newWidth, int newHeight )
191 {
192  resize( newWidth, newHeight );
193 }
194 
195 
196 QPoint YQTimezoneSelectorPrivate::pixPosition( const Location &pos ) const
197 {
198  return QPoint( (int) ( _pix.width() / 2 + _pix.width() / 2 * pos.longitude / 180 ),
199  (int) ( _pix.height() / 2 - _pix.height() / 2 * pos.latitude / 90 ) ) ;
200 }
201 
202 
203 void YQTimezoneSelector::mousePressEvent ( QMouseEvent * event )
204 {
205  if ( event->button() == Qt::RightButton )
206  {
207  d->_zoom = QPoint();
208  d->cachePix = QPixmap();
209  }
210  else if ( event->button() == Qt::LeftButton )
211  {
212  d->_best = d->findBest( event->pos() );
213 
214  if ( d->_zoom.isNull() )
215  {
216  QPoint click = event->pos();
217  /* keep the zoom point in unscaled math */
218  d->_zoom.rx() = (int) ( double( click.x() ) * d->_pix.width() / width() );
219  d->_zoom.ry() = (int) ( double( click.y() ) * d->_pix.height() / height() );
220  }
221 
222  d->cachePix = QPixmap();
223 
224  if ( notify() )
225  YQUI::ui()->sendEvent( new YWidgetEvent( this, YEvent::ValueChanged ) );
226 
227  d->blink->start();
228  } else
229  return;
230 
231  update();
232 }
233 
234 
235 void YQTimezoneSelector::paintEvent( QPaintEvent *event )
236 {
237  QFrame::paintEvent( event );
238  QPainter p( this );
239 
240  if ( d->cachePix.width() != width() || d->cachePix.height() != height() )
241  d->cachePix = QPixmap();
242 
243  if ( d->_zoom.isNull() )
244  {
245  if ( d->cachePix.isNull() )
246  {
247  QImage t = d->_pix.scaled( width(), height(), Qt::KeepAspectRatio );
248  d->cachePix = QPixmap::fromImage( t );
249  }
250  p.drawPixmap( ( width() - d->cachePix.width() ) / 2, ( height() - d->cachePix.height() ) / 2, d->cachePix );
251 
252  Q_INIT_RESOURCE( qt_icons );
253  QIcon icon = QIcon::fromTheme( "zoom-in", QIcon( ":/zoom-in" ) );
254 
255  if ( !icon.isNull() )
256  setCursor( QCursor( icon.pixmap( QSize( 16, 16 ) ) ) );
257  }
258  else
259  {
260  int left = qMin( qMax( d->_zoom.x() - width() / 2, 0 ), d->_pix.width() - width() );
261  int top = qMin( qMax( d->_zoom.y() - height() / 2, 0 ), d->_pix.height() - height() );
262 
263  if ( d->cachePix.isNull() )
264  d->cachePix = QPixmap::fromImage( d->_pix.copy( QRect( QPoint( left, top ), size() ) ) );
265 
266  p.drawPixmap( 0, 0, d->cachePix );
267 
268  setCursor( Qt::CrossCursor );
269  }
270 
271  p.setBrush( QColor( "#D8DF57" ) );
272  p.setPen( QColor( "#B9DFD6" ) );
273 
274  for ( QList<YQTimezoneSelectorPrivate::Location>::const_iterator it = d->locations.begin(); it != d->locations.end(); ++it )
275  {
276  if ( !d->highlight || ( *it ).zone != d->_best.zone )
277  {
278  if ( d->_zoom.isNull() )
279  p.drawEllipse( QRect( d->pixToWindow( ( *it ).pix_pos ) - QPoint( 1,1 ), QSize( 3, 3 ) ) );
280  else
281  p.drawEllipse( QRect( d->pixToWindow( ( *it ).pix_pos ) - QPoint( 2,2 ), QSize( 5, 5 ) ) );
282  }
283  }
284  if ( d->highlight > 0 )
285  {
286  static const QColor blinks[] = { QColor( "#00ff00" ), QColor( "#22dd00" ), QColor( "#44bb00" ),
287  QColor( "#669900" ), QColor( "#887700" ), QColor( "#aa5500" ),
288  QColor( "#887700" ), QColor( "#669900" ), QColor( "#44bb00" ),
289  QColor( "#22dd00" ) };
290  int index = d->highlight - 1;
291  p.setPen( blinks[ index ] );
292  p.setBrush( blinks[ index ] );
293 
294  p.drawEllipse( QRect( d->pixToWindow( d->_best.pix_pos ) - QPoint( 2,2 ), QSize( 5, 5 ) ) );
295 
296  QFont f( font() );
297  f.setBold( true );
298  p.setFont( f );
299  QFontMetrics fm( f );
300 
301  QPoint off = d->pixToWindow( d->_best.pix_pos ) + QPoint( 11, 4 );
302 #if QT_VERSION >= 0x051100
303  int tw = fm.horizontalAdvance( d->_best.tip );
304 #else
305  // Leap 15.0 has an older Qt version, make sure it also compiles there
306  int tw = fm.width( d->_best.tip );
307 #endif
308  if ( tw + off.x() > width() )
309  off.rx() = d->pixToWindow( d->_best.pix_pos ).x() - tw - 10;
310 
311  p.setPen( Qt::black );
312  p.drawText( off, d->_best.tip );
313 
314  p.setPen( Qt::white );
315  p.drawText( off - QPoint( 1, 1 ), d->_best.tip );
316 
317  }
318 }
319 
320 YQTimezoneSelectorPrivate::Location YQTimezoneSelectorPrivate::findBest( const QPoint &pos ) const
321 {
322  double min_dist = 2000;
323  Location best;
324  for ( QList<Location>::const_iterator it = locations.begin(); it != locations.end(); ++it )
325  {
326  double dist = QPoint( pixToWindow( ( *it ).pix_pos ) - pos ).manhattanLength ();
327  if ( dist < min_dist )
328  {
329  min_dist = dist;
330  best = *it;
331  }
332  }
333  return best;
334 }
335 
336 bool YQTimezoneSelector::event(QEvent *event)
337 {
338  if (event->type() == QEvent::ToolTip)
339  {
340  QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
341 
342  YQTimezoneSelectorPrivate::Location best = d->findBest( helpEvent->pos() );
343  QToolTip::showText(helpEvent->globalPos(), best.tip );
344  }
345  return QWidget::event(event);
346 }
347 
348 
350 {
351  return d->_best.zone.toStdString();
352 }
353 
354 QPoint YQTimezoneSelectorPrivate::pixToWindow( const QPoint &pos ) const
355 {
356  if ( _zoom.isNull() )
357  {
358  return QPoint( (int) ( double( pos.x() ) * cachePix.width() / _pix.width() ) + ( parent->width() - cachePix.width() ) / 2,
359  (int) ( double( pos.y() ) * cachePix.height() / _pix.height() ) + ( parent->height() - cachePix.height() ) /2 );
360  }
361  int left = qMin( qMax( _zoom.x() - parent->width() / 2, 0 ), _pix.width() - parent->width() );
362  int top = qMin( qMax( _zoom.y() - parent->height() / 2, 0 ), _pix.height() - parent->height() );
363 
364  return QPoint( pos.x() - left, pos.y() - top );
365 }
366 
367 void YQTimezoneSelector::setCurrentZone( const std::string &_zone, bool zoom )
368 {
369  QString zone = fromUTF8( _zone );
370 
371  if ( d->_best.zone == zone )
372  return;
373 
375 
376  for ( QList<YQTimezoneSelectorPrivate::Location>::const_iterator it = d->locations.begin(); it != d->locations.end(); ++it )
377  {
378  if ( ( *it ).zone == zone )
379  d->_best = *it;
380  }
381 
382  if ( zoom )
383  d->_zoom = d->_best.pix_pos;
384  else
385  d->_zoom = QPoint();
386 
387  d->cachePix = QPixmap();
388  d->highlight = 1;
389 
390  d->blink->start();
391  update();
392 }
393 
394 void YQTimezoneSelector::slotBlink()
395 {
396  if ( d->_best.zone.isNull() )
397  {
398  d->blink->stop();
399  return;
400  }
401 
402  if ( d->highlight++ > 9 )
403  d->highlight = 1;
404 
405  QPoint current = d->pixToWindow( d->_best.pix_pos );
406  update( QRect( current - QPoint( 3, 3 ), QSize( 7, 7 ) ) );
407 }
408 
409 
virtual int preferredHeight()
Preferred height of the widget.
virtual void setSize(int newWidth, int newHeight)
Set the new size of the widget.
virtual std::string currentZone() const
subclasses have to implement this to return value
YQTimezoneSelector(YWidget *parent, const std::string &pixmap, const std::map< std::string, std::string > &timezones)
Constructor.
void sendEvent(YEvent *event)
Widget event handlers (slots) call this when an event occured that should be the answer to a UserInpu...
Definition: YQUI.cc:472
virtual ~YQTimezoneSelector()
Destructor.
virtual int preferredWidth()
Preferred width of the widget.
virtual void setCurrentZone(const std::string &zone, bool zoom)
subclasses have to implement this to set value
static YQUI * ui()
Access the global Qt-UI.
Definition: YQUI.h:83