3 Kv language preprocessing
Enoch edited this page 2018-01-22 14:35:06 -05:00

Brief

Following the principle of separation of concerns kvlang enables separation of the GUI (Graphical User Inerface) definition, "the dashboard", from the general code, "the engine". However, when the GUI is user configurable, kvlang alone is inadequate for its fixed graphical description. Rather than create a *.kv / *.py mix the following method that uses the pyexpander preprocessor module let us do "everything" within the .kv file.

For comments contact: ixew

Software versions

The following example is current with kivy 1.10.0 (405) and pyexpander 1.8.3 (1).

project.py example

Thanks to kivy.context we can modify the kivy.lang Builder instance. Note that the ExpanderBuilder instance operates on the same objects which the Builder earlier created when processing the default style.kv.

from kivy.context import get_current_context
from kivy.lang.builder import Builder, BuilderBase

from copy import copy
from pyexpander.lib import expandToStr

# ...

class ExpanderBuilder(BuilderBase):
    def __init__(self, app, builder):
        super(BuilderBase, self).__init__()
        self.app = app
        self.files = builder.files
        self.dynamic_classes = builder.dynamic_classes
        self.templates = builder.templates
        self.rules = builder.rules
        self.rulectx = builder.rulectx

    def load_string(self, string, **kwargs):
        defs = {'config': self.app.config}
        xstring = expandToStr(string, external_definitions=defs)[0]
        return super(ExpanderBuilder, self).load_string(xstring, **kwargs)


class ProjectApp(App):
    def __init__(self):
        super(ProjectApp, self).__init__()
        context = copy(get_current_context())
        context['Builder'] = ExpanderBuilder(self, Builder)
        context.push()

    def build(self):
        context = get_current_context()
        context.pop()
        return super(ProjectApp, self).build()

project.kv example

$py(many = config.getint('general', 'devices'))

ScreenManager:
$for(x in range(many))
    Device:
        prev: g$(((x-1) % many) + 1).name
        name: g$(x + 1).name
        next: g$(((x+1) % many) + 1).name
$endfor\

Having two devices the builder load_string() would get the following expanded input:

ScreenManager:
# ...
    Device:
        prev: g2.name
        name: g1.name
        next: g2.name

    Device:
        prev: g1.name
        name: g2.name
        next: g1.name