d2dc8fa29aaab0cc7a73f9b6ccdeb1c769c8f805
[pywienerlinien] / gotovienna-qml
1 #!/usr/bin/env python
2
3 """Public transport information for Vienna"""
4
5 __author__ = 'kelvan <kelvan@logic.at>'
6 __version__ = '0.9.0'
7 __website__ = 'http://tinyurl.com/gotoVienna'
8 __license__ = 'GNU General Public License v3 or later'
9
10 from datetime import datetime
11
12 from PySide.QtCore import QAbstractListModel, QModelIndex, QObject, Slot, Signal
13 from PySide.QtGui import QApplication
14 from PySide.QtDeclarative import QDeclarativeView
15
16 from gotovienna.utils import *
17 from gotovienna.realtime import *
18 from gotovienna.gps import *
19 from gotovienna.update import *
20 from gotovienna.config import config as conf
21
22 import urllib2
23 import os
24 import sys
25 import threading
26 from datetime import time
27
28 class Config(QObject):
29     def __init__(self):
30         QObject.__init__(self)
31
32     @Slot(result=bool)
33     def getGpsEnabled(self):
34         return conf.getGpsEnabled()
35
36     @Slot(bool, result=unicode)
37     def setGpsEnabled(self, val):
38         # TODO
39         return conf.setGpsEnabled(val)
40
41     @Slot(result=unicode)
42     def getLastUpdate(self):
43         # TODO
44         return conf.getLastStationsUpdate()
45
46     @Slot(result=unicode)
47     def updateStations(self):
48         # TODO
49         try:
50             update_stations()
51             return datetime.now().strftime('%c')
52         except Exception as e:
53             print e
54             return ''
55
56     @Slot(result=bool)
57     def checkStationsUpdate(self):
58         # FIXME exception handling foo
59         try:
60             return check_stations_update()
61         except:
62             return False
63
64 class AboutInfo(QObject):
65     def __init__(self):
66         QObject.__init__(self)
67
68     @Slot(result=unicode)
69     def getAppName(self):
70         return u'gotoVienna %s' % __version__
71
72     @Slot(result=unicode)
73     def getWebsiteURL(self):
74         return __website__
75
76     @Slot(result=unicode)
77     def getCopyright(self):
78         return 'Copyright 2011, 2012 %s' % __author__
79
80     @Slot(result=unicode)
81     def getLicense(self):
82         return __license__
83
84 class GotoViennaListModel(QAbstractListModel):
85     def __init__(self, objects=None):
86         QAbstractListModel.__init__(self)
87         if objects is None:
88             objects = []
89         self._objects = objects
90         self.setRoleNames({0: 'modelData'})
91
92     def set_objects(self, objects):
93         self._objects = objects
94
95     def get_objects(self):
96         return self._objects
97
98     def get_object(self, index):
99         return self._objects[index.row()]
100
101     def rowCount(self, parent=QModelIndex()):
102         return len(self._objects)
103
104     def data(self, index, role):
105         if index.isValid():
106             if role == 0:
107                 return self.get_object(index)
108         return None
109
110
111 class Gui(QObject):
112     def __init__(self):
113         QObject.__init__(self)
114         self.itip = ITipParser()
115         self.lines = []
116
117         # Read line names in categorized/sorted order
118         for _, lines in categorize_lines(self.itip.lines):
119             self.lines.extend(lines)
120
121         self.current_line = ''
122         self.current_stations = []
123         self.current_departures = []
124
125     @Slot(int, result=str)
126     def get_direction(self, idx):
127         return self.current_stations[idx][0]
128
129     @Slot(str, str, result='QStringList')
130     def get_stations(self, line, direction):
131         print 'line:', line, 'current line:', self.current_line
132         for dx, stations in self.current_stations:
133             print 'dx:', dx, 'direction:', direction
134             if dx == direction:
135                 return [stationname for stationname, url in stations]
136
137         return ['no stations found']
138
139     directionsLoaded = Signal()
140
141     @Slot(str)
142     def load_directions(self, line):
143         def load_async():
144             stations = sorted(self.itip.get_stations(line).items())
145
146             self.current_line = line
147             self.current_stations = stations
148
149             self.directionsLoaded.emit()
150
151         threading.Thread(target=load_async).start()
152
153     def map_departure(self, dep):
154         """ prepare departure list for qml gui
155         """
156         dep['lowfloor'] = 1 if dep['lowfloor'] else 0
157         dep['realtime'] = 1 if dep['realtime'] else 0
158         dep['time'] = dep['ftime']
159         return dep
160
161     departuresLoaded = Signal()
162
163     @Slot(str)
164     def load_departures_test(self, **args):
165         """ valid args combinations
166             station
167             line, station
168         """
169         def load_async():
170             if args.has_key('station'):
171                 if args.has_key('line'):
172                     self.current_departures = map(self.map_departure, \
173                                                   self.itip.get_departures(args['line'], args['station']))
174                     #print self.current_departures
175                     self.departuresLoaded.emit()
176                 else:
177                     self.current_departures = map(self.map_departure, \
178                                                   sort_departures(self.itip.get_departures_by_station(station)))
179             else:
180                 raise KeyError('Missing valid argument combination')
181
182         threading.Thread(target=load_async).start()
183
184     @Slot(str)
185     def load_departures(self, url):
186         def load_async():
187             self.current_departures = map(self.map_departure, \
188                                           self.itip.get_departures(url))
189             #print self.current_departures
190             self.departuresLoaded.emit()
191
192         threading.Thread(target=load_async).start()
193
194     @Slot(str)
195     def load_station_departures(self, station):
196         def load_async():
197             self.current_departures = map(self.map_departure, \
198                                           sort_departures(self.itip.get_departures_by_station(station)))
199             #print self.current_departures
200             self.departuresLoaded.emit()
201
202         threading.Thread(target=load_async).start()
203
204     @Slot(float, float)
205     def load_nearby_departures(self, lat, lon):
206         def load_async():
207             self.current_departures = []
208             try:
209                 stations = get_nearby_stations(lat, lon)
210                 print stations
211                 for station in stations:
212                     print station
213                     try:
214                         self.current_departures += self.itip.get_departures_by_station(station)
215                     except Exception as e:
216                         print e.message
217                 self.current_departures = map(self.map_departure, \
218                                               sort_departures(self.current_departures))
219                 #print self.current_departures
220             except Exception as e:
221                 print e.message
222
223             print 'loaded'
224             self.departuresLoaded.emit()
225
226         threading.Thread(target=load_async).start()
227
228     @Slot(str, str, str, result=str)
229     def get_directions_url(self, line, direction, station):
230         return self.itip.get_url_from_direction(line, direction, station)
231
232     @Slot(result='QStringList')
233     def get_lines(self):
234         return self.lines
235
236     @Slot(result='QVariant')
237     def get_departures(self):
238         return self.current_departures
239
240     @Slot(float, float, result='QStringList')
241     def get_nearby_stations(self, lat, lon):
242         try:
243             return get_nearby_stations(lat, lon)
244         except BaseException as e:
245             # No/wrong stations.db file
246             return []
247
248     @Slot(str, str)
249     def search(self, line, station):
250         line = line.upper()
251         station = station.decode('utf-8')
252         print line, station
253
254         if line not in self.lines:
255             return "Invalid line"
256
257         try:
258             stations = sorted(self.itip.get_stations(line).items())
259             print stations
260             headers, stations = zip(*stations)
261             print headers
262             print stations
263             details = [(direction, name, url) for direction, stops in stations
264                         for name, url in stops if match_station(station, name)]
265             print details
266         except urllib2.URLError as e:
267             print e.message
268             return e.message
269
270 if __name__ == '__main__':
271     app = QApplication(sys.argv)
272
273     view = QDeclarativeView()
274
275     aboutInfo = AboutInfo()
276     config = Config()
277
278     # instantiate the Python object
279     itip = Gui()
280
281     # expose the object to QML
282     context = view.rootContext()
283     context.setContextProperty('itip', itip)
284     context.setContextProperty('aboutInfo', aboutInfo)
285     context.setContextProperty('config', config)
286
287     if os.path.abspath(__file__).startswith('/usr/bin/'):
288         # Assume system-wide installation, QML from /usr/share/
289         view.setSource('/usr/share/gotovienna/qml/main.qml')
290     else:
291         # Assume test from source directory, use relative path
292         view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml'))
293
294     view.showFullScreen()
295
296     sys.exit(app.exec_())
297