#!/usr/bin/env python """Public transport information for Vienna""" __author__ = 'kelvan ' __version__ = '0.9.1' __website__ = 'http://tinyurl.com/gotoVienna' __license__ = 'GNU General Public License v3 or later' from datetime import datetime from PySide.QtCore import QAbstractListModel, QModelIndex, QObject, Slot, Signal, Qt from PySide.QtGui import QApplication, QTextItem from PySide.QtDeclarative import QDeclarativeView from gotovienna.utils import * from gotovienna.realtime import * from gotovienna.gps import * from gotovienna.update import * from gotovienna.config import config as conf from gotovienna import defaults import urllib2 import os import sys import threading import json from datetime import time class Config(QObject): def __init__(self): QObject.__init__(self) @Slot(result=bool) def getGpsEnabled(self): return conf.getGpsEnabled() @Slot(bool, result=unicode) def setGpsEnabled(self, val): # TODO return conf.setGpsEnabled(val) @Slot(result=unicode) def getLastUpdate(self): # TODO return conf.getLastStationsUpdate() @Slot(result=unicode) def updateStations(self): # TODO try: update_stations() return datetime.now().strftime('%c') except Exception as e: print e return '' @Slot(result=bool) def checkStationsUpdate(self): # FIXME exception handling foo try: return check_stations_update() except: return False class AboutInfo(QObject): def __init__(self): QObject.__init__(self) @Slot(result=unicode) def getAppName(self): return u'gotoVienna %s' % __version__ @Slot(result=unicode) def getWebsiteURL(self): return __website__ @Slot(result=unicode) def getCopyright(self): return 'Copyright 2011, 2012 %s' % __author__ @Slot(result=unicode) def getLicense(self): return __license__ class DepartureModel(QAbstractListModel): LINE_ROLE = Qt.UserRole + 1 DIRECTION_ROLE = Qt.UserRole + 2 STATION_ROLE = Qt.UserRole + 3 TIME_ROLE = Qt.UserRole + 4 LOWFLOOR_ROLE = Qt.UserRole + 5 def __init__(self, parent=None): super(DepartureModel, self).__init__(parent) self._data = [] self.keys = {} self.keys[DepartureModel.LINE_ROLE] = 'line' self.keys[DepartureModel.DIRECTION_ROLE] = 'direction' self.keys[DepartureModel.STATION_ROLE] = 'station' self.keys[DepartureModel.TIME_ROLE] = 'time' self.keys[DepartureModel.LOWFLOOR_ROLE] = 'lowfloor' self.setRoleNames(self.keys) def rowCount(self, index): return len(self._data) def data(self, index, role): if not index.isValid(): return None if index.row() > len(self._data): return None departure = self._data[index.row()] if self.keys.has_key(role): return departure[self.keys[role]] else: return None def setDepartures(self, dep): self.beginResetModel() self._data = dep self.endResetModel() class FavoriteManager(QObject): def __init__(self): QObject.__init__(self) self._faves = [] if os.path.exists(defaults.favorites_file): try: self._faves = json.load(open(defaults.favorites_file, 'r')) self._faves = map(tuple, self._faves) print 'faves loaded:', self._faves except Exception, e: print 'faves load error:', e @Slot(result=int) def getCount(self): result = len(self._faves) print 'getCount->', result return result @Slot(int, result=unicode) def getItem(self, index): keys = ['gline', 'gdirection', 'gstation', 'sourceurl', 'isstation'] result = dict(zip(keys, self._faves[index])) result['name'] = u'%(gline)s -> %(gdirection)s @ %(gstation)s' % result result = json.dumps(result) print 'getItem:', index, result return result def _persist(self): print 'persist:', self._faves, '->', defaults.favorites_file try: fp = open(defaults.favorites_file, 'w') json.dump(self._faves, fp) fp.close() except Exception, e: print 'faves save error:', e @Slot(unicode, unicode, unicode, unicode, bool, int, result=bool) def isFavorite(self, gline, gdirection, gstation, sourceurl, isstation, x): k = (gline, gdirection, gstation, sourceurl, isstation) return (k in self._faves) @Slot(unicode, unicode, unicode, unicode, bool) def toggleFavorite(self, gline, gdirection, gstation, sourceurl, isstation): k = (gline, gdirection, gstation, sourceurl, isstation) if k in self._faves: self._faves.remove(k) else: self._faves.append(k) self._persist() class Gui(QObject): def __init__(self, depModel): QObject.__init__(self) self.itip = ITipParser() self.lines = [] self.departureModel = depModel # Read line names in categorized/sorted order for _, lines in categorize_lines(self.itip.lines): self.lines.extend(lines) self.current_line = '' self.current_stations = [] self.current_departures = [] @Slot(int, result=str) def get_direction(self, idx): return self.current_stations[idx][0] @Slot(str, str, result='QStringList') def get_stations(self, line, direction): print 'line:', line, 'current line:', self.current_line for dx, stations in self.current_stations: print 'dx:', dx, 'direction:', direction if dx == direction: return [stationname for stationname, url in stations] return ['no stations found'] directionsLoaded = Signal() @Slot(str) def load_directions(self, line): def load_async(): stations = sorted(self.itip.get_stations(line).items()) self.current_line = line self.current_stations = stations self.directionsLoaded.emit() threading.Thread(target=load_async).start() #def map_departure(self, dep): # """ prepare departure list for qml gui # """ # dep['lowfloor'] = 1 if dep['lowfloor'] else 0 # dep['realtime'] = 1 if dep['realtime'] else 0 # dep['time'] = dep['ftime'] # return dep departuresLoaded = Signal() @Slot(str) def load_departures_test(self, **args): """ valid args combinations station line, station """ def load_async(): if args.has_key('station'): if args.has_key('line'): self.current_departures = map(self.map_departure, \ self.itip.get_departures(args['line'], args['station'])) #print self.current_departures self.departuresLoaded.emit() else: self.current_departures = map(self.map_departure, \ sort_departures(self.itip.get_departures_by_station(station))) else: raise KeyError('Missing valid argument combination') threading.Thread(target=load_async).start() @Slot(str) def load_departures(self, url): def load_async(): self.departureModel.setDepartures(self.itip.get_departures(url)) #print self.current_departures self.departuresLoaded.emit() threading.Thread(target=load_async).start() @Slot(str) def load_station_departures(self, station): def load_async(): self.departureModel.setDepartures(sort_departures(self.itip.get_departures_by_station(station))) self.departuresLoaded.emit() threading.Thread(target=load_async).start() @Slot(float, float) def load_nearby_departures(self, lat, lon): def load_async(): self.current_departures = [] try: stations = get_nearby_stations(lat, lon) print stations for station in stations: print station try: self.current_departures += self.itip.get_departures_by_station(station) except Exception as e: print e.message self.current_departures = map(self.map_departure, \ sort_departures(self.current_departures)) #print self.current_departures except Exception as e: print e.message print 'loaded' self.departuresLoaded.emit() threading.Thread(target=load_async).start() @Slot(str, str, str, result=str) def get_directions_url(self, line, direction, station): return self.itip.get_url_from_direction(line, direction, station) @Slot(result='QStringList') def get_lines(self): return self.lines @Slot(float, float, result='QStringList') def get_nearby_stations(self, lat, lon): try: return get_nearby_stations(lat, lon) except BaseException as e: # No/wrong stations.db file return [] if __name__ == '__main__': app = QApplication(sys.argv) view = QDeclarativeView() aboutInfo = AboutInfo() config = Config() departureModel = DepartureModel() # instantiate the Python object itip = Gui(departureModel) favManager = FavoriteManager() # expose the object to QML context = view.rootContext() context.setContextProperty('itip', itip) context.setContextProperty('aboutInfo', aboutInfo) context.setContextProperty('config', config) context.setContextProperty('resultModel', departureModel) context.setContextProperty('favManager', favManager) if os.path.abspath(__file__).startswith('/usr/bin/'): # Assume system-wide installation, QML from /usr/share/ view.setSource('/usr/share/gotovienna/qml/main.qml') else: # Assume test from source directory, use relative path view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml')) view.showFullScreen() sys.exit(app.exec_())