libyui-qt  2.52.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 
45 
47 {
48  QWidget *parent;
49 
50 public:
51 
52  YQTimezoneSelectorPrivate( QWidget *p ) {
53  parent = p;
54  blink = 0;
55  highlight = 0;
56  }
57  QImage _pix;
58  QPoint _zoom;
59 
60  struct Location
61  {
62  QString country;
63  double latitude;
64  double longitude;
65  QString zone;
66  QString comment;
67  QString tip;
68 
69  QPoint pix_pos;
70 
71  bool operator<(const Location& l2) const;
72  };
73 
74  Location _best;
75 
76  QList<Location> locations;
77 
78  Location findBest( const QPoint &pos ) const;
79 
80  QTimer *blink;
81 
82  int highlight;
83 
84  QPoint pixPosition( const Location &pos ) const;
85 
86  QPoint pixToWindow( const QPoint &pos ) const;
87 
88  QPixmap cachePix;
89 };
90 
91 
92 static float
93 convert_pos (const QString &pos, int digits)
94 {
95  if (pos.length() < 4 || digits > 9) return 0.0;
96 
97  QString whole = pos.left( digits + 1 );
98  QString fraction = pos.mid( digits + 1 );
99 
100  float t1 = whole.toFloat();
101  float t2 = fraction.toFloat();
102 
103  if (t1 >= 0.0)
104  return t1 + t2/pow (10.0, fraction.length() );
105  else
106  return t1 - t2/pow (10.0, fraction.length());
107 }
108 
109 
110 bool YQTimezoneSelectorPrivate::Location::operator<(const Location& l1 ) const
111 {
112  return l1.latitude < latitude;
113 }
114 
115 
117  const string & pixmap,
118  const std::map<string,string> & timezones )
119  : QFrame( (QWidget *) parent->widgetRep() )
120  , YTimezoneSelector( parent, pixmap, timezones )
121 {
122  d = new YQTimezoneSelectorPrivate( this );
123 
124  setWidgetRep( this );
125  setMouseTracking(true);
126  d->_pix.load( fromUTF8( pixmap ) );
127 
128  setStretchable( YD_HORIZ, true );
129  setStretchable( YD_VERT, true );
130 
131  char buf[4096];
132  FILE *tzfile = fopen ("/usr/share/zoneinfo/zone.tab", "r");
133  while (fgets (buf, sizeof(buf), tzfile))
134  {
135  if (*buf == '#') continue;
136 
137  QString sbuf = buf;
138  QStringList arr = sbuf.trimmed().split( '\t' );
139 
140  int split_index = 1;
141  while ( split_index < arr[1].length() && arr[1][split_index] != '-' && arr[1][split_index] != '+' )
142  split_index++;
143 
145  loc.country = arr[0];
146  loc.zone = arr[2];
147  std::map<string, string>::const_iterator tooltip = timezones.find( loc.zone.toStdString() );
148  if (tooltip == timezones.end() )
149  continue;
150 
151  loc.tip = fromUTF8( tooltip->second );
152  if ( arr.size() > 3 )
153  loc.comment = arr[3];
154  loc.latitude = convert_pos ( arr[1].left( split_index ), 2);
155  loc.longitude = convert_pos ( arr[1].mid( split_index ), 3);
156 
157  loc.pix_pos = d->pixPosition( loc );
158 
159  d->locations.push_back( loc );
160  }
161 
162  fclose (tzfile);
163 
164  std::sort( d->locations.begin(), d->locations.end() );
165 
166  d->blink = new QTimer( this );
167  d->blink->setInterval( 200 );
168  connect( d->blink, &pclass(d->blink)::timeout,
169  this, &pclass(this)::slotBlink );
170 
171  d->highlight = 0;
172 }
173 
175 {
176  delete d;
177  // NOP
178 }
179 
180 
182 {
183  return 600;
184 }
185 
186 
188 {
189  return 300;
190 }
191 
192 
193 void YQTimezoneSelector::setSize( int newWidth, int newHeight )
194 {
195  resize( newWidth, newHeight );
196 }
197 
198 
199 QPoint YQTimezoneSelectorPrivate::pixPosition( const Location &pos ) const
200 {
201  return QPoint( (int) ( _pix.width() / 2 + _pix.width() / 2 * pos.longitude / 180 ),
202  (int) ( _pix.height() / 2 - _pix.height() / 2 * pos.latitude / 90 ) ) ;
203 }
204 
205 
206 void YQTimezoneSelector::mousePressEvent ( QMouseEvent * event )
207 {
208  if ( event->button() == Qt::RightButton )
209  {
210  d->_zoom = QPoint();
211  d->cachePix = QPixmap();
212  }
213  else if ( event->button() == Qt::LeftButton )
214  {
215  d->_best = d->findBest( event->pos() );
216 
217  if ( d->_zoom.isNull() )
218  {
219  QPoint click = event->pos();
220  /* keep the zoom point in unscaled math */
221  d->_zoom.rx() = (int) ( double( click.x() ) * d->_pix.width() / width() );
222  d->_zoom.ry() = (int) ( double( click.y() ) * d->_pix.height() / height() );
223  }
224 
225  d->cachePix = QPixmap();
226 
227  if ( notify() )
228  YQUI::ui()->sendEvent( new YWidgetEvent( this, YEvent::ValueChanged ) );
229 
230  d->blink->start();
231  } else
232  return;
233 
234  update();
235 }
236 
237 
238 void YQTimezoneSelector::paintEvent( QPaintEvent *event )
239 {
240  QFrame::paintEvent( event );
241  QPainter p( this );
242 
243  if ( d->cachePix.width() != width() || d->cachePix.height() != height() )
244  d->cachePix = QPixmap();
245 
246  if ( d->_zoom.isNull() )
247  {
248  if ( d->cachePix.isNull() )
249  {
250  QImage t = d->_pix.scaled( width(), height(), Qt::KeepAspectRatio );
251  d->cachePix = QPixmap::fromImage( t );
252  }
253  p.drawPixmap( ( width() - d->cachePix.width() ) / 2, ( height() - d->cachePix.height() ) / 2, d->cachePix );
254 
255  Q_INIT_RESOURCE( qt_icons );
256  QIcon icon = QIcon::fromTheme( "zoom-in", QIcon( ":/zoom-in" ) );
257 
258  if ( !icon.isNull() )
259  setCursor( QCursor( icon.pixmap( QSize( 16, 16 ) ) ) );
260  }
261  else
262  {
263  int left = qMin( qMax( d->_zoom.x() - width() / 2, 0 ), d->_pix.width() - width() );
264  int top = qMin( qMax( d->_zoom.y() - height() / 2, 0 ), d->_pix.height() - height() );
265 
266  if ( d->cachePix.isNull() )
267  d->cachePix = QPixmap::fromImage( d->_pix.copy( QRect( QPoint( left, top ), size() ) ) );
268 
269  p.drawPixmap( 0, 0, d->cachePix );
270 
271  setCursor( Qt::CrossCursor );
272  }
273 
274  p.setBrush( QColor( "#D8DF57" ) );
275  p.setPen( QColor( "#B9DFD6" ) );
276 
277  for ( QList<YQTimezoneSelectorPrivate::Location>::const_iterator it = d->locations.begin(); it != d->locations.end(); ++it )
278  {
279  if ( !d->highlight || ( *it ).zone != d->_best.zone )
280  {
281  if ( d->_zoom.isNull() )
282  p.drawEllipse( QRect( d->pixToWindow( ( *it ).pix_pos ) - QPoint( 1,1 ), QSize( 3, 3 ) ) );
283  else
284  p.drawEllipse( QRect( d->pixToWindow( ( *it ).pix_pos ) - QPoint( 2,2 ), QSize( 5, 5 ) ) );
285  }
286  }
287  if ( d->highlight > 0 )
288  {
289  static const QColor blinks[] = { QColor( "#00ff00" ), QColor( "#22dd00" ), QColor( "#44bb00" ),
290  QColor( "#669900" ), QColor( "#887700" ), QColor( "#aa5500" ),
291  QColor( "#887700" ), QColor( "#669900" ), QColor( "#44bb00" ),
292  QColor( "#22dd00" ) };
293  int index = d->highlight - 1;
294  p.setPen( blinks[ index ] );
295  p.setBrush( blinks[ index ] );
296 
297  p.drawEllipse( QRect( d->pixToWindow( d->_best.pix_pos ) - QPoint( 2,2 ), QSize( 5, 5 ) ) );
298 
299  QFont f( font() );
300  f.setBold( true );
301  p.setFont( f );
302  QFontMetrics fm( f );
303 
304  QPoint off = d->pixToWindow( d->_best.pix_pos ) + QPoint( 11, 4 );
305 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
306  int tw = fm.horizontalAdvance( d->_best.tip );
307 #else
308  // Leap 15.0 has an older Qt version, make sure it also compiles there
309  int tw = fm.width( d->_best.tip );
310 #endif
311  if ( tw + off.x() > width() )
312  off.rx() = d->pixToWindow( d->_best.pix_pos ).x() - tw - 10;
313 
314  p.setPen( Qt::black );
315  p.drawText( off, d->_best.tip );
316 
317  p.setPen( Qt::white );
318  p.drawText( off - QPoint( 1, 1 ), d->_best.tip );
319 
320  }
321 }
322 
323 
324 YQTimezoneSelectorPrivate::Location YQTimezoneSelectorPrivate::findBest( const QPoint &pos ) const
325 {
326  double min_dist = 2000;
327  Location best;
328  for ( QList<Location>::const_iterator it = locations.begin(); it != locations.end(); ++it )
329  {
330  double dist = QPoint( pixToWindow( ( *it ).pix_pos ) - pos ).manhattanLength ();
331  if ( dist < min_dist )
332  {
333  min_dist = dist;
334  best = *it;
335  }
336  }
337  return best;
338 }
339 
340 
341 bool YQTimezoneSelector::event(QEvent *event)
342 {
343  if (event->type() == QEvent::ToolTip)
344  {
345  QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
346 
347  YQTimezoneSelectorPrivate::Location best = d->findBest( helpEvent->pos() );
348  QToolTip::showText(helpEvent->globalPos(), best.tip );
349  }
350  return QWidget::event(event);
351 }
352 
353 
355 {
356  return d->_best.zone.toStdString();
357 }
358 
359 
360 QPoint YQTimezoneSelectorPrivate::pixToWindow( const QPoint &pos ) const
361 {
362  if ( _zoom.isNull() )
363  {
364  return QPoint( (int) ( double( pos.x() ) * cachePix.width() / _pix.width() ) + ( parent->width() - cachePix.width() ) / 2,
365  (int) ( double( pos.y() ) * cachePix.height() / _pix.height() ) + ( parent->height() - cachePix.height() ) /2 );
366  }
367  int left = qMin( qMax( _zoom.x() - parent->width() / 2, 0 ), _pix.width() - parent->width() );
368  int top = qMin( qMax( _zoom.y() - parent->height() / 2, 0 ), _pix.height() - parent->height() );
369 
370  return QPoint( pos.x() - left, pos.y() - top );
371 }
372 
373 
374 void YQTimezoneSelector::setCurrentZone( const string &_zone, bool zoom )
375 {
376  QString zone = fromUTF8( _zone );
377 
378  if ( d->_best.zone == zone )
379  return;
380 
382 
383  for ( QList<YQTimezoneSelectorPrivate::Location>::const_iterator it = d->locations.begin(); it != d->locations.end(); ++it )
384  {
385  if ( ( *it ).zone == zone )
386  d->_best = *it;
387  }
388 
389  if ( zoom )
390  d->_zoom = d->_best.pix_pos;
391  else
392  d->_zoom = QPoint();
393 
394  d->cachePix = QPixmap();
395  d->highlight = 1;
396 
397  d->blink->start();
398  update();
399 }
400 
401 
402 void YQTimezoneSelector::slotBlink()
403 {
404  if ( d->_best.zone.isNull() )
405  {
406  d->blink->stop();
407  return;
408  }
409 
410  if ( d->highlight++ > 9 )
411  d->highlight = 1;
412 
413  QPoint current = d->pixToWindow( d->_best.pix_pos );
414  update( QRect( current - QPoint( 3, 3 ), QSize( 7, 7 ) ) );
415 }
416 
417 
YQTimezoneSelector(YWidget *parent, const std::string &timezoneMap, const std::map< std::string, std::string > &timezones)
Constructor.
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
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:480
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