Page 1 of 1

Python port of Falagard demo (console window with history)

Posted: Sat Feb 12, 2011 18:28
by Leftium
I have completed a Python port of the Falagard demo, a basic console window with history. The added ease of rapid development with PyCEGUI is more than worth the minor 10% performance hit! It is nearly identical to the native version, with some enhancements:
  • Fixed keyboard input so it behaves as expected. (UP/DOWN arrow keys work when edit box is active; F12 always toggles the console regardless of input focus).
  • KeyDown + KeyUp events are always injected in addition to Char events whenever possible.
  • Added some utility functions to easily inspect PyCEGUI objects and conditionally log to console.
  • Some other minor bugs fixed.
Coming soon:
  • Immediate mode Python from (PyCEGUI) console, so PyCEGUI functions can be tested interactively
  • Allegro5 + D3D/OpenGL renderer
About the script:
  • Based on C++ source code of native samples. I tried to maintain the same variable names and comments for easy diff comparisons.
  • Released as one large file for convenience, but logically broken into 4 or 5 files (one file per class + utilities).
  • It is simple to start creating your own PyCEGUI apps in no time by subclassing one of the base classes!
  • Currently only works with Python 2.6 due to an issue with the PyCEGUI bindings.
The code:

Code: Select all

# based on sample from http://www.cegui.org.uk/wiki/index.php/PyCEGUI
import os, sys
import new

# you must have PyCEGUI python package installed (see pypi repository)
# or you must make it work yourself using binary bindings from SDK
from PyCEGUI import *

class PyCeguiBaseApplication(object):
    def __init__(self):
        # TODO: figure out smart default
        self.CEGUI_SAMPLE_DATAPATH = 'C:/Python26/Lib/site-packages/PyCEGUI'
        self.DATAPATH_VAR_NAME = 'CEGUI_SAMPLE_DATAPATH'

    def getDataPathPrefix(self):
        # set data path prefix / base directory.  This will
        # be either from an environment variable, or based on
        # the PyCEGUI library path
        if self.DATAPATH_VAR_NAME in os.environ:
            return os.environ[self.DATAPATH_VAR_NAME]
        else:
            return self.CEGUI_SAMPLE_DATAPATH

    def initialiseResourceGroupDirectories(self):
        # initialise the required dirs for the DefaultResourceProvider
        rp = System.getSingleton().getResourceProvider()

        dataPathPrefix = self.getDataPathPrefix()

        # for each resource type, set a resource group directory
        rp.setResourceGroupDirectory('schemes'
                ,dataPathPrefix + '/datafiles/schemes')
        rp.setResourceGroupDirectory('imagesets'
                ,dataPathPrefix + '/datafiles/imagesets')
        rp.setResourceGroupDirectory('fonts'
                ,dataPathPrefix + '/datafiles/fonts')
        rp.setResourceGroupDirectory('layouts'
                ,dataPathPrefix + '/datafiles/layouts')
        rp.setResourceGroupDirectory('looknfeels'
                ,dataPathPrefix + '/datafiles/looknfeel')
        rp.setResourceGroupDirectory('schemas'
                ,dataPathPrefix + '/datafiles/xml_schemas')
        rp.setResourceGroupDirectory('animations'
                ,dataPathPrefix + '/datafiles/animations')

    def initialiseDefaultResourceGroups(self):
        # set the default resource groups to be used
        Imageset.setDefaultResourceGroup('imagesets')
        Font.setDefaultResourceGroup('fonts')
        Scheme.setDefaultResourceGroup('schemes')
        WidgetLookManager.setDefaultResourceGroup('looknfeels')
        WindowManager.setDefaultResourceGroup('layouts')
        AnimationManager.setDefaultResourceGroup('animations');

        # setup default group for validation schemas
        parser = System.getSingleton().getXMLParser()
        if parser.isPropertyPresent('SchemaDefaultResourceGroup'):
            parser.setProperty('SchemaDefaultResourceGroup', 'schemas')


import string

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import PyCEGUIOpenGLRenderer
class PyCeguiGlutBaseApplication(PyCeguiBaseApplication):
    specialKeyMap = {
            GLUT_KEY_F1: Key.F1
           ,GLUT_KEY_F2: Key.F2
           ,GLUT_KEY_F3: Key.F3
           ,GLUT_KEY_F4: Key.F4
           ,GLUT_KEY_F5: Key.F5
           ,GLUT_KEY_F6: Key.F6
           ,GLUT_KEY_F7: Key.F7
           ,GLUT_KEY_F8: Key.F8
           ,GLUT_KEY_F9: Key.F9
           ,GLUT_KEY_F10: Key.F10
           ,GLUT_KEY_F11: Key.F11
           ,GLUT_KEY_F12: Key.F12
           ,GLUT_KEY_LEFT: Key.ArrowLeft
           ,GLUT_KEY_UP: Key.ArrowUp
           ,GLUT_KEY_RIGHT: Key.ArrowRight
           ,GLUT_KEY_DOWN: Key.ArrowDown
           ,GLUT_KEY_PAGE_UP: Key.PageUp
           ,GLUT_KEY_PAGE_DOWN: Key.PageDown
           ,GLUT_KEY_HOME: Key.Home
           ,GLUT_KEY_END: Key.End
           ,GLUT_KEY_INSERT: Key.Insert}

    def init_static_data(self):
        self.quitFlag = False
        self.lastFrameTime = 0
        self.glut_modifiers = dict(
                shift=dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_SHIFT
                          ,scancode=Key.LeftShift)
               ,ctrl =dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_CTRL
                          ,scancode=Key.LeftControl)
               ,alt  =dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_ALT
                          ,scancode=Key.LeftAlt))
        self.fps_lastTime = 0
        self.fps_frames = 0
        self.fps_textbuff = ''
        self.logo_geometry = None

    def __init__(self, x=0, y=0, width=800, height=600
            ,title='PyCEGUI GLUT Base Application'):
        PyCeguiBaseApplication.__init__(self)

        self.init_static_data()
        self.root = None
        self.rot = 0

        # Do GLUT init
        glutInit(sys.argv)
        glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA)
        glutInitWindowSize(width, height)
        glutInitWindowPosition(x, y)
        glutCreateWindow(title)
        glutSetCursor(GLUT_CURSOR_NONE)

        self.renderer = PyCEGUIOpenGLRenderer.OpenGLRenderer.bootstrapSystem()

        glutDisplayFunc(self.drawFrame)
        glutReshapeFunc(self.reshape)

        glutMotionFunc(self.mouseMotion)
        glutPassiveMotionFunc(self.mouseMotion)
        glutMouseFunc(self.mouseButton)

        glutKeyboardFunc(self.keyChar)
        glutSpecialFunc(self.keySpecial)

        glutKeyboardUpFunc(self.keyCharUp)
        glutSpecialUpFunc(self.keySpecialUp)

        # Set the clear color
        glClearColor(0.0, 0.0, 0.0, 1.0)

        self.initialiseResourceGroupDirectories()
        self.initialiseDefaultResourceGroups()

        # setup required to do direct rendering of FPS value
        scrn = Rect(Vector2(0,0), self.renderer.getDisplaySize())
        self.fps_geometry = self.renderer.createGeometryBuffer()
        self.fps_geometry.setClippingRegion(scrn)

        # setup for logo
        ImagesetManager.getSingleton().createFromImageFile(
                'cegui_logo', 'logo.png', 'imagesets')
        self.logo_geometry = self.renderer.createGeometryBuffer()
        self.logo_geometry.setClippingRegion(scrn)
        self.logo_geometry.setPivot(Vector3(50, 34.75, 0))
        self.logo_geometry.setTranslation(Vector3(10, 520, 0))
        ImagesetManager.getSingleton().get('cegui_logo').getImage(
             'full_image').draw(self.logo_geometry ,Rect(0,0, 100, 69.5) ,None)

        # clearing this queue actually makes sure it's created(!)
        self.renderer.getDefaultRenderingRoot().clearGeometry(RQ_OVERLAY)

        # subscribe handler to render overlay items
        self.renderer.getDefaultRenderingRoot().subscribeEvent(
              RenderingSurface.EventRenderQueueStarted, self, 'overlayHandler')

    def __del__(self):
        PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem()

    def overlayHandler(self, args):
        # queueID attribute missing in CEGUI Python bindings
        # if not args.queueID == RQ_OVERLAY:
        #     return False

        # render FPS:
        fnt = System.getSingleton().getDefaultFont()
        if fnt:
            self.fps_geometry.reset()
            fnt.drawText(self.fps_geometry, self.fps_textbuff, Vector2(0, 0)
                    ,None, colour(0xFFFFFFFF))
            self.fps_geometry.draw()
        self.logo_geometry.draw()
        return True

    def execute(self, sampleApp):
        sampleApp.initialiseSample()

        # set starting time
        self.fps_lastTime = self.lastFrameTime = glutGet(GLUT_ELAPSED_TIME)

        glutMainLoop()

        return True

    def drawFrame(self):
        guiSystem = System.getSingleton()
        # do time based updates
        thisTime = glutGet(GLUT_ELAPSED_TIME)
        elapsed = thisTime - self.lastFrameTime
        self.lastFrameTime = thisTime
        # inject the time pulse
        guiSystem.injectTimePulse(elapsed / 1000.0)
        # update fps fields
        self.doFPSUpdate()

        # update logo rotation
        self.logo_geometry.setRotation(Vector3(self.rot, 0, 0))
        self.rot += (180.0 * elapsed) / 1000.0
        if self.rot > 360.0:
            self.rot -= 360.0

        # do rendering for this frame.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        guiSystem.renderGUI()
        glutPostRedisplay()
        glutSwapBuffers()

        # here we check the 'quitting' state and cleanup as required.
        # this is probably not the best way to do this, but since we're
        # using glut, and glutMainLoop can never return, we need some
        # way of checking when to exit.  And this is it...
        if self.quitFlag:
            PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem()
            exit(0)

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(60.0, float(w)/h, 1.0, 50.0)
        glMatrixMode(GL_MODELVIEW)
        System.getSingleton().notifyDisplaySizeChanged(Size(w, h))

    def mouseMotion(self, x, y):
        System.getSingleton().injectMousePosition(x, y)

    def mouseButton(self, button, state, x, y):
        if button == GLUT_LEFT_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(LeftButton)
            else:
                System.getSingleton().injectMouseButtonDown(LeftButton)

        elif button == GLUT_RIGHT_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(RightButton)
            else:
                System.getSingleton().injectMouseButtonDown(RightButton)

        elif button == GLUT_MIDDLE_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(MiddleButton)
            else:
                System.getSingleton().injectMouseButtonDown(MiddleButton)

    def keyChar(self, key, x, y):
        key = key.encode('ascii', 'ignore')
        self.handleModifierKeys()

        scancode = self.ascii_to_scancode(key)
        if scancode:
            custom_args = KeyEventArgs(None)
            custom_args.scancode = scancode
            logi('BEFORE keyChar.fireEvent: %s' %
                    pretty_attr(custom_args ,'custom_args'))
            System.getSingleton().fireEvent('AppKeyDown', custom_args)
            logc('AFTER  keyChar.fireEvent: %s \n' %
                    pretty_attr(custom_args ,'custom_args'))

            log('INJECT KEYDN: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.down')
            result = System.getSingleton().injectKeyDown(int(scancode))
            log('RESULT injectKeyDown: %s' % result, 'result')

        log('INJECT CHAR: [%3d]' % ord(key), 'inject.char')
        result = System.getSingleton().injectChar(ord(key))
        log('RESULT injectChar: %s' % result, 'result')
        if scancode == Key.Escape:
            self.quitFlag = True

        return False

    def keyCharUp(self, key, x, y):
        key = key.encode('ascii', 'ignore')
        self.handleModifierKeys()

        scancode = self.ascii_to_scancode(key)
        if scancode:
            log('INJECT KEYUP: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.up')
            System.getSingleton().injectKeyUp(int(scancode))
        return False

    def keySpecial(self, key, x, y):
        self.handleModifierKeys()
        scancode = self.special_to_scancode(key)
        if scancode:
            custom_args = KeyEventArgs(None)
            custom_args.scancode = scancode
            logi('BEFORE keySpecial.fireevent: %s' %
                    pretty_attr(custom_args ,'custom_args'))

            GlobalEventSet.getSingleton().fireEvent(
                    'AppKeyDown', custom_args)

            logc('AFTER  keySpecial.fireevent: %s \n' %
                    pretty_attr(custom_args ,'custom_args'))

            log('INJECT KEYDN: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.down')
            result = System.getSingleton().injectKeyDown(int(scancode))
            log('RESULT injectKeyDown: %s' % result, 'result')
        return False

    def keySpecialUp(self, key, x, y):
        self.handleModifierKeys()
        scancode = self.special_to_scancode(key)
        if scancode:
            log('INJECT KEYUP: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.up')
            System.getSingleton().injectKeyUp(int(scancode))
        return False

    def handleModifierKeys(self):
        status = glutGetModifiers()

        for name, key in self.glut_modifiers.items():
            if (status & key['bit_flag']):
                if not key['is_held']:
                    key['is_held'] = True
                    log('INJECT MODDN: %s' % key['scancode'], 'inject.key.down')
                    System.getSingleton().injectKeyDown(key['scancode'])
            else:
                if key['is_held']:
                    key['is_held'] = False
                    log('INJECT MODUP: %s' % key['scancode'], 'inject.key.up')
                    System.getSingleton().injectKeyUp(key['scancode'])

    def doFPSUpdate(self):
        # another frame
        self.fps_frames += 1

        # has at least a second passed since we last updated the text?
        if (self.lastFrameTime - self.fps_lastTime >= 1000):
            # update FPS text to output
            self.fps_textbuff = 'FPS: %d' % self.fps_frames
            self.fps_frames = 0
            self.fps_lastTime = self.lastFrameTime

    mapping = dict([(ord(c), getattr(Key, c.upper()))
                   for c in string.ascii_letters])
    mapping.update({
                 96: Key.Grave,        126: Key.Grave,
                 49: Key.One,           33: Key.One,
                 50: Key.Two,           64: Key.At,
                 51: Key.Three,         35: Key.Three,
                 52: Key.Four,          36: Key.Four,
                 53: Key.Five,          37: Key.Five,
                 54: Key.Six,           94: Key.Six,
                 55: Key.Seven,         38: Key.Seven,
                 56: Key.Eight,         42: Key.Multiply,
                 57: Key.Nine,          40: Key.Nine,
                 48: Key.Zero,          41: Key.Zero,
                 45: Key.Minus,         95: Key.Underline,
                 61: Key.Equals,        43: Key.Equals,
                 91: Key.LeftBracket,  123: Key.LeftBracket,
                 93: Key.RightBracket, 125: Key.RightBracket,
                 59: Key.Semicolon,     58: Key.Colon,
                 39: Key.Apostrophe,    34: Key.Apostrophe,
                 92: Key.Backslash,    124: Key.Backslash,
                 44: Key.Comma,         60: Key.Comma,
                 46: Key.Period,        62: Key.Period,
                 47: Key.Slash,         63: Key.Slash,
                 13: Key.Return,
                  8: Key.Backspace,
                  9: Key.Tab,
                 32: Key.Space,
                127: Key.Delete,
                 27: Key.Escape})

    def ascii_to_scancode(self, a):
        a = ord(a)
        return self.mapping[a] if (a in self.mapping) else 0

    def special_to_scancode(self, c):
        return self.specialKeyMap[c] if (c in self.specialKeyMap) else 0

    def modifier_keys_state(self):
        s = ''
        if self.glut_modifiers['ctrl']['is_held']:
            s += 'CTRL '
        if self.glut_modifiers['alt']['is_held']:
            s += 'ALT '
        if self.glut_modifiers['shift']['is_held']:
            s += 'SHIFT '
        return s



class PyCeguiSample(object):
    def run(self):
        if self.initialise:
            self.initialise()
            self.cleanup()
        return 0

    def initialise(self):
        self.sampleApp = PyCeguiGlutBaseApplication(x=0, y=0
                , width=800, height=600 ,title='PyCEGUI Falagard Demo')

        # execute the base application (which sets
        # up the demo via 'self' and runs it.
        if self.sampleApp.execute(self):
            # signal that app initialised and ran
            return True

        self.sampleApp = None

        # signal app did not initialise and run.
        return False

    def cleanup(self):
        if self.sampleApp:
            self.sampleApp = None



class FalagardDemo1Sample(PyCeguiSample):
    def __init__(self):
        self.console = None

    def initialiseSample(self):
        # Get window manager which we wil use for a few jobs here.
        winMgr = WindowManager.getSingleton()
        # Load the scheme to initialse the VanillaSkin which we use
        # in this sample
        SchemeManager.getSingleton().create('VanillaSkin.scheme')
        # set default mouse image
        System.getSingleton().setDefaultMouseCursor('Vanilla-Images'
                ,'MouseArrow')

        # load an image to use as a background
        ImagesetManager.getSingleton().createFromImageFile('BackgroundImage'
                ,'GPN-2000-001437.tga')

        # here we will use a StaticImage as the root,
        # then we can use it to place a background image
        background = winMgr.createWindow('Vanilla/StaticImage')

        # set area rectangle
        background.setArea(URect(cegui_reldim(0) ,cegui_reldim(0)
                                ,cegui_reldim(1) ,cegui_reldim(1)))

        # disable frame and standard background
        background.setProperty('FrameEnabled', 'false')
        background.setProperty('BackgroundEnabled', 'false')
        #set the background image
        background.setProperty('Image', 'set:BackgroundImage image:full_image')

        # install this as the root GUI sheet
        System.getSingleton().setGUISheet(background)

        FontManager.getSingleton().create('DejaVuSans-10.font')

        # load some demo windows and attach to the background 'root'
        background.addChildWindow(
                winMgr.loadWindowLayout('VanillaWindows.layout'))

        # create an instance of the console class.
        self.console = DemoConsole('Demo')

        # listen for key presses on the root window.
        GlobalEventSet.getSingleton().subscribeEvent('/AppKeyDown'
                ,self, 'handleRootKeydown')

        # activate the background window
        background.activate()

        # success!
        return True

    def cleanupSample(self):
        self.console = None

    def handleRootKeydown(self, keyArgs):
        if keyArgs.scancode == Key.F12:
            self.console.toggleVisibility()
            logi('BEFORE handleRootKeydown: %s' %
                  pretty_attr(keyArgs, 'WindowEventArgs'))
            keyArgs.handled += 1
            logc('AFTER  handleRootKeydown: %s' %
                  pretty_attr(keyArgs, 'WindowEventArgs'))
            return True
        return False



class DemoConsole(object):
    def __init__(self, name, parent=None):
        # these must match the IDs assigned in the layout
        self.submitButtonID = 1;
        self.entryBoxID     = 2;
        self.historyID      = 3;

        window_manager = WindowManager.getSingleton()
        self.console_root = window_manager.loadWindowLayout(
                'VanillaConsole.layout', name)
        self.history_pos = 0
        self.history = []



        # we will destroy the console box windows ourselves
        self.console_root.setDestroyedByParent(False)

        # Do events wire-up
        GlobalEventSet.getSingleton().subscribeEvent('/AppKeyDown',
                self, 'handleKeydown')
        self.console_root.getChild(self.submitButtonID).subscribeEvent(
                PushButton.EventClicked, self, 'handleSubmit')
        self.console_root.getChild(self.entryBoxID).subscribeEvent(
                Editbox.EventTextAccepted, self, 'handleSubmit')

        # decide where to attach the console main window
        parent = parent or System.getSingleton().getGUISheet()

        # attach this window if parent is valid
        if parent:
            parent.addChildWindow(self.console_root)

    def __del__(self):
        # destroy the windows that we loaded earlier
        WindowManager.getSingleton().destroyWindow(self.console_root)

    def toggleVisibility(self):
        self.console_root.hide() if self.console_root.isVisible(True
                ) else self.console_root.show()

    def handleSubmit(self, WindowEventArgs):
        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)
        # get text out of the editbox
        edit_text = editbox.getText()
        # if the string is not empty
        if edit_text:
            # add this entry to the command history buffer
            self.history.append(edit_text)
            # reset history position
            self.history_pos = len(self.history)
            # append newline to this entry
            edit_text += '\n'
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)
            # erase text in text entry box.
            editbox.setText('')

        # re-activate the text entry box
        editbox.activate()
        return True

    def handleKeydown(self, keyEventArgs):
        # if no input focus, ignore events
        if not self.console_root.getActiveChild():
            return False

        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)

        if keyEventArgs.scancode == Key.ArrowUp:
            self.history_pos = max(self.history_pos - 1, -1)
            if self.history_pos >= 0:
                editbox.setText(self.history[self.history_pos])
                editbox.setCaratIndex(sys.maxint)
            else:
                editbox.setText('')
            editbox.activate()

        elif keyEventArgs.scancode == Key.ArrowDown:
            self.history_pos = min(self.history_pos + 1, len(self.history))
            if self.history_pos < len(self.history):
                editbox.setText(self.history[self.history_pos])
                editbox.setCaratIndex(sys.maxint)
            else:
                editbox.setText('')
            editbox.activate()
        else:
            return False

        logi('BEFORE handleKeyDown: %s' %
                pretty_attr(keyEventArgs, 'keyEventArgs'))
        keyEventArgs.handled += 100
        logc('AFTER  handleKeyDown: %s' %
                pretty_attr(keyEventArgs, 'keyEventArgs'))
        return True

# macro to aid in the creation of UDims (copied from CEGUIUDim.h)
def cegui_reldim(x):
    return UDim(x, 0)


def pretty_attr(py_object, label, interesting_classes='default'):
    if interesting_classes == 'default':
        interesting_classes = pretty_attr.default_classes

    def _pretty_attr(py_object, label, interesting_classes, indent='.'):
        if isinstance(py_object, pretty_attr.instancemethodType):
            return label + ' [instancemethod]'
        elif interesting_classes and isinstance(py_object, interesting_classes):
            result = ''
            for attribute_name in dir(py_object):
                if not attribute_name.startswith('__'):
                    attribute = getattr(py_object, attribute_name)
                    result += indent + _pretty_attr(attribute
                                                   ,attribute_name + ' = '
                                                   ,interesting_classes
                                                   ,indent + '.') + '\n'
            return label + str(py_object) + '\n' + result
        # TODO: add support for lists/tuples/dicts
        else:
            # just print simple string for uninteresting objects
            return label + str(py_object)

    return _pretty_attr(py_object, label + ' = ', interesting_classes)[:-1]

pretty_attr.instancemethodType = type(
            new.instancemethod(pretty_attr, None, object))

pretty_attr.default_classes = (
        KeyEventArgs
       ,WindowEventArgs)


import fnmatch;
def block_indent(ss):
    return ss.replace('\n', '\n' +  log.indent)


def log(message, message_class='default', indent=None):
    if indent is None:
        indent = log.indent_level * ' '
    for wildcard_pattern in log.classes:
        if fnmatch.fnmatch(message_class, wildcard_pattern):
            print block_indent(indent + message)

def logi(message, message_class='default'):
    log(message, message_class, log.indent_level * ' ')
    log.indent_level += 2
    log.indent = ' ' * log.indent_level

def logc(message, message_class='default'):
    log.indent_level  = log.indent_level - 2
    log.indent = ' ' * log.indent_level
    log(message, message_class, log.indent_level * ' ')

log.indent_level = 0
log.indent = ''
log.classes = (
        'default'
       #,'inject*'
       #,'result'
       ,)

app = FalagardDemo1Sample()
app.run()
del app


Re: Python port of Falagard demo (console window with histor

Posted: Wed Feb 16, 2011 08:08
by CrazyEddie
Thanks for posting this. I really am interested in what people can achieve now that CEGUI is so accessible via Python - it's like the dawn of a new age :lol:

CE.

Re: Python port of Falagard demo (console window with histor

Posted: Tue Jul 26, 2011 02:43
by Leftium
OK, here is an initial version of the Falagard console with immediate mode Python. You can try PyCEGUI commands and see the results right away! Although it needs to be made a little more robust and easier to edit, it's already proven useful!

Class PyConsole simply sub-classes the original DemoConsole class from above to add the interactive Python interpreter:

Code: Select all

import code
class PyConsole(DemoConsole):
    def __init__(self, name, parent):
        DemoConsole.__init__(self, name=name, parent=parent)

        sys.stdout = self
        # sys.stderr = self

        self.inter = code.InteractiveInterpreter(globals())

    def write(self, edit_text):
        if edit_text and edit_text != '\n':
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)

    def handleSubmit(self, WindowEventArgs):
        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)
        # get text out of the editbox
        edit_text = editbox.getText()
        # if the string is not empty
        if edit_text:
            # add this entry to the command history buffer
            self.history.append(edit_text)
            # reset history position
            self.history_pos = len(self.history)
            # append newline to this entry
            # edit_text += '\n'
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)
            # erase text in text entry box.
            editbox.setText('')

            self.inter.runsource(edit_text)


For convenience, all the code in one file. You can directly run this script:

Code: Select all

# based on sample from http://www.cegui.org.uk/wiki/index.php/PyCEGUI
import os, sys
import new
import string

# you must have PyCEGUI python package installed (see pypi repository)
# or you must make it work yourself using binary bindings from SDK
from PyCEGUI import *

class PyCeguiBaseApplication(object):
    def __init__(self):
        # TODO: figure out smart default
        self.CEGUI_SAMPLE_DATAPATH = './'
        self.DATAPATH_VAR_NAME = 'CEGUI_SAMPLE_DATAPATH'

    def baseDataPath(self):
        # CEGUI data base path prefix / base directory.  This will be either
        # from an environment variable or based on the PyCEGUI library path
        if self.DATAPATH_VAR_NAME in os.environ:
            return os.environ[self.DATAPATH_VAR_NAME]
        else:
            return self.CEGUI_SAMPLE_DATAPATH

    def initialiseResourceGroupDirectories(self):
        # initialise the required dirs for the DefaultResourceProvider
        rp = System.getSingleton().getResourceProvider()

        basePath = self.baseDataPath() + 'datafiles/'

        # for each resource type, set a resource group directory
        rp.setResourceGroupDirectory('schemes', basePath + 'schemes')
        rp.setResourceGroupDirectory('imagesets', basePath + 'imagesets')
        rp.setResourceGroupDirectory('fonts', basePath + 'fonts')
        rp.setResourceGroupDirectory('layouts', basePath + 'layouts')
        rp.setResourceGroupDirectory('looknfeels', basePath + 'looknfeel')
        rp.setResourceGroupDirectory('schemas', basePath + 'xml_schemas')
        rp.setResourceGroupDirectory('animations', basePath + 'animations')

    def initialiseDefaultResourceGroups(self):
        # set the default resource groups to be used
        Imageset.setDefaultResourceGroup('imagesets')
        Font.setDefaultResourceGroup('fonts')
        Scheme.setDefaultResourceGroup('schemes')
        WidgetLookManager.setDefaultResourceGroup('looknfeels')
        WindowManager.setDefaultResourceGroup('layouts')
        AnimationManager.setDefaultResourceGroup('animations')

        # set up default group for validation schemas
        parser = System.getSingleton().getXMLParser()
        if parser.isPropertyPresent('SchemaDefaultResourceGroup'):
            parser.setProperty('SchemaDefaultResourceGroup', 'schemas')


from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import PyCEGUIOpenGLRenderer
class PyCeguiGlutBaseApplication(PyCeguiBaseApplication):
    specialKeyMap = {
            GLUT_KEY_F1: Key.F1
           ,GLUT_KEY_F2: Key.F2
           ,GLUT_KEY_F3: Key.F3
           ,GLUT_KEY_F4: Key.F4
           ,GLUT_KEY_F5: Key.F5
           ,GLUT_KEY_F6: Key.F6
           ,GLUT_KEY_F7: Key.F7
           ,GLUT_KEY_F8: Key.F8
           ,GLUT_KEY_F9: Key.F9
           ,GLUT_KEY_F10: Key.F10
           ,GLUT_KEY_F11: Key.F11
           ,GLUT_KEY_F12: Key.F12
           ,GLUT_KEY_LEFT: Key.ArrowLeft
           ,GLUT_KEY_UP: Key.ArrowUp
           ,GLUT_KEY_RIGHT: Key.ArrowRight
           ,GLUT_KEY_DOWN: Key.ArrowDown
           ,GLUT_KEY_PAGE_UP: Key.PageUp
           ,GLUT_KEY_PAGE_DOWN: Key.PageDown
           ,GLUT_KEY_HOME: Key.Home
           ,GLUT_KEY_END: Key.End
           ,GLUT_KEY_INSERT: Key.Insert}

    def init_static_data(self):
        self.quitFlag = False
        self.lastFrameTime = 0
        self.glut_modifiers = dict(
                shift=dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_SHIFT
                          ,scancode=Key.LeftShift)
               ,ctrl =dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_CTRL
                          ,scancode=Key.LeftControl)
               ,alt  =dict(is_held=False
                          ,bit_flag=GLUT_ACTIVE_ALT
                          ,scancode=Key.LeftAlt))
        self.fps_lastTime = 0
        self.fps_frames = 0
        self.fps_textbuff = ''
        self.logo_geometry = None

    def __init__(self, x=0, y=0, width=800, height=600
            ,title='PyCEGUI GLUT Base Application'):
        PyCeguiBaseApplication.__init__(self)

        self.init_static_data()
        self.root = None
        self.rot = 0

        # Do GLUT init
        glutInit(sys.argv)
        glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA)
        glutInitWindowSize(width, height)
        glutInitWindowPosition(x, y)
        glutCreateWindow(title)
        glutSetCursor(GLUT_CURSOR_NONE)

        self.renderer = PyCEGUIOpenGLRenderer.OpenGLRenderer.bootstrapSystem()

        glutDisplayFunc(self.drawFrame)
        glutReshapeFunc(self.reshape)

        glutMotionFunc(self.mouseMotion)
        glutPassiveMotionFunc(self.mouseMotion)
        glutMouseFunc(self.mouseButton)

        glutKeyboardFunc(self.keyChar)
        glutSpecialFunc(self.keySpecial)

        glutKeyboardUpFunc(self.keyCharUp)
        glutSpecialUpFunc(self.keySpecialUp)

        # Set the clear color
        glClearColor(0.0, 0.0, 0.0, 1.0)

        self.initialiseResourceGroupDirectories()
        self.initialiseDefaultResourceGroups()

        # setup required to do direct rendering of FPS value
        scrn = Rect(Vector2(0,0), self.renderer.getDisplaySize())
        self.fps_geometry = self.renderer.createGeometryBuffer()
        self.fps_geometry.setClippingRegion(scrn)

        # setup for logo
        ImagesetManager.getSingleton().createFromImageFile(
                'cegui_logo', 'logo.png', 'imagesets')
        self.logo_geometry = self.renderer.createGeometryBuffer()
        self.logo_geometry.setClippingRegion(scrn)
        self.logo_geometry.setPivot(Vector3(50, 34.75, 0))
        self.logo_geometry.setTranslation(Vector3(10, 520, 0))
        ImagesetManager.getSingleton().get('cegui_logo').getImage(
             'full_image').draw(self.logo_geometry ,Rect(0,0, 100, 69.5) ,None)

        # clearing this queue actually makes sure it's created(!)
        self.renderer.getDefaultRenderingRoot().clearGeometry(RQ_OVERLAY)

        # subscribe handler to render overlay items
        self.renderer.getDefaultRenderingRoot().subscribeEvent(
              RenderingSurface.EventRenderQueueStarted, self, 'overlayHandler')

    def __del__(self):
        PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem()

    def overlayHandler(self, args):
        # queueID attribute missing in CEGUI Python bindings
        # if not args.queueID == RQ_OVERLAY:
        #     return False

        # render FPS:
        fnt = System.getSingleton().getDefaultFont()
        if fnt:
            self.fps_geometry.reset()
            fnt.drawText(self.fps_geometry, self.fps_textbuff, Vector2(0, 0)
                    ,None, colour(0xFFFFFFFF))
            self.fps_geometry.draw()
        self.logo_geometry.draw()
        return True

    def execute(self, sampleApp):
        sampleApp.initialiseSample()

        # set starting time
        self.fps_lastTime = self.lastFrameTime = glutGet(GLUT_ELAPSED_TIME)

        glutMainLoop()

        return True

    def drawFrame(self):
        guiSystem = System.getSingleton()
        # do time based updates
        thisTime = glutGet(GLUT_ELAPSED_TIME)
        elapsed = thisTime - self.lastFrameTime
        self.lastFrameTime = thisTime
        # inject the time pulse
        guiSystem.injectTimePulse(elapsed / 1000.0)
        # update fps fields
        self.doFPSUpdate()

        # update logo rotation
        self.logo_geometry.setRotation(Vector3(self.rot, 0, 0))
        self.rot += (180.0 * elapsed) / 1000.0
        if self.rot > 360.0:
            self.rot -= 360.0

        # do rendering for this frame.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        guiSystem.renderGUI()
        glutPostRedisplay()
        glutSwapBuffers()

        # here we check the 'quitting' state and cleanup as required.
        # this is probably not the best way to do this, but since we're
        # using glut, and glutMainLoop can never return, we need some
        # way of checking when to exit.  And this is it...
        if self.quitFlag:
            PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem()
            exit(0)

    def reshape(self, w, h):
            glViewport(0, 0, w, h)
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            gluPerspective(60.0, float(w)/h, 1.0, 50.0)
            glMatrixMode(GL_MODELVIEW)
            System.getSingleton().notifyDisplaySizeChanged(Size(w, h))

    def mouseMotion(self, x, y):
        System.getSingleton().injectMousePosition(x, y)

    def mouseButton(self, button, state, x, y):
        if button == GLUT_LEFT_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(LeftButton)
            else:
                System.getSingleton().injectMouseButtonDown(LeftButton)

        elif button == GLUT_RIGHT_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(RightButton)
            else:
                System.getSingleton().injectMouseButtonDown(RightButton)

        elif button == GLUT_MIDDLE_BUTTON:
            if state == GLUT_UP:
                System.getSingleton().injectMouseButtonUp(MiddleButton)
            else:
                System.getSingleton().injectMouseButtonDown(MiddleButton)

    def keyChar(self, key, x, y):
        key = key.encode('ascii', 'ignore')
        self.handleModifierKeys()

        scancode = self.ascii_to_scancode(key)
        if scancode:
            custom_args = KeyEventArgs(None)
            custom_args.scancode = scancode
            logi('BEFORE keyChar.fireEvent: %s' %
                    pretty_attr(custom_args ,'custom_args'))
            System.getSingleton().fireEvent('AppKeyDown', custom_args)
            logc('AFTER  keyChar.fireEvent: %s \n' %
                    pretty_attr(custom_args ,'custom_args'))

            log('INJECT KEYDN: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.down')
            result = System.getSingleton().injectKeyDown(int(scancode))
            log('RESULT injectKeyDown: %s' % result, 'result')

        log('INJECT CHAR: [%3d]' % ord(key), 'inject.char')
        result = System.getSingleton().injectChar(ord(key))
        log('RESULT injectChar: %s' % result, 'result')
        if scancode == Key.Escape:
            self.quitFlag = True

        return False

    def keyCharUp(self, key, x, y):
        key = key.encode('ascii', 'ignore')
        self.handleModifierKeys()

        scancode = self.ascii_to_scancode(key)
        if scancode:
            log('INJECT KEYUP: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.up')
            System.getSingleton().injectKeyUp(int(scancode))
        return False

    def keySpecial(self, key, x, y):
        self.handleModifierKeys()
        scancode = self.special_to_scancode(key)
        if scancode:
            custom_args = KeyEventArgs(None)
            custom_args.scancode = scancode
            logi('BEFORE keySpecial.fireevent: %s' %
                    pretty_attr(custom_args ,'custom_args'))

            GlobalEventSet.getSingleton().fireEvent(
                    'AppKeyDown', custom_args)

            logc('AFTER  keySpecial.fireevent: %s \n' %
                    pretty_attr(custom_args ,'custom_args'))

            log('INJECT KEYDN: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.down')
            result = System.getSingleton().injectKeyDown(int(scancode))
            log('RESULT injectKeyDown: %s' % result, 'result')
        return False

    def keySpecialUp(self, key, x, y):
        self.handleModifierKeys()
        scancode = self.special_to_scancode(key)
        if scancode:
            log('INJECT KEYUP: %s %s' % (scancode, self.modifier_keys_state())
                    ,'inject.key.up')
            System.getSingleton().injectKeyUp(int(scancode))
        return False

    def handleModifierKeys(self):
        status = glutGetModifiers()

        for name, key in self.glut_modifiers.items():
            if (status & key['bit_flag']):
                if not key['is_held']:
                    key['is_held'] = True
                    log('INJECT MODDN: %s' % key['scancode'], 'inject.key.down')
                    System.getSingleton().injectKeyDown(key['scancode'])
            else:
                if key['is_held']:
                    key['is_held'] = False
                    log('INJECT MODUP: %s' % key['scancode'], 'inject.key.up')
                    System.getSingleton().injectKeyUp(key['scancode'])

    def doFPSUpdate(self):
        # another frame
        self.fps_frames += 1

        # has at least a second passed since we last updated the text?
        if (self.lastFrameTime - self.fps_lastTime >= 1000):
            # update FPS text to output
            self.fps_textbuff = 'FPS: %d' % self.fps_frames
            self.fps_frames = 0
            self.fps_lastTime = self.lastFrameTime

    mapping = dict([(ord(c), getattr(Key, c.upper()))
                   for c in string.ascii_letters])
    mapping.update({
                 96: Key.Grave,        126: Key.Grave,
                 49: Key.One,           33: Key.One,
                 50: Key.Two,           64: Key.At,
                 51: Key.Three,         35: Key.Three,
                 52: Key.Four,          36: Key.Four,
                 53: Key.Five,          37: Key.Five,
                 54: Key.Six,           94: Key.Six,
                 55: Key.Seven,         38: Key.Seven,
                 56: Key.Eight,         42: Key.Multiply,
                 57: Key.Nine,          40: Key.Nine,
                 48: Key.Zero,          41: Key.Zero,
                 45: Key.Minus,         95: Key.Underline,
                 61: Key.Equals,        43: Key.Equals,
                 91: Key.LeftBracket,  123: Key.LeftBracket,
                 93: Key.RightBracket, 125: Key.RightBracket,
                 59: Key.Semicolon,     58: Key.Colon,
                 39: Key.Apostrophe,    34: Key.Apostrophe,
                 92: Key.Backslash,    124: Key.Backslash,
                 44: Key.Comma,         60: Key.Comma,
                 46: Key.Period,        62: Key.Period,
                 47: Key.Slash,         63: Key.Slash,
                 13: Key.Return,
                  8: Key.Backspace,
                  9: Key.Tab,
                 32: Key.Space,
                127: Key.Delete,
                 27: Key.Escape})

    def ascii_to_scancode(self, a):
        a = ord(a)
        return self.mapping[a] if (a in self.mapping) else 0

    def special_to_scancode(self, c):
        return self.specialKeyMap[c] if (c in self.specialKeyMap) else 0

    def modifier_keys_state(self):
        s = ''
        if self.glut_modifiers['ctrl']['is_held']:
            s += 'CTRL '
        if self.glut_modifiers['alt']['is_held']:
            s += 'ALT '
        if self.glut_modifiers['shift']['is_held']:
            s += 'SHIFT '
        return s



class PyCeguiBaseSample(object):
    def run(self):
        if self.initialise:
            self.initialise()
            self.cleanup()
        return 0

    def initialise(self):
        self.sampleApp = PyCeguiGlutBaseApplication(x=0, y=0
                , width=800, height=600 ,title='PyCEGUI Falagard Demo')

        # execute the base application (which sets
        # up the demo via 'self' and runs it.)
        if self.sampleApp.execute(self):
            # signal that app initialised and ran
            return True

        self.sampleApp = None

        # signal app did not initialise and run.
        return False

    def cleanup(self):
        if self.sampleApp:
            self.sampleApp = None



class FalagardDemo1Sample(PyCeguiBaseSample):
    def __init__(self):
        self.console = None

    def initialiseSample(self):
        # Get window manager which we wil use for a few jobs here.
        winMgr = WindowManager.getSingleton()
        # Load the scheme to initialse the VanillaSkin which we use
        # in this sample
        SchemeManager.getSingleton().create('VanillaSkin.scheme')
        # set default mouse image
        System.getSingleton().setDefaultMouseCursor('Vanilla-Images'
                ,'MouseArrow')

        # load an image to use as a background
        ImagesetManager.getSingleton().createFromImageFile('BackgroundImage'
                ,'GPN-2000-001437.tga')

        # here we will use a StaticImage as the root,
        # then we can use it to place a background image
        background = winMgr.createWindow('Vanilla/StaticImage', 'bg')

        # set area rectangle
        background.setArea(URect(cegui_reldim(0) ,cegui_reldim(0)
                                ,cegui_reldim(1) ,cegui_reldim(1)))

        # disable frame and standard background
        background.setProperty('FrameEnabled', 'false')
        background.setProperty('BackgroundEnabled', 'false')
        #set the background iwm.mage
        background.setProperty('Image', 'set:BackgroundImage image:full_image')

        # install this as the root GUI sheet
        System.getSingleton().setGUISheet(background)

        FontManager.getSingleton().create('DejaVuSans-10.font')

        # load some demo windows and attach to the background 'root'
        # background.addChildWindow(
        #     winMgr.loadWindowLayout('VanillaWindows.layout'))

        # create an instance of the console class.
        self.console = PyConsole('Demo', None)

        # listen for key presses on the root window.
        GlobalEventSet.getSingleton().subscribeEvent('/AppKeyDown'
                ,self, 'handleRootKeydown')

        # activate the background window
        background.activate()

        # success!
        return True

    def cleanupSample(self):
        self.console = None

    def handleRootKeydown(self, keyArgs):
        if keyArgs.scancode == Key.F12:
            self.console.toggleVisibility()
            logi('BEFORE handleRootKeydown: %s' %
                  pretty_attr(keyArgs, 'WindowEventArgs'))
            keyArgs.handled += 1
            logc('AFTER  handleRootKeydown: %s' %
                  pretty_attr(keyArgs, 'WindowEventArgs'))
            return True
        return False



class DemoConsole(object):
    def __init__(self, name, parent=None):
        # these must match the IDs assigned in the layout
        self.submitButtonID = 1;
        self.entryBoxID     = 2;
        self.historyID      = 3;

        window_manager = WindowManager.getSingleton()
        self.console_root = window_manager.loadWindowLayout(
                'VanillaConsole.layout', name)
        self.history_pos = 0
        self.history = []



        # we will destroy the console box windows ourselves
        self.console_root.setDestroyedByParent(False)

        # Do events wire-up
        GlobalEventSet.getSingleton().subscribeEvent('/AppKeyDown',
                self, 'handleKeydown')
        self.console_root.getChild(self.submitButtonID).subscribeEvent(
                PushButton.EventClicked, self, 'handleSubmit')
        self.console_root.getChild(self.entryBoxID).subscribeEvent(
                Editbox.EventTextAccepted, self, 'handleSubmit')

        # decide where to attach the console main window
        parent = parent or System.getSingleton().getGUISheet()

        # attach this window if parent is valid
        if parent:
            parent.addChildWindow(self.console_root)

    def __del__(self):
        # destroy the windows that we loaded earlier
        WindowManager.getSingleton().destroyWindow(self.console_root)

    def toggleVisibility(self):
        self.console_root.hide() if self.console_root.isVisible(True
                ) else self.console_root.show()

    def handleSubmit(self, WindowEventArgs):
        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)
        # get text out of the editbox
        edit_text = editbox.getText()
        # if the string is not empty
        if edit_text:
            # add this entry to the command history buffer
            self.history.append(edit_text)
            # reset history position
            self.history_pos = len(self.history)
            # append newline to this entry
            edit_text += '\n'
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)
            # erase text in text entry box.
            editbox.setText('')

        # re-activate the text entry box
        editbox.activate()
        return True

    def handleKeydown(self, keyEventArgs):
        # if no input focus, ignore events
        if not self.console_root.getActiveChild():
            return False

        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)

        if keyEventArgs.scancode == Key.ArrowUp:
            self.history_pos = max(self.history_pos - 1, -1)
            if self.history_pos >= 0:
                editbox.setText(self.history[self.history_pos])
                editbox.setCaratIndex(sys.maxint)
            else:
                editbox.setText('')
            editbox.activate()

        elif keyEventArgs.scancode == Key.ArrowDown:
            self.history_pos = min(self.history_pos + 1, len(self.history))
            if self.history_pos < len(self.history):
                editbox.setText(self.history[self.history_pos])
                editbox.setCaratIndex(sys.maxint)
            else:
                editbox.setText('')
            editbox.activate()
        else:
            return False

        logi('BEFORE handleKeyDown: %s' %
                pretty_attr(keyEventArgs, 'keyEventArgs'))
        keyEventArgs.handled += 100
        logc('AFTER  handleKeyDown: %s' %
                pretty_attr(keyEventArgs, 'keyEventArgs'))
        return True

import code
class PyConsole(DemoConsole):
    def __init__(self, name, parent):
        DemoConsole.__init__(self, name=name, parent=parent)

        sys.stdout = self
        # sys.stderr = self

        self.inter = code.InteractiveInterpreter(globals())

    def write(self, edit_text):
        if edit_text and edit_text != '\n':
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)

    def handleSubmit(self, WindowEventArgs):
        # get the text entry editbox
        editbox = self.console_root.getChild(self.entryBoxID)
        # get text out of the editbox
        edit_text = editbox.getText()
        # if the string is not empty
        if edit_text:
            # add this entry to the command history buffer
            self.history.append(edit_text)
            # reset history position
            self.history_pos = len(self.history)
            # append newline to this entry
            # edit_text += '\n'
            # get history window
            history = self.console_root.getChild(self.historyID)
            # append new text to history output
            history.setText(history.getText() + edit_text)
            # scroll to bottom of history output
            history.setCaratIndex(sys.maxint)
            # erase text in text entry box.
            editbox.setText('')

            self.inter.runsource(edit_text)


# macro to aid in the creation of UDims (copied from CEGUIUDim.h)
def cegui_reldim(x):
    return UDim(x, 0)


def pretty_attr(py_object, label, interesting_classes='default'):
    if interesting_classes == 'default':
        interesting_classes = pretty_attr.default_classes

    def _pretty_attr(py_object, label, interesting_classes, indent='.'):
        if isinstance(py_object, pretty_attr.instancemethodType):
            return label + ' [instancemethod]'
        elif interesting_classes and isinstance(py_object, interesting_classes):
            result = ''
            for attribute_name in dir(py_object):
                if not attribute_name.startswith('__'):
                    attribute = getattr(py_object, attribute_name)
                    result += indent + _pretty_attr(attribute
                                                   ,attribute_name + ' = '
                                                   ,interesting_classes
                                                   ,indent + '.') + '\n'
            return label + str(py_object) + '\n' + result
        # TODO: add support for lists/tuples/dicts
        else:
            # just print simple string for uninteresting objects
            return label + str(py_object)

    return _pretty_attr(py_object, label + ' = ', interesting_classes)[:-1]

pretty_attr.instancemethodType = type(
            new.instancemethod(pretty_attr, None, object))

pretty_attr.default_classes = (
        KeyEventArgs
       ,WindowEventArgs)


import fnmatch;
def block_indent(ss):
    return ss.replace('\n', '\n' +  log.indent)


def log(message, message_class='default', indent=None):
    if indent is None:
        indent = log.indent_level * ' '
    for wildcard_pattern in log.classes:
        if fnmatch.fnmatch(message_class, wildcard_pattern):
            print block_indent(indent + message)

def logi(message, message_class='default'):
    log(message, message_class, log.indent_level * ' ')

    log.indent_level += 2
    log.indent = ' ' * log.indent_level

def logc(message, message_class='default'):
    log.indent_level  = log.indent_level - 2
    log.indent = ' ' * log.indent_level

    log(message, message_class, log.indent_level * ' ')

log.indent_level = 0
log.indent = ''
log.classes = (
       '_default'
       #,'inject*'
       #,'result'
       ,)

app = FalagardDemo1Sample()
app.run()
del app



Re: Python port of Falagard demo (console window with histor

Posted: Tue Jul 26, 2011 08:26
by Kulik
Wow, great work!

I think it would be great to add this to CEGUI itself (and the SDKs shipped) because it could serve as both a demo and a development tool (trying commands right away is definitely a great feature to have).