3 """Public transport information for Vienna"""
5 __author__ = 'kelvan <kelvan@logic.at>'
7 __website__ = 'http://tinyurl.com/gotoVienna'
8 __license__ = 'GNU General Public License v3 or later'
10 from datetime import datetime
12 from PySide.QtCore import QAbstractListModel, QModelIndex, QObject, Slot, Signal, Qt
13 from PySide.QtGui import QApplication, QTextItem
14 from PySide.QtDeclarative import QDeclarativeView
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
22 from gotovienna import defaults
29 from datetime import time
31 class Config(QObject):
33 QObject.__init__(self)
36 def getGpsEnabled(self):
37 return conf.getGpsEnabled()
39 @Slot(bool, result=unicode)
40 def setGpsEnabled(self, val):
42 return conf.setGpsEnabled(val)
45 def getLastUpdate(self):
47 return conf.getLastStationsUpdate()
50 def updateStations(self):
54 return datetime.now().strftime('%c')
55 except Exception as e:
60 def checkStationsUpdate(self):
61 # FIXME exception handling foo
63 return check_stations_update()
67 class AboutInfo(QObject):
69 QObject.__init__(self)
73 return u'gotoVienna %s' % __version__
76 def getWebsiteURL(self):
80 def getCopyright(self):
81 return 'Copyright 2011, 2012 %s' % __author__
87 class DepartureModel(QAbstractListModel):
88 LINE_ROLE = Qt.UserRole + 1
89 DIRECTION_ROLE = Qt.UserRole + 2
90 STATION_ROLE = Qt.UserRole + 3
91 TIME_ROLE = Qt.UserRole + 4
92 LOWFLOOR_ROLE = Qt.UserRole + 5
94 def __init__(self, parent=None):
95 super(DepartureModel, self).__init__(parent)
99 self.keys[DepartureModel.LINE_ROLE] = 'line'
100 self.keys[DepartureModel.DIRECTION_ROLE] = 'direction'
101 self.keys[DepartureModel.STATION_ROLE] = 'station'
102 self.keys[DepartureModel.TIME_ROLE] = 'time'
103 self.keys[DepartureModel.LOWFLOOR_ROLE] = 'lowfloor'
104 self.setRoleNames(self.keys)
106 def rowCount(self, index):
107 return len(self._data)
109 def data(self, index, role):
110 if not index.isValid():
113 if index.row() > len(self._data):
116 departure = self._data[index.row()]
118 if self.keys.has_key(role):
119 return departure[self.keys[role]]
123 def setDepartures(self, dep):
124 self.beginResetModel()
128 class FavoriteManager(QObject):
130 QObject.__init__(self)
132 if os.path.exists(defaults.favorites_file):
134 self._faves = json.load(open(defaults.favorites_file, 'r'))
135 self._faves = map(tuple, self._faves)
136 print 'faves loaded:', self._faves
138 print 'faves load error:', e
142 result = len(self._faves)
143 print 'getCount->', result
146 @Slot(int, result=unicode)
147 def getItem(self, index):
148 keys = ['gline', 'gdirection', 'gstation', 'sourceurl', 'isstation']
149 result = dict(zip(keys, self._faves[index]))
150 result['name'] = u'%(gline)s -> %(gdirection)s @ %(gstation)s' % result
151 result = json.dumps(result)
152 print 'getItem:', index, result
156 print 'persist:', self._faves, '->', defaults.favorites_file
158 fp = open(defaults.favorites_file, 'w')
159 json.dump(self._faves, fp)
162 print 'faves save error:', e
164 @Slot(unicode, unicode, unicode, unicode, bool, int, result=bool)
165 def isFavorite(self, gline, gdirection, gstation, sourceurl, isstation, x):
166 k = (gline, gdirection, gstation, sourceurl, isstation)
167 return (k in self._faves)
169 @Slot(unicode, unicode, unicode, unicode, bool)
170 def toggleFavorite(self, gline, gdirection, gstation, sourceurl, isstation):
171 k = (gline, gdirection, gstation, sourceurl, isstation)
173 self._faves.remove(k)
175 self._faves.append(k)
180 def __init__(self, depModel):
181 QObject.__init__(self)
182 self.itip = ITipParser()
184 self.departureModel = depModel
186 # Read line names in categorized/sorted order
187 for _, lines in categorize_lines(self.itip.lines):
188 self.lines.extend(lines)
190 self.current_line = ''
191 self.current_stations = []
192 self.current_departures = []
194 @Slot(int, result=str)
195 def get_direction(self, idx):
196 return self.current_stations[idx][0]
198 @Slot(str, str, result='QStringList')
199 def get_stations(self, line, direction):
200 print 'line:', line, 'current line:', self.current_line
201 for dx, stations in self.current_stations:
202 print 'dx:', dx, 'direction:', direction
204 return [stationname for stationname, url in stations]
206 return ['no stations found']
208 directionsLoaded = Signal()
211 def load_directions(self, line):
213 stations = sorted(self.itip.get_stations(line).items())
215 self.current_line = line
216 self.current_stations = stations
218 self.directionsLoaded.emit()
220 threading.Thread(target=load_async).start()
222 #def map_departure(self, dep):
223 # """ prepare departure list for qml gui
225 # dep['lowfloor'] = 1 if dep['lowfloor'] else 0
226 # dep['realtime'] = 1 if dep['realtime'] else 0
227 # dep['time'] = dep['ftime']
230 departuresLoaded = Signal()
233 def load_departures_test(self, **args):
234 """ valid args combinations
239 if args.has_key('station'):
240 if args.has_key('line'):
241 self.current_departures = map(self.map_departure, \
242 self.itip.get_departures(args['line'], args['station']))
243 #print self.current_departures
244 self.departuresLoaded.emit()
246 self.current_departures = map(self.map_departure, \
247 sort_departures(self.itip.get_departures_by_station(station)))
249 raise KeyError('Missing valid argument combination')
251 threading.Thread(target=load_async).start()
254 def load_departures(self, url):
256 self.departureModel.setDepartures(self.itip.get_departures(url))
257 #print self.current_departures
258 self.departuresLoaded.emit()
260 threading.Thread(target=load_async).start()
263 def load_station_departures(self, station):
265 self.departureModel.setDepartures(sort_departures(self.itip.get_departures_by_station(station)))
266 self.departuresLoaded.emit()
268 threading.Thread(target=load_async).start()
271 def load_nearby_departures(self, lat, lon):
273 self.current_departures = []
275 stations = get_nearby_stations(lat, lon)
277 for station in stations:
280 self.current_departures += self.itip.get_departures_by_station(station)
281 except Exception as e:
283 self.current_departures = map(self.map_departure, \
284 sort_departures(self.current_departures))
285 #print self.current_departures
286 except Exception as e:
290 self.departuresLoaded.emit()
292 threading.Thread(target=load_async).start()
294 @Slot(str, str, str, result=str)
295 def get_directions_url(self, line, direction, station):
296 return self.itip.get_url_from_direction(line, direction, station)
298 @Slot(result='QStringList')
302 @Slot(float, float, result='QStringList')
303 def get_nearby_stations(self, lat, lon):
305 return get_nearby_stations(lat, lon)
306 except BaseException as e:
307 # No/wrong stations.db file
311 if __name__ == '__main__':
312 app = QApplication(sys.argv)
314 view = QDeclarativeView()
316 aboutInfo = AboutInfo()
318 departureModel = DepartureModel()
320 # instantiate the Python object
321 itip = Gui(departureModel)
323 favManager = FavoriteManager()
325 # expose the object to QML
326 context = view.rootContext()
327 context.setContextProperty('itip', itip)
328 context.setContextProperty('aboutInfo', aboutInfo)
329 context.setContextProperty('config', config)
330 context.setContextProperty('resultModel', departureModel)
331 context.setContextProperty('favManager', favManager)
333 if os.path.abspath(__file__).startswith('/usr/bin/'):
334 # Assume system-wide installation, QML from /usr/share/
335 view.setSource('/usr/share/gotovienna/qml/main.qml')
337 # Assume test from source directory, use relative path
338 view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml'))
340 view.showFullScreen()
342 sys.exit(app.exec_())