mirror of
https://github.com/NaN-tic/tryton-gtk2.git
synced 2023-12-14 03:12:58 +01:00
Add some files for view_form
This commit is contained in:
parent
d338a50c01
commit
3780639e03
25 changed files with 3092 additions and 6 deletions
0
tryton/gui/window/view_form/model/__init__.py
Normal file
0
tryton/gui/window/view_form/model/__init__.py
Normal file
369
tryton/gui/window/view_form/model/field.py
Normal file
369
tryton/gui/window/view_form/model/field.py
Normal 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,
|
||||
}
|
282
tryton/gui/window/view_form/model/group.py
Normal file
282
tryton/gui/window/view_form/model/group.py
Normal 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
|
238
tryton/gui/window/view_form/model/record.py
Normal file
238
tryton/gui/window/view_form/model/record.py
Normal 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)
|
|
@ -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]
|
||||
|
|
0
tryton/gui/window/view_form/view/__init__.py
Normal file
0
tryton/gui/window/view_form/view/__init__.py
Normal file
0
tryton/gui/window/view_form/view/form_gtk/__init__.py
Normal file
0
tryton/gui/window/view_form/view/form_gtk/__init__.py
Normal file
177
tryton/gui/window/view_form/view/form_gtk/interface.py
Normal file
177
tryton/gui/window/view_form/view/form_gtk/interface.py
Normal 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
|
381
tryton/gui/window/view_form/view/form_gtk/many2one.py
Normal file
381
tryton/gui/window/view_form/view/form_gtk/many2one.py
Normal 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'})
|
26
tryton/gui/window/view_form/view/interface.py
Normal file
26
tryton/gui/window/view_form/view/interface.py
Normal 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
|
72
tryton/gui/window/view_form/view/screen_container.py
Normal file
72
tryton/gui/window/view_form/view/screen_container.py
Normal 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()
|
1
tryton/gui/window/view_form/view/tree_gtk/__init__.py
Normal file
1
tryton/gui/window/view_form/view/tree_gtk/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from parser import *
|
244
tryton/gui/window/view_form/view/tree_gtk/editabletree.py
Normal file
244
tryton/gui/window/view_form/view/tree_gtk/editabletree.py
Normal 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())
|
477
tryton/gui/window/view_form/view/tree_gtk/parser.py
Normal file
477
tryton/gui/window/view_form/view/tree_gtk/parser.py
Normal 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,
|
||||
}
|
46
tryton/gui/window/view_form/view/widget_parse.py
Normal file
46
tryton/gui/window/view_form/view/widget_parse.py
Normal 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
|
1
tryton/gui/window/view_form/widget_search/__init__.py
Normal file
1
tryton/gui/window/view_form/widget_search/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from form import *
|
124
tryton/gui/window/view_form/widget_search/calendar.py
Normal file
124
tryton/gui/window/view_form/widget_search/calendar.py
Normal 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)
|
41
tryton/gui/window/view_form/widget_search/char.py
Normal file
41
tryton/gui/window/view_form/widget_search/char.py
Normal 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)
|
32
tryton/gui/window/view_form/widget_search/checkbox.py
Normal file
32
tryton/gui/window/view_form/widget_search/checkbox.py
Normal 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'))
|
309
tryton/gui/window/view_form/widget_search/form.py
Normal file
309
tryton/gui/window/view_form/widget_search/form.py
Normal 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),
|
||||
}
|
34
tryton/gui/window/view_form/widget_search/interface.py
Normal file
34
tryton/gui/window/view_form/widget_search/interface.py
Normal 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
|
56
tryton/gui/window/view_form/widget_search/reference.py
Normal file
56
tryton/gui/window/view_form/widget_search/reference.py
Normal 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 = ''
|
56
tryton/gui/window/view_form/widget_search/selection.py
Normal file
56
tryton/gui/window/view_form/widget_search/selection.py
Normal 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)
|
64
tryton/gui/window/view_form/widget_search/spinbutton.py
Normal file
64
tryton/gui/window/view_form/widget_search/spinbutton.py
Normal 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)
|
56
tryton/gui/window/view_form/widget_search/spinint.py
Normal file
56
tryton/gui/window/view_form/widget_search/spinint.py
Normal 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)
|
Loading…
Reference in a new issue