fix
[openerp-client.git] / bin / modules / gui / main.py
blob5df28cc1e2ec19c2826e65f8e68cc9c89f8a9596
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 time
32 import os
33 import gettext
34 import urlparse
36 import gobject
37 import gtk
38 from gtk import glade
39 import translate
41 import rpc
43 import service
44 import options
45 import common
47 from window import win_preference, win_extension
48 import tools
49 import re
50 import xmlrpclib
51 import base64
53 def _refresh_dblist(db_widget, url, dbtoload=None):
54 if not dbtoload:
55 dbtoload = options.options['login.db']
56 index = 0
57 liststore = db_widget.get_model()
58 liststore.clear()
59 result = rpc.session.list_db(url)
60 if result==-1:
61 return -1
62 for db_num, db_name in enumerate(rpc.session.list_db(url)):
63 liststore.append([db_name])
64 if db_name == dbtoload:
65 index = db_num
66 db_widget.set_active(index)
67 return len(liststore)
69 def _refresh_langlist(lang_widget, url):
70 liststore = lang_widget.get_model()
71 liststore.clear()
72 lang_list = rpc.session.db_exec_no_except(url, 'list_lang')
73 lang_list.append( ('en_US','English') )
74 for key,val in lang_list:
75 liststore.insert(0, (val,key))
76 lang_widget.set_active(0)
77 return lang_list
79 def _server_ask(server_widget, parent=None):
80 result = False
81 win_gl = glade.XML(common.terp_path("openerp.glade"),"win_server",gettext.textdomain())
82 win = win_gl.get_widget('win_server')
83 if not parent:
84 parent = service.LocalService('gui.main').window
85 win.set_transient_for(parent)
86 win.set_icon(common.OPENERP_ICON)
87 win.show_all()
88 win.set_default_response(gtk.RESPONSE_OK)
89 host_widget = win_gl.get_widget('ent_host')
90 port_widget = win_gl.get_widget('ent_port')
91 protocol_widget = win_gl.get_widget('protocol')
93 protocol={'XML-RPC': 'http://',
94 'XML-RPC secure': 'https://',
95 'NET-RPC (faster)': 'socket://',}
96 listprotocol = gtk.ListStore(str)
97 protocol_widget.set_model(listprotocol)
100 m = re.match('^(http[s]?://|socket://)([\w.-]+):(\d{1,5})$', server_widget.get_text())
101 if m:
102 host_widget.set_text(m.group(2))
103 port_widget.set_text(m.group(3))
105 index = 0
106 i = 0
107 for p in protocol:
108 listprotocol.append([p])
109 if m and protocol[p] == m.group(1):
110 index = i
111 i += 1
112 protocol_widget.set_active(index)
114 res = win.run()
115 if res == gtk.RESPONSE_OK:
116 protocol = protocol[protocol_widget.get_active_text()]
117 url = '%s%s:%s' % (protocol, host_widget.get_text(), port_widget.get_text())
118 server_widget.set_text(url)
119 result = url
120 parent.present()
121 win.destroy()
122 return result
125 class db_login(object):
126 def __init__(self):
127 self.win_gl = glade.XML(common.terp_path("openerp.glade"),"win_login",gettext.textdomain())
129 def refreshlist(self, widget, db_widget, label, url, butconnect=False):
131 def check_server_version(url):
132 try:
133 import release
134 full_server_version = rpc.session.db_exec_no_except(url, 'server_version')
135 server_version = full_server_version.split('.')
136 client_version = release.version.split('.')
137 return (server_version[:2] == client_version[:2], full_server_version, release.version)
138 except:
139 # the server doesn't understand the request. It's mean that it's an old version of the server
140 return (False, _('Unknown'), release.version)
142 res = _refresh_dblist(db_widget, url)
143 if res == -1:
144 label.set_label('<b>'+_('Could not connect to server !')+'</b>')
145 db_widget.hide()
146 label.show()
147 if butconnect:
148 butconnect.set_sensitive(False)
149 else:
150 if res==0:
151 label.set_label('<b>'+_('No database found, you must create one !')+'</b>')
152 db_widget.hide()
153 label.show()
154 if butconnect:
155 butconnect.set_sensitive(False)
156 else:
157 label.hide()
158 db_widget.show()
159 if butconnect:
160 butconnect.set_sensitive(True)
162 is_same_version, server_version, client_version = check_server_version(url)
163 if not is_same_version:
164 common.warning(_('The versions of the server (%s) and the client (%s) missmatch. The client may not work properly. Use it at your own risks.') % (server_version, client_version,))
165 return res
167 def refreshlist_ask(self,widget, server_widget, db_widget, label, butconnect = False, url=False, parent=None):
168 url = _server_ask(server_widget, parent) or url
169 return self.refreshlist(widget, db_widget, label, url, butconnect)
171 def run(self, dbname=None, parent=None):
172 uid = 0
173 win = self.win_gl.get_widget('win_login')
174 if not parent:
175 parent = service.LocalService('gui.main').window
176 win.set_transient_for(parent)
177 win.set_icon(common.OPENERP_ICON)
178 win.show_all()
179 img = self.win_gl.get_widget('image_tinyerp')
180 img.set_from_file(common.terp_path_pixmaps('openerp.png'))
181 login = self.win_gl.get_widget('ent_login')
182 passwd = self.win_gl.get_widget('ent_passwd')
183 server_widget = self.win_gl.get_widget('ent_server')
184 but_connect = self.win_gl.get_widget('button_connect')
185 db_widget = self.win_gl.get_widget('combo_db')
186 change_button = self.win_gl.get_widget('but_server')
187 label = self.win_gl.get_widget('combo_label')
188 label.hide()
190 host = options.options['login.server']
191 port = options.options['login.port']
192 protocol = options.options['login.protocol']
194 url = '%s%s:%s' % (protocol, host, port)
195 server_widget.set_text(url)
196 login.set_text(options.options['login.login'])
198 # construct the list of available db and select the last one used
199 liststore = gtk.ListStore(str)
200 db_widget.set_model(liststore)
201 cell = gtk.CellRendererText()
202 db_widget.pack_start(cell, True)
203 db_widget.add_attribute(cell, 'text', 0)
205 res = self.refreshlist(None, db_widget, label, url, but_connect)
206 change_button.connect_after('clicked', self.refreshlist_ask, server_widget, db_widget, label, but_connect, url, win)
208 if dbname:
209 iter = liststore.get_iter_root()
210 while iter:
211 if liststore.get_value(iter, 0)==dbname:
212 db_widget.set_active_iter(iter)
213 break
214 iter = liststore.iter_next(iter)
216 res = win.run()
217 m = re.match('^(http[s]?://|socket://)([\w.\-]+):(\d{1,5})$', server_widget.get_text() or '')
218 if m:
219 options.options['login.server'] = m.group(2)
220 options.options['login.login'] = login.get_text()
221 options.options['login.port'] = m.group(3)
222 options.options['login.protocol'] = m.group(1)
223 options.options['login.db'] = db_widget.get_active_text()
224 result = (login.get_text(), passwd.get_text(), m.group(2), m.group(3), m.group(1), db_widget.get_active_text())
225 else:
226 parent.present()
227 win.destroy()
228 raise Exception('QueryCanceled')
229 if res <> gtk.RESPONSE_OK:
230 parent.present()
231 win.destroy()
232 raise Exception('QueryCanceled')
233 parent.present()
234 win.destroy()
235 return result
237 class db_create(object):
238 def set_sensitive(self, sensitive):
239 self.dialog.get_widget('button_db_ok').set_sensitive(False)
240 return sensitive
242 def server_change(self, widget=None, parent=None):
243 url = _server_ask(self.server_widget)
244 try:
245 if self.lang_widget and url:
246 _refresh_langlist(self.lang_widget, url)
247 self.set_sensitive(True)
248 except:
249 self.set_sensitive(False)
250 return False
251 return url
253 def __init__(self, sig_login, terp_main):
254 self.dialog = glade.XML(common.terp_path("openerp.glade"), "win_createdb", gettext.textdomain())
255 self.sig_login = sig_login
256 self.terp_main = terp_main
258 def entry_changed(self, *args):
259 up1 = self.dialog.get_widget('ent_user_pass1').get_text()
260 up2 = self.dialog.get_widget('ent_user_pass2').get_text()
261 self.dialog.get_widget('button_db_ok').set_sensitive(bool(up1 and (up1==up2)))
263 def run(self, parent=None):
264 win = self.dialog.get_widget('win_createdb')
265 self.dialog.signal_connect('on_ent_user_pass1_changed', self.entry_changed)
266 self.dialog.signal_connect('on_ent_user_pass2_changed', self.entry_changed)
267 win.set_default_response(gtk.RESPONSE_OK)
268 if not parent:
269 parent = service.LocalService('gui.main').window
270 win.set_transient_for(parent)
271 win.show_all()
272 lang_dict = {}
273 pass_widget = self.dialog.get_widget('ent_password_new')
274 self.server_widget = self.dialog.get_widget('ent_server_new')
275 change_button = self.dialog.get_widget('but_server_new')
276 self.lang_widget = self.dialog.get_widget('db_create_combo')
277 self.db_widget = self.dialog.get_widget('ent_db_new')
278 demo_widget = self.dialog.get_widget('check_demo')
279 demo_widget.set_active(True)
281 change_button.connect_after('clicked', self.server_change, win)
282 protocol = options.options['login.protocol']
283 url = '%s%s:%s' % (protocol, options.options['login.server'], options.options['login.port'])
285 self.server_widget.set_text(url)
286 liststore = gtk.ListStore(str, str)
287 self.lang_widget.set_model(liststore)
288 try:
289 _refresh_langlist(self.lang_widget, url)
290 except:
291 self.set_sensitive(False)
293 while True:
294 res = win.run()
295 db_name = self.db_widget.get_text()
296 if (res==gtk.RESPONSE_OK) and (db_name in ('table','new','create','as','select','from','where','in','inner','outer','join','group')):
297 common.warning(_("Sorry,'" +db_name + "' cannot be the name of the database,it's a Reserved Keyword."), _('Bad database name !'), parent=parent)
298 continue
299 if (res==gtk.RESPONSE_OK) and ((not db_name) or (not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', db_name))):
300 common.warning(_('The database name must contain only normal characters or "_".\nYou must avoid all accents, space or special characters.'), _('Bad database name !'), parent=parent)
302 else:
303 break
304 demo_data = demo_widget.get_active()
306 langidx = self.lang_widget.get_active_iter()
307 langreal = langidx and self.lang_widget.get_model().get_value(langidx,1)
308 passwd = pass_widget.get_text()
309 user_pass = self.dialog.get_widget('ent_user_pass1').get_text()
310 url = self.server_widget.get_text()
311 m = re.match('^(http[s]?://|socket://)([\w.\-]+):(\d{1,5})$', url or '')
312 if m:
313 options.options['login.server'] = m.group(2)
314 options.options['login.port'] = m.group(3)
315 options.options['login.protocol'] = m.group(1)
316 options.options['login.db'] = db_name
317 parent.present()
318 win.destroy()
320 if res == gtk.RESPONSE_OK:
321 try:
322 id=rpc.session.db_exec(url, 'list')
323 if db_name in id:
324 raise Exception('DbExist')
325 id = rpc.session.db_exec(url, 'create', passwd, db_name, demo_data, langreal, user_pass)
326 win = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
327 win.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
328 win.set_title(_('OpenERP Database Installation'))
329 vbox = gtk.VBox(False, 0)
330 hbox = gtk.HBox(False, 13)
331 hbox.set_border_width(10)
332 img = gtk.Image()
333 img.set_from_stock('gtk-dialog-info', gtk.ICON_SIZE_DIALOG)
334 hbox.pack_start(img, expand=True, fill=False)
335 vbox2 = gtk.VBox(False, 0)
336 label = gtk.Label()
337 label.set_markup(_('<b>Operation in progress</b>'))
338 label.set_alignment(0.0, 0.5)
339 vbox2.pack_start(label, expand=True, fill=False)
340 vbox2.pack_start(gtk.HSeparator(), expand=True, fill=True)
341 vbox2.pack_start(gtk.Label(_("Please wait,\nthis operation may take a while...")), expand=True, fill=False)
342 hbox.pack_start(vbox2, expand=True, fill=True)
343 vbox.pack_start(hbox)
344 pb = gtk.ProgressBar()
345 pb.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT)
346 vbox.pack_start(pb, expand=True, fill=False)
347 win.add(vbox)
348 if not parent:
349 parent = service.LocalService('gui.main').window
350 win.set_transient_for(parent)
351 win.show_all()
352 self.timer = gobject.timeout_add(1000, self.progress_timeout, pb, url, passwd, id, win, db_name, parent)
353 except Exception, e:
354 if e.args == ('DbExist',):
355 common.warning(_("Could not create database."),_('Database already exists !'))
356 elif ('faultString' in e and e.faultString=='AccessDenied:None') or str(e)=='AccessDenied':
357 common.warning(_('Bad database administrator password !'), _("Could not create database."))
358 else:
359 common.warning(_("Could not create database."),_('Error during database creation !'))
361 def progress_timeout(self, pbar, url, passwd, id, win, dbname, parent=None):
362 try:
363 progress,users = rpc.session.db_exec_no_except(url, 'get_progress', passwd, id)
364 except:
365 win.destroy()
366 common.warning(_("The server crashed during installation.\nWe suggest you to drop this database."),_("Error during database creation !"))
367 return False
369 pbar.pulse()
370 if progress == 1.0:
371 win.destroy()
373 pwdlst = '\n'.join(map(lambda x: ' - %s: %s / %s' % (x['name'],x['login'],x['password']), users))
374 dialog = glade.XML(common.terp_path("openerp.glade"), "dia_dbcreate_ok", gettext.textdomain())
375 win = dialog.get_widget('dia_dbcreate_ok')
376 if not parent:
377 parent = service.LocalService('gui.main').window
378 win.set_transient_for(parent)
379 win.show_all()
380 buffer = dialog.get_widget('dia_tv').get_buffer()
382 buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
383 iter_start = buffer.get_start_iter()
384 buffer.insert(iter_start, _('The following users have been installed on your database:')+'\n\n'+ pwdlst + '\n\n'+_('You can now connect to the database as an administrator.'))
385 res = win.run()
386 parent.present()
387 win.destroy()
389 if res == gtk.RESPONSE_OK:
390 m = re.match('^(http[s]?://|socket://)([\w.]+):(\d{1,5})$', url)
391 ok = False
392 for x in users:
393 if x['login']=='admin' and m:
394 res = [x['login'], x['password']]
395 res.append( m.group(2) )
396 res.append( m.group(3) )
397 res.append( m.group(1) )
398 res.append( dbname )
399 log_response = rpc.session.login(*res)
400 if log_response == 1:
401 options.options['login.login'] = x['login']
402 id = self.terp_main.sig_win_menu(quiet=False)
403 ok = True
404 break
405 if not ok:
406 self.sig_login(dbname=dbname)
407 return False
408 return True
410 def process(self):
411 return False
414 class terp_main(service.Service):
415 def __init__(self, name='gui.main', audience='gui.*'):
416 service.Service.__init__(self, name, audience)
417 self.exportMethod(self.win_add)
419 self._handler_ok = True
420 self.glade = glade.XML(common.terp_path("openerp.glade"),"win_main",gettext.textdomain())
421 self.status_bar_main = self.glade.get_widget('hbox_status_main')
422 self.toolbar = self.glade.get_widget('main_toolbar')
423 self.sb_requests = self.glade.get_widget('sb_requests')
424 self.sb_username = self.glade.get_widget('sb_user_name')
425 self.sb_servername = self.glade.get_widget('sb_user_server')
426 id = self.sb_servername.get_context_id('message')
427 self.sb_servername.push(id, _('Press Ctrl+O to login'))
428 self.secure_img = self.glade.get_widget('secure_img')
429 self.secure_img.hide()
431 window = self.glade.get_widget('win_main')
432 window.connect("destroy", self.sig_quit)
433 window.connect("delete_event", self.sig_delete)
434 self.window = window
435 self.window.set_icon(common.OPENERP_ICON)
437 self.notebook = gtk.Notebook()
438 self.notebook.popup_enable()
439 self.notebook.set_scrollable(True)
440 self.sig_id = self.notebook.connect_after('switch-page', self._sig_page_changt)
441 vbox = self.glade.get_widget('vbox_main')
442 vbox.pack_start(self.notebook, expand=True, fill=True)
444 self.shortcut_menu = self.glade.get_widget('shortcut')
447 # Code to add themes to the options->theme menu
449 submenu = gtk.Menu()
450 menu = self.glade.get_widget('menu_theme')
451 old = None
452 themes_path = common.terp_path('themes')
453 if themes_path:
454 for dname in os.listdir(themes_path):
455 if dname.startswith('.'):
456 continue
457 fname = common.terp_path(os.path.join('themes', dname, 'gtkrc'))
458 if fname and os.path.isfile(fname):
459 open_item = gtk.RadioMenuItem(old, dname)
460 old = open_item
461 submenu.append(open_item)
462 if dname == options.options['client.theme']:
463 open_item.set_active(True)
464 open_item.connect('toggled', self.theme_select, dname)
466 submenu.append(gtk.SeparatorMenuItem())
467 open_item = gtk.RadioMenuItem(old, _('Default Theme'))
468 submenu.append(open_item)
469 if 'none'==options.options['client.theme']:
470 open_item.set_active(True)
471 open_item.connect('toggled', self.theme_select, 'none')
472 submenu.show_all()
473 menu.set_submenu(submenu)
476 # Default Notebook
479 self.notebook.show()
480 self.pages = []
481 self.current_page = 0
482 self.last_page = 0
484 dict = {
485 'on_login_activate': self.sig_login,
486 'on_logout_activate': self.sig_logout,
487 'on_win_next_activate': self.sig_win_next,
488 'on_win_prev_activate': self.sig_win_prev,
489 'on_plugin_execute_activate': self.sig_plugin_execute,
490 'on_quit_activate': self.sig_close,
491 'on_but_menu_clicked': self.sig_win_menu,
492 'on_win_new_activate': self.sig_win_menu,
493 'on_win_home_activate': self.sig_home_new,
494 'on_win_close_activate': self.sig_win_close,
495 'on_support_activate': common.support,
496 'on_preference_activate': self.sig_user_preferences,
497 'on_read_requests_activate': self.sig_request_open,
498 'on_send_request_activate': self.sig_request_new,
499 'on_request_wait_activate': self.sig_request_wait,
500 'on_opt_save_activate': lambda x: options.options.save(),
501 'on_menubar_icons_activate': lambda x: self.sig_menubar('icons'),
502 'on_menubar_text_activate': lambda x: self.sig_menubar('text'),
503 'on_menubar_both_activate': lambda x: self.sig_menubar('both'),
504 'on_mode_normal_activate': lambda x: self.sig_mode_change(False),
505 'on_mode_pda_activate': lambda x: self.sig_mode_change(True),
506 'on_opt_form_tab_top_activate': lambda x: self.sig_form_tab('top'),
507 'on_opt_form_tab_left_activate': lambda x: self.sig_form_tab('left'),
508 'on_opt_form_tab_right_activate': lambda x: self.sig_form_tab('right'),
509 'on_opt_form_tab_bottom_activate': lambda x: self.sig_form_tab('bottom'),
510 'on_opt_form_tab_orientation_horizontal_activate': lambda x: self.sig_form_tab_orientation(0),
511 'on_opt_form_tab_orientation_vertical_activate': lambda x: self.sig_form_tab_orientation(90),
512 'on_help_index_activate': self.sig_help_index,
513 'on_help_contextual_activate': self.sig_help_context,
514 'on_help_licence_activate': self.sig_licence,
515 'on_about_activate': self.sig_about,
516 'on_shortcuts_activate' : self.sig_shortcuts,
517 'on_db_new_activate': self.sig_db_new,
518 'on_db_restore_activate': self.sig_db_restore,
519 'on_db_backup_activate': self.sig_db_dump,
520 'on_db_drop_activate': self.sig_db_drop,
521 'on_admin_password_activate': self.sig_db_password,
522 'on_extension_manager_activate': self.sig_extension_manager,
524 for signal in dict:
525 self.glade.signal_connect(signal, dict[signal])
527 self.buttons = {}
528 for button in ('but_new', 'but_save', 'but_remove', 'but_search', 'but_previous', 'but_next', 'but_action', 'but_open', 'but_print', 'but_close', 'but_reload', 'but_switch','but_attach', 'radio_tree','radio_form','radio_graph','radio_calendar'):
529 self.glade.signal_connect('on_'+button+'_clicked', self._sig_child_call, button)
530 self.buttons[button]=self.glade.get_widget(button)
532 menus = {
533 'form_del': 'but_remove',
534 'form_new': 'but_new',
535 'form_copy': 'but_copy',
536 'form_reload': 'but_reload',
537 'form_log': 'but_log',
538 'form_open': 'but_open',
539 'form_search': 'but_search',
540 'form_previous': 'but_previous',
541 'form_next': 'but_next',
542 'form_save': 'but_save',
543 'goto_id': 'but_goto_id',
544 'form_print': 'but_print',
545 'form_print_html': 'but_print_html',
546 'form_save_as': 'but_save_as',
547 'form_import': 'but_import',
548 'form_filter': 'but_filter',
549 'form_repeat': 'but_print_repeat'
551 for menu in menus:
552 self.glade.signal_connect('on_'+menu+'_activate', self._sig_child_call, menus[menu])
554 spool = service.LocalService('spool')
555 spool.subscribe('gui.window', self.win_add)
558 # we now create the icon for the attachment button when there are attachments
559 self.__img_no_attachments = gtk.Image()
560 pxbf = self.window.render_icon(self.buttons['but_attach'].get_stock_id(), self.toolbar.get_icon_size())
561 self.__img_no_attachments.set_from_pixbuf(pxbf)
562 self.__img_no_attachments.show()
564 pxbf = pxbf.copy()
565 w, h = pxbf.get_width(), pxbf.get_height()
566 overlay = self.window.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU)
567 ow, oh = overlay.get_width(), overlay.get_height()
568 overlay.composite(pxbf,
569 0, h - oh,
570 ow, oh,
571 0, h - oh,
572 1.0, 1.0,
573 gtk.gdk.INTERP_NEAREST,
574 255)
576 self.__img_attachments = gtk.Image()
577 self.__img_attachments.set_from_pixbuf(pxbf)
578 self.__img_attachments.show()
580 self.sb_set()
582 settings = gtk.settings_get_default()
583 settings.set_long_property('gtk-button-images', 1, 'TinyERP:gui.main')
585 def fnc_menuitem(menuitem, opt_name):
586 options.options[opt_name] = menuitem.get_active()
587 dict = {
588 'on_opt_print_preview_activate': (fnc_menuitem, 'printer.preview', 'opt_print_preview'),
589 'on_opt_form_toolbar_activate': (fnc_menuitem, 'form.toolbar', 'opt_form_toolbar'),
591 self.glade.get_widget('menubar_'+(options.options['client.toolbar'] or 'both')).set_active(True)
592 self.sig_menubar(options.options['client.toolbar'] or 'both')
593 self.glade.get_widget('opt_form_tab_'+(options.options['client.form_tab'] or 'left')).set_active(True)
594 self.sig_form_tab(options.options['client.form_tab'] or 'left')
595 self.glade.get_widget('opt_form_tab_orientation_'+(str(options.options['client.form_tab_orientation']) or '0')).set_active(True)
596 self.sig_form_tab_orientation(options.options['client.form_tab_orientation'] or 0)
597 if options.options['client.modepda']:
598 self.glade.get_widget('mode_pda').set_active(True)
599 else:
600 self.glade.get_widget('mode_normal').set_active(True)
601 self.sig_mode()
602 for signal in dict:
603 self.glade.signal_connect(signal, dict[signal][0], dict[signal][1])
604 self.glade.get_widget(dict[signal][2]).set_active(int(bool(options.options[dict[signal][1]])))
606 # Adding a timer the check to requests
607 gobject.timeout_add(5 * 60 * 1000, self.request_set)
609 def shortcut_edit(self, widget, model='ir.ui.menu'):
610 obj = service.LocalService('gui.window')
611 domain = [('user_id', '=', rpc.session.uid), ('resource', '=', model)]
612 obj.create(False, 'ir.ui.view_sc', res_id=None, domain=domain, view_type='form', mode='tree,form')
614 def shortcut_set(self, sc=None):
615 def _action_shortcut(widget, action):
616 if action:
617 ctx = rpc.session.context.copy()
618 obj = service.LocalService('action.main')
619 obj.exec_keyword('tree_but_open', {'model': 'ir.ui.menu', 'id': action[0],
620 'ids': [action[0]], 'report_type': 'pdf', 'window': self.window}, context=ctx)
622 if sc is None:
623 uid = rpc.session.uid
624 sc = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'get_sc', uid, 'ir.ui.menu', rpc.session.context) or []
626 menu = gtk.Menu()
627 for s in sc:
628 menuitem = gtk.MenuItem(s['name'])
629 menuitem.connect('activate', _action_shortcut, s['res_id'])
630 menu.add(menuitem)
632 menu.add(gtk.SeparatorMenuItem())
633 menuitem = gtk.MenuItem(_('Edit'))
634 menuitem.connect('activate', self.shortcut_edit)
635 menu.add(menuitem)
637 menu.show_all()
638 self.shortcut_menu.set_submenu(menu)
639 self.shortcut_menu.set_sensitive(True)
641 def shortcut_unset(self):
642 menu = gtk.Menu()
643 menu.show_all()
644 self.shortcut_menu.set_submenu(menu)
645 self.shortcut_menu.set_sensitive(False)
647 def theme_select(self, widget, theme):
648 options.options['client.theme'] = theme
649 common.theme_set()
650 self.window.show_all()
651 return True
653 def sig_mode_change(self, pda_mode=False):
654 options.options['client.modepda'] = pda_mode
655 return self.sig_mode()
657 def sig_mode(self):
658 pda_mode = options.options['client.modepda']
659 if pda_mode:
660 self.status_bar_main.hide()
661 else:
662 self.status_bar_main.show()
663 return pda_mode
665 def sig_menubar(self, option):
666 options.options['client.toolbar'] = option
667 if option=='both':
668 self.toolbar.set_style(gtk.TOOLBAR_BOTH)
669 elif option=='text':
670 self.toolbar.set_style(gtk.TOOLBAR_TEXT)
671 elif option=='icons':
672 self.toolbar.set_style(gtk.TOOLBAR_ICONS)
674 def sig_form_tab(self, option):
675 options.options['client.form_tab'] = option
677 def sig_form_tab_orientation(self, option):
678 options.options['client.form_tab_orientation'] = option
680 def sig_win_next(self, args):
681 pn = self.notebook.get_current_page()
682 if pn == len(self.pages)-1:
683 pn = -1
684 self.notebook.set_current_page(pn+1)
686 def sig_win_prev(self, args):
687 pn = self.notebook.get_current_page()
688 self.notebook.set_current_page(pn-1)
690 def sig_user_preferences(self, *args):
691 win =win_preference.win_preference(parent=self.window)
692 win.run()
693 return True
695 def sig_win_close(self, *args):
696 self._sig_child_call(args[0], 'but_close')
698 def sig_request_new(self, args=None):
699 obj = service.LocalService('gui.window')
700 try:
701 return obj.create(None, 'res.request', False,
702 [('act_from', '=', rpc.session.uid)], 'form',
703 mode='form,tree', window=self.window,
704 context={'active_test': False})
705 except:
706 return False
708 def sig_request_open(self, args=None):
709 ids,ids2 = self.request_set()
710 obj = service.LocalService('gui.window')
711 try:
712 return obj.create(False, 'res.request', ids,
713 [('act_to', '=', rpc.session.uid), ('active', '=', True)],
714 'form', mode='tree,form', window=self.window,
715 context={'active_test': False})
716 except:
717 return False
719 def sig_request_wait(self, args=None):
720 ids,ids2 = self.request_set()
721 obj = service.LocalService('gui.window')
722 try:
723 return obj.create(False, 'res.request', ids,
724 [('act_from', '=', rpc.session.uid),
725 ('state', '=', 'waiting'), ('active', '=', True)],
726 'form', mode='tree,form', window=self.window,
727 context={'active_test': False})
728 except:
729 return False
731 def request_set(self):
732 try:
733 uid = rpc.session.uid
734 ids,ids2 = rpc.session.rpc_exec_auth_try('/object', 'execute',
735 'res.request', 'request_get')
736 if len(ids):
737 message = _('%s request(s)') % len(ids)
738 else:
739 message = _('No request')
740 if len(ids2):
741 message += _(' - %s request(s) sended') % len(ids2)
742 id = self.sb_requests.get_context_id('message')
743 self.sb_requests.push(id, message)
744 return (ids,ids2)
745 except:
746 return ([],[])
748 def sig_login(self, widget=None, dbname=False):
749 RES_OK = 1
750 RES_BAD_PASSWORD = -2
751 RES_CNX_ERROR = -1
752 try:
753 log_response = RES_BAD_PASSWORD
754 res = None
755 while log_response == RES_BAD_PASSWORD:
756 try:
757 l = db_login()
758 res = l.run(dbname=dbname, parent=self.window)
759 except Exception, e:
760 if e.args == ('QueryCanceled',):
761 return False
762 raise
763 service.LocalService('gui.main').window.present()
764 self.sig_logout(widget)
765 log_response = rpc.session.login(*res)
766 if log_response == RES_OK:
767 options.options.save()
768 id = self.sig_win_menu(quiet=False)
769 if id:
770 self.sig_home_new(quiet=True, except_id=id)
771 if res[4] == 'https://':
772 self.secure_img.show()
773 else:
774 self.secure_img.hide()
775 self.request_set()
776 elif log_response == RES_CNX_ERROR:
777 common.message(_('Connection error !\nUnable to connect to the server !'))
778 elif log_response == RES_BAD_PASSWORD:
779 common.message(_('Connection error !\nBad username or password !'))
780 except rpc.rpc_exception:
781 rpc.session.logout()
782 raise
783 self.glade.get_widget('but_menu').set_sensitive(True)
784 self.glade.get_widget('user').set_sensitive(True)
785 self.glade.get_widget('form').set_sensitive(True)
786 self.glade.get_widget('plugins').set_sensitive(True)
787 return True
789 def sig_logout(self, widget):
790 res = True
791 while res:
792 wid = self._wid_get()
793 if wid:
794 if 'but_close' in wid.handlers:
795 res = wid.handlers['but_close']()
796 if not res:
797 return False
798 res = self._win_del()
799 else:
800 res = False
801 id = self.sb_requests.get_context_id('message')
802 self.sb_requests.push(id, '')
803 id = self.sb_username.get_context_id('message')
804 self.sb_username.push(id, _('Not logged !'))
805 id = self.sb_servername.get_context_id('message')
806 self.sb_servername.push(id, _('Press Ctrl+O to login'))
807 self.secure_img.hide()
808 self.shortcut_unset()
809 self.glade.get_widget('but_menu').set_sensitive(False)
810 self.glade.get_widget('user').set_sensitive(False)
811 self.glade.get_widget('form').set_sensitive(False)
812 self.glade.get_widget('plugins').set_sensitive(False)
813 rpc.session.logout()
814 return True
816 def sig_help_index(self, widget):
817 tools.launch_browser(options.options['help.index'])
819 def sig_help_context(self, widget):
820 model = self._wid_get().model
821 l = rpc.session.context.get('lang','en_US')
822 tools.launch_browser(options.options['help.context']+'?model=%s&lang=%s' % (model,l))
824 def sig_licence(self, widget):
825 dialog = glade.XML(common.terp_path("openerp.glade"), "win_licence", gettext.textdomain())
826 dialog.signal_connect("on_but_ok_pressed", lambda obj: dialog.get_widget('win_licence').destroy())
828 win = dialog.get_widget('win_licence')
829 win.set_transient_for(self.window)
830 win.show_all()
832 def sig_about(self, widget):
833 about = glade.XML(common.terp_path("openerp.glade"), "win_about", gettext.textdomain())
834 buffer = about.get_widget('textview2').get_buffer()
835 about_txt = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
836 buffer.set_text(about_txt % openerp_version)
837 about.signal_connect("on_but_ok_pressed", lambda obj: about.get_widget('win_about').destroy())
839 win = about.get_widget('win_about')
840 win.set_transient_for(self.window)
841 win.show_all()
843 def sig_shortcuts(self, widget):
844 shortcuts_win = glade.XML(common.terp_path('openerp.glade'), 'shortcuts_dia', gettext.textdomain())
845 shortcuts_win.signal_connect("on_but_ok_pressed", lambda obj: shortcuts_win.get_widget('shortcuts_dia').destroy())
847 win = shortcuts_win.get_widget('shortcuts_dia')
848 win.set_transient_for(self.window)
849 win.show_all()
851 def sig_win_menu(self, widget=None, quiet=True):
852 for p in range(len(self.pages)):
853 if self.pages[p].model=='ir.ui.menu':
854 self.notebook.set_current_page(p)
855 return True
856 res = self.sig_win_new(widget, type='menu_id', quiet=quiet)
857 if not res:
858 return self.sig_win_new(widget, type='action_id', quiet=quiet)
859 return res
861 def sig_win_new(self, widget=None, type='menu_id', quiet=True, except_id=False):
862 try:
863 act_id = rpc.session.rpc_exec_auth('/object', 'execute', 'res.users',
864 'read', [rpc.session.uid], [type,'name'], rpc.session.context)
865 except:
866 return False
867 id = self.sb_username.get_context_id('message')
868 self.sb_username.push(id, act_id[0]['name'] or '')
869 id = self.sb_servername.get_context_id('message')
870 data = urlparse.urlsplit(rpc.session._url)
871 self.sb_servername.push(id, data[0]+':'+(data[1] and '//'+data[1] \
872 or data[2])+' ['+options.options['login.db']+']')
873 if not act_id[0][type]:
874 if quiet:
875 return False
876 common.warning(_("You can not log into the system !\nAsk the administrator to verify\nyou have an action defined for your user."),'Access Denied !')
877 rpc.session.logout()
878 return False
879 act_id = act_id[0][type][0]
880 if except_id and act_id == except_id:
881 return act_id
882 obj = service.LocalService('action.main')
883 win = obj.execute(act_id, {'window':self.window})
884 try:
885 user = rpc.session.rpc_exec_auth_wo('/object', 'execute', 'res.users',
886 'read', [rpc.session.uid], [type,'name'], rpc.session.context)
887 if user[0][type]:
888 act_id = user[0][type][0]
889 except:
890 pass
891 return act_id
893 def sig_home_new(self, widget=None, quiet=True, except_id=False):
894 return self.sig_win_new(widget, type='action_id', quiet=quiet,
895 except_id=except_id)
897 def sig_plugin_execute(self, widget):
898 import plugins
899 pn = self.notebook.get_current_page()
900 datas = {'model': self.pages[pn].model, 'ids':self.pages[pn].ids_get(), 'id' : self.pages[pn].id_get()}
901 plugins.execute(datas)
903 def sig_quit(self, widget):
904 options.options.save()
905 gtk.main_quit()
907 def sig_close(self, widget):
908 if common.sur(_("Do you really want to quit ?"), parent=self.window):
909 if not self.sig_logout(widget):
910 return False
911 options.options.save()
912 gtk.main_quit()
914 def sig_delete(self, widget, event, data=None):
915 if common.sur(_("Do you really want to quit ?"), parent=self.window):
916 if not self.sig_logout(widget):
917 return True
918 return False
919 return True
921 def win_add(self, win, datas):
922 self.pages.append(win)
923 self.notebook.append_page(win.widget, gtk.Label(win.name))
924 self.notebook.set_current_page(-1)
926 def message(self, message):
927 id = self.status_bar.get_context_id('message')
928 self.status_bar.push(id, message)
930 def __attachment_callback(self, view, objid):
931 current_view = self._wid_get()
932 current_id = current_view and current_view.id_get()
933 if current_view == view and objid == current_id:
934 cpt = None
935 if objid and view.screen.current_view.view_type == 'form':
936 cpt = rpc.session.rpc_exec_auth('/object', 'execute',
937 'ir.attachment', 'search_count',
938 [('res_model', '=', view.model), ('res_id', '=', objid)])
939 if cpt:
940 self.buttons['but_attach'].set_icon_widget(self.__img_attachments)
941 self.buttons['but_attach'].set_label(_('Attachments (%d)') % cpt)
944 def _update_attachment_button(self, view = None):
946 Update the attachment icon for display the number of attachments
948 if not view:
949 view = self._wid_get()
951 id = view and view.id_get()
952 gobject.timeout_add(1500, self.__attachment_callback, view, id)
953 self.buttons['but_attach'].set_icon_widget(self.__img_no_attachments)
954 self.buttons['but_attach'].set_label(_('Attachments'))
957 def sb_set(self, view=None):
958 if not view:
959 view = self._wid_get()
960 if view and hasattr(view, 'screen'):
961 self._handler_ok = False
962 self.glade.get_widget('radio_'+view.screen.current_view.view_type).set_active(True)
963 self._handler_ok = True
964 self._update_attachment_button(view)
965 for x in self.buttons:
966 if self.buttons[x]:
967 self.buttons[x].set_sensitive(view and (x in view.handlers))
969 def _win_del(self):
970 pn = self.notebook.get_current_page()
971 if pn != -1:
972 self.notebook.disconnect(self.sig_id)
973 page = self.pages.pop(pn)
974 self.notebook.remove_page(pn)
975 self.sig_id = self.notebook.connect_after('switch-page', self._sig_page_changt)
976 self.sb_set()
978 page.destroy()
979 del page
980 return self.notebook.get_current_page() != -1
982 def _wid_get(self):
983 pn = self.notebook.get_current_page()
984 if pn == -1:
985 return False
986 return self.pages[pn]
988 def _sig_child_call(self, widget, button_name, *args):
989 if not self._handler_ok:
990 return
991 wid = self._wid_get()
992 if wid:
993 res = True
994 if button_name.startswith('radio_'):
995 act = self.glade.get_widget(button_name).get_active()
996 if not act: return False
998 if button_name in wid.handlers:
999 res = wid.handlers[button_name]()
1000 # for those buttons, we refresh the attachment button.
1001 # for the "switch view" button, the action has already
1002 # been called by the Screen object of the view (wid)
1003 if button_name in ('but_new', 'but_remove', 'but_search', \
1004 'but_previous', 'but_next', 'but_open', \
1005 'but_close', 'but_reload', 'but_attach', 'but_goto_id'):
1006 self._update_attachment_button(wid)
1007 if button_name.startswith('radio_'):
1008 pass
1009 #mode = wid.screen.current_view.view_type
1010 #print 'RADIO CALLED', button_name, args, self.glade.get_widget(button_name).get_active(), button_name[6:]
1011 #if self.glade.get_widget(button_name).get_active() and (mode<>button_name[6:]):
1012 # print 'SWITCH', button_name
1013 # wid.sig_switch()
1014 if button_name=='but_close' and res:
1015 self._win_del()
1017 def _sig_page_changt(self, widget=None, *args):
1018 self.last_page = self.current_page
1019 self.current_page = self.notebook.get_current_page()
1020 self.sb_set()
1022 def sig_db_new(self, widget):
1023 if not self.sig_logout(widget):
1024 return False
1025 dia = db_create(self.sig_login, self)
1026 res = dia.run(self.window)
1027 if res:
1028 options.options.save()
1029 return res
1031 def sig_db_drop(self, widget):
1032 if not self.sig_logout(widget):
1033 return False
1034 url, db_name, passwd = self._choose_db_select(_('Delete a database'))
1035 if not db_name:
1036 return
1038 try:
1039 rpc.session.db_exec(url, 'drop', passwd, db_name)
1040 common.message(_("Database dropped successfully !"), parent=self.window)
1041 except Exception, e:
1042 if ('faultString' in e and e.faultString=='AccessDenied:None') or str(e)=='AccessDenied':
1043 common.warning(_('Bad database administrator password !'),_("Could not drop database."), parent=self.window)
1044 else:
1045 common.warning(_("Couldn't drop database"), parent=self.window)
1047 def sig_db_restore(self, widget):
1048 filename = common.file_selection(_('Open...'), parent=self.window, preview=False)
1049 if not filename:
1050 return
1052 url, db_name, passwd = self._choose_db_ent()
1053 if db_name:
1054 try:
1055 f = file(filename, 'rb')
1056 data_b64 = base64.encodestring(f.read())
1057 f.close()
1058 rpc.session.db_exec(url, 'restore', passwd, db_name, data_b64)
1059 common.message(_("Database restored successfully !"), parent=self.window)
1060 except Exception,e:
1061 if ('faultString' in e and e.faultString=='AccessDenied:None') or str(e)=='AccessDenied':
1062 common.warning(_('Bad database administrator password !'),_("Could not restore database."), parent=self.window)
1063 else:
1064 common.warning(_("Couldn't restore database"), parent=self.window)
1066 def sig_extension_manager(self,widget):
1067 win = win_extension.win_extension(self.window)
1068 win.run()
1070 def sig_db_password(self, widget):
1071 dialog = glade.XML(common.terp_path("openerp.glade"), "dia_passwd_change",
1072 gettext.textdomain())
1073 win = dialog.get_widget('dia_passwd_change')
1074 win.set_icon(common.OPENERP_ICON)
1075 win.set_transient_for(self.window)
1076 win.show_all()
1077 server_widget = dialog.get_widget('ent_server2')
1078 old_pass_widget = dialog.get_widget('old_passwd')
1079 new_pass_widget = dialog.get_widget('new_passwd')
1080 new_pass2_widget = dialog.get_widget('new_passwd2')
1081 change_button = dialog.get_widget('but_server_change1')
1082 change_button.connect_after('clicked', lambda a,b: _server_ask(b, win), server_widget)
1084 host = options.options['login.server']
1085 port = options.options['login.port']
1086 protocol = options.options['login.protocol']
1087 url = '%s%s:%s' % (protocol, host, port)
1088 server_widget.set_text(url)
1090 res = win.run()
1091 if res == gtk.RESPONSE_OK:
1092 url = server_widget.get_text()
1093 old_passwd = old_pass_widget.get_text()
1094 new_passwd = new_pass_widget.get_text()
1095 new_passwd2 = new_pass2_widget.get_text()
1096 if new_passwd != new_passwd2:
1097 common.warning(_("Confirmation password do not match " \
1098 "new password, operation cancelled!"),
1099 _("Validation Error."), parent=win)
1100 else:
1101 try:
1102 rpc.session.db_exec(url, 'change_admin_password',
1103 old_passwd, new_passwd)
1104 except Exception,e:
1105 if ('faultString' in e and e.faultString=='AccessDenied:None') \
1106 or str(e)=='AccessDenied':
1107 common.warning(_("Could not change password database."),
1108 _('Bas password provided !'), parent=win)
1109 else:
1110 common.warning(_("Error, password not changed."),
1111 parent=win)
1112 self.window.present()
1113 win.destroy()
1115 def sig_db_dump(self, widget):
1116 url, db_name, passwd = self._choose_db_select(_('Backup a database'))
1117 if not db_name:
1118 return
1119 filename = common.file_selection(_('Save As...'),
1120 action=gtk.FILE_CHOOSER_ACTION_SAVE, parent=self.window, preview=False)
1122 if filename:
1123 try:
1124 dump_b64 = rpc.session.db_exec(url, 'dump', passwd, db_name)
1125 dump = base64.decodestring(dump_b64)
1126 f = file(filename, 'wb')
1127 f.write(dump)
1128 f.close()
1129 common.message(_("Database backuped successfully !"), parent=self.window)
1130 except:
1131 common.warning(_("Couldn't backup database."), parent=self.window)
1133 def _choose_db_select(self, title=_("Backup a database")):
1134 def refreshlist(widget, db_widget, label, url):
1135 res = _refresh_dblist(db_widget, url)
1136 if res == -1:
1137 label.set_label('<b>'+_('Could not connect to server !')+'</b>')
1138 db_widget.hide()
1139 label.show()
1140 elif res==0:
1141 label.set_label('<b>'+_('No database found, you must create one !')+'</b>')
1142 db_widget.hide()
1143 label.show()
1144 else:
1145 label.hide()
1146 db_widget.show()
1147 return res
1149 def refreshlist_ask(widget, server_widget, db_widget, label, parent=None):
1150 url = _server_ask(server_widget, parent)
1151 if not url:
1152 return None
1153 refreshlist(widget, db_widget, label, url)
1154 return url
1156 dialog = glade.XML(common.terp_path("openerp.glade"), "win_db_select",
1157 gettext.textdomain())
1158 win = dialog.get_widget('win_db_select')
1159 win.set_icon(common.OPENERP_ICON)
1160 win.set_default_response(gtk.RESPONSE_OK)
1161 win.set_transient_for(self.window)
1162 win.show_all()
1164 pass_widget = dialog.get_widget('ent_passwd_select')
1165 server_widget = dialog.get_widget('ent_server_select')
1166 db_widget = dialog.get_widget('combo_db_select')
1167 label = dialog.get_widget('label_db_select')
1170 dialog.get_widget('db_select_label').set_markup('<b>'+title+'</b>')
1172 protocol = options.options['login.protocol']
1173 url = '%s%s:%s' % (protocol, options.options['login.server'], options.options['login.port'])
1174 server_widget.set_text(url)
1176 liststore = gtk.ListStore(str)
1177 db_widget.set_model(liststore)
1179 refreshlist(None, db_widget, label, url)
1180 change_button = dialog.get_widget('but_server_select')
1181 change_button.connect_after('clicked', refreshlist_ask, server_widget, db_widget, label, win)
1183 cell = gtk.CellRendererText()
1184 db_widget.pack_start(cell, True)
1185 db_widget.add_attribute(cell, 'text', 0)
1187 res = win.run()
1189 db = False
1190 url = False
1191 passwd = False
1192 if res == gtk.RESPONSE_OK:
1193 db = db_widget.get_active_text()
1194 url = server_widget.get_text()
1195 passwd = pass_widget.get_text()
1196 self.window.present()
1197 win.destroy()
1198 return (url,db,passwd)
1200 def _choose_db_ent(self):
1201 dialog = glade.XML(common.terp_path("openerp.glade"), "win_db_ent",
1202 gettext.textdomain())
1203 win = dialog.get_widget('win_db_ent')
1204 win.set_icon(common.OPENERP_ICON)
1205 win.set_transient_for(self.window)
1206 win.show_all()
1208 db_widget = dialog.get_widget('ent_db')
1209 widget_pass = dialog.get_widget('ent_password')
1210 widget_url = dialog.get_widget('ent_server1')
1212 protocol = options.options['login.protocol']
1213 url = '%s%s:%s' % (protocol, options.options['login.server'],
1214 options.options['login.port'])
1215 widget_url.set_text(url)
1217 change_button = dialog.get_widget('but_server_change')
1218 change_button.connect_after('clicked', lambda a,b: _server_ask(b, win),
1219 widget_url)
1221 res = win.run()
1223 db = False
1224 passwd = False
1225 url = False
1226 if res == gtk.RESPONSE_OK:
1227 db = db_widget.get_text()
1228 url = widget_url.get_text()
1229 passwd = widget_pass.get_text()
1230 self.window.present()
1231 win.destroy()
1232 return url, db, passwd
1235 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: