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