Commits

Ali Afshar  committed f57b349

Basic form and flatland support, with examples

  • Participants
  • Parent commits 499da17

Comments (0)

Files changed (6)

File docs/api_forms.rst

+
+.. literalinclude:: ../examples/forms.py
+
+.. figure:: media/simpleform.png
+
+    The resulting form
+
+.. image:: media/pygtkhelpers_forms.png
 
 .. automodule:: pygtkhelpers.forms
     :members:
 
-.. image:: media/pygtkhelpers_forms.png
 

File docs/media/simpleform.png

Added
New image

File examples/forms.py

+
+from flatland import Form, Dict, String, Integer, Boolean
+from flatland.validation import ValueAtLeast, ValueAtMost
+
+from pygtkhelpers.forms import FormView
+
+
+class PersonSchema(Form):
+
+    name = String
+
+    age = Integer.using(validators=[
+        ValueAtLeast(minimum=18),
+        ValueAtMost(maximum=120)
+    ])
+
+    weight = Integer.using(validators=[
+        ValueAtLeast(minimum=0),
+        ValueAtMost(maximum=300)
+    ])
+    weight.render_options = dict(
+        style='slider'
+    )
+
+    friendly = Boolean
+
+
+class PersonView(FormView):
+
+    schema_type = PersonSchema
+
+if __name__ == '__main__':
+    PersonView().show_and_run()
+

File pygtkhelpers/delegates.py

     def create_default_toplevel(self):
         return gtk.VBox()
 
+    def show_and_run(self):
+        """Show the main widget in a window and run the gtk loop"""
+        self.display_widget = gtk.Window()
+        self.display_widget.add(self.widget)
+        self.display_widget.show()
+        BaseDelegate.show_and_run(self)
+
 class ToplevelView(BaseDelegate):
     """A View that is a toplevel widget"""
 

File pygtkhelpers/forms.py

     :license: LGPL 2 or later (see README/COPYING/LICENSE)
 """
 
+import sys
 import gtk
 
 from flatland import Dict, String, Integer, Boolean
 
-from pygtkhelpers.proxy import ProxyGroup
+from pygtkhelpers.proxy import ProxyGroup, proxy_for
 from pygtkhelpers.delegates import SlaveView
+from pygtkhelpers.utils import gsignal
 
 
 def _view_type_for_element(element):
     # now do something with element.__class__
     ## something nasty
-    bases = [element.__class__] + list(element.__class__.__bases__)
+    bases = (c.__name__ for c in
+             [element.__class__] + list(element.__class__.__bases__))
     for base in bases:
         if base in element_views:
             return element_views[base]
     return builder(element)
 
 
-class Field(SlaveView):
+class Field(object):
+    """Encapsulates the widget and the label display
+    """
 
-    def __init__(self, widget=None, label=None):
-        self.field_widget = widget
-        self.label_widget = label
-        SlaveView.__init__(self)
+    def __init__(self, element, widget, label_widget=None):
+        self.element = element
+        self.widget = widget
+        self.proxy = proxy_for(widget)
+        self.label_widget = gtk.Label()
+
+    def set_label(self, text):
+        self.label_widget.set_text(text)
+
+    def _unparent(self):
+        self.widget.unparent()
+        self.label_widget.unparent()
+
+    def layout_as_table(self, table, row):
+        self._unparent()
+        self.label_widget.set_alignment(1.0, 0.5)
+        table.attach(self.label_widget, 0, 1, row, row+1,
+            xoptions=gtk.SHRINK|gtk.FILL, yoptions=gtk.SHRINK)
+        table.attach(self.widget, 1, 2, row, row+1,
+            xoptions=gtk.EXPAND|gtk.FILL, yoptions=gtk.SHRINK)
+
+
+
+
+
+class FieldSet(object):
+
+    def __init__(self, delegate, schema_type):
+        self.delegate = delegate
+        self.schema = schema_type()
+        self.proxies = ProxyGroup()
+        self.fields = {}
+        self.proxies.connect('changed', self._on_proxies_changed)
+        for name, element in self.schema.items():
+            self._setup_widget(name, element)
+
+    def _setup_widget(self, name, element):
+        widget = getattr(self.delegate, name, None)
+        #XXX (AA) this will always be the case, we are running too soon
+        if widget is None:
+            widget = widget_for(element)
+            setattr(self.delegate, name, widget)
+        field = self.fields[name] = Field(element, widget=widget)
+        field.set_label(name.capitalize())
+        self.proxies.add_proxy(name, field.proxy)
+
+    def _on_proxies_changed(self, group, proxy, name, value):
+        self.schema[name].set(value)
+
+    def layout_as_table(self):
+        table = gtk.Table(len(self.fields), 2)
+        table.set_row_spacings(6)
+        table.set_col_spacings(6)
+        table.set_border_width(6)
+        for row, name in enumerate(self.fields):
+            self.fields[name].layout_as_table(table, row)
+        return table
 
 
 class FormView(SlaveView):
 
     schema_type = None
 
-    def __init__(self):
-        self.schema = self.schema_type()
-        self.proxies = ProxyGroup()
-        SlaveView.__init__(self)
-        for name, element in self.schema.items():
-            self._setup_widget(name, element)
+    def create_ui(self):
+        self.form = FieldSet(self, self.schema_type)
+        self.widget.pack_start(self.form.layout_as_table())
 
-    def _setup_widget(self, name, element):
-        widget = getattr(self, name, None)
-        if widget is None:
-            widget = widget_for(element)
-            setattr(self, name, widget)
-        self.proxies.add_proxy_for(name, widget)
-
-    def on_proxies__changed(self, group, proxy, name, value):
-        self.schema[name].set(value)
 
 
 class WidgetBuilder(object):
         return self.widget_type()
 
 
+class IntegerBuilder(object):
+
+    def __call__(self, element):
+        render_options = getattr(element, 'render_options', {})
+        print element.name, render_options
+        style = render_options.get('style', 'spin')
+        widget_types = {
+            'spin': gtk.SpinButton,
+            'slider': gtk.HScale,
+        }
+        widget = widget_types.get(style)()
+        widget.set_digits(0)
+        adj = widget.get_adjustment()
+        min, max = -sys.maxint, sys.maxint
+        for v in element.validators:
+            if hasattr(v, 'minimum'):
+                min = v.minimum
+            elif hasattr(v, 'maximum'):
+                max = v.maximum
+        adj.set_all(min, min, max, 1.0, 10.0)
+        return widget
+
+
 VIEW_ENTRY = 'entry'
 VIEW_PASSWORD = 'password'
 VIEW_TEXT = 'text'
 
 #: Map of flatland element types to view types
 element_views = {
-    String: VIEW_ENTRY,
-    Integer: VIEW_NUMBER,
-    Boolean: VIEW_CHECK,
+    'String': VIEW_ENTRY,
+    'Integer': VIEW_NUMBER,
+    'Boolean': VIEW_CHECK,
 }
 
 #: map of view types to flatland element types
 view_widgets = {
     VIEW_ENTRY: WidgetBuilder(gtk.Entry),
-    VIEW_NUMBER: WidgetBuilder(gtk.SpinButton),
+    VIEW_NUMBER: IntegerBuilder(),
     VIEW_CHECK: WidgetBuilder(gtk.CheckButton),
 }
 

File tests/test_forms.py

 
 def test_form_field_value_changed():
     f = PersonForm()
-    check = CheckCalled(f.proxies, 'changed')
+    check = CheckCalled(f.form.proxies, 'changed')
     f.name.set_text('hello')
     assert check.called[2] == 'name'
     assert check.called[3] == 'hello'
 
 def test_update_schema_value():
     f = PersonForm()
-    assert f.schema['name'].value == None
+    assert f.form.schema['name'].value == None
     f.name.set_text('hello')
-    assert f.schema['name'].value == 'hello'
+    assert f.form.schema['name'].value == 'hello'
 
 
 def test_update_schema_value_typed():
     f = PersonForm()
-    assert f.schema['friendly'].value == None
+    assert f.form.schema['friendly'].value == None
     f.friendly.set_active(True)
-    assert f.schema['friendly'].value == True
+    assert f.form.schema['friendly'].value == True
     f.friendly.set_active(False)
-    assert f.schema['friendly'].value == False
+    assert f.form.schema['friendly'].value == False