kivy/examples/demo/kivycatalog/main.py

139 lines
5.2 KiB
Python
Raw Normal View History

import os
from kivy.app import App
from kivy.factory import Factory
from kivy.lang import Builder, Parser, ParserException
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.label import Label
'''List of classes that need to be instantiated in the factory from .kv files.
'''
CONTAINER_CLASSES = [c[:-3] for c in os.listdir('container_kvs')
if c.endswith('.kv')]
def factoryable(cls):
'''Class decorator that automatically records
the resultant class in the Factory.
This allows the class to be accessed
inside the .kv language file in the Builder.
'''
Factory.register(cls.__name__, cls)
return cls
@factoryable
class Container(BoxLayout):
'''A container is essentially a class that loads its root from a known
.kv file.
The name of the .kv file is taken from the Container's class.
We can't just use kv rules because the class may be edited
in the interface and reloaded by the user.
See :meth: change_kv where this happens.
'''
def __init__(self, **kwargs):
super(Container, self).__init__(**kwargs)
parser = Parser(content=file(self.kv_file).read())
widget = Factory.get(parser.root.name)()
Builder._apply_rule(widget, parser.root, parser.root)
self.add_widget(widget)
@property
def kv_file(self):
'''Get the name of the kv file, a lowercase version of the class name.'''
return os.path.join('container_kvs',
self.__class__.__name__ + ".kv")
for class_name in CONTAINER_CLASSES:
globals()[class_name] = type(class_name, (Container,), {})
class Catalog(BoxLayout):
'''Catalog of widgets. This is the root widget of the app. It contains
a tabbed pain of widgets that can be displayed and a textbox where .kv
language files for widgets being demoed can be edited.
The entire interface for the Catalog is defined in kivycatalog.kv, although
individual containers are defined in the container_kvs directory.
To add a container to the catalog,
first create the .kv file in container_kvs
The name of the file (sans .kv) will be the name of the widget available
inside the kivycatalog.kv
Finally modify kivycatalog.kv to add an AccordionItem
to hold the new widget.
Follow the examples in kivycatalog.kv to ensure the item
has an appropriate id and the class has been referenced.
You do not need to edit any python code, just .kv language files!
'''
language_box = ObjectProperty()
def __init__(self, **kwargs):
super(Catalog, self).__init__(**kwargs)
self.kv_container = None
from kivy.clock import Clock
Clock.schedule_once(self.show_kv)
def show_kv(self, object, collapsed=None):
'''Called when an accordionitem is collapsed or expanded. If it
was expanded, we need to show the .kv language file associated with
the newly revealed container.'''
if collapsed == "down": # a tabbed panel was clicked, not an accordion
object = object.content.children[0]
collapsed = False
if collapsed is False and hasattr(object, "kv_container"):
with open(object.kv_container.kv_file) as file:
self.language_box.background_color = (1, 1, 1, 1)
self.language_box.readonly = False
self.language_box.text = file.read()
self.kv_container = object.kv_container
else:
self.language_box.background_color = (1, 1, 1, .5)
self.language_box.text = ""
self.language_box.readonly = True
self.kv_container = None
def change_kv(self, button):
'''Called when the update button is clicked. Needs to update the
interface for the currently active kv widget, if there is one based
on the kv file the user entered. If there is an error in their kv
syntax, show a nice popup.'''
if self.kv_container:
try:
parser = Parser(content=self.language_box.text.encode('utf8'))
self.kv_container.clear_widgets()
widget = Factory.get(parser.root.name)()
Builder._apply_rule(widget, parser.root, parser.root)
self.kv_container.add_widget(widget)
except (SyntaxError, ParserException) as e:
content = Label(text=str(e), text_size=(350, None))
popup = Popup(title="Parse Error in Kivy Language Markup",
content=content, text_size=(350, None),
size_hint=(None, None), size=(400, 400))
popup.open()
except:
import traceback
traceback.print_exc()
popup = Popup(title="Boom",
content=Label(text="Something horrible happened while parsing your Kivy Language", text_size=(350, None)),
text_size=(350, None),
size_hint=(None, None), size=(400, 400))
popup.open()
class KivyCatalogApp(App):
'''The kivy App that runs the main root. All we do is build a catalog
widget into the root.'''
def build(self):
return Catalog()
if __name__ == "__main__":
KivyCatalogApp().run()