# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

# GUITRACKER
# Experimental software to record and playback QT events.
#
import time
import re
import os
from qt import *

from coral import log
logger=log.getLogger("guitracker")

class GuiTracker(QApplication):
    def __init__(self, args, logfile, track):
        QApplication.__init__(self, args)
        if track:
            logger.info("Starting Coral GUI recorder")
        else:
            logger.info("Starting Coral GUI playback")
        #
        # Events we know are useless
        #
        self.ignore_events = {
            QEvent.WindowActivate: 1,
            QEvent.WindowDeactivate: 1,
            QEvent.Enter: 1,
            QEvent.Leave: 1,
            QEvent.Paint: 1,
            QEvent.Timer: 1,
            QEvent.SockAct: 1,
            }
        #
        # Events we recognize
        #
        self.interesting_events = {
            QEvent.MouseButtonPress: 1,
            QEvent.MouseButtonRelease: 1,
            QEvent.MouseButtonDblClick: 1,
            QEvent.MouseMove: 1,
            QEvent.KeyPress: 1,
            QEvent.KeyRelease: 1,
            QEvent.FocusIn: 1,
            QEvent.FocusOut: 1,
            }

        self.track = track
        self.start_time = time.time()
        if self.track:
            # Track events --- write log file
            self.file = file(logfile, "w")
        else:
            # Playback events --- read log file
            self.file = file(logfile, "r")
            self.latest_event = 0.0
            self.__getNextEvent()

    def __parseNames(self, names):
        names = names[1:-1] # strip [ and ]
        pairs = []
        while 1:
            pair = re.match("(,\s+)?(\('([^']+)', '([^']+)'\))", names)
            if pair:
                #print "Name", pair.group(3), "type", pair.group(4)
                pairs.append( (pair.group(3), pair.group(4)) )
                end = pair.end()
                if end >= len(names):
                    break
                names = names[end:]
            else:
                break
        #print "pairs: ", pairs
        pairs.reverse()
        all_widgets = self.allWidgets()
        found_widgets = []
        for orig in all_widgets:
            w = orig
            found = 0
            i = 0
            #print orig.name(), str(orig.__class__)
            while 1:
                if i >= len(pairs):
                    break
                if str(w.name()) != pairs[i][0]:
                    break
                if str(w.__class__) != pairs[i][1]:
                    break
                w = w.parent()
                i += 1
                if w is None:
                    found = 1
            if found:
                found_widgets.append(orig)
        if len(found_widgets) > 1:
            pairs.reverse()
            logger.debug("Ambiguous widgets"+unicode(found_widgets))
            logger.debug("Name: "+unicode(pairs))
            return found_widgets[0]
        elif len(found_widgets) == 1:
            w = found_widgets[0]
            return w
        else:
            return self
        return None
                
    def __getNextEvent(self):
        """This function is called to get the next event from the logfile.
        It will tell QT to call self.insert_new_event after a certain
        time period. It sets the arguments self.receiver and self.event."""
        assert not self.track
        l = self.file.readline()
        if not l:
            # No more events, let's just quit.
            # But let the current event from __insert_new_event
            # be handled in, say, 2 seconds.
            QTimer.singleShot(1000 * 2, self.quit)
            return
        m = re.search("Notify\s+event\s+([0-9]+)\s+time\s+(.*)\s+names\s+(.*)\s+", l)
        assert m

        names = m.group(3)
        t = float(m.group(2))
        etype = int(m.group(1))
        s = t
        t -= self.latest_event
        self.latest_event = s
        self.object = self.__parseNames(names)
        self.event = None
        #
        # Parse the event
        #
        #
        if etype in [ QEvent.MouseMove, QEvent.MouseButtonPress, QEvent.MouseButtonRelease, QEvent.MouseButtonDblClick ]:
            l = self.file.readline()
            assert l
            m = re.match("\s+pos (.*) (.*) globalpos (.*) (.*) buttons (.*) state (.*)", l)
            assert m
            x = int(m.group(1))
            y = int(m.group(2))
            gx = int(m.group(3))
            gy = int(m.group(4))
            buttons = int(m.group(5))
            state = int(m.group(6))
            self.event = QMouseEvent(etype, QPoint(x, y), QPoint(gx, gy), buttons, state)

        elif etype in [QEvent.FocusIn, QEvent.FocusOut ]:
            self.event = QFocusEvent(etype)
        elif etype in [QEvent.KeyPress, QEvent.KeyRelease ]:
            l = self.file.readline()
            assert l
            m = re.match("\s+key (.*) ascii (.*) state (.*) str (.*) autorep (.*) count (.*)", l)
            assert m
            key = int(m.group(1))
            ascii = int(m.group(2))
            state = int(m.group(3))
            s = m.group(4)
            autorep = int(m.group(5))
            count = int(m.group(6))
            self.event = QKeyEvent(etype, key, ascii, state, s, autorep, count)
        else:
            logger.debug("Didn't recognize"+unicode(etype))


        if t < 0:
            t = 0.001
        # Trigger it at the correct time, with a factor
        # due to the asynchronous nature of X
        QTimer.singleShot(t * 1000 * 2, self.__insert_new_event)

    def __insert_new_event(self):
        logger.info("__insert_new_event")
        if self.object and self.event:
            QApplication.postEvent(self.object, self.event)
        self.__getNextEvent()



    

    def notify(self, receiver, event):
        """This function is called every time an event is created.
        We're interested in collecting them for tracking purposes.
        The function saves the events into a logfile."""
        logger.info("notify "+unicode(event.type()))
            
        if not self.track:
            return QApplication.notify(self, receiver, event)
        etype = event.type()
        #print "Type", etype
        if not self.interesting_events.has_key(etype):
            if not self.ignore_events.has_key(etype):
                logger.debug("Ignoring"+unicode(etype))
                pass
            return QApplication.notify(self, receiver, event)

        names = []
        o = receiver
        while o:
            oname = o.name()
            oclass = str(o.__class__)
            names.append( (oname, oclass) )
            o = o.parent()
        names.reverse()

        now = time.time() - self.start_time

        txt = "Notify event %d time %f names %s" % (etype, now, names)
        if etype in [ QEvent.MouseMove, QEvent.MouseButtonPress, QEvent.MouseButtonRelease, QEvent.MouseButtonDblClick ]:
            self.file.write("%s\n" % txt)
            self.file.write("  pos %d %d globalpos %d %d buttons %d state %d\n" % ( event.x(), event.y(), event.globalX(), event.globalY(), event.button(), event.state() ) )

        elif etype in [ QEvent.FocusIn, QEvent.FocusOut ]:
            self.file.write("%s\n" % txt)

        elif etype in [ QEvent.KeyPress, QEvent.KeyRelease ]:
            self.file.write("%s\n" % txt)
            self.file.write("  key %d ascii %d state %d str %s autorep %d count %d\n" % (event.key(), event.ascii(), event.state(), event.text(), event.isAutoRepeat(), event.count() ) )
            
        return QApplication.notify(self, receiver, event)



