1 ##############################################################################
3 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ##############################################################################
37 from rpc
import RPCProxy
38 from editabletree
import EditableTreeView
39 from decoratedtree
import DecoratedTreeView
40 from widget
.view
import interface
44 from widget
.view
.form_gtk
.many2one
import dialog
as M2ODialog
45 from modules
.gui
.window
.win_search
import win_search
52 def send_keys(renderer
, editable
, position
, treeview
):
53 editable
.connect('key_press_event', treeview
.on_keypressed
)
54 editable
.editing_done_id
= editable
.connect('editing_done', treeview
.on_editing_done
)
55 if isinstance(editable
, gtk
.ComboBoxEntry
):
56 editable
.connect('changed', treeview
.on_editing_done
)
58 def sort_model(column
, treeview
):
59 model
= treeview
.get_model()
60 model
.sort(column
.name
)
62 class parser_tree(interface
.parser_interface
):
63 def parse(self
, model
, root_node
, fields
):
65 attrs
= tools
.node_attributes(root_node
)
66 on_write
= attrs
.get('on_write', '')
67 editable
= attrs
.get('editable', False)
69 treeview
= EditableTreeView(editable
)
71 treeview
= DecoratedTreeView(editable
)
72 treeview
.colors
= dict()
73 self
.treeview
= treeview
74 for color_spec
in attrs
.get('colors', '').split(';'):
76 colour
, test
= color_spec
.split(':')
77 treeview
.colors
[colour
] = test
78 treeview
.set_property('rules-hint', True)
80 self
.title
= attrs
.get('string', 'Unknown')
82 for node
in root_node
.childNodes
:
83 node_attrs
= tools
.node_attributes(node
)
84 if node
.localName
== 'field':
85 fname
= str(node_attrs
['name'])
86 for boolean_fields
in ('readonly', 'required'):
87 if boolean_fields
in node_attrs
:
88 node_attrs
[boolean_fields
] = bool(int(node_attrs
[boolean_fields
]))
89 fields
[fname
].update(node_attrs
)
90 node_attrs
.update(fields
[fname
])
91 cell
= Cell(fields
[fname
]['type'])(fname
, treeview
, node_attrs
,
93 treeview
.cells
[fname
] = cell
94 renderer
= cell
.renderer
95 if editable
and not node_attrs
.get('readonly', False):
96 if isinstance(renderer
, gtk
.CellRendererToggle
):
97 renderer
.set_property('activatable', True)
99 renderer
.set_property('editable', True)
100 renderer
.connect_after('editing-started', send_keys
, treeview
)
101 # renderer.connect_after('editing-canceled', self.editing_canceled)
103 if isinstance(renderer
, gtk
.CellRendererToggle
):
104 renderer
.set_property('activatable', False)
106 col
= gtk
.TreeViewColumn(fields
[fname
]['string'], renderer
)
108 col
._type
= fields
[fname
]['type']
109 col
.set_cell_data_func(renderer
, cell
.setter
)
110 col
.set_clickable(True)
123 if 'width' in fields
[fname
]:
124 width
= int(fields
[fname
]['width'])
126 width
= twidth
.get(fields
[fname
]['type'], 100)
127 col
.set_min_width(width
)
128 col
.connect('clicked', sort_model
, treeview
)
129 col
.set_resizable(True)
130 #col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
131 visval
= eval(fields
[fname
].get('invisible', 'False'), {'context':self
.screen
.context
})
132 col
.set_visible(not visval
)
133 n
= treeview
.append_column(col
)
134 if 'sum' in fields
[fname
] and fields
[fname
]['type'] \
135 in ('integer', 'float', 'float_time'):
137 label
.set_use_markup(True)
138 label_str
= fields
[fname
]['sum'] + ': '
139 label_bold
= bool(int(fields
[fname
].get('sum_bold', 0)))
141 label
.set_markup('<b>%s</b>' % label_str
)
143 label
.set_markup(label_str
)
144 label_sum
= gtk
.Label()
145 label_sum
.set_use_markup(True)
146 dict_widget
[n
] = (fname
, label
, label_sum
,
147 fields
.get('digits', (16,2))[1], label_bold
)
148 return treeview
, dict_widget
, [], on_write
150 class UnsettableColumn(Exception):
154 def __new__(self
, type):
155 klass
= CELLTYPES
.get(type, CELLTYPES
['char'])
160 def __init__(self
, field_name
, treeview
=None, attrs
=None, window
=None):
161 self
.field_name
= field_name
162 self
.attrs
= attrs
or {}
163 self
.renderer
= gtk
.CellRendererText()
164 self
.treeview
= treeview
166 window
= service
.LocalService('gui.main').window
169 def setter(self
, column
, cell
, store
, iter):
170 model
= store
.get_value(iter, 0)
171 text
= self
.get_textual_value(model
)
172 cell
.set_property('text', text
)
173 color
= self
.get_color(model
)
174 cell
.set_property('foreground', str(color
))
175 if self
.attrs
['type'] in ('float', 'integer', 'boolean'):
179 if self
.treeview
.editable
:
180 field
= model
[self
.field_name
]
181 if not field
.get_state_attrs(model
).get('valid', True):
182 cell
.set_property('background', common
.colors
.get('invalid', 'white'))
183 elif bool(int(field
.get_state_attrs(model
).get('required', 0))):
184 cell
.set_property('background', common
.colors
.get('required', 'white'))
185 cell
.set_property('xalign', align
)
187 def get_color(self
, model
):
189 for color
, expr
in self
.treeview
.colors
.items():
190 if model
.expr_eval(expr
, check_load
=False):
193 return to_display
or 'black'
195 def open_remote(self
, model
, create
, changed
=False, text
=None):
196 raise NotImplementedError
198 def get_textual_value(self
, model
):
199 return model
[self
.field_name
].get_client(model
) or ''
201 def value_from_text(self
, model
, text
):
206 def value_from_text(self
, model
, text
):
209 def get_textual_value(self
, model
):
210 return locale
.format('%d',
211 model
[self
.field_name
].get_client(model
) or 0, True)
215 def __init__(self
, *args
):
216 super(Boolean
, self
).__init
__(*args
)
217 self
.renderer
= gtk
.CellRendererToggle()
218 self
.renderer
.connect('toggled', self
._sig
_toggled
)
220 def get_textual_value(self
, model
):
221 return model
[self
.field_name
].get_client(model
) or 0
223 def setter(self
, column
, cell
, store
, iter):
224 model
= store
.get_value(iter, 0)
225 value
= self
.get_textual_value(model
)
226 cell
.set_active(bool(value
))
228 def _sig_toggled(self
, renderer
, path
):
229 store
= self
.treeview
.get_model()
230 model
= store
.get_value(store
.get_iter(path
), 0)
231 field
= model
[self
.field_name
]
232 if not field
.get_state_attrs(model
).get('readonly', False):
233 value
= model
[self
.field_name
].get_client(model
)
234 model
[self
.field_name
].set_client(model
, int(not value
))
235 self
.treeview
.set_cursor(path
)
239 class GenericDate(Char
):
241 def get_textual_value(self
, model
):
242 value
= model
[self
.field_name
].get_client(model
)
245 date
= time
.strptime(value
, self
.server_format
)
246 return time
.strftime(self
.display_format
, date
)
248 def value_from_text(self
, model
, text
):
252 dt
= time
.strptime(text
, self
.display_format
)
255 dt
= list(time
.localtime())
260 return time
.strftime(self
.server_format
, dt
)
262 if not hasattr(locale
, 'nl_langinfo'):
263 locale
.nl_langinfo
= lambda *a
: '%x'
265 if not hasattr(locale
, 'D_FMT'):
268 class Date(GenericDate
):
269 server_format
= '%Y-%m-%d'
270 display_format
= locale
.nl_langinfo(locale
.D_FMT
).replace('%y', '%Y')
272 class Datetime(GenericDate
):
273 server_format
= '%Y-%m-%d %H:%M:%S'
274 display_format
= locale
.nl_langinfo(locale
.D_FMT
).replace('%y', '%Y')+' %H:%M:%S'
276 def get_textual_value(self
, model
):
277 value
= model
[self
.field_name
].get_client(model
)
280 date
= time
.strptime(value
, self
.server_format
)
281 if 'tz' in rpc
.session
.context
:
284 lzone
= pytz
.timezone(rpc
.session
.context
['tz'])
285 szone
= pytz
.timezone(rpc
.session
.timezone
)
286 dt
= DT
.datetime(date
[0], date
[1], date
[2], date
[3], date
[4], date
[5], date
[6])
287 sdt
= szone
.localize(dt
, is_dst
=True)
288 ldt
= sdt
.astimezone(lzone
)
289 date
= ldt
.timetuple()
292 return time
.strftime(self
.display_format
, date
)
294 def value_from_text(self
, model
, text
):
298 date
= time
.strptime(text
, self
.display_format
)
301 dt
= list(time
.localtime())
306 if 'tz' in rpc
.session
.context
:
309 lzone
= pytz
.timezone(rpc
.session
.context
['tz'])
310 szone
= pytz
.timezone(rpc
.session
.timezone
)
311 dt
= DT
.datetime(date
[0], date
[1], date
[2], date
[3], date
[4], date
[5], date
[6])
312 ldt
= lzone
.localize(dt
, is_dst
=True)
313 sdt
= ldt
.astimezone(szone
)
314 date
= sdt
.timetuple()
317 return time
.strftime(self
.server_format
, date
)
320 def get_textual_value(self
, model
):
321 interger
, digit
= self
.attrs
.get('digits', (16,2) )
322 return locale
.format('%.'+str(digit
)+'f',
323 model
[self
.field_name
].get_client(model
) or 0.0, True)
325 def value_from_text(self
, model
, text
):
327 return locale
.atof(text
)
331 from mx
.DateTime
import DateTimeDelta
333 class FloatTime(Char
):
334 def get_textual_value(self
, model
):
335 val
= model
[self
.field_name
].get_client(model
)
336 t
= '%02d:%02d' % (math
.floor(abs(val
)),round(abs(val
)%1+0.01,2) * 60)
341 def value_from_text(self
, model
, text
):
343 if text
and ':' in text
:
344 return round(int(text
.split(':')[0]) + int(text
.split(':')[1]) / 60.0,2)
346 return locale
.atof(text
)
353 def value_from_text(self
, model
, text
):
357 relation
= model
[self
.field_name
].attrs
['relation']
358 rpc
= RPCProxy(relation
)
360 domain
= model
[self
.field_name
].domain_get(model
)
361 context
= model
[self
.field_name
].context_get(model
)
363 names
= rpc
.name_search(text
, domain
, 'ilike', context
)
365 return self
.search_remote(relation
, [x
[0] for x
in names
],
366 domain
=domain
, context
=context
)[0]
369 def open_remote(self
, model
, create
=True, changed
=False, text
=None):
370 modelfield
= model
.mgroup
.mfields
[self
.field_name
]
371 relation
= modelfield
.attrs
['relation']
373 domain
=modelfield
.domain_get(model
)
374 context
=modelfield
.context_get(model
)
378 id = modelfield
.get(model
)
380 rpc
= RPCProxy(relation
)
382 names
= rpc
.name_search(text
, domain
, 'ilike', context
)
384 return True, names
[0]
385 searched
= self
.search_remote(relation
, [x
[0] for x
in names
], domain
=domain
, context
=context
)
387 return True, searched
389 dia
= M2ODialog(relation
, id, domain
=domain
, context
=context
,
391 ok
, value
= dia
.run()
398 def search_remote(self
, relation
, ids
=[], domain
=[], context
={}):
399 rpc
= RPCProxy(relation
)
401 win
= win_search(relation
, sel_multi
=False, ids
=ids
, context
=context
, domain
=domain
)
404 return rpc
.name_get([found
[0]], context
)[0]
410 def get_textual_value(self
, model
):
411 return '( '+str(len(model
[self
.field_name
].get_client(model
).models
)) + ' )'
413 def value_from_text(self
, model
, text
):
414 raise UnsettableColumn('Can not set column of type o2m')
418 def get_textual_value(self
, model
):
419 value
= model
[self
.field_name
].get_client(model
)
421 return '(%s)' % len(value
)
425 def value_from_text(self
, model
, text
):
428 if not (text
[0]<>'('):
429 return model
[self
.field_name
].get(model
)
430 relation
= model
[self
.field_name
].attrs
['relation']
431 rpc
= RPCProxy(relation
)
432 domain
= model
[self
.field_name
].domain_get(model
)
433 context
= model
[self
.field_name
].context_get(model
)
434 names
= rpc
.name_search(text
, domain
, 'ilike', context
)
435 ids
= [x
[0] for x
in names
]
436 win
= win_search(relation
, sel_multi
=True, ids
=ids
, context
=context
, domain
=domain
)
440 def open_remote(self
, model
, create
=True, changed
=False, text
=None):
441 modelfield
= model
[self
.field_name
]
442 relation
= modelfield
.attrs
['relation']
444 rpc
= RPCProxy(relation
)
445 context
= model
[self
.field_name
].context_get(model
)
446 domain
= model
[self
.field_name
].domain_get(model
)
448 if text
and len(text
) and text
[0]<>'(':
449 domain
.append(('name','=',text
))
450 ids
= rpc
.search(domain
)
451 if ids
and len(ids
)==1:
454 ids
= model
[self
.field_name
].get_client(model
)
455 win
= win_search(relation
, sel_multi
=True, ids
=ids
, context
=context
, domain
=domain
)
462 class Selection(Char
):
464 def __init__(self
, *args
):
465 super(Selection
, self
).__init
__(*args
)
466 self
.renderer
= gtk
.CellRendererCombo()
467 selection_data
= gtk
.ListStore(str, str)
468 for x
in self
.attrs
.get('selection', []):
469 selection_data
.append(x
)
470 self
.renderer
.set_property('model', selection_data
)
471 self
.renderer
.set_property('text-column', 1)
473 def get_textual_value(self
, model
):
474 selection
= dict(model
[self
.field_name
].attrs
['selection'])
475 return selection
.get(model
[self
.field_name
].get(model
), '')
477 def value_from_text(self
, model
, text
):
478 selection
= model
[self
.field_name
].attrs
['selection']
480 for val
, txt
in selection
:
481 if txt
[:len(text
)].lower() == text
.lower():
482 if len(txt
) == len(text
):
493 'selection': Selection
,
495 'float_time': FloatTime
,
497 'datetime': Datetime
,