Creating custom widgets

Presenter Notes

Make the server aware

  • PROBLEM: The tryton server validates the widget
  • SOLUTION: We have to make the server aware of the new widget
  • You just have to inherit from ir.ui.view and override the get_rng class method.

Presenter Notes

The tryton architecture

  • form & tree & graph
    • gui/window/view_form/view/form_gtk
    • gui/window/view_form/view/list_gtk
    • gui/window/view_form/view/graph_gtk
  • field & widget
    • gui/window/view_form/model/field.py
    • gui/window/view_form/view/form_gtk/*
  • record & group
    • gui/window/view_form/model/record.py
    • gui/window/view_form/model/group.py

Presenter Notes

Form Widget

Presenter Notes

Methods to set data into the form

  1. in the widget a call to display
  2. in the field a call to get_client
  3. GTk code to do the required stuffs

Presenter Notes

Example of char.py

def display(self, record, field):
    super(Char, self).display(record, field)
    if record and self.autocomplete:
        
    elif self.autocomplete:
        

    # Set size
    if self.autocomplete:
        
    else:
        
    if record:
        
    else:
        

    if not field:
        value = ''
    else:
        value = field.get_client(record)

    if not self.autocomplete:
        self.entry.set_text(value)
    else:
        

Presenter Notes

super().display

Set differents states of the widget

def display(self, record, field):
    if not field:
        self._readonly_set(self.attrs.get('readonly', True))
        self.invisible_set(self.attrs.get('invisible', False))
        return
    self._readonly_set(self.attrs.get('readonly',
        field.get_state_attrs(record).get('readonly', False)))
    if self.attrs.get('readonly',
            field.get_state_attrs(record).get('readonly', False)):
        self.color_set('readonly')
    elif not field.get_state_attrs(record).get('valid', True):
        self.color_set('invalid')
    elif field.get_state_attrs(record).get('required', False):
        self.color_set('required')
    else:
        self.color_set('normal')
    self.invisible_set(self.attrs.get('invisible',
        field.get_state_attrs(record).get('invisible', False)))

Presenter Notes

Methods to get data from the form

  1. on the widget focus-out a call to set_value
  2. inside set_value a computation of the GTK widget content
  3. in the widget set_value method a call to the field's set_client
  4. in the field set_client the record is updated

Presenter Notes

Example with the char widget

In the GTk widget:

def set_value(self, record, field):
    entry = self.entry.get_child() if self.autocomplete else self.entry
    value = entry.get_text() or self._default_value
    return field.set_client(record, value)

In the field:

def set_client(self, record, value, force_change=False):
    previous_value = self.get(record)
    self.set(record, value)
    if previous_value != self.get(record):
        record.modified_fields.setdefault(self.name)
        record.signal('record-modified')
        self.sig_changed(record)
        record.validate(softvalidation=True)
        record.signal('record-changed')
    elif force_change:
        self.sig_changed(record)
        record.validate(softvalidation=True)
        record.signal('record-changed')

Presenter Notes

Plugging your widget in the client

Add your field in gui/window/view_form/model/field.py

             self.set(record, filename)
         return self.get(record)

+
+class DictField(CharField):
+
+    _default = {}
+
+
 TYPES = {
     'char': CharField,
     'sha': CharField,
@@ -941,4 +947,5 @@
     'time': TimeField,
     'one2one': O2OField,
     'binary': BinaryField,
+    'dict': DictField,
 }

Presenter Notes

Plugging your widget in the client

Add your widget in gui/window/view_form/view/form_gtk/parser.py

@@ -547,6 +548,7 @@
 from progressbar import ProgressBar
 from one2one import One2One
 from richtextbox import RichTextBox
+from dictionary import DictWidget


 WIDGETS_TYPE = {
@@ -576,4 +578,5 @@
     'progressbar': (ProgressBar, 1, False, False),
     'one2one': (One2One, 1, False, False),
     'richtext': (RichTextBox, 1, True, True),
+    'dict': (DictWidget, 1, False, False),
 }

The number is useless. The first boolean represent the yexpand property. The second boolean represent the yfill property.

Presenter Notes

Tree widget

Presenter Notes

Adding a tree widget

In gui/window/view_form/view/list_gtk/parser.py you can add a class to represent in the list view your widget.

class Geometry(Char):

    def __init__(self, field_name, model_name, treeview, attrs=None):
        super(Geometry, self).__init__(field_name, model_name, treeview,
            attrs=attrs)

    def get_textual_value(self, record):
        val = record[self.field_name].get_client(record)
        return val.wkt if val else ''

get_textual_value will return the value that must be displayed in the cell. value_from_text will set the value in the record from the text of the cell.

You can also define your own CellRenderer, they are examples in common/.

Presenter Notes