replaced get_departures with DataModel in pyside
[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, Qt
13 from PySide.QtGui import QApplication, QTextItem
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 DepartureModel(QAbstractListModel):
85     LINE_ROLE = Qt.UserRole + 1
86     DIRECTION_ROLE = Qt.UserRole + 2
87     STATION_ROLE = Qt.UserRole + 3
88     TIME_ROLE = Qt.UserRole + 4
89     LOWFLOOR_ROLE = Qt.UserRole + 5
90
91     def __init__(self, parent=None):
92         super(DepartureModel, self).__init__(parent)
93         self._data = []
94         
95         self.keys = {}
96         self.keys[DepartureModel.LINE_ROLE] = 'line'
97         self.keys[DepartureModel.DIRECTION_ROLE] = 'direction'
98         self.keys[DepartureModel.STATION_ROLE] = 'station'
99         self.keys[DepartureModel.TIME_ROLE] = 'time'
100         self.keys[DepartureModel.LOWFLOOR_ROLE] = 'lowfloor'
101         self.setRoleNames(self.keys)
102
103     def rowCount(self, index):
104         return len(self._data)
105
106     def data(self, index, role):
107         if not index.isValid():
108             return None
109
110         if index.row() > len(self._data):
111             return None
112             
113         departure = self._data[index.row()]
114         
115         if self.keys.has_key(role):
116             return departure[self.keys[role]]
117         else:
118             return None
119             
120     def setDepartures(self, dep):
121         self.beginResetModel()
122         self._data = dep
123         self.endResetModel()
124
125 class Gui(QObject):
126     def __init__(self, depModel):
127         QObject.__init__(self)
128         self.itip = ITipParser()
129         self.lines = []
130         self.departureModel = depModel
131
132         # Read line names in categorized/sorted order
133         for _, lines in categorize_lines(self.itip.lines):
134             self.lines.extend(lines)
135
136         self.current_line = ''
137         self.current_stations = []
138         self.current_departures = []
139
140     @Slot(int, result=str)
141     def get_direction(self, idx):
142         return self.current_stations[idx][0]
143
144     @Slot(str, str, result='QStringList')
145     def get_stations(self, line, direction):
146         print 'line:', line, 'current line:', self.current_line
147         for dx, stations in self.current_stations:
148             print 'dx:', dx, 'direction:', direction
149             if dx == direction:
150                 return [stationname for stationname, url in stations]
151
152         return ['no stations found']
153
154     directionsLoaded = Signal()
155
156     @Slot(str)
157     def load_directions(self, line):
158         def load_async():
159             stations = sorted(self.itip.get_stations(line).items())
160
161             self.current_line = line
162             self.current_stations = stations
163
164             self.directionsLoaded.emit()
165
166         threading.Thread(target=load_async).start()
167
168     #def map_departure(self, dep):
169     #    """ prepare departure list for qml gui
170     #    """
171     #    dep['lowfloor'] = 1 if dep['lowfloor'] else 0
172     #    dep['realtime'] = 1 if dep['realtime'] else 0
173     #    dep['time'] = dep['ftime']
174     #    return dep
175
176     departuresLoaded = Signal()
177
178     @Slot(str)
179     def load_departures_test(self, **args):
180         """ valid args combinations
181             station
182             line, station
183         """
184         def load_async():
185             if args.has_key('station'):
186                 if args.has_key('line'):
187                     self.current_departures = map(self.map_departure, \
188                                                   self.itip.get_departures(args['line'], args['station']))
189                     #print self.current_departures
190                     self.departuresLoaded.emit()
191                 else:
192                     self.current_departures = map(self.map_departure, \
193                                                   sort_departures(self.itip.get_departures_by_station(station)))
194             else:
195                 raise KeyError('Missing valid argument combination')
196
197         threading.Thread(target=load_async).start()
198
199     @Slot(str)
200     def load_departures(self, url):
201         def load_async():
202             self.departureModel.setDepartures(self.itip.get_departures(url))
203             #print self.current_departures
204             self.departuresLoaded.emit()
205
206         threading.Thread(target=load_async).start()
207
208     @Slot(str)
209     def load_station_departures(self, station):
210         def load_async():
211             self.departureModel.setDepartures(sort_departures(self.itip.get_departures_by_station(station)))
212             self.departuresLoaded.emit()
213
214         threading.Thread(target=load_async).start()
215
216     @Slot(float, float)
217     def load_nearby_departures(self, lat, lon):
218         def load_async():
219             self.current_departures = []
220             try:
221                 stations = get_nearby_stations(lat, lon)
222                 print stations
223                 for station in stations:
224                     print station
225                     try:
226                         self.current_departures += self.itip.get_departures_by_station(station)
227                     except Exception as e:
228                         print e.message
229                 self.current_departures = map(self.map_departure, \
230                                               sort_departures(self.current_departures))
231                 #print self.current_departures
232             except Exception as e:
233                 print e.message
234
235             print 'loaded'
236             self.departuresLoaded.emit()
237
238         threading.Thread(target=load_async).start()
239
240     @Slot(str, str, str, result=str)
241     def get_directions_url(self, line, direction, station):
242         return self.itip.get_url_from_direction(line, direction, station)
243
244     @Slot(result='QStringList')
245     def get_lines(self):
246         return self.lines
247
248     @Slot(float, float, result='QStringList')
249     def get_nearby_stations(self, lat, lon):
250         try:
251             return get_nearby_stations(lat, lon)
252         except BaseException as e:
253             # No/wrong stations.db file
254             return []
255
256
257 if __name__ == '__main__':
258     app = QApplication(sys.argv)
259
260     view = QDeclarativeView()
261
262     aboutInfo = AboutInfo()
263     config = Config()
264     departureModel = DepartureModel()
265
266     # instantiate the Python object
267     itip = Gui(departureModel)
268
269     # expose the object to QML
270     context = view.rootContext()
271     context.setContextProperty('itip', itip)
272     context.setContextProperty('aboutInfo', aboutInfo)
273     context.setContextProperty('config', config)
274     context.setContextProperty('resultModel', departureModel)
275     
276     if os.path.abspath(__file__).startswith('/usr/bin/'):
277         # Assume system-wide installation, QML from /usr/share/
278         view.setSource('/usr/share/gotovienna/qml/main.qml')
279     else:
280         # Assume test from source directory, use relative path
281         view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml'))
282
283     view.showFullScreen()
284
285     sys.exit(app.exec_())
286