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
13 from PySide.QtGui import QApplication
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 FavoriteManager(QObject):
89 QObject.__init__(self)
91 if os.path.exists(defaults.favorites_file):
93 self._faves = json.load(open(defaults.favorites_file, 'r'))
94 self._faves = map(tuple, self._faves)
95 print 'faves loaded:', self._faves
97 print 'faves load error:', e
101 result = len(self._faves)
102 print 'getCount->', result
105 @Slot(int, result=unicode)
106 def getItem(self, index):
107 keys = ['gline', 'gdirection', 'gstation', 'sourceurl', 'isstation']
108 result = dict(zip(keys, self._faves[index]))
109 result['name'] = u'%(gline)s -> %(gdirection)s @ %(gstation)s' % result
110 result = json.dumps(result)
111 print 'getItem:', index, result
115 print 'persist:', self._faves, '->', defaults.favorites_file
117 fp = open(defaults.favorites_file, 'w')
118 json.dump(self._faves, fp)
121 print 'faves save error:', e
123 @Slot(unicode, unicode, unicode, unicode, bool, int, result=bool)
124 def isFavorite(self, gline, gdirection, gstation, sourceurl, isstation, x):
125 k = (gline, gdirection, gstation, sourceurl, isstation)
126 return (k in self._faves)
128 @Slot(unicode, unicode, unicode, unicode, bool)
129 def toggleFavorite(self, gline, gdirection, gstation, sourceurl, isstation):
130 k = (gline, gdirection, gstation, sourceurl, isstation)
132 self._faves.remove(k)
134 self._faves.append(k)
139 class GotoViennaListModel(QAbstractListModel):
140 def __init__(self, objects=None):
141 QAbstractListModel.__init__(self)
144 self._objects = objects
145 self.setRoleNames({0: 'modelData'})
147 def set_objects(self, objects):
148 self._objects = objects
151 def get_objects(self):
154 def get_object(self, index):
155 return self._objects[index.row()]
157 def rowCount(self, parent=QModelIndex()):
158 return len(self._objects)
160 def data(self, index, role):
163 return self.get_object(index)
169 QObject.__init__(self)
170 self.itip = ITipParser()
173 # Read line names in categorized/sorted order
174 for _, lines in categorize_lines(self.itip.lines):
175 self.lines.extend(lines)
177 self.current_line = ''
178 self.current_stations = []
179 self.current_departures = []
181 @Slot(int, result=str)
182 def get_direction(self, idx):
183 return self.current_stations[idx][0]
185 @Slot(str, str, result='QStringList')
186 def get_stations(self, line, direction):
187 print 'line:', line, 'current line:', self.current_line
188 for dx, stations in self.current_stations:
189 print 'dx:', dx, 'direction:', direction
191 return [stationname for stationname, url in stations]
193 return ['no stations found']
195 directionsLoaded = Signal()
198 def load_directions(self, line):
200 stations = sorted(self.itip.get_stations(line).items())
202 self.current_line = line
203 self.current_stations = stations
205 self.directionsLoaded.emit()
207 threading.Thread(target=load_async).start()
209 def map_departure(self, dep):
210 """ prepare departure list for qml gui
212 dep['lowfloor'] = 1 if dep['lowfloor'] else 0
213 dep['realtime'] = 1 if dep['realtime'] else 0
214 dep['time'] = dep['ftime']
217 departuresLoaded = Signal()
220 def load_departures_test(self, **args):
221 """ valid args combinations
226 if args.has_key('station'):
227 if args.has_key('line'):
228 self.current_departures = map(self.map_departure, \
229 self.itip.get_departures(args['line'], args['station']))
230 #print self.current_departures
231 self.departuresLoaded.emit()
233 self.current_departures = map(self.map_departure, \
234 sort_departures(self.itip.get_departures_by_station(station)))
236 raise KeyError('Missing valid argument combination')
238 threading.Thread(target=load_async).start()
241 def load_departures(self, url):
243 self.current_departures = map(self.map_departure, \
244 self.itip.get_departures(url))
245 #print self.current_departures
246 self.departuresLoaded.emit()
248 threading.Thread(target=load_async).start()
251 def load_station_departures(self, station):
253 self.current_departures = map(self.map_departure, \
254 sort_departures(self.itip.get_departures_by_station(station)))
255 #print self.current_departures
256 self.departuresLoaded.emit()
258 threading.Thread(target=load_async).start()
261 def load_nearby_departures(self, lat, lon):
263 self.current_departures = []
265 stations = get_nearby_stations(lat, lon)
267 for station in stations:
270 self.current_departures += self.itip.get_departures_by_station(station)
271 except Exception as e:
273 self.current_departures = map(self.map_departure, \
274 sort_departures(self.current_departures))
275 #print self.current_departures
276 except Exception as e:
280 self.departuresLoaded.emit()
282 threading.Thread(target=load_async).start()
284 @Slot(str, str, str, result=str)
285 def get_directions_url(self, line, direction, station):
286 return self.itip.get_url_from_direction(line, direction, station)
288 @Slot(result='QStringList')
292 @Slot(result='QVariant')
293 def get_departures(self):
294 return self.current_departures
296 @Slot(float, float, result='QStringList')
297 def get_nearby_stations(self, lat, lon):
299 return get_nearby_stations(lat, lon)
300 except BaseException as e:
301 # No/wrong stations.db file
305 def search(self, line, station):
307 station = station.decode('utf-8')
310 if line not in self.lines:
311 return "Invalid line"
314 stations = sorted(self.itip.get_stations(line).items())
316 headers, stations = zip(*stations)
319 details = [(direction, name, url) for direction, stops in stations
320 for name, url in stops if match_station(station, name)]
322 except urllib2.URLError as e:
326 if __name__ == '__main__':
327 app = QApplication(sys.argv)
329 view = QDeclarativeView()
331 aboutInfo = AboutInfo()
334 # instantiate the Python object
337 favManager = FavoriteManager()
339 # expose the object to QML
340 context = view.rootContext()
341 context.setContextProperty('itip', itip)
342 context.setContextProperty('aboutInfo', aboutInfo)
343 context.setContextProperty('config', config)
344 context.setContextProperty('favManager', favManager)
346 if os.path.abspath(__file__).startswith('/usr/bin/'):
347 # Assume system-wide installation, QML from /usr/share/
348 view.setSource('/usr/share/gotovienna/qml/main.qml')
350 # Assume test from source directory, use relative path
351 view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml'))
353 view.showFullScreen()
355 sys.exit(app.exec_())