fixing routing.py bug, if time is 24:00
[pywienerlinien] / gotovienna-qml
index 57cccb6..4393d0e 100755 (executable)
-#!/usr/env/python
+#!/usr/bin/env python
 
 """Public transport information for Vienna"""
 
 __author__ = 'kelvan <kelvan@logic.at>'
-__version__ = '0.8.1'
-__website__ = 'https://github.com/kelvan/gotoVienna/'
+__version__ = '0.9.1'
+__website__ = 'http://tinyurl.com/gotoVienna'
 __license__ = 'GNU General Public License v3 or later'
 
 from datetime import datetime
 
-t = datetime.now()
-
-from PySide.QtCore import QAbstractListModel, QModelIndex, QObject, Slot, Signal
-from PySide.QtGui import QApplication
+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 GotoViennaListModel(QAbstractListModel):
-    def __init__(self, objects=None):
-        QAbstractListModel.__init__(self)
-        if objects is None:
-            objects = []
-        self._objects = objects
-        self.setRoleNames({0: 'modelData'})
+class Config(QObject):
+    def __init__(self):
+        QObject.__init__(self)
 
-    def set_objects(self, objects):
-        self._objects = objects
+    @Slot(result=bool)
+    def getGpsEnabled(self):
+        return conf.getGpsEnabled()
 
-    def get_objects(self):
-        return self._objects
+    @Slot(bool, result=unicode)
+    def setGpsEnabled(self, val):
+        # TODO
+        return conf.setGpsEnabled(val)
 
-    def get_object(self, index):
-        return self._objects[index.row()]
+    @Slot(result=unicode)
+    def getLastUpdate(self):
+        # TODO
+        return conf.getLastStationsUpdate()
 
-    def rowCount(self, parent=QModelIndex()):
-        return len(self._objects)
+    @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
 
-    def data(self, index, role):
-        if index.isValid():
-            if role == 0:
-                return self.get_object(index)
-        return None
+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)
 
-class Gui(QObject):
+    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):
@@ -92,14 +219,74 @@ class Gui(QObject):
 
         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.current_departures = [x['ftime'] for x in
-                    self.itip.get_departures(url)]
-            print self.current_departures
+            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()
@@ -112,43 +299,36 @@ class Gui(QObject):
     def get_lines(self):
         return self.lines
 
-    @Slot(result='QStringList')
-    def get_departures(self):
-        return self.current_departures
-
-    @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/
@@ -157,8 +337,6 @@ if __name__ == '__main__':
         # Assume test from source directory, use relative path
         view.setSource(os.path.join(os.path.dirname(__file__), 'qml/main.qml'))
 
-    print datetime.now() - t
-
     view.showFullScreen()
 
     sys.exit(app.exec_())