Add some files for view_form

This commit is contained in:
ced 2007-12-22 01:23:01 +01:00
parent d338a50c01
commit 3780639e03
25 changed files with 3092 additions and 6 deletions

View file

@ -0,0 +1,369 @@
from tryton.rpc import RPCProxy
import tryton.rpc as rpc
class ModelField(object):
'''
get: return the values to write to the server
get_client: return the value for the client widget (form_gtk)
set: save the value from the server
set_client: save the value from the widget
'''
def __new__(cls, ctype):
klass = TYPES.get(ctype, CharField)
return klass
class CharField(object):
def __init__(self, parent, attrs):
self.parent = parent
self.attrs = attrs
self.name = attrs['name']
self.internal = False
self.default_attrs = {}
def sig_changed(self, model):
if self.get_state_attrs(model).get('readonly', False):
return
if self.attrs.get('on_change', False):
model.on_change(self.attrs['on_change'])
if self.attrs.get('change_default', False):
model.cond_default(self.attrs['name'], self.get(model))
def domain_get(self, model):
dom = self.attrs.get('domain', '[]')
return model.expr_eval(dom)
def context_get(self, model, check_load=True, eval_context=True):
context = {}
context.update(self.parent.context)
if eval_context:
field_context_str = self.attrs.get('context', '{}') or '{}'
field_context = model.expr_eval('dict(%s)' % field_context_str,
check_load=check_load)
context.update(field_context)
return context
def validate(self, model):
res = True
if bool(int(self.get_state_attrs(model).get('required', 0))):
if not model.value[self.name]:
res = False
self.get_state_attrs(model)['valid'] = res
return res
def set(self, model, value, test_state=True, modified=False):
model.value[self.name] = value
if modified:
model.modified = True
model.modified_fields.setdefault(self.name)
return True
def get(self, model, check_load=True, readonly=True, modified=False):
return model.value.get(self.name, False) or False
def set_client(self, model, value, test_state=True, force_change=False):
internal = model.value.get(self.name, False)
self.set(model, value, test_state)
if (internal or False) != (model.value.get(self.name, False) or False):
model.modified = True
model.modified_fields.setdefault(self.name)
self.sig_changed(model)
model.signal('record-changed', model)
def get_client(self, model):
return model.value[self.name] or False
def set_default(self, model, value):
res = self.set(model, value)
if self.attrs.get('on_change', False):
model.on_change(self.attrs['on_change'])
return res
def get_default(self, model):
return self.get(model)
def create(self, model):
return False
def state_set(self, model, state='draft'):
state_changes = dict(self.attrs.get('states', {}).get(state, []))
for key in ('readonly', 'required'):
if key in state_changes:
self.get_state_attrs(model)[key] = state_changes[key]
else:
self.get_state_attrs(model)[key] = self.attrs[key]
if 'value' in state_changes:
self.set(model, state_changes['value'], test_state=False,
modified=True)
def get_state_attrs(self, model):
if self.name not in model.state_attrs:
model.state_attrs[self.name] = self.attrs.copy()
return model.state_attrs[self.name]
class SelectionField(CharField):
def set(self, model, value, test_state=True, modified=False):
if value in [sel[0] for sel in self.attrs['selection']]:
super(SelectionField, self).set(model, value, test_state, modified)
class FloatField(CharField):
def validate(self, model):
self.get_state_attrs(model)['valid'] = True
return True
def set_client(self, model, value, test_state=True, force_change=False):
internal = model.value[self.name]
self.set(model, value, test_state)
if abs(float(internal or 0.0) - float(model.value[self.name] or 0.0)) \
>= (10.0**(-int(self.attrs.get('digits', (12,4))[1]))):
if not self.get_state_attrs(model).get('readonly', False):
model.modified = True
model.modified_fields.setdefault(self.name)
self.sig_changed(model)
model.signal('record-changed', model)
class IntegerField(CharField):
def get(self, model, check_load=True, readonly=True, modified=False):
return model.value.get(self.name, 0) or 0
def get_client(self, model):
return model.value[self.name] or 0
def validate(self, model):
self.get_state_attrs(model)['valid'] = True
return True
class M2OField(CharField):
'''
internal = (id, name)
'''
def create(self, model):
return False
def get(self, model, check_load=True, readonly=True, modified=False):
if model.value[self.name]:
return model.value[self.name][0] or False
return False
def get_client(self, model):
#model._check_load()
if model.value[self.name]:
return model.value[self.name][1]
return False
def set(self, model, value, test_state=False, modified=False):
if value and isinstance(value, (int, str, unicode, long)):
rpc2 = RPCProxy(self.attrs['relation'])
result = rpc2.name_get([value], rpc.session.context)
model.value[self.name] = result[0]
else:
model.value[self.name] = value
if modified:
model.modified = True
model.modified_fields.setdefault(self.name)
def set_client(self, model, value, test_state=False, force_change=False):
internal = model.value[self.name]
self.set(model, value, test_state)
if internal != model.value[self.name]:
model.modified = True
model.modified_fields.setdefault(self.name)
self.sig_changed(model)
model.signal('record-changed', model)
elif force_change:
self.sig_changed(model)
class M2MField(CharField):
'''
internal = [id]
'''
def __init__(self, parent, attrs):
super(M2MField, self).__init__(parent, attrs)
def create(self, model):
return []
def get(self, model, check_load=True, readonly=True, modified=False):
return [(6, 0, model.value[self.name] or [])]
def get_client(self, model):
return model.value[self.name] or []
def set(self, model, value, test_state=False, modified=False):
model.value[self.name] = value or []
if modified:
model.modified = True
model.modified_fields.setdefault(self.name)
def set_client(self, model, value, test_state=False, force_change=False):
internal = model.value[self.name]
self.set(model, value, test_state, modified=False)
if set(internal) != set(value):
model.modified = True
model.modified_fields.setdefault(self.name)
self.sig_changed(model)
model.signal('record-changed', model)
def get_default(self, model):
return self.get_client(model)
class O2MField(CharField):
'''
internal = ModelRecordGroup of the related objects
'''
def __init__(self, parent, attrs):
super(O2MField, self).__init__(parent, attrs)
self.context = {}
def create(self, model):
from group import ModelRecordGroup
mod = ModelRecordGroup(resource=self.attrs['relation'],
fields={}, parent=model)
mod.signal_connect(mod, 'model-changed', self._model_changed)
return mod
def _model_changed(self, group, model):
model.parent.modified = True
model.parent.modified_fields.setdefault(self.name)
self.sig_changed(model.parent)
self.parent.signal('record-changed', model)
def get_client(self, model):
return model.value[self.name]
def get(self, model, check_load=True, readonly=True, modified=False):
if not model.value[self.name]:
return []
result = []
for model2 in model.value[self.name].models:
if (modified and not model2.is_modified()) or \
(not model2.id and not model2.is_modified()):
continue
if model2.id:
result.append((1, model2.id,
model2.get(check_load=check_load, get_readonly=readonly)))
else:
result.append((0, 0,
model2.get(check_load=check_load, get_readonly=readonly)))
for rm_id in model.value[self.name].model_removed:
result.append((2, rm_id, False))
return result
def set(self, model, value, test_state=False, modified=False):
from group import ModelRecordGroup
mod = ModelRecordGroup(resource=self.attrs['relation'],
fields={}, parent=model)
mod.signal_connect(mod, 'model-changed', self._model_changed)
model.value[self.name] = mod
#self.internal.signal_connect(self.internal, 'model-changed',
# self._model_changed)
model.value[self.name].pre_load(value, display=False)
#self.internal.signal_connect(self.internal, 'model-changed',
# self._model_changed)
def set_client(self, model, value, test_state=False, force_change=False):
self.set(model, value, test_state=test_state)
model.signal('record-changed', model)
def set_default(self, model, value):
from group import ModelRecordGroup
fields = {}
if value and len(value):
context = self.context_get(model)
rpc2 = RPCProxy(self.attrs['relation'])
fields = rpc2.fields_get(value[0].keys(), context)
model.value[self.name] = ModelRecordGroup(
resource=self.attrs['relation'], fields=fields, parent=model)
model.value[self.name].signal_connect(model.value[self.name],
'model-changed', self._model_changed)
mod = None
for record in (value or []):
mod = model.value[self.name].model_new(default=False)
mod.set_default(record)
model.value[self.name].model_add(mod)
model.value[self.name].current_model = mod
#mod.signal('record-changed')
return True
def get_default(self, model):
res = [x.get_default() for x in model.value[self.name].models or []]
return res
def validate(self, model):
res = True
for model2 in model.value[self.name].models:
if not model2.validate():
if not model2.is_modified():
model.value[self.name].models.remove(model2)
else:
res = False
if not super(O2MField, self).validate(model):
res = False
self.get_state_attrs(model)['valid'] = res
return res
class ReferenceField(CharField):
def get_client(self, model):
if model.value[self.name]:
return model.value[self.name]
return False
def get(self, model, check_load=True, readonly=True, modified=False):
if model.value[self.name]:
return '%s,%d' % (model.value[self.name][0],
model.value[self.name][1][0])
return False
def set_client(self, model, value, test_state=False, force_change=False):
internal = model.value[self.name]
model.value[self.name] = value
if (internal or False) != (model.value[self.name] or False):
model.modified = True
model.modified_fields.setdefault(self.name)
self.sig_changed(model)
model.signal('record-changed', model)
def set(self, model, value, test_state=False, modified=False):
if not value:
model.value[self.name] = False
return
ref_model, ref_id = value.split(',')
rpc2 = RPCProxy(ref_model)
result = rpc2.name_get([ref_id], rpc.session.context)
if result:
model.value[self.name] = ref_model, result[0]
else:
model.value[self.name] = False
if modified:
model.modified = True
model.modified_fields.setdefault(self.name)
TYPES = {
'char' : CharField,
'float_time': FloatField,
'integer' : IntegerField,
'float' : FloatField,
'many2one' : M2OField,
'many2many' : M2MField,
'one2many' : O2MField,
'reference' : ReferenceField,
'selection': SelectionField,
'boolean': IntegerField,
}

View file

@ -0,0 +1,282 @@
from tryton.rpc import RPCProxy
import tryton.rpc as rpc
from record import ModelRecord
import field
from tryton.signal_event import SignalEvent
class ModelList(list):
def __init__(self, screen):
super(ModelList, self).__init__()
self.lock_signal = False
self.__screen = screen
def insert(self, pos, obj):
super(ModelList, self).insert(pos, obj)
if not self.lock_signal:
self.__screen.signal('record-changed', ('record-added', pos))
def append(self, obj):
super(ModelList, self).append(obj)
if not self.lock_signal:
self.__screen.signal('record-changed', ('record-added', -1))
def remove(self, obj):
idx = self.index(obj)
super(ModelList, self).remove(obj)
if not self.lock_signal:
self.__screen.signal('record-changed', ('record-removed', idx))
def clear(self):
while self:
self.pop()
if not self.lock_signal:
self.__screen.signal('record-changed',
('record-removed', len(self)))
def __setitem__(self, key, value):
super(ModelList, self).__setitem__(key, value)
if not self.lock_signal:
self.__screen.signal('record-changed', ('record-changed', key))
class ModelRecordGroup(SignalEvent):
def __init__(self, resource, fields, ids=None, parent=None, context=None):
super(ModelRecordGroup, self).__init__()
self.parent = parent
self._context = context or {}
self._context.update(rpc.session.context)
self.resource = resource
self.rpc = RPCProxy(resource)
self.fields = fields
self.mfields = {}
ModelRecordGroup.mfields_load(fields.keys(), self)
self.models = ModelList(self)
self.current_idx = None
self.load(ids)
self.model_removed = []
self.on_write = ''
@staticmethod
def mfields_load(fkeys, models):
for fname in fkeys:
fvalue = models.fields[fname]
modelfield = field.ModelField(fvalue['type'])
fvalue['name'] = fname
models.mfields[fname] = modelfield(models, fvalue)
def save(self):
for model in self.models:
saved = model.save()
self.writen(saved)
def writen(self, edited_id):
if not self.on_write:
return
new_ids = getattr(self.rpc, self.on_write)(edited_id, self.context)
model_idx = self.models.index(self[edited_id])
result = False
for new_id in new_ids:
cont = False
for model in self.models:
if model.id == new_id:
cont = True
model.reload()
if cont:
continue
newmod = ModelRecord(self.resource, new_id,
parent=self.parent, group=self)
newmod.reload()
if not result:
result = newmod
new_index = min(model_idx, len(self.models)-1)
self.model_add(newmod, new_index)
return result
def pre_load(self, ids, display=True):
if not ids:
return True
if len(ids)>10:
self.models.lock_signal = True
for obj_id in ids:
newmod = ModelRecord(self.resource, obj_id,
parent=self.parent, group=self)
self.model_add(newmod)
if display:
self.signal('model-changed', newmod)
if len(ids)>10:
self.models.lock_signal = False
self.signal('record-cleared')
return True
def load_for(self, values):
if len(values)>10:
self.models.lock_signal = True
for value in values:
newmod = ModelRecord(self.resource, value['id'],
parent=self.parent, group=self)
newmod.set(value)
self.models.append(newmod)
newmod.signal_connect(self, 'record-changed', self._record_changed)
if len(values)>10:
self.models.lock_signal = False
self.signal('record-cleared')
def load(self, ids, display=True):
if not ids:
return True
if not self.fields:
return self.pre_load(ids, display)
ctx = rpc.session.context.copy()
ctx.update(self.context)
values = self.rpc.read(ids, self.fields.keys(), ctx)
if not values:
return False
newmod = False
self.load_for(values)
if newmod and display:
self.signal('model-changed', newmod)
self.current_idx = 0
return True
def clear(self):
self.models.clear()
self.model_removed = []
def _get_context(self):
ctx = {}
ctx.update(self._context)
return ctx
context = property(_get_context)
def model_add(self, model, position=-1):
#TODO To be checked
if not model.mgroup is self:
fields = {}
for i in model.mgroup.fields:
fields[model.mgroup.fields[i]['name']] = \
model.mgroup.fields[i]
self.add_fields(fields, self)
self.add_fields(self.fields, model.mgroup)
model.mgroup = self
if position == -1:
self.models.append(model)
else:
self.models.insert(position, model)
self.current_idx = position
model.parent = self.parent
model.signal_connect(self, 'record-changed', self._record_changed)
return model
def model_new(self, default=True, domain=None, context=None):
newmod = ModelRecord(self.resource, None, group=self,
parent=self.parent, new=True)
newmod.signal_connect(self, 'record-changed', self._record_changed)
if default:
ctx = {}
ctx.update(context or {})
ctx.update(self.context)
newmod.default_get(domain, ctx)
self.signal('model-changed', newmod)
return newmod
def model_remove(self, model):
idx = self.models.index(model)
self.models.remove(model)
if model.parent:
model.parent.modified = True
if self.models:
self.current_idx = min(idx, len(self.models)-1)
else:
self.current_idx = None
def _record_changed(self, model, signal_data):
self.signal('model-changed', model)
def prev(self):
if self.models and self.current_idx is not None:
self.current_idx = (self.current_idx - 1) % len(self.models)
elif self.models:
self.current_idx = 0
else:
return None
return self.models[self.current_idx]
def next(self):
if self.models and self.current_idx is not None:
self.current_idx = (self.current_idx + 1) % len(self.models)
elif self.models:
self.current_idx = 0
else:
return None
return self.models[self.current_idx]
def remove(self, model):
idx = self.models.index(model)
if self.models[idx].id:
self.model_removed.append(self.models[idx].id)
if model.parent:
model.parent.modified = True
self.models.remove(self.models[idx])
def add_fields_custom(self, fields, models):
to_add = []
for field_add in fields.keys():
if not field_add in models.fields:
models.fields[field_add] = fields[field_add]
models.fields[field_add]['name'] = field_add
to_add.append(field_add)
else:
models.fields[field_add].update(fields[field_add])
ModelRecordGroup.mfields_load(to_add, models)
for fname in to_add:
for model in models.models:
model.value[fname] = self.mfields[fname].create(model)
return to_add
def add_fields(self, fields, models, context=None):
if context is None:
context = {}
to_add = self.add_fields_custom(fields, models)
models = models.models
if not len(models):
return True
old = []
new = []
for model in models:
if model.id:
old.append(model.id)
else:
new.append(model)
ctx = context.copy()
if len(old) and len(to_add):
ctx.update(rpc.session.context)
ctx.update(self.context)
values = self.rpc.read(old, to_add, ctx)
if values:
for value in values:
value_id = value['id']
if 'id' not in to_add:
del value['id']
self[value_id].set(value, signal=False)
if len(new) and len(to_add):
ctx.update(self.context)
values = self.rpc.default_get(to_add, ctx)
for field_to_add in to_add:
if field_to_add not in values:
values[field_to_add] = False
for mod in new:
mod.set_default(values)
def __iter__(self):
return iter(self.models)
def get_by_id(self, m_id):
for model in self.models:
if model.id == m_id:
return model
__getitem__ = get_by_id

View file

@ -0,0 +1,238 @@
import re
import time
from tryton.rpc import RPCProxy
import tryton.rpc as rpc
from tryton.signal_event import SignalEvent
import field
class EvalEnvironment(object):
def __init__(self, parent):
self.parent = parent
def __getattr__(self, item):
if item == 'parent' and self.parent.parent:
return EvalEnvironment(self.parent.parent)
if item == "current_date":
return time.strftime('%Y-%m-%d')
if item == "time":
return time
return self.parent.get(includeid=True)[item]
class ModelRecord(SignalEvent):
def __init__(self, resource, obj_id, group=None, parent=None, new=False ):
super(ModelRecord, self).__init__()
self.resource = resource
self.rpc = RPCProxy(self.resource)
self.id = obj_id
self._loaded = False
self.parent = parent
self.mgroup = group
self.value = {}
self.state_attrs = {}
self.modified = False
self.modified_fields = {}
self.read_time = time.time()
for key, val in self.mgroup.mfields.items():
self.value[key] = val.create(self)
if (new and val.attrs['type']=='one2many') \
and (val.attrs.get('mode','tree,form').startswith('form')):
mod = self.value[key].model_new()
self.value[key].model_add(mod)
def __getitem__(self, name):
return self.mgroup.mfields.get(name, False)
def __repr__(self):
return '<ModelRecord %s@%s>' % (self.id, self.resource)
def is_modified(self):
return self.modified
def fields_get(self):
return self.mgroup.mfields
def _check_load(self):
if not self._loaded:
self.reload()
return True
return False
def get(self, get_readonly=True, includeid=False, check_load=True,
get_modifiedonly=False):
if check_load:
self._check_load()
value = []
for name, mfield in self.mgroup.mfields.items():
if (get_readonly or \
not mfield.get_state_attrs(self).get('readonly', False)) \
and (not get_modifiedonly \
or (mfield.name in self.modified_fields \
or isinstance(mfield, mfield.O2MField))):
value.append((name, mfield.get(self, readonly=get_readonly,
modified=get_modifiedonly)))
value = dict(value)
if includeid:
value['id'] = self.id
return value
def cancel(self):
self._loaded = False
self.reload()
def save(self, force_reload=True):
self._check_load()
if not self.id:
value = self.get(get_readonly=False)
self.id = self.rpc.create(value, self.context_get())
else:
if not self.is_modified():
return self.id
value = self.get(get_readonly=False, get_modifiedonly=True)
context = self.context_get()
context = context.copy()
#XXX must compute delta on server side
context['read_delta'] = time.time() - self.read_time
if not rpc.session.rpc_exec_auth('/object', 'execute',
self.resource, 'write', [self.id], value, context):
return False
self._loaded = False
if force_reload:
self.reload()
return self.id
def default_get(self, domain=None, context=None):
if domain is None:
domain = []
if len(self.mgroup.fields):
val = self.rpc.default_get(self.mgroup.fields.keys(), context)
for clause in domain:
if clause[0] in self.mgroup.fields and clause[1] == '=':
val[clause[0]] = clause[2]
self.set_default(val)
def name_get(self):
name = self.rpc.name_get([self.id], rpc.session.context)[0]
return name
def validate_set(self):
change = self._check_load()
for fname in self.mgroup.mfields:
mfield = self.mgroup.mfields[fname]
change = change or \
not mfield.get_state_attrs(self).get('valid', True)
mfield.get_state_attrs(self)['valid'] = True
if change:
self.signal('record-changed')
return change
def validate(self):
self._check_load()
res = True
for fname in self.mgroup.mfields:
if not self.mgroup.mfields[fname].validate(self):
res = False
return res
def _get_invalid_fields(self):
res = []
for fname, mfield in self.mgroup.mfields.items():
if not mfield.get_state_attrs(self).get('valid', True):
res.append((fname, mfield.attrs['string']))
return dict(res)
invalid_fields = property(_get_invalid_fields)
def context_get(self):
return self.mgroup.context
def get_default(self):
self._check_load()
value = dict([(name, mfield.get_default(self))
for name, mfield in self.mgroup.mfields.items()])
return value
def set_default(self, val):
for fieldname, value in val.items():
if fieldname not in self.mgroup.mfields:
continue
self.mgroup.mfields[fieldname].set_default(self, value)
self._loaded = True
self.signal('record-changed')
def set(self, val, modified=False, signal=True):
later = {}
for fieldname, value in val.items():
if fieldname not in self.mgroup.mfields:
continue
if isinstance(self.mgroup.mfields[fieldname], field.O2MField):
later[fieldname] = value
continue
self.mgroup.mfields[fieldname].set(self, value, modified=modified)
for fieldname, value in later.items():
self.mgroup.mfields[fieldname].set(self, value, modified=modified)
self._loaded = True
self.modified = modified
if not self.modified:
self.modified_fields = {}
if signal:
self.signal('record-changed')
def reload(self):
if not self.id:
return
ctx = rpc.session.context.copy()
ctx.update(self.context_get())
res = self.rpc.read([self.id], self.mgroup.mfields.keys(), ctx)
if res:
value = res[0]
self.read_time = time.time()
self.set(value)
def expr_eval(self, dom, check_load=True):
if not isinstance(dom, basestring):
return dom
if check_load:
self._check_load()
ctx = {}
for name, mfield in self.mgroup.mfields.items():
ctx[name] = mfield.get(self, check_load=check_load)
ctx['current_date'] = time.strftime('%Y-%m-%d')
ctx['time'] = time
ctx['context'] = self.context_get()
ctx['active_id'] = self.id
if self.parent:
ctx['parent'] = EvalEnvironment(self.parent)
val = eval(dom, ctx)
return val
#XXX Shoud use changes of attributes (ro, ...)
def on_change(self, callback):
match = re.match('^(.*?)\((.*)\)$', callback)
if not match:
raise Exception, 'ERROR: Wrong on_change trigger: %s' % callback
func_name = match.group(1)
arg_names = [n.strip() for n in match.group(2).split(',')]
args = [self.expr_eval(arg) for arg in arg_names]
ids = self.id and [self.id] or []
response = getattr(self.rpc, func_name)(ids, *args)
if response:
self.set(response.get('value', {}), modified=True)
if 'domain' in response:
for fieldname, value in response['domain'].items():
if fieldname not in self.mgroup.mfields:
continue
self.mgroup.mfields[fieldname].attrs['domain'] = value
self.signal('record-changed')
def cond_default(self, field_name, value):
ir_values = RPCProxy('ir.values')
values = ir_values.get('default', '%s=%s' % (field_name, value),
[(self.resource, False)], False, {})
data = {}
for index, fname, value in values:
data[fname] = value
self.set_default(data)

View file

@ -2,9 +2,9 @@
import xml.dom.minidom
from tryton.rpc import RPCProxy
import tryton.rpc as rpc
from widget.model.group import ModelRecordGroup
from widget.view.screen_container import screen_container
import widget_search
from tryton.gui.window.view_form.model.group import ModelRecordGroup
from tryton.gui.window.view_form.view.screen_container import ScreenContainer
from tryton.gui.window.view_form.widget_search import Form
from tryton.signal_event import SignalEvent
from tryton.common import node_attributes
@ -60,7 +60,7 @@ class Screen(SignalEvent):
context=self.context)
self.models_set(models)
self.current_model = None
self.screen_container = screen_container()
self.screen_container = ScreenContainer()
self.filter_widget = None
self.widget = self.screen_container.widget_get()
self.__current_view = 0
@ -83,7 +83,7 @@ class Screen(SignalEvent):
view_form = rpc.session.rpc_exec_auth('/object', 'execute',
self.name, 'fields_view_get', False, 'form',
self.context)
self.filter_widget = widget_search.form(view_form['arch'],
self.filter_widget = Form(view_form['arch'],
view_form['fields'], self.name, self.window,
self.domain, (self, self.search_filter))
self.screen_container.add_filter(self.filter_widget.widget,
@ -239,7 +239,7 @@ class Screen(SignalEvent):
dom = xml.dom.minidom.parseString(arch)
_parse_fields(dom, fields)
from widget.view.widget_parse import widget_parse
from tryton.gui.window.view_form.view.widget_parse import widget_parse
models = self.models.models
if self.current_model and (self.current_model not in models):
models = models + [self.current_model]

View file

@ -0,0 +1,177 @@
import gtk
from gtk import glade
import tryton.rpc as rpc
from tryton.common import warning, COLORS
from tryton.config import GLADE, TRYTON_ICON
import gettext
_ = gettext.gettext
_ATTRS_BOOLEAN = {
'required': False,
'readonly': False
}
def field_pref_set(field, name, model, value, dependance=None, window=None):
win_gl = glade.XML(GLADE, 'win_field_pref', gettext.textdomain())
if dependance is None:
dependance = []
win = win_gl.get_widget('win_field_pref')
win.set_transient_for(window)
win.set_icon(TRYTON_ICON)
ent = win_gl.get_widget('ent_field')
ent.set_text(name)
ent = win_gl.get_widget('ent_domain')
ent.set_text(model)
ent = win_gl.get_widget('ent_value')
ent.set_text((value and str(value)) or '/')
radio = win_gl.get_widget('radio_user_pref')
vbox = win_gl.get_widget('pref_vbox')
widgets = {}
addwidget = False
for (fname, fvalue, rname, rvalue) in dependance:
if rvalue:
addwidget = True
widget = gtk.CheckButton(fname+' = '+str(rname))
widgets[(fvalue, rvalue)] = widget
vbox.pack_start(widget)
if not len(dependance) or not addwidget:
vbox.pack_start(gtk.Label(_('Always applicable !')))
vbox.show_all()
res = win.run()
deps = False
for val in widgets.keys():
if widgets[val].get_active():
deps = val[0] + '=' + str(val[1])
break
window.present()
win.destroy()
if res == gtk.RESPONSE_OK:
rpc.session.rpc_exec_auth('/object', 'execute', 'ir.values', 'set',
'default', deps, field, [(model,False)], value, True, False,
False, radio.get_active(), True)
return True
return False
class WidgetInterface(object):
def __init__(self, window, parent=None, model=None, attrs=None):
if attrs is None:
attrs = {}
self.parent = parent
self._window = window
self._view = None
self.attrs = attrs
for key, val in _ATTRS_BOOLEAN.items():
self.attrs[key] = attrs.get(key, False) not in ('False', '0', False)
self.default_readonly = self.attrs.get('readonly', False)
self._menu_entries = [
(_('Set to default value'),
lambda x: self._menu_sig_default_get(), 1),
(_('Set as default'),
lambda x: self._menu_sig_default_set(), 1),
]
self.widget = None
def destroy(self):
pass
def _menu_sig_default_get(self):
try:
if self._view.modelfield.get_state_attrs(self._view.model)\
.get('readonly', False):
return False
model = self._view.modelfield.parent.resource
res = rpc.session.rpc_exec_auth_try('/object', 'execute', model,
'default_get', [self.attrs['name']])
self._view.modelfield.set(self._view.model,
res.get(self.attrs['name'], False))
self.display(self._view.model, self._view.modelfield)
except:
warning(_('You can not set to the default value here!'),
_('Operation not permited'))
return False
def sig_activate(self, widget=None):
# emulate a focus_out so that the onchange is called if needed
self._focus_out()
def _readonly_set(self, readonly):
pass
def _color_widget(self):
return self.widget
def color_set(self, name):
widget = self._color_widget()
colormap = widget.get_colormap()
colour = colormap.alloc_color(COLORS.get(name,'white'))
widget.modify_bg(gtk.STATE_ACTIVE, colour)
widget.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
widget.modify_base(gtk.STATE_NORMAL, colour)
widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
widget.modify_text(gtk.STATE_INSENSITIVE, gtk.gdk.color_parse("black"))
def _menu_sig_default_set(self):
deps = []
wid = self._view.view_form.widgets
for wname, wview in self._view.view_form.widgets.items():
if wview.modelfield.attrs.get('change_default', False):
value = wview.modelfield.get(self._view.model)
deps.append((wname, wname, value, value))
value = self._view.modelfield.get_default(self._view.model)
model = self._view.modelfield.parent.resource
field_pref_set(self._view.widget_name,
self.attrs.get('string', self._view.widget_name), model,
value, deps, window=self._window)
def _menu_open(self, obj, event):
if event.button == 3:
menu = gtk.Menu()
for stock_id, callback, sensitivity in self._menu_entries:
if stock_id:
item = gtk.ImageMenuItem(stock_id)
if callback:
item.connect("activate", callback)
item.set_sensitive(sensitivity)
else:
item = gtk.SeparatorMenuItem()
item.show()
menu.append(item)
menu.popup(None, None, None, event.button, event.time)
return True
def _focus_in(self):
pass
def _focus_out(self):
if not self._view.modelfield:
return False
self.set_value(self._view.model, self._view.modelfield)
def display(self, model, modelfield):
if not modelfield:
self._readonly_set(self.attrs.get('readonly', False))
return
self._readonly_set(modelfield.get_state_attrs(model).\
get('readonly', False))
if modelfield.get_state_attrs(model).get('readonly', False):
self.color_set('readonly')
elif not modelfield.get_state_attrs(model).get('valid', True):
self.color_set('invalid')
elif modelfield.get_state_attrs(model).get('required', False):
self.color_set('required')
else:
self.color_set('normal')
def sig_changed(self):
if self.attrs.get('on_change', False):
self._view.view_form.screen.on_change(self.attrs['on_change'])
def set_value(self, model, model_field):
pass

View file

@ -0,0 +1,381 @@
import gobject
import gtk
import gettext
from interface import WidgetInterface
import tryton.common as common
from tryton.gui.window.view_form.screen import Screen
from tryton.gui.window.win_search import win_search
import tryton.rpc as rpc
from tryton.action import Action
_ = gettext.gettext
class Dialog(object):
def __init__(self, model, obj_id=None, attrs=None, domain=None,
context=None, window=None):
if attrs is None:
attrs = {}
if domain is None:
domain = []
if context is None:
context = {}
self.dia = gtk.Dialog(_('Tryton - Link'), window,
gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
self.window = window
if ('string' in attrs) and attrs['string']:
self.dia.set_title(self.dia.get_title() + ' - ' + attrs['string'])
self.dia.set_property('default-width', 760)
self.dia.set_property('default-height', 500)
self.dia.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
self.dia.set_icon(common.TINYERP_ICON)
self.accel_group = gtk.AccelGroup()
self.dia.add_accel_group(self.accel_group)
self.but_cancel = self.dia.add_button(gtk.STOCK_CANCEL,
gtk.RESPONSE_CANCEL)
self.but_cancel.add_accelerator('clicked', self.accel_group,
gtk.keysyms.Escape, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
self.but_ok = self.dia.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
self.but_ok.add_accelerator('clicked', self.accel_group,
gtk.keysyms.Return, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.set_placement(gtk.CORNER_TOP_LEFT)
scroll.set_shadow_type(gtk.SHADOW_NONE)
self.dia.vbox.pack_start(scroll, expand=True, fill=True)
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
scroll.add(viewport)
self.screen = Screen(model, domain=domain, context=context,
window=self.dia, view_type=['form'])
if obj_id:
self.screen.load([obj_id])
else:
self.screen.new()
viewport.add(self.screen.widget)
i, j = self.screen.screen_container.size_get()
viewport.set_size_request(i, j + 30)
self.dia.show_all()
self.screen.display()
def run(self):
while True:
res = self.dia.run()
if res == gtk.RESPONSE_OK:
if self.screen.current_model.validate() \
and self.screen.save_current():
return (True, self.screen.current_model.name_get())
else:
self.screen.display()
else:
break
return (False, False)
def destroy(self):
self.window.present()
self.dia.destroy()
class Many2One(WidgetInterface):
def __init__(self, window, parent, model, attrs=None):
if attrs is None:
attrs = {}
WidgetInterface.__init__(self, window, parent, model, attrs)
self.widget = gtk.HBox(spacing=3)
self.widget.set_property('sensitive', True)
self.widget.connect('focus-in-event', lambda x, y: self._focus_in())
self.widget.connect('focus-out-event', lambda x, y: self._focus_out())
self.wid_text = gtk.Entry()
self.wid_text.set_property('width-chars', 13)
self.wid_text.connect('key_press_event', self.sig_key_press)
self.wid_text.connect('button_press_event', self._menu_open)
self.wid_text.connect_after('changed', self.sig_changed)
self.wid_text.connect_after('activate', self.sig_activate)
self.wid_text_focus_out_id = \
self.wid_text.connect_after('focus-out-event',
self.sig_activate, True)
self.widget.pack_start(self.wid_text, expand=True, fill=True)
self.but_new = gtk.Button()
img_new = gtk.Image()
img_new.set_from_stock('gtk-new', gtk.ICON_SIZE_BUTTON)
self.but_new.set_image(img_new)
self.but_new.set_relief(gtk.RELIEF_NONE)
self.but_new.connect('clicked', self.sig_new)
self.but_new.set_alignment(0.5, 0.5)
self.but_new.set_property('can-focus', False)
self.widget.pack_start(self.but_new, expand=False, fill=False)
self.but_open = gtk.Button()
img_find = gtk.Image()
img_find.set_from_stock('gtk-find', gtk.ICON_SIZE_BUTTON)
img_open = gtk.Image()
img_open.set_from_stock('gtk-open', gtk.ICON_SIZE_BUTTON)
self.but_open.set_image(img_find)
self.but_open.set_relief(gtk.RELIEF_NONE)
self.but_open.connect('clicked', self.sig_edit)
self.but_open.set_alignment(0.5, 0.5)
self.but_open.set_property('can-focus', False)
self.widget.pack_start(self.but_open, padding=2, expand=False,
fill=False)
self.tooltips = gtk.Tooltips()
self.tooltips.set_tip(self.but_new, _('Create a new resource'))
self.tooltips.set_tip(self.but_open, _('Open a resource'))
self.tooltips.enable()
self.activate = True
self._readonly = False
self.model_type = attrs['relation']
self._menu_loaded = False
self._menu_entries = []
self._menu_entries.append((None, None, None))
self._menu_entries.append((_('Action'),
lambda x: self.click_and_action('client_action_multi'),0))
self._menu_entries.append((_('Report'),
lambda x: self.click_and_action('client_print_multi'),0))
self.completion = gtk.EntryCompletion()
self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
if attrs.get('completion', False):
ids = rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['relation'], 'name_search', '', [], 'ilike', {})
if ids:
self.load_completion(ids)
def _focus_out(self):
return WidgetInterface._focus_out(self)
def _focus_in(self):
return WidgetInterface._focus_in(self)
def load_completion(self, ids):
self.completion.set_match_func(self.match_func, None)
self.completion.connect("match-selected", self.on_completion_match)
self.wid_text.set_completion(self.completion)
self.completion.set_model(self.liststore)
self.completion.set_text_column(0)
for i, word in enumerate(ids):
if word[1][0] == '[':
i = word[1].find(']')
str1 = word[1][1:i]
str2 = word[1][i+2:]
self.liststore.append([("%s %s" % (str1, str2)), str2])
else:
self.liststore.append([word[1], word[1]])
def match_func(self, completion, key_string, iter, data):
model = self.completion.get_model()
modelstr = model[iter][0].lower()
return modelstr.startswith(key_string)
def on_completion_match(self, completion, model, iter):
name = model[iter][1]
domain = self._view.modelfield.domain_get(self._view.model)
context = self._view.modelfield.context_get(self._view.model)
ids = rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['relation'], 'name_search', name, domain, 'ilike',
context)
if len(ids)==1:
self._view.modelfield.set_client(self._view.model, ids[0])
self.display(self._view.model, self._view.modelfield)
self.activate = True
else:
win = win_search(self.attrs['relation'], sel_multi=False,
ids = [x[0] for x in ids], context=context,
domain=domain, window=self._window)
ids = win.go()
if ids:
name = rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['relation'], 'name_get', [ids[0]],
rpc.session.context)[0]
self._view.modelfield.set_client(self._view.model, name)
return True
def _readonly_set(self, value):
self._readonly = value
self.wid_text.set_editable(not value)
self.but_new.set_sensitive(not value)
def _color_widget(self):
return self.wid_text
def _menu_sig_pref(self, obj):
self._menu_sig_default_set()
def _menu_sig_default(self, obj):
rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['model'], 'default_get', [self.attrs['name']])
def sig_activate(self, widget, event=None, leave=False):
self.activate = False
value = self._view.modelfield.get(self._view.model)
self.wid_text.disconnect(self.wid_text_focus_out_id)
if value:
if not leave:
domain = self._view.modelfield.domain_get(self._view.model)
dia = Dialog(self.attrs['relation'],
self._view.modelfield.get(self._view.model),
attrs=self.attrs, window=self._window, domain=domain)
res, value = dia.run()
if res:
self._view.modelfield.set_client(self._view.model, value,
force_change=True)
dia.destroy()
else:
if not self._readonly and ( self.wid_text.get_text() or not leave):
domain = self._view.modelfield.domain_get(self._view.model)
context = self._view.modelfield.context_get(self._view.model)
self.wid_text.grab_focus()
ids = rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['relation'], 'name_search',
self.wid_text.get_text(), domain, 'ilike', context)
if len(ids)==1:
self._view.modelfield.set_client(self._view.model, ids[0],
force_change=True)
self.wid_text_focus_out_id = \
self.wid_text.connect_after('focus-out-event',
self.sig_activate, True)
self.display(self._view.model, self._view.modelfield)
self.activate = True
return True
win = win_search(self.attrs['relation'], sel_multi=False,
ids = [x[0] for x in ids], context=context,
domain=domain, parent=self._window)
ids = win.go()
if ids:
name = rpc.session.rpc_exec_auth('/object', 'execute',
self.attrs['relation'], 'name_get', [ids[0]],
rpc.session.context)[0]
self._view.modelfield.set_client(self._view.model, name,
force_change=True)
self.wid_text_focus_out_id = \
self.wid_text.connect_after('focus-out-event',
self.sig_activate, True)
self.display(self._view.model, self._view.modelfield)
self.activate = True
def sig_new(self, *args):
self.wid_text.disconnect(self.wid_text_focus_out_id)
domain = self._view.modelfield.domain_get(self._view.model)
dia = Dialog(self.attrs['relation'], attrs=self.attrs,
window=self._window, domain=domain)
res, value = dia.run()
if res:
self._view.modelfield.set_client(self._view.model, value)
self.display(self._view.model, self._view.modelfield)
dia.destroy()
self.wid_text_focus_out_id = \
self.wid_text.connect_after('focus-out-event',
self.sig_activate, True)
sig_edit = sig_activate
def sig_key_press(self, widget, event, *args):
if event.keyval == gtk.keysyms.F1:
self.sig_new(widget, event)
elif event.keyval==gtk.keysyms.F2:
self.sig_activate(widget, event)
elif event.keyval == gtk.keysyms.Tab:
if self._view.modelfield.get(self._view.model) or \
not self.wid_text.get_text():
return False
self.sig_activate(widget, event, leave=True)
return True
return False
def sig_changed(self, *args):
if self.activate:
if self._view.modelfield.get(self._view.model):
self._view.modelfield.set_client(self._view.model, False)
self.display(self._view.model, self._view.modelfield)
return False
def set_value(self, model, model_field):
pass # No update of the model, the model is updated in real time !
def display(self, model, model_field):
if not model_field:
self.activate = False
self.wid_text.set_text('')
return False
WidgetInterface.display(self, model, model_field)
self.activate = False
res = model_field.get_client(model)
self.wid_text.set_text((res and str(res)) or '')
img = gtk.Image()
if res:
img.set_from_stock('gtk-open', gtk.ICON_SIZE_BUTTON)
self.but_open.set_image(img)
self.tooltips.set_tip(self.but_open, _('Open a resource'))
else:
img.set_from_stock('gtk-find', gtk.ICON_SIZE_BUTTON)
self.but_open.set_image(img)
self.tooltips.set_tip(self.but_open, _('Search a resource'))
self.activate = True
def _menu_open(self, obj, event):
if event.button == 3:
value = self._view.modelfield.get(self._view.model)
if not self._menu_loaded:
resrelate = rpc.session.rpc_exec_auth('/object', 'execute',
'ir.values', 'get', 'action', 'client_action_relate',
[(self.model_type, False)], False, rpc.session.context)
resrelate = [x[2] for x in resrelate]
self._menu_entries.append((None, None, None))
for i in resrelate:
i['string'] = i['name']
fct = lambda action: lambda x: self.click_and_relate(action)
self._menu_entries.append(('... ' + i['name'], fct(i), 0))
self._menu_loaded = True
menu = gtk.Menu()
for stock_id, callback, sensitivity in self._menu_entries:
if stock_id:
item = gtk.ImageMenuItem(stock_id)
if callback:
item.connect("activate", callback)
item.set_sensitive(bool(sensitivity or value))
else:
item = gtk.SeparatorMenuItem()
item.show()
menu.append(item)
menu.popup(None, None, None, event.button, event.time)
return True
return False
def click_and_relate(self, action):
data = {}
context = {}
act = action.copy()
obj_id = self._view.modelfield.get(self._view.model)
if not obj_id:
common.message(_('You must select a record to use the relation !'))
return False
screen = Screen(self.attrs['relation'])
screen.load([obj_id])
act['domain'] = screen.current_model.expr_eval(act['domain'],
check_load=False)
act['context'] = str(screen.current_model.expr_eval(act['context'],
check_load=False))
return Action._exec_action(act, data, context)
def click_and_action(self, atype):
obj_id = self._view.modelfield.get(self._view.model)
return Action.exec_keyword(atype, {'model': self.model_type,
'id': obj_id or False, 'ids': [obj_id], 'report_type': 'pdf'})

View file

@ -0,0 +1,26 @@
"Interfaces"
class ParserInterface(object):
def __init__(self, window, parent=None, attrs=None, screen=None):
self.window = window
self.parent = parent
self.attrs = attrs
self.title = None
self.buttons = {}
self.screen = screen
class ParserView(object):
def __init__(self, window, screen, widget, children=None, buttons=None,
toolbar=None):
self.window = window
self.screen = screen
self.widget = widget
self.children = children
if buttons is None:
buttons = []
self.buttons = buttons
self.toolbar = toolbar

View file

@ -0,0 +1,72 @@
import gtk
import gettext
_ = gettext.gettext
class ScreenContainer(object):
def __init__(self):
self.old_widget = False
self.scrolledwindow = gtk.ScrolledWindow()
self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
self.viewport = gtk.Viewport()
self.viewport.set_shadow_type(gtk.SHADOW_NONE)
self.vbox = gtk.VBox()
self.vbox.pack_end(self.scrolledwindow)
self.filter_vbox = None
self.button = None
def widget_get(self):
return self.vbox
def add_filter(self, widget, fnct, clear_fnct):
self.filter_vbox = gtk.VBox(spacing=1)
self.filter_vbox.set_border_width(1)
label = gtk.Label(_('Search'))
label.set_alignment(0.0, 0.5)
label.show()
self.filter_vbox.pack_start(label, expand=True, fill=False)
hseparator = gtk.HSeparator()
hseparator.show()
self.filter_vbox.pack_start(hseparator, expand=True, fill=False)
self.filter_vbox.pack_start(widget, expand=True, fill=True)
hbuttonbox = gtk.HButtonBox()
hbuttonbox.set_spacing(5)
hbuttonbox.set_layout(gtk.BUTTONBOX_END)
button_clear = gtk.Button(stock=gtk.STOCK_CLEAR)
button_clear.connect('clicked', clear_fnct)
hbuttonbox.pack_start(button_clear, expand=False, fill=False)
self.button = gtk.Button(stock=gtk.STOCK_FIND)
self.button.connect('clicked', fnct)
self.button.set_property('can_default', True)
hbuttonbox.pack_start(self.button, expand=False, fill=False)
hbuttonbox.show_all()
self.filter_vbox.pack_start(hbuttonbox, expand=False, fill=False)
hseparator = gtk.HSeparator()
hseparator.show()
self.filter_vbox.pack_start(hseparator, expand=True, fill=False)
self.vbox.pack_start(self.filter_vbox, expand=False, fill=True)
def show_filter(self):
if self.filter_vbox:
self.filter_vbox.show()
def hide_filter(self):
if self.filter_vbox:
self.filter_vbox.hide()
def set(self, widget):
if self.viewport.get_child():
self.viewport.remove(self.viewport.get_child())
if self.scrolledwindow.get_child():
self.scrolledwindow.remove(self.scrolledwindow.get_child())
if not isinstance(widget, gtk.TreeView):
self.viewport.add(widget)
widget = self.viewport
self.scrolledwindow.add(widget)
self.scrolledwindow.show_all()
def size_get(self):
return self.scrolledwindow.get_child().size_request()

View file

@ -0,0 +1 @@
from parser import *

View file

@ -0,0 +1,244 @@
import gtk
import parser
import gettext
_ = gettext.gettext
class EditableTreeView(gtk.TreeView):
leaving_model_events = (gtk.keysyms.Up, gtk.keysyms.Down,
gtk.keysyms.Return, gtk.keysyms.KP_Enter)
leaving_events = leaving_model_events + (gtk.keysyms.Tab,
gtk.keysyms.ISO_Left_Tab)
def __init__(self, position):
super(EditableTreeView, self).__init__()
self.editable = position
self.cells = {}
self.colors = dict()
def on_quit_cell(self, current_model, fieldname, value):
modelfield = current_model[fieldname]
if hasattr(modelfield, 'editabletree_entry'):
del modelfield.editabletree_entry
cell = self.cells[fieldname]
# The value has not changed ... do nothing.
if value == cell.get_textual_value(current_model):
return
try:
real_value = cell.value_from_text(current_model, value)
modelfield.set_client(current_model, real_value)
except parser.UnsettableColumn:
return
# And now the on_change stuff ... only 3 lines are enough.
#callback = modelfield.attrs.get('on_change', '')
#if callback:
# current_model.on_change(callback)
# And finally the conditional default
#if modelfield.attrs.get('change_default', False):
# current_model.cond_default(fieldname, real_value)
def on_open_remote(self, current_model, fieldname, create, value):
modelfield = current_model[fieldname]
cell = self.cells[fieldname]
if value != cell.get_textual_value(current_model) or not value:
changed = True
else:
changed = False
try:
valid, value = cell.open_remote(current_model, create,
changed, value)
if valid:
modelfield.set_client(current_model, value)
except NotImplementedError:
pass
return cell.get_textual_value(current_model)
def on_create_line(self):
model = self.get_model()
if self.editable == 'top':
method = model.prepend
else:
method = model.append
ctx = self.screen.context.copy()
if self.screen.current_model.parent:
ctx.update(self.screen.current_model.parent.expr_eval(
self.screen.default_get))
new_model = model.model_group.model_new(domain=self.screen.domain,
context=ctx)
res = method(new_model)
return res
def __next_column(self, col):
cols = self.get_columns()
current = cols.index(col)
idx = (current + 1) % len(cols)
return cols[idx]
def __prev_column(self, col):
cols = self.get_columns()
current = cols.index(col)
idx = (current - 1) % len(cols)
return cols[idx]
def set_cursor(self, path, focus_column=None, start_editing=False):
if focus_column and (focus_column._type in ('many2one','many2many')):
self.warn('misc-message',
_('Relation Field: F1: New F2: Open/Search'))
elif focus_column and (focus_column._type in ('boolean')):
start_editing = False
else:
self.warn('misc-message', '')
return super(EditableTreeView, self).set_cursor(path, focus_column,
start_editing)
def set_value(self):
path, column = self.get_cursor()
store = self.get_model()
if not path or not column:
return True
model = store.get_value(store.get_iter(path), 0)
modelfield = model[column.name]
if hasattr(modelfield, 'editabletree_entry'):
entry = modelfield.editabletree_entry
if isinstance(entry, gtk.Entry):
txt = entry.get_text()
else:
txt = entry.get_active_text()
self.on_quit_cell(model, column.name, txt)
return True
def on_keypressed(self, entry, event):
path, column = self.get_cursor()
store = self.get_model()
model = store.get_value(store.get_iter(path), 0)
if event.keyval in self.leaving_events:
shift_pressed = bool(gtk.gdk.SHIFT_MASK & event.state)
if isinstance(entry, gtk.Entry):
txt = entry.get_text()
elif isinstance(entry, gtk.ComboBoxEntry) \
and shift_pressed \
and event.keyval != gtk.keysyms.ISO_Left_Tab:
model = entry.get_property('model')
txt = entry.get_active_text()
idx = 0
for idx, line in enumerate(model):
if line[1] == txt:
break
if event.keyval == gtk.keysyms.Up:
entry.set_active((idx - 1) % 3)
elif event.keyval == gtk.keysyms.Down:
entry.set_active((idx + 1) % 3)
return True
else:
txt = entry.get_active_text()
entry.disconnect(entry.editing_done_id)
self.on_quit_cell(model, column.name, txt)
entry.editing_done_id = entry.connect('editing_done',
self.on_editing_done)
if event.keyval in self.leaving_model_events:
if model.validate() and self.screen.tree_saves:
obj_id = model.save()
if not obj_id:
return True
else:
res = store.saved(obj_id)
if res:
self.screen.display(res.id)
elif self.screen.tree_saves:
invalid_fields = model.invalid_fields
col = None
for col in self.get_columns():
if col.name in invalid_fields:
break
self.set_cursor(path, col, True)
self.warn('misc-message',
_('Warning; field "%s" is required !') % \
invalid_fields[col.name])
return True
if event.keyval == gtk.keysyms.Tab:
new_col = self.__next_column(column)
self.set_cursor(path, new_col, True)
elif event.keyval == gtk.keysyms.ISO_Left_Tab:
new_col = self.__prev_column(column)
self.set_cursor(path, new_col, True)
elif event.keyval == gtk.keysyms.Up:
self._key_up(path, store, column)
elif event.keyval == gtk.keysyms.Down:
self._key_down(path, store, column)
elif event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
if self.editable == 'top':
new_path = self._key_up(path, store, column)
else:
new_path = self._key_down(path, store, column)
col = self.get_columns()[0]
self.set_cursor(new_path, col, True)
elif event.keyval == gtk.keysyms.Escape:
if model.id is None:
store.remove(store.get_iter(path))
self.screen.current_model = False
if not path[0]:
self.screen.current_model = False
self.screen.display()
self.set_cursor(path, column, False)
elif event.keyval in (gtk.keysyms.F1, gtk.keysyms.F2):
if isinstance(entry, gtk.Entry):
value = entry.get_text()
else:
value = entry.get_active_text()
entry.disconnect(entry.editing_done_id)
newval = self.on_open_remote(model, column.name,
create=(event.keyval==gtk.keysyms.F1),
value=value)
if isinstance(entry, gtk.Entry):
entry.set_text(newval)
else:
entry.set_active_text(value)
entry.editing_done_id = entry.connect('editing_done',
self.on_editing_done)
self.set_cursor(path, column, True)
else:
modelfield = model[column.name]
if isinstance(entry, gtk.Entry):
entry.set_max_length(int(modelfield.attrs.get('size', 0)))
# store in the model the entry widget to get the value in set_value
modelfield.editabletree_entry = entry
model.modified = True
model.modified_fields.setdefault(column.name)
return False
return True
def _key_down(self, path, store, column):
if path[0] == len(store) - 1 and self.editable == 'bottom':
self.on_create_line()
new_path = (path[0] + 1) % len(store)
self.set_cursor(new_path, column, True)
return new_path
def _key_up(self, path, store, column):
if path[0] == 0 and self.editable == 'top':
self.on_create_line()
new_path = 0
else:
new_path = (path[0] - 1) % len(store)
self.set_cursor(new_path, column, True)
return new_path
def on_editing_done(self, entry):
path, column = self.get_cursor()
if not path:
return True
store = self.get_model()
model = store.get_value(store.get_iter(path), 0)
if isinstance(entry, gtk.Entry):
self.on_quit_cell(model, column.name, entry.get_text())
elif isinstance(entry, gtk.ComboBoxEntry):
self.on_quit_cell(model, column.name, entry.get_active_text())

View file

@ -0,0 +1,477 @@
import locale
import gtk
import math
from tryton.rpc import RPCProxy
from editabletree import EditableTreeView
from tryton.gui.window.view_form.view.interface import ParserInterface
import time
from tryton.gui.window.view_form.view.form_gtk.many2one import Dialog \
as M2ODialog
from tryton.gui.window.win_search import win_search
import tryton.rpc as rpc
import datetime as DT
from tryton.common import DT_FORMAT, DHM_FORMAT, COLORS, node_attributes
if not hasattr(locale, 'nl_langinfo'):
locale.nl_langinfo = lambda *a: '%x'
if not hasattr(locale, 'D_FMT'):
locale.D_FMT = None
def send_keys(renderer, editable, position, treeview):
editable.connect('key_press_event', treeview.on_keypressed)
editable.editing_done_id = editable.connect('editing_done',
treeview.on_editing_done)
def sort_model(column, treeview):
model = treeview.get_model()
model.sort(column.name)
class ParserTree(ParserInterface):
def __init__(self, window, parent=None, attrs=None, screen=None):
super(ParserTree, self).__init__(window, parent, attrs, screen)
self.treeview = None
def parse(self, model, root_node, fields):
dict_widget = {}
attrs = node_attributes(root_node)
on_write = attrs.get('on_write', '')
editable = attrs.get('editable', False)
if editable:
treeview = EditableTreeView(editable)
else:
treeview = gtk.TreeView()
treeview.editable = editable
treeview.cells = {}
self.treeview = treeview
for color_spec in attrs.get('colors', '').split(';'):
if color_spec:
colour, test = color_spec.split(':')
treeview.colors[colour] = test
treeview.set_property('rules-hint', True)
if not self.title:
self.title = attrs.get('string', 'Unknown')
for node in root_node.childNodes:
node_attrs = node_attributes(node)
if node.localName == 'field':
fname = str(node_attrs['name'])
for boolean_fields in ('readonly', 'required'):
if boolean_fields in node_attrs:
node_attrs[boolean_fields] = \
bool(int(node_attrs[boolean_fields]))
fields[fname].update(node_attrs)
node_attrs.update(fields[fname])
cell = CELLTYPES.get(fields[fname]['type'])(fname, treeview,
node_attrs, self.window)
treeview.cells[fname] = cell
renderer = cell.renderer
if editable and not node_attrs.get('readonly', False):
if isinstance(renderer, gtk.CellRendererToggle):
renderer.set_property('activatable', True)
else:
renderer.set_property('editable', True)
renderer.connect_after('editing-started', send_keys,
treeview)
else:
if isinstance(renderer, gtk.CellRendererToggle):
renderer.set_property('activatable', False)
col = gtk.TreeViewColumn(fields[fname]['string'], renderer)
col.name = fname
col._type = fields[fname]['type']
col.set_cell_data_func(renderer, cell.setter)
col.set_clickable(True)
twidth = {
'integer': 60,
'float': 80,
'float_time': 80,
'date': 70,
'datetime': 120,
'selection': 90,
'char': 100,
'one2many': 50,
'many2many': 50,
'boolean': 20,
}
if 'width' in fields[fname]:
width = int(fields[fname]['width'])
else:
width = twidth.get(fields[fname]['type'], 100)
col.set_min_width(width)
col.connect('clicked', sort_model, treeview)
col.set_resizable(True)
#col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
col.set_visible(not fields[fname].get('invisible', False))
i = treeview.append_column(col)
if 'sum' in fields[fname] and fields[fname]['type'] \
in ('integer', 'float', 'float_time'):
label = gtk.Label()
label.set_use_markup(True)
label_str = fields[fname]['sum'] + ': '
label_bold = bool(int(fields[fname].get('sum_bold', 0)))
if label_bold:
label.set_markup('<b>%s</b>' % label_str)
else:
label.set_markup(label_str)
label_sum = gtk.Label()
label_sum.set_use_markup(True)
dict_widget[i] = (fname, label, label_sum,
fields.get('digits', (16,2))[1], label_bold)
return treeview, dict_widget, [], on_write
class Char(object):
def __init__(self, field_name, treeview=None, attrs=None, window=None):
self.field_name = field_name
self.attrs = attrs or {}
self.renderer = gtk.CellRendererText()
self.treeview = treeview
self.window = window
def setter(self, column, cell, store, iter):
model = store.get_value(iter, 0)
text = self.get_textual_value(model)
cell.set_property('text', text)
color = self.get_color(model)
cell.set_property('foreground', str(color))
if self.attrs['type'] in ('float', 'integer', 'boolean'):
align = 1
else:
align = 0
if self.treeview.editable:
field = model[self.field_name]
if not field.get_state_attrs(model).get('valid', True):
cell.set_property('background',
COLORS.get('invalid', 'white'))
elif bool(int(field.get_state_attrs(model).get('required', 0))):
cell.set_property('background',
COLORS.get('required', 'white'))
cell.set_property('xalign', align)
def get_color(self, model):
to_display = ''
for color, expr in self.treeview.colors.items():
if model.expr_eval(expr, check_load=False):
to_display = color
break
return to_display or 'black'
def open_remote(self, model, create, changed=False, text=None):
raise NotImplementedError
def get_textual_value(self, model):
return model[self.field_name].get_client(model) or ''
def value_from_text(self, model, text):
return text
class Int(Char):
def value_from_text(self, model, text):
return int(text)
def get_textual_value(self, model):
return locale.format('%d',
model[self.field_name].get_client(model) or 0, True)
class Boolean(Int):
def __init__(self, *args):
super(Boolean, self).__init__(*args)
self.renderer = gtk.CellRendererToggle()
self.renderer.connect('toggled', self._sig_toggled)
def get_textual_value(self, model):
return model[self.field_name].get_client(model) or 0
def setter(self, column, cell, store, iter):
model = store.get_value(iter, 0)
value = self.get_textual_value(model)
cell.set_active(bool(value))
def _sig_toggled(self, renderer, path):
store = self.treeview.get_model()
model = store.get_value(store.get_iter(path), 0)
field = model[self.field_name]
if not field.get_state_attrs(model).get('readonly', False):
value = model[self.field_name].get_client(model)
model[self.field_name].set_client(model, int(not value))
self.treeview.set_cursor(path)
return True
class Date(Char):
server_format = DT_FORMAT
display_format = locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')
def get_textual_value(self, model):
value = model[self.field_name].get_client(model)
if not value:
return ''
date = time.strptime(value, self.server_format)
return time.strftime(self.display_format, date)
def value_from_text(self, model, text):
if not text:
return False
try:
date = time.strptime(text, self.display_format)
except:
try:
date = list(time.localtime())
date[2] = int(text)
date = tuple(date)
except:
return False
return time.strftime(self.server_format, date)
class Datetime(Date):
server_format = DHM_FORMAT
display_format = locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y') + \
' %H:%M:%S'
def get_textual_value(self, model):
value = model[self.field_name].get_client(model)
if not value:
return ''
date = time.strptime(value, self.server_format)
if 'tz' in rpc.session.context:
try:
import pytz
lzone = pytz.timezone(rpc.session.context['tz'])
szone = pytz.timezone(rpc.session.timezone)
datetime = DT.datetime(date[0], date[1], date[2], date[3],
date[4], date[5], date[6])
sdt = szone.localize(datetime, is_dst=True)
ldt = sdt.astimezone(lzone)
date = ldt.timetuple()
except:
pass
return time.strftime(self.display_format, date)
def value_from_text(self, model, text):
if not text:
return False
try:
date = time.strptime(text, self.display_format)
except:
try:
datetime = list(time.localtime())
datetime[2] = int(text)
date = tuple(datetime)
except:
return False
if 'tz' in rpc.session.context:
try:
import pytz
lzone = pytz.timezone(rpc.session.context['tz'])
szone = pytz.timezone(rpc.session.timezone)
datetime = DT.datetime(date[0], date[1], date[2], date[3],
date[4], date[5], date[6])
ldt = lzone.localize(datetime, is_dst=True)
sdt = ldt.astimezone(szone)
date = sdt.timetuple()
except:
pass
return time.strftime(self.server_format, date)
class Float(Char):
def get_textual_value(self, model):
digit = self.attrs.get('digits', (16, 2))[1]
return locale.format('%.'+str(digit)+'f',
model[self.field_name].get_client(model) or 0.0, True)
def value_from_text(self, model, text):
try:
return locale.atof(text)
except:
return 0.0
class FloatTime(Char):
def get_textual_value(self, model):
val = model[self.field_name].get_client(model)
value = '%02d:%02d' % (math.floor(abs(val)),
round(abs(val) % 1 + 0.01, 2) * 60)
if val < 0:
value = '-' + value
return value
def value_from_text(self, model, text):
try:
if text and ':' in text:
return round(int(text.split(':')[0]) + \
int(text.split(':')[1]) / 60.0,2)
else:
return locale.atof(text)
except:
pass
return 0.0
class M2O(Char):
def value_from_text(self, model, text):
if not text:
return False
relation = model[self.field_name].attrs['relation']
rpc_relation = RPCProxy(relation)
domain = model[self.field_name].domain_get(model)
context = model[self.field_name].context_get(model)
names = rpc_relation.name_search(text, domain, 'ilike', context)
if len(names) != 1:
return self.search_remote(relation, [x[0] for x in names],
domain=domain, context=context)[0]
return names[0]
def open_remote(self, model, create=True, changed=False, text=None):
modelfield = model.mgroup.mfields[self.field_name]
relation = modelfield.attrs['relation']
domain = modelfield.domain_get(model)
context = modelfield.context_get(model)
if create:
obj_id = None
elif not changed:
obj_id = modelfield.get(model)
else:
rpc_relation = RPCProxy(relation)
names = rpc_relation.name_search(text, domain, 'ilike', context)
if len(names) == 1:
return True, names[0]
searched = self.search_remote(relation, [x[0] for x in names],
domain=domain, context=context)
if searched[0]:
return True, searched
return False, False
dia = M2ODialog(relation, obj_id, domain=domain, context=context,
window=self.window)
res, value = dia.run()
dia.destroy()
if res:
return True, value
else:
return False, False
def search_remote(self, relation, ids=None, domain=None, context=None):
rpc_relation = RPCProxy(relation)
win = win_search(relation, sel_multi=False, ids=ids, context=context,
domain=domain)
found = win.go()
if found:
return rpc_relation.name_get([found[0]], context)[0]
else:
return False, None
class UnsettableColumn(Exception):
def __init__(self):
Exception.__init__()
class O2M(Char):
def get_textual_value(self, model):
return '( ' + str(len(model[self.field_name].\
get_client(model).models)) + ' )'
def value_from_text(self, model, text):
raise UnsettableColumn('Can not set column of type o2m')
class M2M(Char):
def get_textual_value(self, model):
value = model[self.field_name].get_client(model)
if value:
return '(%s)' % len(value)
else:
return '(0)'
def value_from_text(self, model, text):
if not text:
return []
if not (text[0] != '('):
return model[self.field_name].get(model)
relation = model[self.field_name].attrs['relation']
rpc_relation = RPCProxy(relation)
domain = model[self.field_name].domain_get(model)
context = model[self.field_name].context_get(model)
names = rpc_relation.name_search(text, domain, 'ilike', context)
ids = [x[0] for x in names]
win = win_search(relation, sel_multi=True, ids=ids, context=context,
domain=domain)
found = win.go()
return found or []
def open_remote(self, model, create=True, changed=False, text=None):
modelfield = model[self.field_name]
relation = modelfield.attrs['relation']
rpc_relation = RPCProxy(relation)
context = model[self.field_name].context_get(model)
domain = model[self.field_name].domain_get(model)
if create:
if text and len(text) and text[0] != '(':
domain.append(('name', '=', text))
ids = rpc_relation.search(domain)
if ids and len(ids)==1:
return True, ids
else:
ids = model[self.field_name].get_client(model)
win = win_search(relation, sel_multi=True, ids=ids, context=context,
domain=domain)
found = win.go()
if found:
return True, found
else:
return False, None
class Selection(Char):
def __init__(self, *args):
super(Selection, self).__init__(*args)
self.renderer = gtk.CellRendererCombo()
selection_data = gtk.ListStore(str, str)
for i in self.attrs.get('selection', []):
selection_data.append(i)
self.renderer.set_property('model', selection_data)
self.renderer.set_property('text-column', 1)
def get_textual_value(self, model):
selection = dict(model[self.field_name].attrs['selection'])
return selection.get(model[self.field_name].get(model), '')
def value_from_text(self, model, text):
selection = model[self.field_name].attrs['selection']
for val, txt in selection:
if txt == text:
return val
return False
CELLTYPES = {
'char': Char,
'many2one': M2O,
'date': Date,
'one2many': O2M,
'many2many': M2M,
'selection': Selection,
'float': Float,
'float_time': FloatTime,
'integer': Int,
'datetime': Datetime,
'boolean': Boolean,
}

View file

@ -0,0 +1,46 @@
from interface import ParserInterface
#import form_gtk
import tree_gtk
#import graph_gtk
#import calendar_gtk
#from form import ViewForm
from list import ViewList
#from graph import ViewGraph
#from calendar import ViewCalendar
PARSERS = {
# 'form': form_gtk.parser_form,
'tree': tree_gtk.parser_tree,
# 'graph': graph_gtk.parser_graph,
# 'calendar': calendar_gtk.parser_calendar,
}
PARSERS2 = {
# 'form': ViewForm,
'tree': ViewList,
# 'graph': ViewGraph,
# 'calendar': ViewCalendar,
}
class WidgetParse(ParserInterface):
def parse(self, screen, root_node, fields, toolbar=None):
widget = None
for node in root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
if node.localName in PARSERS:
widget = PARSERS[node.localName](self.window, self.parent,
self.attrs, screen)
wid, child, buttons, on_write = widget.parse(screen.resource,
node, fields)
screen.set_on_write(on_write)
res = PARSERS2[node.localName](self.window, screen, wid, child,
buttons, toolbar)
res.title = widget.title
widget = res
break
else:
pass
return widget

View file

@ -0,0 +1 @@
from form import *

View file

@ -0,0 +1,124 @@
import time
import datetime as DT
import gtk
import gettext
import locale
from interface import Interface
from tryton.common import DT_FORMAT
_ = gettext.gettext
if not hasattr(locale, 'nl_langinfo'):
locale.nl_langinfo = lambda *a: '%x'
if not hasattr(locale, 'D_FMT'):
locale.D_FMT = None
class Calendar(Interface):
def __init__(self, name, parent, attrs=None):
super(Calendar, self).__init__(self, name, parent, attrs)
tooltips = gtk.Tooltips()
self.widget = gtk.HBox(spacing=3)
self.entry1 = gtk.Entry()
self.entry1.set_property('width-chars', 10)
self.entry1.set_property('activates_default', True)
tooltips.set_tip(self.entry1, _('Start date'))
self.widget.pack_start(self.entry1, expand=False, fill=True)
self.eb1 = gtk.EventBox()
tooltips.set_tip(self.eb1, _('Open the calendar widget'))
self.eb1.set_events(gtk.gdk.BUTTON_PRESS)
self.eb1.connect('button_press_event', self.cal_open, self.entry1,
parent)
img = gtk.Image()
img.set_from_stock('gtk-zoom-in', gtk.ICON_SIZE_MENU)
img.set_alignment(0.5, 0.5)
self.eb1.add(img)
self.widget.pack_start(self.eb1, expand=False, fill=False)
self.widget.pack_start(gtk.Label('-'), expand=False, fill=False)
self.entry2 = gtk.Entry()
self.entry2.set_property('width-chars', 10)
self.entry2.set_property('activates_default', True)
tooltips.set_tip(self.entry2, _('End date'))
self.widget.pack_start(self.entry2, expand=False, fill=True)
self.eb2 = gtk.EventBox()
tooltips.set_tip(self.eb2, _('Open the calendar widget'))
self.eb2.set_events(gtk.gdk.BUTTON_PRESS)
self.eb2.connect('button_press_event', self.cal_open, self.entry2,
parent)
img = gtk.Image()
img.set_from_stock('gtk-zoom-in', gtk.ICON_SIZE_MENU)
img.set_alignment(0.5, 0.5)
self.eb2.add(img)
self.widget.pack_start(self.eb2, expand=False, fill=False)
tooltips.enable()
def _date_get(self, value):
try:
date = time.strptime(value,
locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))
except:
return False
return time.strftime(DT_FORMAT, date)
def _value_get(self):
res = []
val = self.entry1.get_text()
if val:
res.append((self.name, '>=', self._date_get(val)))
val = self.entry2.get_text()
if val:
res.append((self.name, '<=', self._date_get(val)))
return res
def _value_set(self, value):
pass
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))
def cal_open(self, widget, event, dest, parent=None):
win = gtk.Dialog(_('Tiny ERP - Date selection'), parent,
gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
cal = gtk.Calendar()
cal.display_options(gtk.CALENDAR_SHOW_HEADING | \
gtk.CALENDAR_SHOW_DAY_NAMES | \
gtk.CALENDAR_SHOW_WEEK_NUMBERS)
cal.connect('day-selected-double-click',
lambda *x: win.response(gtk.RESPONSE_OK))
win.vbox.pack_start(cal, expand=True, fill=True)
win.show_all()
try:
val = self._date_get(dest.get_text())
if val:
cal.select_month(int(val[5:7])-1, int(val[0:4]))
cal.select_day(int(val[8:10]))
except ValueError:
pass
response = win.run()
if response == gtk.RESPONSE_OK:
year, month, day = cal.get_date()
date = DT.date(year, month+1, day)
dest.set_text(date.strftime(
locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')))
win.destroy()
def clear(self):
self.value = ''
def sig_activate(self, fct):
self.entry1.connect_after('activate', fct)
self.entry2.connect_after('activate', fct)

View file

@ -0,0 +1,41 @@
import gtk
import gettext
from interface import Interface
_ = gettext.gettext
class Char(Interface):
def __init__(self, name, parent, attrs=None):
if attrs is None:
attrs = {}
super(Char, self).__init__(self, name, parent, attrs)
self.widget = gtk.Entry()
self.widget.set_max_length(int(attrs.get('size', 16)))
self.widget.set_width_chars(5)
self.widget.set_property('activates_default', True)
def _value_get(self):
value = self.widget.get_text()
if value:
return [(self.name, self.attrs.get('comparator', 'ilike'), value)]
else:
return []
def _value_set(self, value):
self.widget.set_text(value)
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))
def clear(self):
self.value = ''
def _readonly_set(self, value):
self.widget.set_editable(not value)
self.widget.set_sensitive(not value)
def sig_activate(self, fct):
self.widget.connect_after('activate', fct)

View file

@ -0,0 +1,32 @@
import gtk
import gettext
from interface import Interface
_ = gettext.gettext
class CheckBox(Interface):
def __init__(self, name, parent, attrs=None):
super(CheckBox, self).__init__(name, parent, attrs)
self.widget = gtk.combo_box_entry_new_text()
self.widget.append_text('')
self.widget.append_text(_('Yes'))
self.widget.append_text(_('No'))
self.entry = self.widget.child
self.entry.set_property('activates_default', True)
self.entry.set_editable(False)
def _value_get(self):
val = self.entry.get_text()
if val:
return [(self.name, '=', int(val == _('Yes')))]
return []
def _value_set(self, value):
pass
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))

View file

@ -0,0 +1,309 @@
import gtk
from xml.parsers import expat
import sys
import gettext
_ = gettext.gettext
class _container(object):
def __init__(self, max_width):
self.cont = []
self.max_width = max_width
self.width = {}
self.count = 0
self.col = 0
def new(self, col=8):
self.col = col+1
table = gtk.Table(1, col)
table.set_homogeneous(False)
table.set_col_spacings(3)
table.set_row_spacings(0)
table.set_border_width(1)
self.cont.append( (table, 1, 0) )
def get(self):
return self.cont[-1][0]
def pop(self):
return self.cont.pop()[0]
def newline(self):
(table, i, j) = self.cont[-1]
if i > 0:
self.cont[-1] = (table, 1, j+1)
table.resize(j + 1, self.col)
def wid_add(self, widget, length=1, name=None, expand=False, ypadding=0):
self.count += 1
(table, i, j) = self.cont[-1]
if length > self.col:
length = self.col
if length + i > self.col:
self.newline()
(table, i, j) = self.cont[-1]
if name:
vbox = gtk.VBox(homogeneous=False, spacing=1)
label = gtk.Label(name)
label.set_alignment(0.0, 0.5)
vbox.pack_start(label, expand=False)
vbox.pack_start(widget, expand=expand, fill=True)
wid = vbox
else:
wid = widget
yopt = False
if expand:
yopt = yopt | gtk.EXPAND |gtk.FILL
table.attach(wid, i, i+length, j, j+1,
yoptions=yopt, xoptions=gtk.FILL|gtk.EXPAND,
ypadding=ypadding, xpadding=5)
self.cont[-1] = (table, i+length, j)
width = 750
if widget:
width = widget.size_request()[0]
self.width[('%d.%d') % (i, j)] = width
return wid
class Parse(object):
def __init__(self, parent, fields, model=''):
self.fields = fields
self.parent = parent
self.model = model
self.col = 8
self.focusable = None
self.add_widget_end = []
self.container = None
self.button_param = gtk.Button()
self.spin_limit = gtk.SpinButton(climb_rate=1, digits=0)
self.spin_offset = gtk.SpinButton(climb_rate=1, digits=0)
self.title = 'Form'
self.notebooks = []
self.dict_widget = {}
self.hb_param = None
def _psr_start(self, name, attrs):
if name in ('form','tree'):
self.title = attrs.get('string', self.title)
self.container.new(self.col)
elif name=='field':
val = attrs.get('select', False) \
or self.fields[str(attrs['name'])].get('select', False)
if val:
if int(val) <= 1:
self.add_widget(attrs, val)
else:
self.add_widget_end.append((attrs, val))
def add_widget(self, attrs, val):
ftype = attrs.get('widget', self.fields[str(attrs['name'])]['ftype'])
self.fields[str(attrs['name'])].update(attrs)
self.fields[str(attrs['name'])]['model']=self.model
if ftype not in WIDGETS_TYPE:
return False
widget_act = WIDGETS_TYPE[ftype][0](str(attrs['name']), self.parent,
self.fields[attrs['name']])
if 'string' in self.fields[str(attrs['name'])]:
label = self.fields[str(attrs['name'])]['string']+' :'
else:
label = None
size = WIDGETS_TYPE[ftype][1]
if not self.focusable:
self.focusable = widget_act.widget
wid = self.container.wid_add(widget_act.widget, size, label,
int(self.fields[str(attrs['name'])].get('expand',0)))
if int(val) <= 1:
wid.show()
self.dict_widget[str(attrs['name'])] = (widget_act, wid, int(val))
def add_parameters(self):
hb_param = gtk.HBox(spacing=3)
hb_param.pack_start(gtk.Label(_('Limit :')), expand=False, fill=False)
self.spin_limit.set_numeric(False)
self.spin_limit.set_adjustment(gtk.Adjustment(value=80, lower=1,
upper=sys.maxint, step_incr=10, page_incr=100, page_size=100))
self.spin_limit.set_property('visible', True)
hb_param.pack_start(self.spin_limit, expand=False, fill=False)
hb_param.pack_start(gtk.Label(_('Offset :')), expand=False, fill=False)
self.spin_offset.set_numeric(False)
self.spin_offset.set_adjustment(gtk.Adjustment(value=0, lower=0,
upper=sys.maxint, step_incr=80, page_incr=100, page_size=100))
hb_param.pack_start(self.spin_offset, expand=False, fill=False)
return hb_param
def _psr_end(self, name):
pass
def _psr_char(self, name):
pass
def parse(self, xml_data, max_width):
psr = expat.ParserCreate()
psr.StartElementHandler = self._psr_start
psr.EndElementHandler = self._psr_end
psr.CharacterDataHandler = self._psr_char
self.container = _container(max_width)
psr.Parse(xml_data)
for i in self.add_widget_end:
self.add_widget(*i)
self.add_widget_end = []
img = gtk.Image()
img.set_from_stock('gtk-add', gtk.ICON_SIZE_BUTTON)
self.button_param.set_image(img)
self.button_param.set_relief(gtk.RELIEF_NONE)
self.button_param.set_alignment(0.5, 0.5)
table = self.container.get()
table.attach(self.button_param, 0, 1, 0, 1,
yoptions=gtk.FILL, xoptions=gtk.FILL, ypadding=2, xpadding=0)
self.hb_param = self.container.wid_add(self.add_parameters(), l=8,
name=_('Parameters :'))
return (self.dict_widget, self.container.pop())
class Form(object):
def __init__(self, xml, fields, model=None, parent=None, domain=None,
call=None):
if domain is None:
domain = []
parser = Parse(parent, fields, model=model)
self.parent = parent
self.fields = fields
self.model = model
self.parser = parser
self.call = call
#get the size of the window and the limite / decalage Hbox element
width = 640
if self.parent:
width = self.parent.size_request()[0]
(self.widgets, self.widget) = parser.parse(xml, width)
self.widget.show_all()
self.hb_param = parser.hb_param
self.button_param = parser.button_param
self.button_param.connect('clicked', self.toggle)
self.spin_limit = parser.spin_limit
self.spin_limit.connect('value-changed', self.limit_changed)
self.spin_limit.set_activates_default(True)
self.spin_offset = parser.spin_offset
self.spin_offset.set_activates_default(True)
self.focusable = parser.focusable
self.id = 0
self.name = parser.title
self._hide = True
self.hide()
for i in domain:
if i[0] in self.widgets:
if i[1] == '=':
self.widgets[i[0]][0]._readonly_set(True)
for i in self.widgets.values():
i[0].sig_activate(self.sig_activate)
self.spin_limit.connect_after('activate', self.sig_activate)
self.spin_offset.connect_after('activate', self.sig_activate)
def clear(self, widget):
self.id = 0
for i in self.widgets.values():
i[0].clear()
def show(self):
for i, widget, value in self.widgets.values():
if value >= 2:
widget.show()
self.hb_param.show()
self._hide = False
def hide(self):
for i, widget, value in self.widgets.values():
if value >= 2:
widget.hide()
self.hb_param.hide()
self._hide = True
def toggle(self, widget):
img = gtk.Image()
if self._hide:
self.show()
img.set_from_stock('gtk-remove', gtk.ICON_SIZE_BUTTON)
widget.set_image(img)
else:
self.hide()
img.set_from_stock('gtk-add', gtk.ICON_SIZE_BUTTON)
widget.set_image(img)
def limit_changed(self, widget):
self.spin_offset.set_increments(step=self.spin_limit.get_value(),
page=100)
def set_limit(self, value):
return self.spin_limit.set_value(value)
def get_limit(self):
return self.spin_limit.get_value()
def get_offset(self):
return self.spin_offset.get_value()
def sig_activate(self, *args):
if self.call:
obj, fct = self.call
fct(obj)
def _value_get(self):
res = []
for i in self.widgets:
res += self.widgets[i][0].value
return res
def _value_set(self, value):
for i in value:
if i in self.widgets:
self.widgets[i][0].value = value[i]
value = property(_value_get, _value_set, None,
_('The content of the form or excpetion if not valid'))
import calendar
import spinbutton
import spinint
import selection
import char
import checkbox
import reference
WIDGETS_TYPE = {
'date': (calendar.Calendar, 2),
'datetime': (calendar.Calendar, 2),
'float': (spinbutton.SpinButton, 2),
'integer': (spinint.SpinInt, 2),
'selection': (selection.Selection, 2),
'many2one_selection': (selection.Selection, 2),
'char': (char.Char, 2),
'boolean': (checkbox.CheckBox, 2),
'reference': (reference.Reference, 2),
'text': (char.Char, 2),
'email': (char.Char, 2),
'url': (char.Char, 2),
'many2one': (char.Char, 2),
'one2many': (char.Char, 2),
'one2many_form': (char.Char, 2),
'one2many_list': (char.Char, 2),
'many2many_edit': (char.Char, 2),
'many2many': (char.Char, 2),
'callto': (char.Char, 2),
'sip': (char.Char, 2),
}

View file

@ -0,0 +1,34 @@
import gettext
_ = gettext.gettext
class Interface(object):
"Interface for search widget"
def __init__(self, name, parent, attrs=None):
if attrs is None:
attrs = {}
self._value = None
self.parent = parent
self.name = name
self.model = attrs.get('model', None)
self.attrs = attrs
def clear(self):
self.value = ''
def _value_get(self):
return self._value
def _value_set(self, value):
self._value = value
value = property(_value_get, _value_set, None,
_('The content of the widget or excpetion if not valid'))
def _readonly_set(self, value):
pass
def sig_activate(self, fct):
pass

View file

@ -0,0 +1,56 @@
import gtk
import gettext
from interface import Interface
_ = gettext.gettext
class Reference(Interface):
def __init__(self, name, parent, attrs=None):
if attrs is None:
attrs = {}
super(Reference, self).__init__(name, parent, attrs)
self.widget = gtk.combo_box_entry_new_text()
self.widget.child.set_editable(False)
self.set_popdown(attrs.get('selection', []))
self._selection = {}
def get_model(self):
res = self.widget.child.get_text()
return self._selection.get(res, False)
def set_popdown(self, selection):
model = self.widget.get_model()
model.clear()
lst = []
for (i, j) in selection:
name = str(j)
if type(i) == type(1):
name += ' ('+str(i)+')'
lst.append(name)
self._selection[name] = i
self.widget.append_text('')
for name in lst:
self.widget.append_text(name)
return lst
def _value_get(self):
if self.get_model():
return [(self.name, 'like', self.get_model() + ',')]
else:
return []
def _value_set(self, value):
if value == False:
value = ''
for sel in self._selection:
if self._selection[sel] == value:
self.widget.child.set_text(sel)
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))
def clear(self):
self.value = ''

View file

@ -0,0 +1,56 @@
import gtk
from interface import Interface
class Selection(Interface):
def __init__(self, name, parent, attrs=None):
if attrs is None:
attrs = {}
super(Selection, self).__init__(self, name, parent, attrs)
self.widget = gtk.combo_box_entry_new_text()
self.widget.child.set_editable(False)
self._selection = {}
if 'selection' in attrs:
self.set_popdown(attrs.get('selection', []))
def set_popdown(self, selection):
model = self.widget.get_model()
model.clear()
self._selection = {}
lst = []
for (i, j) in selection:
name = str(j)
if type(i) == type(1):
name += ' (' + str(i) + ')'
lst.append(name)
self._selection[name] = i
self.widget.append_text('')
for name in lst:
self.widget.append_text(name)
return lst
def _value_get(self):
model = self.widget.get_model()
index = self.widget.get_active()
if index >= 0:
res = self._selection.get(model[index][0], False)
if res:
return [(self.name, '=', res)]
return []
def _value_set(self, value):
if value == False:
value = ''
for sel in self._selection:
if self._selection[sel] == value:
self.widget.child.set_text(sel)
def clear(self):
self.value = ''
value = property(_value_get, _value_set, None,
'The content of the widget or ValueError if not valid')
def _readonly_set(self, value):
self.widget.set_sensitive(not value)

View file

@ -0,0 +1,64 @@
import gtk
import sys
import gettext
from interface import Interface
_ = gettext.gettext
class SpinButton(Interface):
def __init__(self, name, parent, attrs=None):
if attrs is None:
attrs = {}
super(SpinButton, self).__init__(self, name, parent, attrs)
self.widget = gtk.HBox(spacing=3)
adj1 = gtk.Adjustment(0.0, -sys.maxint, sys.maxint, 1.0, 5.0, 5.0)
self.spin1 = gtk.SpinButton(adj1, 1.0,
digits=int(attrs.get('digits', (14, 2))[1]))
self.spin1.set_numeric(True)
self.spin1.set_activates_default(True)
self.widget.pack_start(self.spin1, expand=False, fill=True)
self.widget.pack_start(gtk.Label('-'), expand=False, fill=False)
adj2 = gtk.Adjustment(0.0, -sys.maxint, sys.maxint, 1.0, 5.0, 5.0)
self.spin2 = gtk.SpinButton(adj2, 1.0,
digits=int(attrs.get('digits', (14, 2))[1]))
self.spin2.set_numeric(True)
self.spin2.set_activates_default(True)
self.widget.pack_start(self.spin2, expand=False, fill=True)
def _value_get(self):
res = []
self.spin1.update()
self.spin2.update()
if self.spin1.get_value() > self.spin2.get_value():
if self.spin2.get_value() != 0.0:
res.append((self.name, '>=', self.spin2.get_value()))
res.append((self.name, '<=', self.spin1.get_value()))
else:
res.append((self.name, '>=', self.spin1.get_value()))
elif self.spin2.get_value() > self.spin1.get_value():
res.append((self.name, '<=', self.spin2.get_value()))
res.append((self.name, '>=', self.spin1.get_value()))
elif (self.spin2.get_value() == self.spin1.get_value()) \
and (self.spin1.get_value() != 0.0):
res.append((self.name, '=', self.spin1.get_value()))
return res
def _value_set(self, value):
self.spin1.set_value(value)
self.spin2.set_value(value)
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))
def clear(self):
self.value = 0.00
def sig_activate(self, fct):
self.spin1.connect_after('activate', fct)
self.spin2.connect_after('activate', fct)

View file

@ -0,0 +1,56 @@
import gtk
import gettext
import sys
from interface import Interface
_ = gettext.gettext
class SpinInt(Interface):
def __init__(self, name, parent, attrs=None):
super(Spinint, self).__init__(name, parent, attrs=attrs)
self.widget = gtk.HBox(spacing=3)
adj1 = gtk.Adjustment(0.0, 0.0, sys.maxint, 1.0, 5.0, 5.0)
self.spin1 = gtk.SpinButton(adj1, 1, digits=0)
self.spin1.set_numeric(True)
self.spin1.set_activates_default(True)
self.widget.pack_start(self.spin1, expand=False, fill=True)
self.widget.pack_start(gtk.Label('-'), expand=False, fill=False)
adj2 = gtk.Adjustment(0.0, 0.0, sys.maxint, 1.0, 5.0, 5.0)
self.spin2 = gtk.SpinButton(adj2, 1, digits=0)
self.spin2.set_numeric(True)
self.spin2.set_activates_default(True)
self.widget.pack_start(self.spin2, expand=False, fill=True)
def _value_get(self):
res = []
self.spin1.update()
self.spin2.update()
if self.spin1.get_value_as_int() > self.spin2.get_value_as_int():
if self.spin2.get_value_as_int() != 0:
res.append((self.name, '>=', self.spin2.get_value_as_int()))
res.append((self.name, '<=', self.spin1.get_value_as_int()))
else:
res.append((self.name, '>=', self.spin1.get_value_as_int()))
elif self.spin2.get_value_as_int() > self.spin1.get_value_as_int():
res.append((self.name, '<=', self.spin2.get_value_as_int()))
res.append((self.name, '>=', self.spin1.get_value_as_int()))
elif (self.spin2.get_value_as_int() == self.spin1.get_value_as_int()) \
and (self.spin1.get_value_as_int() != 0):
res.append((self.name, '=', self.spin1.get_value_as_int()))
return res
def _value_set(self, value):
self.spin1.set_value(value)
self.spin2.set_value(value)
value = property(_value_get, _value_set, None,
_('The content of the widget or ValueError if not valid'))
def clear(self):
self.value = 0.0
def sig_activate(self, fct):
self.spin1.connect_after('activate', fct)
self.spin2.connect_after('activate', fct)