~elementary-os/ubuntu-package-imports/ubiquity-wily

« back to all changes in this revision

Viewing changes to ubiquity/frontend/kde_components/Timezone.py

  • Committer: RabbitBot
  • Date: 2015-09-13 21:17:45 UTC
  • Revision ID: rabbitbot@elementaryos.org-20150913211745-n41jcvcqcnvmyjyx
Initial import, version 2.21.29

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*-
 
2
 
 
3
from __future__ import print_function
 
4
 
 
5
import datetime
 
6
import math
 
7
 
 
8
from PyQt4 import QtCore, QtGui
 
9
 
 
10
import ubiquity.tz
 
11
 
 
12
 
 
13
class City:
 
14
    """Contains information about a geographical timezone city."""
 
15
 
 
16
    def __init__(self, loc, pixmap):
 
17
        self.loc = loc
 
18
        self.pixmap = pixmap
 
19
 
 
20
 
 
21
class TimezoneMap(QtGui.QWidget):
 
22
 
 
23
    zoneChanged = QtCore.pyqtSignal(object, object)
 
24
 
 
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
 
31
        self.cities = {}
 
32
        self.setObjectName("timezone_map")
 
33
 
 
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)
 
44
 
 
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)
 
49
 
 
50
        # load the pixmaps for the zone overlays
 
51
        zones = [
 
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',
 
57
        ]
 
58
 
 
59
        zonePixmaps = {}
 
60
 
 
61
        for zone in zones:
 
62
            # print('%s/timezone_%s.png' % (self.imagePath, zone))
 
63
            zonePixmaps[zone] = QtGui.QPixmap(
 
64
                '%s/timezone_%s.png' % (self.imagePath, zone))
 
65
 
 
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('/')
 
70
 
 
71
            if len(zone_bits) == 1:
 
72
                continue
 
73
 
 
74
            # zone is the hours offset from 0
 
75
            zoneHour = (location.raw_utc_offset.seconds / 3600.0 +
 
76
                        location.raw_utc_offset.days * 24)
 
77
 
 
78
            # wrap around
 
79
            if zoneHour > 13.0:
 
80
                zoneHour -= 24.0
 
81
 
 
82
            # set the pixamp to show for the city
 
83
            zoneS = str(zoneHour)
 
84
 
 
85
            # try to find the closest zone
 
86
            if zoneS not in zonePixmaps:
 
87
                zoneS = None
 
88
                for offset in (.25, -.25, .5, -.5):
 
89
                    zstring = str(zoneHour + offset)
 
90
                    if zstring in zonePixmaps:
 
91
                        zoneS = zstring
 
92
                        break
 
93
 
 
94
            pixmap = zoneS and zonePixmaps[zoneS]
 
95
 
 
96
            # make new city
 
97
            self.cities[location.zone] = City(location, pixmap)
 
98
 
 
99
    # taken from gtk side
 
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)
 
107
        x = x * self.width()
 
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)
 
111
        return x
 
112
 
 
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)
 
124
        return y
 
125
 
 
126
    def sizeHint(self):
 
127
        return self.pixmap.size()
 
128
 
 
129
    def heightForWidth(self, w):
 
130
        size = self.pixmap.size()
 
131
        if w > size.width():
 
132
            w = size.width()
 
133
        return w * size.height() / size.width()
 
134
 
 
135
    def paintEvent(self, unused_paintEvent):
 
136
        painter = QtGui.QPainter(self)
 
137
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
 
138
 
 
139
        path = QtGui.QPainterPath()
 
140
        path.addRoundedRect(QtCore.QRectF(self.rect()), 5, 5)
 
141
        painter.setClipPath(path)
 
142
 
 
143
        painter.drawPixmap(self.rect(), self.pixmap)
 
144
 
 
145
        if self.selected_city is not None:
 
146
            c = self.selected_city
 
147
            cpos = self.getPosition(c.loc.latitude, c.loc.longitude)
 
148
 
 
149
            if (c.pixmap):
 
150
                painter.drawPixmap(self.rect(), c.pixmap)
 
151
 
 
152
            painter.setBrush(QtGui.QColor(30, 30, 30, 200))
 
153
            painter.setPen(QtCore.Qt.white)
 
154
 
 
155
            # mark the location with a dot
 
156
            painter.drawEllipse(cpos, 3, 3)
 
157
 
 
158
            # paint the time instead of the name
 
159
            try:
 
160
                now = datetime.datetime.now(
 
161
                    ubiquity.tz.SystemTzInfo(c.loc.zone))
 
162
                timestring = now.strftime('%X')
 
163
 
 
164
                start = cpos + QtCore.QPoint(3, -3)
 
165
                margin = 2
 
166
 
 
167
                # correct the text render position if text will render off
 
168
                # widget
 
169
                text_size = painter.fontMetrics().size(
 
170
                    QtCore.Qt.TextSingleLine, timestring)
 
171
                text_size += QtCore.QSize(margin * 2, margin * 2)
 
172
 
 
173
                rect = QtCore.QRect(
 
174
                    start, start + QtCore.QPoint(
 
175
                        text_size.width(), -text_size.height()))
 
176
 
 
177
                # check bounds of the time display
 
178
                if rect.top() < 0:
 
179
                    rect.moveTop(start.y() + 3)
 
180
                if rect.right() > self.width():
 
181
                    rect.moveRight(start.x() - 3)
 
182
 
 
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)
 
187
 
 
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
 
192
                pass
 
193
 
 
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)
 
197
 
 
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)'''
 
201
 
 
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)
 
212
        xdeg_offset = -6
 
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
 
215
        # offset
 
216
        x = ((width * (180.0 + lo) / 360.0) +
 
217
             (width * xdeg_offset / 180.0))
 
218
        x = x % width
 
219
 
 
220
        # top and bottom clipping latitudes
 
221
        topLat = 81
 
222
        bottomLat = -59
 
223
 
 
224
        # percent of entire possible range
 
225
        topPer = topLat / 180.0
 
226
 
 
227
        # get the y in rectangular coordinates
 
228
        y = 1.25 * math.log(math.tan(math.pi / 4.0 + 0.4 * math.radians(la)))
 
229
 
 
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
 
235
        mapRange = abs(
 
236
            1.25 * math.log(math.tan(
 
237
                math.pi / 4.0 + 0.4 * math.radians(bottomLat))) - topOffset)
 
238
 
 
239
        # Convert to a percentage of the map range
 
240
        y = abs(y - topOffset)
 
241
        y = y / mapRange
 
242
 
 
243
        # this then becomes the percentage of the height
 
244
        y = y * height
 
245
 
 
246
        return QtCore.QPoint(int(x), int(y))
 
247
 
 
248
    def mouseReleaseEvent(self, mouseEvent):
 
249
        pos = mouseEvent.pos()
 
250
 
 
251
        # get closest city to the point clicked
 
252
        closest = None
 
253
        bestdist = 0
 
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):
 
258
                closest = c
 
259
                bestdist = dist
 
260
 
 
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)
 
265
 
 
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)
 
269
 
 
270
    # internal set timezone based on a city
 
271
    def _set_timezone(self, loc, zone=None):
 
272
        city = loc and self.cities[loc.zone]
 
273
        if city:
 
274
            self.selected_city = city
 
275
            self.selected_zone = zone or loc.zone
 
276
            self.zoneChanged.emit(loc, self.selected_zone)
 
277
            self.repaint()
 
278
 
 
279
    # return the full timezone string
 
280
    def get_timezone(self):
 
281
        return self.selected_zone