Coming from the stable 4.2, this patch allows the user to associate an application...
[openerp-client.git] / bin / widget / view / form_gtk / many2one.py
blobf14432a76df89c255faf1e160230676b49a98b26
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
6 # $Id$
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
13 # Service Company
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ##############################################################################
31 import gobject
32 import gtk
33 import copy
35 import gettext
37 import interface
38 import wid_common
39 import common
41 import widget
42 from widget.screen import Screen
44 from modules.gui.window.win_search import win_search
45 import rpc
47 import service
50 class dialog(object):
51 def __init__(self, model, id=None, attrs=None ,domain=None, context=None, window=None, view_ids=None,target=False):
52 if attrs is None:
53 attrs = {}
54 if domain is None:
55 domain = []
56 if context is None:
57 context = {}
59 if not window:
60 window = service.LocalService('gui.main').window
62 self.dia = gtk.Dialog(_('Tiny ERP - Link'), window,
63 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
64 self.window = window
65 if ('string' in attrs) and attrs['string']:
66 self.dia.set_title(self.dia.get_title() + ' - ' + attrs['string'])
67 if not target:
68 self.dia.set_property('default-width', 760)
69 self.dia.set_property('default-height', 500)
70 self.dia.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
71 self.dia.set_icon(common.TINYERP_ICON)
73 self.accel_group = gtk.AccelGroup()
74 self.dia.add_accel_group(self.accel_group)
76 self.but_cancel = self.dia.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
77 self.but_cancel.add_accelerator('clicked', self.accel_group, gtk.keysyms.Escape, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
79 self.but_ok = self.dia.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
80 self.but_ok.add_accelerator('clicked', self.accel_group, gtk.keysyms.Return, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
82 scroll = gtk.ScrolledWindow()
83 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
84 scroll.set_placement(gtk.CORNER_TOP_LEFT)
85 scroll.set_shadow_type(gtk.SHADOW_NONE)
86 self.dia.vbox.pack_start(scroll, expand=True, fill=True)
88 vp = gtk.Viewport()
89 vp.set_shadow_type(gtk.SHADOW_NONE)
90 scroll.add(vp)
91 self.screen = Screen(model, view_ids=view_ids, domain=domain, context=context, window=self.dia, view_type=['form'])
92 if id:
93 self.screen.load([id])
94 else:
95 self.screen.new()
96 vp.add(self.screen.widget)
98 x,y = self.screen.screen_container.size_get()
99 width, height = window.get_size()
100 vp.set_size_request(min(width - 20, x + 20),min(height - 60, y + 25))
101 self.dia.show_all()
102 self.screen.display()
104 def run(self, datas={}):
105 while True:
106 res = self.dia.run()
107 if res==gtk.RESPONSE_OK:
108 if self.screen.current_model.validate() and self.screen.save_current():
109 return (True, self.screen.current_model.name_get())
110 else:
111 self.screen.display()
112 else:
113 break
114 return (False, False)
116 def destroy(self):
117 self.window.present()
118 self.dia.destroy()
120 class many2one(interface.widget_interface):
121 def __init__(self, window, parent, model, attrs={}):
122 interface.widget_interface.__init__(self, window, parent, model, attrs)
124 self.widget = gtk.HBox(spacing=3)
125 self.widget.set_property('sensitive', True)
126 self.widget.connect('focus-in-event', lambda x,y: self._focus_in())
127 self.widget.connect('focus-out-event', lambda x,y: self._focus_out())
129 self.wid_text = gtk.Entry()
130 self.wid_text.set_property('width-chars', 13)
131 self.wid_text.connect('key_press_event', self.sig_key_press)
132 self.wid_text.connect('button_press_event', self._menu_open)
133 self.wid_text.connect_after('changed', self.sig_changed)
134 self.wid_text.connect_after('activate', self.sig_activate)
135 self.wid_text_focus_out_id = self.wid_text.connect_after('focus-out-event', self.sig_activate, True)
136 self.widget.pack_start(self.wid_text, expand=True, fill=True)
138 self.but_new = gtk.Button()
139 img_new = gtk.Image()
140 img_new.set_from_stock('gtk-new',gtk.ICON_SIZE_BUTTON)
141 self.but_new.set_image(img_new)
142 self.but_new.set_relief(gtk.RELIEF_NONE)
143 self.but_new.connect('clicked', self.sig_new)
144 self.but_new.set_alignment(0.5, 0.5)
145 self.but_new.set_property('can-focus', False)
146 self.widget.pack_start(self.but_new, expand=False, fill=False)
148 self.but_open = gtk.Button()
149 img_find = gtk.Image()
150 img_find.set_from_stock('gtk-find',gtk.ICON_SIZE_BUTTON)
151 img_open = gtk.Image()
152 img_open.set_from_stock('gtk-open',gtk.ICON_SIZE_BUTTON)
153 self.but_open.set_image(img_find)
154 self.but_open.set_relief(gtk.RELIEF_NONE)
155 self.but_open.connect('clicked', self.sig_edit)
156 self.but_open.set_alignment(0.5, 0.5)
157 self.but_open.set_property('can-focus', False)
158 self.widget.pack_start(self.but_open, padding=2, expand=False, fill=False)
160 self.tooltips = gtk.Tooltips()
161 self.tooltips.set_tip(self.but_new, _('Create a new resource'))
162 self.tooltips.set_tip(self.but_open, _('Open a resource'))
163 self.tooltips.enable()
165 self.ok = True
166 self._readonly = False
167 self.model_type = attrs['relation']
168 self._menu_loaded = False
169 self._menu_entries.append((None, None, None))
170 self._menu_entries.append((_('Action'), lambda x: self.click_and_action('client_action_multi'),0))
171 self._menu_entries.append((_('Report'), lambda x: self.click_and_action('client_print_multi'),0))
174 if attrs.get('completion',False):
175 ids = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['relation'], 'name_search', '', [], 'ilike', {})
176 if ids:
177 self.load_completion(ids,attrs)
179 def load_completion(self,ids,attrs):
180 self.completion = gtk.EntryCompletion()
181 self.completion.set_match_func(self.match_func, None)
182 self.completion.connect("match-selected", self.on_completion_match)
183 self.wid_text.set_completion(self.completion)
184 self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
185 self.completion.set_model(self.liststore)
186 self.completion.set_text_column(0)
187 for i,word in enumerate(ids):
188 if word[1][0] == '[':
189 i = word[1].find(']')
190 s = word[1][1:i]
191 s2 = word[1][i+2:]
192 self.liststore.append([("%s %s" % (s,s2)),s2])
193 else:
194 self.liststore.append([word[1],word[1]])
196 def match_func(self, completion, key_string, iter, data):
197 model = self.completion.get_model()
198 modelstr = model[iter][0].lower()
199 return modelstr.startswith(key_string)
201 def on_completion_match(self, completion, model, iter):
202 name = model[iter][1]
203 domain = self._view.modelfield.domain_get(self._view.model)
204 context = self._view.modelfield.context_get(self._view.model)
205 ids = rpc.session.rpc_exec_auth('/object', 'execute',
206 self.attrs['relation'], 'name_search', name, domain, 'ilike',
207 context)
208 if len(ids)==1:
209 self._view.modelfield.set_client(self._view.model, ids[0])
210 self.display(self._view.model, self._view.modelfield)
211 self.ok = True
212 else:
213 win = win_search(self.attrs['relation'], sel_multi=False,
214 ids=map(lambda x: x[0], ids), context=context,
215 domain=domain, window=self._window)
216 ids = win.go()
217 if ids:
218 name = rpc.session.rpc_exec_auth('/object', 'execute',
219 self.attrs['relation'], 'name_get', [ids[0]],
220 rpc.session.context)[0]
221 self._view.modelfield.set_client(self._view.model, name)
222 return True
226 def _readonly_set(self, value):
227 self._readonly = value
228 self.wid_text.set_editable(not value)
229 self.but_new.set_sensitive(not value)
231 def _color_widget(self):
232 return self.wid_text
234 def _menu_sig_pref(self, obj):
235 self._menu_sig_default_set()
237 def _menu_sig_default(self, obj):
238 res = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['model'], 'default_get', [self.attrs['name']])
240 def sig_activate(self, widget, event=None, leave=False):
241 self.ok = False
242 value = self._view.modelfield.get(self._view.model)
244 self.wid_text.disconnect(self.wid_text_focus_out_id)
245 if value:
246 if not leave:
247 domain = self._view.modelfield.domain_get(self._view.model)
248 context = self._view.modelfield.context_get(self._view.model)
249 dia = dialog(self.attrs['relation'], self._view.modelfield.get(self._view.model), attrs=self.attrs, window=self._window, domain=domain, context=context)
250 ok, value = dia.run()
251 if ok:
252 self._view.modelfield.set_client(self._view.model, value,
253 force_change=True)
254 dia.destroy()
255 else:
256 if not self._readonly and ( self.wid_text.get_text() or not leave):
257 domain = self._view.modelfield.domain_get(self._view.model)
258 context = self._view.modelfield.context_get(self._view.model)
259 self.wid_text.grab_focus()
261 ids = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['relation'], 'name_search', self.wid_text.get_text(), domain, 'ilike', context)
262 if len(ids)==1:
263 self._view.modelfield.set_client(self._view.model, ids[0],
264 force_change=True)
265 self.wid_text_focus_out_id = self.wid_text.connect_after('focus-out-event', self.sig_activate, True)
266 self.display(self._view.model, self._view.modelfield)
267 self.ok = True
268 return True
270 win = win_search(self.attrs['relation'], sel_multi=False, ids=map(lambda x: x[0], ids), context=context, domain=domain, parent=self._window)
271 ids = win.go()
272 if ids:
273 name = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['relation'], 'name_get', [ids[0]], rpc.session.context)[0]
274 self._view.modelfield.set_client(self._view.model, name,
275 force_change=True)
276 self.wid_text_focus_out_id = self.wid_text.connect_after('focus-out-event', self.sig_activate, True)
277 self.display(self._view.model, self._view.modelfield)
278 self.ok=True
280 def sig_new(self, *args):
281 self.wid_text.disconnect(self.wid_text_focus_out_id)
282 domain = self._view.modelfield.domain_get(self._view.model)
283 context = self._view.modelfield.context_get(self._view.model)
284 dia = dialog(self.attrs['relation'], attrs=self.attrs, window=self._window, domain=domain ,context=context)
285 ok, value = dia.run()
286 if ok:
287 self._view.modelfield.set_client(self._view.model, value)
288 self.display(self._view.model, self._view.modelfield)
289 dia.destroy()
290 self.wid_text_focus_out_id = self.wid_text.connect_after('focus-out-event', self.sig_activate, True)
291 sig_edit = sig_activate
293 def sig_key_press(self, widget, event, *args):
294 if event.keyval==gtk.keysyms.F1:
295 self.sig_new(widget, event)
296 elif event.keyval==gtk.keysyms.F2:
297 self.sig_activate(widget, event)
298 elif event.keyval == gtk.keysyms.Tab:
299 if self._view.modelfield.get(self._view.model) or \
300 not self.wid_text.get_text():
301 return False
302 self.sig_activate(widget, event, leave=True)
303 return True
304 return False
306 def sig_changed(self, *args):
307 if self.ok:
308 if self._view.modelfield.get(self._view.model):
309 self._view.modelfield.set_client(self._view.model, False)
310 self.display(self._view.model, self._view.modelfield)
311 return False
313 def set_value(self, model, model_field):
314 pass # No update of the model, the model is updated in real time !
316 def display(self, model, model_field):
317 if not model_field:
318 self.ok = False
319 self.wid_text.set_text('')
320 return False
321 super(many2one, self).display(model, model_field)
322 self.ok=False
323 res = model_field.get_client(model)
324 self.wid_text.set_text((res and str(res)) or '')
325 img = gtk.Image()
326 if res:
327 img.set_from_stock('gtk-open',gtk.ICON_SIZE_BUTTON)
328 self.but_open.set_image(img)
329 self.tooltips.set_tip(self.but_open, _('Open a resource'))
330 else:
331 img.set_from_stock('gtk-find',gtk.ICON_SIZE_BUTTON)
332 self.but_open.set_image(img)
333 self.tooltips.set_tip(self.but_open, _('Search a resource'))
334 self.ok=True
336 def _menu_open(self, obj, event):
337 if event.button == 3:
338 value = self._view.modelfield.get(self._view.model)
339 if not self._menu_loaded:
340 resrelate = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.values', 'get', 'action', 'client_action_relate', [(self.model_type, False)], False, rpc.session.context)
341 resrelate = map(lambda x:x[2], resrelate)
342 self._menu_entries.append((None, None, None))
343 for x in resrelate:
344 x['string'] = x['name']
345 f = lambda action: lambda x: self.click_and_relate(action)
346 self._menu_entries.append(('... '+x['name'], f(x), 0))
347 self._menu_loaded = True
349 menu = gtk.Menu()
350 for stock_id,callback,sensitivity in self._menu_entries:
351 if stock_id:
352 item = gtk.ImageMenuItem(stock_id)
353 if callback:
354 item.connect("activate",callback)
355 item.set_sensitive(bool(sensitivity or value))
356 else:
357 item=gtk.SeparatorMenuItem()
358 item.show()
359 menu.append(item)
360 menu.popup(None,None,None,event.button,event.time)
361 return True
362 return False
364 def click_and_relate(self, action):
365 data={}
366 context={}
367 act=action.copy()
368 id = self._view.modelfield.get(self._view.model)
369 if not(id):
370 common.message(_('You must select a record to use the relation !'))
371 return False
372 screen = Screen(self.attrs['relation'])
373 screen.load([id])
374 act['domain'] = screen.current_model.expr_eval(act['domain'], check_load=False)
375 act['context'] = str(screen.current_model.expr_eval(act['context'], check_load=False))
376 obj = service.LocalService('action.main')
377 value = obj._exec_action(act, data, context)
378 return value
380 def click_and_action(self, type):
381 id = self._view.modelfield.get(self._view.model)
382 obj = service.LocalService('action.main')
383 res = obj.exec_keyword(type, {'model':self.model_type, 'id': id or False, 'ids':[id], 'report_type': 'pdf'})
384 return True
387 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: