1
# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*-
3
from __future__ import print_function
8
from PyQt4 import QtCore, QtGui
14
"""Contains information about a geographical timezone city."""
16
def __init__(self, loc, pixmap):
21
class TimezoneMap(QtGui.QWidget):
23
zoneChanged = QtCore.pyqtSignal(object, object)
25
def __init__(self, parent):
26
QtGui.QWidget.__init__(self, parent)
27
# currently active city
28
self.selected_city = None
29
self.selected_zone = None
30
# dictionary of full name (ie. 'Australia/Sydney') -> city
32
self.setObjectName("timezone_map")
34
# load background pixmap
35
self.imagePath = "/usr/share/ubiquity/pixmaps/timezone"
36
self.pixmap = QtGui.QPixmap("%s/bg.png" % self.imagePath)
37
self.setMinimumSize(self.pixmap.size() / 2)
38
self.setMaximumSize(self.pixmap.size())
39
policy = QtGui.QSizePolicy(
40
QtGui.QSizePolicy.Preferred,
41
QtGui.QSizePolicy.Preferred)
42
policy.setHeightForWidth(True)
43
self.setSizePolicy(policy)
45
# redraw timer for selected city time
46
self.timer = QtCore.QTimer(self)
47
self.timer.timeout.connect(self.update)
48
self.timer.start(1000)
50
# load the pixmaps for the zone overlays
52
'0.0', '1.0', '2.0', '3.0', '3.5', '4.0', '4.5', '5.0', '5.5',
53
'5.75', '6.0', '6.5', '7.0', '8.0', '8.5', '9.0', '9.5', '10.0',
54
'10.5', '11.0', '11.5', '12.0', '12.75', '13.0', '-1.0', '-2.0',
55
'-3.0', '-3.5', '-4.0', '-4.5', '-5.0', '-5.5', '-6.0', '-7.0',
56
'-8.0', '-9.0', '-9.5', '-10.0', '-11.0',
62
# print('%s/timezone_%s.png' % (self.imagePath, zone))
63
zonePixmaps[zone] = QtGui.QPixmap(
64
'%s/timezone_%s.png' % (self.imagePath, zone))
66
# load the timezones from database
67
self.tzdb = ubiquity.tz.Database()
68
for location in self.tzdb.locations:
69
zone_bits = location.zone.split('/')
71
if len(zone_bits) == 1:
74
# zone is the hours offset from 0
75
zoneHour = (location.raw_utc_offset.seconds / 3600.0 +
76
location.raw_utc_offset.days * 24)
82
# set the pixamp to show for the city
85
# try to find the closest zone
86
if zoneS not in zonePixmaps:
88
for offset in (.25, -.25, .5, -.5):
89
zstring = str(zoneHour + offset)
90
if zstring in zonePixmaps:
94
pixmap = zoneS and zonePixmaps[zoneS]
97
self.cities[location.zone] = City(location, pixmap)
100
def longitudeToX(self, longitude):
101
# Miller cylindrical map projection is just the longitude as the
102
# calculation is the longitude from the central meridian of the
103
# projection. Convert to radians.
104
x = (longitude * (math.pi / 180)) + math.pi # 0 ... 2pi
105
# Convert to a percentage.
106
x = x / (2 * math.pi)
108
# Adjust for the visible map starting near 170 degrees.
109
# Percentage shift required, grabbed from measurements using The GIMP.
110
x = x - (self.width() * 0.039073402)
113
def latitudeToY(self, latitude):
114
# Miller cylindrical map projection, as used in the source map from
115
# the CIA world factbook. Convert latitude to radians.
116
y = 1.25 * math.log(math.tan(
117
(0.25 * math.pi) + (0.4 * (latitude * (math.pi / 180)))))
118
# Convert to a percentage.
119
y = abs(y - 2.30341254338) # 0 ... 4.606825
120
y = y / 4.6068250867599998
121
# Adjust for the visible map not including anything beyond 60
122
# degrees south (150 degrees vs 180 degrees).
123
y = y * (self.height() * 1.2)
127
return self.pixmap.size()
129
def heightForWidth(self, w):
130
size = self.pixmap.size()
133
return w * size.height() / size.width()
135
def paintEvent(self, unused_paintEvent):
136
painter = QtGui.QPainter(self)
137
painter.setRenderHint(QtGui.QPainter.Antialiasing)
139
path = QtGui.QPainterPath()
140
path.addRoundedRect(QtCore.QRectF(self.rect()), 5, 5)
141
painter.setClipPath(path)
143
painter.drawPixmap(self.rect(), self.pixmap)
145
if self.selected_city is not None:
146
c = self.selected_city
147
cpos = self.getPosition(c.loc.latitude, c.loc.longitude)
150
painter.drawPixmap(self.rect(), c.pixmap)
152
painter.setBrush(QtGui.QColor(30, 30, 30, 200))
153
painter.setPen(QtCore.Qt.white)
155
# mark the location with a dot
156
painter.drawEllipse(cpos, 3, 3)
158
# paint the time instead of the name
160
now = datetime.datetime.now(
161
ubiquity.tz.SystemTzInfo(c.loc.zone))
162
timestring = now.strftime('%X')
164
start = cpos + QtCore.QPoint(3, -3)
167
# correct the text render position if text will render off
169
text_size = painter.fontMetrics().size(
170
QtCore.Qt.TextSingleLine, timestring)
171
text_size += QtCore.QSize(margin * 2, margin * 2)
174
start, start + QtCore.QPoint(
175
text_size.width(), -text_size.height()))
177
# check bounds of the time display
179
rect.moveTop(start.y() + 3)
180
if rect.right() > self.width():
181
rect.moveRight(start.x() - 3)
183
painter.setPen(QtCore.Qt.NoPen)
184
painter.drawRoundedRect(rect, 3, 3)
185
painter.setPen(QtCore.Qt.white)
186
painter.drawText(rect, QtCore.Qt.AlignCenter, timestring)
188
except (ValueError, OverflowError):
189
# Some versions of Python have problems with clocks set
190
# before the epoch (http://python.org/sf/1646728).
191
# ignore and don't display a string
194
# debug info for making sure the cities are in proper places
195
'''for c in self.zones['America']['cities']:
196
cpos = self.getPosition(c.lat, c.long)
198
painter.drawLine(cpos + QPoint(1,1), cpos - QPoint(1,1))
199
painter.drawLine(cpos + QPoint(1,-1), cpos - QPoint(1,-1))
200
#painter.drawText(cpos + QPoint(2,-2), c.city_name)'''
202
# @return pixel coordinate of a latitude and longitude for self
203
# map uses Miller Projection, but is also clipped
204
def getPosition(self, la, lo):
205
width = min(self.width(), self.pixmap.width())
206
height = min(self.height(), self.pixmap.height())
207
# need to add/sub magic numbers because the map doesn't actually go
208
# from -180...180, -90...90 thus the upper corner is not -180, -90
209
# and we have to compensate
210
# we need a better method of determining the actual range so we can
211
# better place cities (shtylman)
213
# the 180 - 360 accounts for the fact that the map does not span the
214
# entire -90 to 90 the map does span the entire 360 though, just
216
x = ((width * (180.0 + lo) / 360.0) +
217
(width * xdeg_offset / 180.0))
220
# top and bottom clipping latitudes
224
# percent of entire possible range
225
topPer = topLat / 180.0
227
# get the y in rectangular coordinates
228
y = 1.25 * math.log(math.tan(math.pi / 4.0 + 0.4 * math.radians(la)))
230
# calculate the map range (smaller than full range because the map
231
# is clipped on top and bottom)
232
fullRange = 4.6068250867599998
233
# the amount of the full range devoted to the upper hemisphere
234
topOffset = fullRange * topPer
236
1.25 * math.log(math.tan(
237
math.pi / 4.0 + 0.4 * math.radians(bottomLat))) - topOffset)
239
# Convert to a percentage of the map range
240
y = abs(y - topOffset)
243
# this then becomes the percentage of the height
246
return QtCore.QPoint(int(x), int(y))
248
def mouseReleaseEvent(self, mouseEvent):
249
pos = mouseEvent.pos()
251
# get closest city to the point clicked
254
for c in self.tzdb.locations:
255
np = pos - self.getPosition(c.latitude, c.longitude)
256
dist = np.x() * np.x() + np.y() * np.y()
257
if (dist < bestdist or closest is None):
261
# we need to set the combo boxes
262
# this will cause the redraw we need
263
if closest is not None:
264
self._set_timezone(closest)
266
# sets the timezone based on the full name (i.e 'Australia/Sydney')
267
def set_timezone(self, name):
268
self._set_timezone(self.tzdb.get_loc(name), name)
270
# internal set timezone based on a city
271
def _set_timezone(self, loc, zone=None):
272
city = loc and self.cities[loc.zone]
274
self.selected_city = city
275
self.selected_zone = zone or loc.zone
276
self.zoneChanged.emit(loc, self.selected_zone)
279
# return the full timezone string
280
def get_timezone(self):
281
return self.selected_zone