tuikit/application.py
author Radek Brich <radek.brich@devl.cz>
Sun, 27 Jan 2013 23:51:59 +0100
changeset 70 db2eab0beb45
parent 69 4e7be77bafff
child 74 23767a33a781
permissions -rw-r--r--
Update drivers: Rename setcolor to defcolor, add real setcolor which ignores color stack. Application: Add overridable startup() method. Fix Widget draw clipping.

# -*- coding: utf-8 -*-

import logging
import time

from tuikit.layout import AnchorLayout


class Timer:
    def __init__(self):
        self.timeouts = []
        self.timelast = None

    def add_timeout(self, delay, callback):
        """Register callback to be called after delay seconds.

        delay -- in seconds, float
        callback -- function to be called with no parameters

        """
        if not len(self.timeouts):
            self.timelast = time.time()
        self.timeouts += [[delay, callback]]

    def remove_timeout(self, callback):
        """Unregister callback previously registered with add_timeout."""
        for timeout in self.timeouts[:]:
            if timeout[1] == callback:
                self.timeouts.remove(timeout)

    def has_timeout(self):
        return len(self.timeouts) > 0

    def nearest_timeout(self):
        if not self.has_timeout():
            return None
        return min(self.timeouts)[0]

    def process_timeouts(self):
        if not self.has_timeout():
            return
        now = time.time()
        lasted = now - self.timelast
        self.timelast = now
        for timeout in self.timeouts[:]:
            adjusted_delay = timeout[0] - lasted
            if adjusted_delay <= 0.0:
                timeout[1]()
                self.timeouts.remove(timeout)
            else:
                timeout[0] = adjusted_delay


class Application:

    '''Application class. Defines main loop.'''

    def __init__(self, top_layout=AnchorLayout, driver='curses'):
        '''Create application.'''
        self._setup_logging()

        # Top widget
        self._top = None
        self._timer = Timer()
        self.top = top_layout()

        self.quit = False

        #: Driver class instance (render + input), e.g. DriverCurses.
        self.driver = self.get_driver_instance(driver)

    def _setup_logging(self):
        self.log = logging.getLogger('tuikit')
        self.log.setLevel(logging.DEBUG)
        handler = logging.FileHandler('./tuikit.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)-5s %(message)s', '%y-%m-%d %H:%M:%S')
        handler.setFormatter(formatter)
        self.log.addHandler(handler)

    @property
    def top(self):
        return self._top

    @top.setter
    def top(self, value):
        self._top = value
        self._top.top = value
        self._top.timer = self._timer
        self._top.add_handler('draw', self._on_top_draw)

    def _on_top_draw(self, ev):
        ev.driver.erase()

    def start(self):
        '''Start application. Runs main loop.'''
        self.log.info('=== start ===')
        self.driver.start(self.main_loop)

    def terminate(self):
        '''Terminate application.'''
        self.quit = True

    def main_loop(self):
        '''The main loop.'''
        self.startup()
        self._top._size = self.driver.size  # link top widget size to screen size
        self._top.emit('resize')
        timer = self._timer

        while True:
            self._top.draw(self.driver, 0, 0)
            self.driver.commit()

            timeout = timer.nearest_timeout()
            events = self.driver.getevents(timeout)
            timer.process_timeouts()

            for event in events:
                if event[0] == 'quit':
                    self.quit = True
                else:
                    self._top.emit(event[0], *event[1:])

            if self.quit:
                break
        self.log.info('=== quit ===')

    def startup(self):
        """This is called after startup, before entering event loop."""
        self.apply_theme()

    def apply_theme(self):
        #TODO: allow custom colors:
        #    e.g. "blue (#2020FF) on black (#101010), underline"
        #    color in brackets is used when driver supports custom colors
        drv = self.driver
        drv.defcolor('normal',                  'lightgray on black')
        drv.defcolor('strong',                  'white on black, bold')
        drv.defcolor('active',                  'black on cyan')
        drv.defcolor('window:normal',           'lightgray on blue')
        drv.defcolor('window:controls',         'white on blue, bold')
        drv.defcolor('window:controls-active',  'cyan on blue, bold')
        drv.defcolor('button',                  'black on white')
        drv.defcolor('button-active',           'black on cyan')
        drv.defcolor('menu',                    'black on cyan')
        drv.defcolor('menu-active',             'white on cyan, bold')
        drv.defcolor('combo:normal',            'black on green')


    def get_driver_instance(self, name):
        module = __import__('tuikit.driver_' + name, fromlist=['driverclass'])
        return module.driverclass()