-#!/usr/env/python
+#!/usr/bin/env python
"""Public transport information for Vienna"""
__author__ = 'kelvan <kelvan@logic.at>'
-__version__ = '1.0'
-__website__ = 'https://github.com/kelvan/gotoVienna/'
+__version__ = '0.9.1'
+__website__ = 'http://tinyurl.com/gotoVienna'
__license__ = 'GNU General Public License v3 or later'
-from PySide.QtCore import *
-from PySide.QtGui import *
-from PySide.QtDeclarative import *
+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__
-class GotoViennaListModel(QAbstractListModel):
- def __init__(self, objects=None):
- QAbstractListModel.__init__(self)
- if objects is None:
- objects = []
- self._objects = objects
- self.setRoleNames({0: 'modelData'})
+ @Slot(result=unicode)
+ def getWebsiteURL(self):
+ return __website__
- def set_objects(self, objects):
- self._objects = objects
+ @Slot(result=unicode)
+ def getCopyright(self):
+ return 'Copyright 2011, 2012 %s' % __author__
- def get_objects(self):
- return self._objects
+ @Slot(result=unicode)
+ def getLicense(self):
+ return __license__
- def get_object(self, index):
- return self._objects[index.row()]
+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 rowCount(self, parent=QModelIndex()):
- return len(self._objects)
+ 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 index.isValid():
- if role == 0:
- return self.get_object(index)
- return None
+ 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 Gui(QObject):
+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(str, str)
- def search(self, line, station):
- line = line.upper()
- station = station.decode('utf-8')
- print line, station
-
- if line not in self.lines:
- return "Invalid line"
-
+ @Slot(float, float, result='QStringList')
+ def get_nearby_stations(self, lat, lon):
try:
- stations = sorted(self.itip.get_stations(line).items())
- print stations
- headers, stations = zip(*stations)
- print headers
- print stations
- details = [(direction, name, url) for direction, stops in stations
- for name, url in stops if match_station(station, name)]
- print details
- except urllib2.URLError as e:
- print e.message
- return e.message
+ 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()
+ 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/