1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
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
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 ##############################################################################
47 from window
import win_preference
, win_extension
53 def _refresh_dblist(db_widget
, url
, dbtoload
=None):
55 dbtoload
= options
.options
['login.db']
57 liststore
= db_widget
.get_model()
59 result
= rpc
.session
.list_db(url
)
62 for db_num
, db_name
in enumerate(rpc
.session
.list_db(url
)):
63 liststore
.append([db_name
])
64 if db_name
== dbtoload
:
66 db_widget
.set_active(index
)
69 def _refresh_langlist(lang_widget
, url
):
70 liststore
= lang_widget
.get_model()
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)
79 def _server_ask(server_widget
, parent
=None):
81 win_gl
= glade
.XML(common
.terp_path("terp.glade"),"win_server",gettext
.textdomain())
82 win
= win_gl
.get_widget('win_server')
84 parent
= service
.LocalService('gui.main').window
85 win
.set_transient_for(parent
)
86 win
.set_icon(common
.TINYERP_ICON
)
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())
102 host_widget
.set_text(m
.group(2))
103 port_widget
.set_text(m
.group(3))
108 listprotocol
.append([p
])
109 if m
and protocol
[p
] == m
.group(1):
112 protocol_widget
.set_active(index
)
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
)
125 class db_login(object):
127 self
.win_gl
= glade
.XML(common
.terp_path("terp.glade"),"win_login",gettext
.textdomain())
129 def refreshlist(self
, widget
, db_widget
, label
, url
, butconnect
=False):
131 def check_server_version(url
):
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
)
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
)
144 label
.set_label('<b>'+_('Could not connect to server !')+'</b>')
148 butconnect
.set_sensitive(False)
151 label
.set_label('<b>'+_('No database found, you must create one !')+'</b>')
155 butconnect
.set_sensitive(False)
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
,))
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):
173 win
= self
.win_gl
.get_widget('win_login')
175 parent
= service
.LocalService('gui.main').window
176 win
.set_transient_for(parent
)
177 win
.set_icon(common
.TINYERP_ICON
)
179 img
= self
.win_gl
.get_widget('image_tinyerp')
180 img
.set_from_file(common
.terp_path_pixmaps('tinyerp.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')
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
)
209 iter = liststore
.get_iter_root()
211 if liststore
.get_value(iter, 0)==dbname
:
212 db_widget
.set_active_iter(iter)
214 iter = liststore
.iter_next(iter)
217 m
= re
.match('^(http[s]?://|socket://)([\w.\-]+):(\d{1,5})$', server_widget
.get_text() or '')
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())
228 raise Exception('QueryCanceled')
229 if res
<> gtk
.RESPONSE_OK
:
232 raise Exception('QueryCanceled')
237 class db_create(object):
238 def set_sensitive(self
, sensitive
):
239 self
.dialog
.get_widget('button_db_ok').set_sensitive(False)
242 def server_change(self
, widget
=None, parent
=None):
243 url
= _server_ask(self
.server_widget
)
245 if self
.lang_widget
and url
:
246 _refresh_langlist(self
.lang_widget
, url
)
247 self
.set_sensitive(True)
249 self
.set_sensitive(False)
253 def __init__(self
, sig_login
, terp_main
):
254 self
.dialog
= glade
.XML(common
.terp_path("terp.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
)
269 parent
= service
.LocalService('gui.main').window
270 win
.set_transient_for(parent
)
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
)
289 _refresh_langlist(self
.lang_widget
, url
)
291 self
.set_sensitive(False)
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
)
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
)
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 '')
313 options
.options
['login.server'] = m
.group(2)
314 options
.options
['login.port'] = m
.group(3)
315 options
.options
['login.protocol'] = m
.group(1)
319 if res
== gtk
.RESPONSE_OK
:
321 id=rpc
.session
.db_exec(url
, 'list')
323 raise Exception('DbExist')
324 id = rpc
.session
.db_exec(url
, 'create', passwd
, db_name
, demo_data
, langreal
, user_pass
)
325 win
= gtk
.Window(type=gtk
.WINDOW_TOPLEVEL
)
326 win
.set_position(gtk
.WIN_POS_CENTER_ON_PARENT
)
327 win
.set_title(_('OpenERP Database Installation'))
328 vbox
= gtk
.VBox(False, 0)
329 hbox
= gtk
.HBox(False, 13)
330 hbox
.set_border_width(10)
332 img
.set_from_stock('gtk-dialog-info', gtk
.ICON_SIZE_DIALOG
)
333 hbox
.pack_start(img
, expand
=True, fill
=False)
334 vbox2
= gtk
.VBox(False, 0)
336 label
.set_markup(_('<b>Operation in progress</b>'))
337 label
.set_alignment(0.0, 0.5)
338 vbox2
.pack_start(label
, expand
=True, fill
=False)
339 vbox2
.pack_start(gtk
.HSeparator(), expand
=True, fill
=True)
340 vbox2
.pack_start(gtk
.Label(_("Please wait,\nthis operation may take a while...")), expand
=True, fill
=False)
341 hbox
.pack_start(vbox2
, expand
=True, fill
=True)
342 vbox
.pack_start(hbox
)
343 pb
= gtk
.ProgressBar()
344 pb
.set_orientation(gtk
.PROGRESS_LEFT_TO_RIGHT
)
345 vbox
.pack_start(pb
, expand
=True, fill
=False)
348 parent
= service
.LocalService('gui.main').window
349 win
.set_transient_for(parent
)
351 self
.timer
= gobject
.timeout_add(1000, self
.progress_timeout
, pb
, url
, passwd
, id, win
, db_name
, parent
)
353 if e
.args
== ('DbExist',):
354 common
.warning(_("Could not create database."),_('Database already exists !'))
355 elif ('faultString' in e
and e
.faultString
=='AccessDenied:None') or str(e
)=='AccessDenied':
356 common
.warning(_('Bad database administrator password !'), _("Could not create database."))
358 common
.warning(_("Could not create database."),_('Error during database creation !'))
360 def progress_timeout(self
, pbar
, url
, passwd
, id, win
, dbname
, parent
=None):
362 progress
,users
= rpc
.session
.db_exec_no_except(url
, 'get_progress', passwd
, id)
365 common
.warning(_("The server crashed during installation.\nWe suggest you to drop this database."),_("Error during database creation !"))
372 pwdlst
= '\n'.join(map(lambda x
: ' - %s: %s / %s' % (x
['name'],x
['login'],x
['password']), users
))
373 dialog
= glade
.XML(common
.terp_path("terp.glade"), "dia_dbcreate_ok", gettext
.textdomain())
374 win
= dialog
.get_widget('dia_dbcreate_ok')
376 parent
= service
.LocalService('gui.main').window
377 win
.set_transient_for(parent
)
379 buffer = dialog
.get_widget('dia_tv').get_buffer()
381 buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
382 iter_start
= buffer.get_start_iter()
383 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.'))
388 if res
== gtk
.RESPONSE_OK
:
389 m
= re
.match('^(http[s]?://|socket://)([\w.]+):(\d{1,5})$', url
)
392 if x
['login']=='admin' and m
:
393 res
= [x
['login'], x
['password']]
394 res
.append( m
.group(2) )
395 res
.append( m
.group(3) )
396 res
.append( m
.group(1) )
398 log_response
= rpc
.session
.login(*res
)
399 if log_response
== 1:
400 id = self
.terp_main
.sig_win_menu(quiet
=False)
403 self
.sig_login(dbname
=dbname
)
411 class terp_main(service
.Service
):
412 def __init__(self
, name
='gui.main', audience
='gui.*'):
413 service
.Service
.__init
__(self
, name
, audience
)
414 self
.exportMethod(self
.win_add
)
416 self
._handler
_ok
= True
417 self
.glade
= glade
.XML(common
.terp_path("terp.glade"),"win_main",gettext
.textdomain())
418 self
.status_bar_main
= self
.glade
.get_widget('hbox_status_main')
419 self
.toolbar
= self
.glade
.get_widget('main_toolbar')
420 self
.sb_requests
= self
.glade
.get_widget('sb_requests')
421 self
.sb_username
= self
.glade
.get_widget('sb_user_name')
422 self
.sb_servername
= self
.glade
.get_widget('sb_user_server')
423 id = self
.sb_servername
.get_context_id('message')
424 self
.sb_servername
.push(id, _('Press Ctrl+O to login'))
425 self
.secure_img
= self
.glade
.get_widget('secure_img')
426 self
.secure_img
.hide()
428 window
= self
.glade
.get_widget('win_main')
429 window
.connect("destroy", self
.sig_quit
)
430 window
.connect("delete_event", self
.sig_delete
)
432 self
.window
.set_icon(common
.TINYERP_ICON
)
434 self
.notebook
= gtk
.Notebook()
435 self
.notebook
.popup_enable()
436 self
.notebook
.set_scrollable(True)
437 self
.sig_id
= self
.notebook
.connect_after('switch-page', self
._sig
_page
_changt
)
438 vbox
= self
.glade
.get_widget('vbox_main')
439 vbox
.pack_start(self
.notebook
, expand
=True, fill
=True)
441 self
.shortcut_menu
= self
.glade
.get_widget('shortcut')
444 # Code to add themes to the options->theme menu
447 menu
= self
.glade
.get_widget('menu_theme')
449 themes_path
= common
.terp_path('themes')
451 for dname
in os
.listdir(themes_path
):
452 if dname
.startswith('.'):
454 fname
= common
.terp_path(os
.path
.join('themes', dname
, 'gtkrc'))
455 if fname
and os
.path
.isfile(fname
):
456 open_item
= gtk
.RadioMenuItem(old
, dname
)
458 submenu
.append(open_item
)
459 if dname
== options
.options
['client.theme']:
460 open_item
.set_active(True)
461 open_item
.connect('toggled', self
.theme_select
, dname
)
463 submenu
.append(gtk
.SeparatorMenuItem())
464 open_item
= gtk
.RadioMenuItem(old
, _('Default Theme'))
465 submenu
.append(open_item
)
466 if 'none'==options
.options
['client.theme']:
467 open_item
.set_active(True)
468 open_item
.connect('toggled', self
.theme_select
, 'none')
470 menu
.set_submenu(submenu
)
478 self
.current_page
= 0
482 'on_login_activate': self
.sig_login
,
483 'on_logout_activate': self
.sig_logout
,
484 'on_win_next_activate': self
.sig_win_next
,
485 'on_win_prev_activate': self
.sig_win_prev
,
486 'on_plugin_execute_activate': self
.sig_plugin_execute
,
487 'on_quit_activate': self
.sig_close
,
488 'on_but_menu_clicked': self
.sig_win_menu
,
489 'on_win_new_activate': self
.sig_win_menu
,
490 'on_win_home_activate': self
.sig_home_new
,
491 'on_win_close_activate': self
.sig_win_close
,
492 'on_support_activate': common
.support
,
493 'on_preference_activate': self
.sig_user_preferences
,
494 'on_read_requests_activate': self
.sig_request_open
,
495 'on_send_request_activate': self
.sig_request_new
,
496 'on_request_wait_activate': self
.sig_request_wait
,
497 'on_opt_save_activate': lambda x
: options
.options
.save(),
498 'on_menubar_icons_activate': lambda x
: self
.sig_menubar('icons'),
499 'on_menubar_text_activate': lambda x
: self
.sig_menubar('text'),
500 'on_menubar_both_activate': lambda x
: self
.sig_menubar('both'),
501 'on_mode_normal_activate': lambda x
: self
.sig_mode_change(False),
502 'on_mode_pda_activate': lambda x
: self
.sig_mode_change(True),
503 'on_opt_form_tab_top_activate': lambda x
: self
.sig_form_tab('top'),
504 'on_opt_form_tab_left_activate': lambda x
: self
.sig_form_tab('left'),
505 'on_opt_form_tab_right_activate': lambda x
: self
.sig_form_tab('right'),
506 'on_opt_form_tab_bottom_activate': lambda x
: self
.sig_form_tab('bottom'),
507 'on_opt_form_tab_orientation_horizontal_activate': lambda x
: self
.sig_form_tab_orientation(0),
508 'on_opt_form_tab_orientation_vertical_activate': lambda x
: self
.sig_form_tab_orientation(90),
509 'on_help_index_activate': self
.sig_help_index
,
510 'on_help_contextual_activate': self
.sig_help_context
,
511 'on_help_licence_activate': self
.sig_licence
,
512 'on_about_activate': self
.sig_about
,
513 'on_shortcuts_activate' : self
.sig_shortcuts
,
514 'on_db_new_activate': self
.sig_db_new
,
515 'on_db_restore_activate': self
.sig_db_restore
,
516 'on_db_backup_activate': self
.sig_db_dump
,
517 'on_db_drop_activate': self
.sig_db_drop
,
518 'on_admin_password_activate': self
.sig_db_password
,
519 'on_extension_manager_activate': self
.sig_extension_manager
,
522 self
.glade
.signal_connect(signal
, dict[signal
])
525 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'):
526 self
.glade
.signal_connect('on_'+button
+'_clicked', self
._sig
_child
_call
, button
)
527 self
.buttons
[button
]=self
.glade
.get_widget(button
)
530 'form_del': 'but_remove',
531 'form_new': 'but_new',
532 'form_copy': 'but_copy',
533 'form_reload': 'but_reload',
534 'form_log': 'but_log',
535 'form_open': 'but_open',
536 'form_search': 'but_search',
537 'form_previous': 'but_previous',
538 'form_next': 'but_next',
539 'form_save': 'but_save',
540 'goto_id': 'but_goto_id',
541 'form_print': 'but_print',
542 'form_print_html': 'but_print_html',
543 'form_save_as': 'but_save_as',
544 'form_import': 'but_import',
545 'form_filter': 'but_filter',
546 'form_repeat': 'but_print_repeat'
549 self
.glade
.signal_connect('on_'+menu
+'_activate', self
._sig
_child
_call
, menus
[menu
])
551 spool
= service
.LocalService('spool')
552 spool
.subscribe('gui.window', self
.win_add
)
555 # we now create the icon for the attachment button when there are attachments
556 self
.__img
_no
_attachments
= gtk
.Image()
557 pxbf
= self
.window
.render_icon(self
.buttons
['but_attach'].get_stock_id(), self
.toolbar
.get_icon_size())
558 self
.__img
_no
_attachments
.set_from_pixbuf(pxbf
)
559 self
.__img
_no
_attachments
.show()
562 w
, h
= pxbf
.get_width(), pxbf
.get_height()
563 overlay
= self
.window
.render_icon(gtk
.STOCK_APPLY
, gtk
.ICON_SIZE_MENU
)
564 ow
, oh
= overlay
.get_width(), overlay
.get_height()
565 overlay
.composite(pxbf
,
570 gtk
.gdk
.INTERP_NEAREST
,
573 self
.__img
_attachments
= gtk
.Image()
574 self
.__img
_attachments
.set_from_pixbuf(pxbf
)
575 self
.__img
_attachments
.show()
579 settings
= gtk
.settings_get_default()
580 settings
.set_long_property('gtk-button-images', 1, 'TinyERP:gui.main')
582 def fnc_menuitem(menuitem
, opt_name
):
583 options
.options
[opt_name
] = menuitem
.get_active()
585 'on_opt_print_preview_activate': (fnc_menuitem
, 'printer.preview', 'opt_print_preview'),
586 'on_opt_form_toolbar_activate': (fnc_menuitem
, 'form.toolbar', 'opt_form_toolbar'),
588 self
.glade
.get_widget('menubar_'+(options
.options
['client.toolbar'] or 'both')).set_active(True)
589 self
.sig_menubar(options
.options
['client.toolbar'] or 'both')
590 self
.glade
.get_widget('opt_form_tab_'+(options
.options
['client.form_tab'] or 'left')).set_active(True)
591 self
.sig_form_tab(options
.options
['client.form_tab'] or 'left')
592 self
.glade
.get_widget('opt_form_tab_orientation_'+(str(options
.options
['client.form_tab_orientation']) or '0')).set_active(True)
593 self
.sig_form_tab_orientation(options
.options
['client.form_tab_orientation'] or 0)
594 if options
.options
['client.modepda']:
595 self
.glade
.get_widget('mode_pda').set_active(True)
597 self
.glade
.get_widget('mode_normal').set_active(True)
600 self
.glade
.signal_connect(signal
, dict[signal
][0], dict[signal
][1])
601 self
.glade
.get_widget(dict[signal
][2]).set_active(int(bool(options
.options
[dict[signal
][1]])))
603 # Adding a timer the check to requests
604 gobject
.timeout_add(5 * 60 * 1000, self
.request_set
)
606 def shortcut_edit(self
, widget
, model
='ir.ui.menu'):
607 obj
= service
.LocalService('gui.window')
608 domain
= [('user_id', '=', rpc
.session
.uid
), ('resource', '=', model
)]
609 obj
.create(False, 'ir.ui.view_sc', res_id
=None, domain
=domain
, view_type
='form', mode
='tree,form')
611 def shortcut_set(self
, sc
=None):
612 def _action_shortcut(widget
, action
):
614 ctx
= rpc
.session
.context
.copy()
615 obj
= service
.LocalService('action.main')
616 obj
.exec_keyword('tree_but_open', {'model': 'ir.ui.menu', 'id': action
[0],
617 'ids': [action
[0]], 'report_type': 'pdf', 'window': self
.window
}, context
=ctx
)
620 uid
= rpc
.session
.uid
621 sc
= rpc
.session
.rpc_exec_auth('/object', 'execute', 'ir.ui.view_sc', 'get_sc', uid
, 'ir.ui.menu', rpc
.session
.context
) or []
625 menuitem
= gtk
.MenuItem(s
['name'])
626 menuitem
.connect('activate', _action_shortcut
, s
['res_id'])
629 menu
.add(gtk
.SeparatorMenuItem())
630 menuitem
= gtk
.MenuItem(_('Edit'))
631 menuitem
.connect('activate', self
.shortcut_edit
)
635 self
.shortcut_menu
.set_submenu(menu
)
636 self
.shortcut_menu
.set_sensitive(True)
638 def shortcut_unset(self
):
641 self
.shortcut_menu
.set_submenu(menu
)
642 self
.shortcut_menu
.set_sensitive(False)
644 def theme_select(self
, widget
, theme
):
645 options
.options
['client.theme'] = theme
647 self
.window
.show_all()
650 def sig_mode_change(self
, pda_mode
=False):
651 options
.options
['client.modepda'] = pda_mode
652 return self
.sig_mode()
655 pda_mode
= options
.options
['client.modepda']
657 self
.status_bar_main
.hide()
659 self
.status_bar_main
.show()
662 def sig_menubar(self
, option
):
663 options
.options
['client.toolbar'] = option
665 self
.toolbar
.set_style(gtk
.TOOLBAR_BOTH
)
667 self
.toolbar
.set_style(gtk
.TOOLBAR_TEXT
)
668 elif option
=='icons':
669 self
.toolbar
.set_style(gtk
.TOOLBAR_ICONS
)
671 def sig_form_tab(self
, option
):
672 options
.options
['client.form_tab'] = option
674 def sig_form_tab_orientation(self
, option
):
675 options
.options
['client.form_tab_orientation'] = option
677 def sig_win_next(self
, args
):
678 pn
= self
.notebook
.get_current_page()
679 if pn
== len(self
.pages
)-1:
681 self
.notebook
.set_current_page(pn
+1)
683 def sig_win_prev(self
, args
):
684 pn
= self
.notebook
.get_current_page()
685 self
.notebook
.set_current_page(pn
-1)
687 def sig_user_preferences(self
, *args
):
688 win
=win_preference
.win_preference(parent
=self
.window
)
692 def sig_win_close(self
, *args
):
693 self
._sig
_child
_call
(args
[0], 'but_close')
695 def sig_request_new(self
, args
=None):
696 obj
= service
.LocalService('gui.window')
698 return obj
.create(None, 'res.request', False,
699 [('act_from', '=', rpc
.session
.uid
)], 'form',
700 mode
='form,tree', window
=self
.window
,
701 context
={'active_test': False})
705 def sig_request_open(self
, args
=None):
706 ids
,ids2
= self
.request_set()
707 obj
= service
.LocalService('gui.window')
709 return obj
.create(False, 'res.request', ids
,
710 [('act_to', '=', rpc
.session
.uid
), ('active', '=', True)],
711 'form', mode
='tree,form', window
=self
.window
,
712 context
={'active_test': False})
716 def sig_request_wait(self
, args
=None):
717 ids
,ids2
= self
.request_set()
718 obj
= service
.LocalService('gui.window')
720 return obj
.create(False, 'res.request', ids
,
721 [('act_from', '=', rpc
.session
.uid
),
722 ('state', '=', 'waiting'), ('active', '=', True)],
723 'form', mode
='tree,form', window
=self
.window
,
724 context
={'active_test': False})
728 def request_set(self
):
730 uid
= rpc
.session
.uid
731 ids
,ids2
= rpc
.session
.rpc_exec_auth_try('/object', 'execute',
732 'res.request', 'request_get')
734 message
= _('%s request(s)') % len(ids
)
736 message
= _('No request')
738 message
+= _(' - %s request(s) sended') % len(ids2
)
739 id = self
.sb_requests
.get_context_id('message')
740 self
.sb_requests
.push(id, message
)
745 def sig_login(self
, widget
=None, dbname
=False):
747 RES_BAD_PASSWORD
= -2
750 log_response
= RES_BAD_PASSWORD
752 while log_response
== RES_BAD_PASSWORD
:
755 res
= l
.run(dbname
=dbname
, parent
=self
.window
)
757 if e
.args
== ('QueryCanceled',):
760 service
.LocalService('gui.main').window
.present()
761 self
.sig_logout(widget
)
762 log_response
= rpc
.session
.login(*res
)
763 if log_response
== RES_OK
:
764 options
.options
.save()
765 id = self
.sig_win_menu(quiet
=False)
767 self
.sig_home_new(quiet
=True, except_id
=id)
768 if res
[4] == 'https://':
769 self
.secure_img
.show()
771 self
.secure_img
.hide()
773 elif log_response
== RES_CNX_ERROR
:
774 common
.message(_('Connection error !\nUnable to connect to the server !'))
775 elif log_response
== RES_BAD_PASSWORD
:
776 common
.message(_('Connection error !\nBad username or password !'))
777 except rpc
.rpc_exception
:
780 self
.glade
.get_widget('but_menu').set_sensitive(True)
781 self
.glade
.get_widget('user').set_sensitive(True)
782 self
.glade
.get_widget('form').set_sensitive(True)
783 self
.glade
.get_widget('plugins').set_sensitive(True)
786 def sig_logout(self
, widget
):
789 wid
= self
._wid
_get
()
791 if 'but_close' in wid
.handlers
:
792 res
= wid
.handlers
['but_close']()
795 res
= self
._win
_del
()
798 id = self
.sb_requests
.get_context_id('message')
799 self
.sb_requests
.push(id, '')
800 id = self
.sb_username
.get_context_id('message')
801 self
.sb_username
.push(id, _('Not logged !'))
802 id = self
.sb_servername
.get_context_id('message')
803 self
.sb_servername
.push(id, _('Press Ctrl+O to login'))
804 self
.secure_img
.hide()
805 self
.shortcut_unset()
806 self
.glade
.get_widget('but_menu').set_sensitive(False)
807 self
.glade
.get_widget('user').set_sensitive(False)
808 self
.glade
.get_widget('form').set_sensitive(False)
809 self
.glade
.get_widget('plugins').set_sensitive(False)
813 def sig_help_index(self
, widget
):
814 tools
.launch_browser(options
.options
['help.index'])
816 def sig_help_context(self
, widget
):
817 model
= self
._wid
_get
().model
818 l
= rpc
.session
.context
.get('lang','en_US')
819 tools
.launch_browser(options
.options
['help.context']+'?model=%s&lang=%s' % (model
,l
))
821 def sig_licence(self
, widget
):
822 dialog
= glade
.XML(common
.terp_path("terp.glade"), "win_licence", gettext
.textdomain())
823 dialog
.signal_connect("on_but_ok_pressed", lambda obj
: dialog
.get_widget('win_licence').destroy())
825 win
= dialog
.get_widget('win_licence')
826 win
.set_transient_for(self
.window
)
829 def sig_about(self
, widget
):
830 about
= glade
.XML(common
.terp_path("terp.glade"), "win_about", gettext
.textdomain())
831 buffer = about
.get_widget('textview2').get_buffer()
832 about_txt
= buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
833 buffer.set_text(about_txt
% tinyerp_version
)
834 about
.signal_connect("on_but_ok_pressed", lambda obj
: about
.get_widget('win_about').destroy())
836 win
= about
.get_widget('win_about')
837 win
.set_transient_for(self
.window
)
840 def sig_shortcuts(self
, widget
):
841 shortcuts_win
= glade
.XML(common
.terp_path('terp.glade'), 'shortcuts_dia', gettext
.textdomain())
842 shortcuts_win
.signal_connect("on_but_ok_pressed", lambda obj
: shortcuts_win
.get_widget('shortcuts_dia').destroy())
844 win
= shortcuts_win
.get_widget('shortcuts_dia')
845 win
.set_transient_for(self
.window
)
848 def sig_win_menu(self
, widget
=None, quiet
=True):
849 for p
in range(len(self
.pages
)):
850 if self
.pages
[p
].model
=='ir.ui.menu':
851 self
.notebook
.set_current_page(p
)
853 res
= self
.sig_win_new(widget
, type='menu_id', quiet
=quiet
)
855 return self
.sig_win_new(widget
, type='action_id', quiet
=quiet
)
858 def sig_win_new(self
, widget
=None, type='menu_id', quiet
=True, except_id
=False):
860 act_id
= rpc
.session
.rpc_exec_auth('/object', 'execute', 'res.users',
861 'read', [rpc
.session
.uid
], [type,'name'], rpc
.session
.context
)
864 id = self
.sb_username
.get_context_id('message')
865 self
.sb_username
.push(id, act_id
[0]['name'] or '')
866 id = self
.sb_servername
.get_context_id('message')
867 data
= urlparse
.urlsplit(rpc
.session
._url
)
868 self
.sb_servername
.push(id, data
[0]+':'+(data
[1] and '//'+data
[1] \
869 or data
[2])+' ['+options
.options
['login.db']+']')
870 if not act_id
[0][type]:
873 common
.warning(_("You can not log into the system !\nAsk the administrator to verify\nyou have an action defined for your user."),'Access Denied !')
876 act_id
= act_id
[0][type][0]
877 if except_id
and act_id
== except_id
:
879 obj
= service
.LocalService('action.main')
880 win
= obj
.execute(act_id
, {'window':self
.window
})
882 user
= rpc
.session
.rpc_exec_auth_wo('/object', 'execute', 'res.users',
883 'read', [rpc
.session
.uid
], [type,'name'], rpc
.session
.context
)
885 act_id
= user
[0][type][0]
890 def sig_home_new(self
, widget
=None, quiet
=True, except_id
=False):
891 return self
.sig_win_new(widget
, type='action_id', quiet
=quiet
,
894 def sig_plugin_execute(self
, widget
):
896 pn
= self
.notebook
.get_current_page()
897 datas
= {'model': self
.pages
[pn
].model
, 'ids':self
.pages
[pn
].ids_get(), 'id' : self
.pages
[pn
].id_get()}
898 plugins
.execute(datas
)
900 def sig_quit(self
, widget
):
901 options
.options
.save()
904 def sig_close(self
, widget
):
905 if common
.sur(_("Do you really want to quit ?"), parent
=self
.window
):
906 if not self
.sig_logout(widget
):
908 options
.options
.save()
911 def sig_delete(self
, widget
, event
, data
=None):
912 if common
.sur(_("Do you really want to quit ?"), parent
=self
.window
):
913 if not self
.sig_logout(widget
):
918 def win_add(self
, win
, datas
):
919 self
.pages
.append(win
)
920 self
.notebook
.append_page(win
.widget
, gtk
.Label(win
.name
))
921 self
.notebook
.set_current_page(-1)
923 def message(self
, message
):
924 id = self
.status_bar
.get_context_id('message')
925 self
.status_bar
.push(id, message
)
927 def __attachment_callback(self
, view
, objid
):
928 current_view
= self
._wid
_get
()
929 current_id
= current_view
and current_view
.id_get()
930 if current_view
== view
and objid
== current_id
:
932 if objid
and view
.screen
.current_view
.view_type
== 'form':
933 cpt
= rpc
.session
.rpc_exec_auth('/object', 'execute',
934 'ir.attachment', 'search_count',
935 [('res_model', '=', view
.model
), ('res_id', '=', objid
)])
937 self
.buttons
['but_attach'].set_icon_widget(self
.__img
_attachments
)
938 self
.buttons
['but_attach'].set_label(_('Attachments (%d)') % cpt
)
941 def _update_attachment_button(self
, view
= None):
943 Update the attachment icon for display the number of attachments
946 view
= self
._wid
_get
()
948 id = view
and view
.id_get()
949 gobject
.timeout_add(1500, self
.__attachment
_callback
, view
, id)
950 self
.buttons
['but_attach'].set_icon_widget(self
.__img
_no
_attachments
)
951 self
.buttons
['but_attach'].set_label(_('Attachments'))
954 def sb_set(self
, view
=None):
956 view
= self
._wid
_get
()
957 if view
and hasattr(view
, 'screen'):
958 self
._handler
_ok
= False
959 self
.glade
.get_widget('radio_'+view
.screen
.current_view
.view_type
).set_active(True)
960 self
._handler
_ok
= True
961 self
._update
_attachment
_button
(view
)
962 for x
in self
.buttons
:
964 self
.buttons
[x
].set_sensitive(view
and (x
in view
.handlers
))
967 pn
= self
.notebook
.get_current_page()
969 self
.notebook
.disconnect(self
.sig_id
)
970 page
= self
.pages
.pop(pn
)
971 self
.notebook
.remove_page(pn
)
972 self
.sig_id
= self
.notebook
.connect_after('switch-page', self
._sig
_page
_changt
)
977 return self
.notebook
.get_current_page() != -1
980 pn
= self
.notebook
.get_current_page()
983 return self
.pages
[pn
]
985 def _sig_child_call(self
, widget
, button_name
, *args
):
986 if not self
._handler
_ok
:
988 wid
= self
._wid
_get
()
991 if button_name
.startswith('radio_'):
992 act
= self
.glade
.get_widget(button_name
).get_active()
993 if not act
: return False
995 if button_name
in wid
.handlers
:
996 res
= wid
.handlers
[button_name
]()
997 # for those buttons, we refresh the attachment button.
998 # for the "switch view" button, the action has already
999 # been called by the Screen object of the view (wid)
1000 if button_name
in ('but_new', 'but_remove', 'but_search', \
1001 'but_previous', 'but_next', 'but_open', \
1002 'but_close', 'but_reload', 'but_attach', 'but_goto_id'):
1003 self
._update
_attachment
_button
(wid
)
1004 if button_name
.startswith('radio_'):
1006 #mode = wid.screen.current_view.view_type
1007 #print 'RADIO CALLED', button_name, args, self.glade.get_widget(button_name).get_active(), button_name[6:]
1008 #if self.glade.get_widget(button_name).get_active() and (mode<>button_name[6:]):
1009 # print 'SWITCH', button_name
1011 if button_name
=='but_close' and res
:
1014 def _sig_page_changt(self
, widget
=None, *args
):
1015 self
.last_page
= self
.current_page
1016 self
.current_page
= self
.notebook
.get_current_page()
1019 def sig_db_new(self
, widget
):
1020 if not self
.sig_logout(widget
):
1022 dia
= db_create(self
.sig_login
, self
)
1023 res
= dia
.run(self
.window
)
1025 options
.options
.save()
1028 def sig_db_drop(self
, widget
):
1029 if not self
.sig_logout(widget
):
1031 url
, db_name
, passwd
= self
._choose
_db
_select
(_('Delete a database'))
1036 rpc
.session
.db_exec(url
, 'drop', passwd
, db_name
)
1037 common
.message(_("Database dropped successfully !"), parent
=self
.window
)
1038 except Exception, e
:
1039 if ('faultString' in e
and e
.faultString
=='AccessDenied:None') or str(e
)=='AccessDenied':
1040 common
.warning(_('Bad database administrator password !'),_("Could not drop database."), parent
=self
.window
)
1042 common
.warning(_("Couldn't drop database"), parent
=self
.window
)
1044 def sig_db_restore(self
, widget
):
1045 filename
= common
.file_selection(_('Open...'), parent
=self
.window
, preview
=False)
1049 url
, db_name
, passwd
= self
._choose
_db
_ent
()
1052 f
= file(filename
, 'rb')
1053 data_b64
= base64
.encodestring(f
.read())
1055 rpc
.session
.db_exec(url
, 'restore', passwd
, db_name
, data_b64
)
1056 common
.message(_("Database restored successfully !"), parent
=self
.window
)
1058 if ('faultString' in e
and e
.faultString
=='AccessDenied:None') or str(e
)=='AccessDenied':
1059 common
.warning(_('Bad database administrator password !'),_("Could not restore database."), parent
=self
.window
)
1061 common
.warning(_("Couldn't restore database"), parent
=self
.window
)
1063 def sig_extension_manager(self
,widget
):
1064 win
= win_extension
.win_extension(self
.window
)
1067 def sig_db_password(self
, widget
):
1068 dialog
= glade
.XML(common
.terp_path("terp.glade"), "dia_passwd_change",
1069 gettext
.textdomain())
1070 win
= dialog
.get_widget('dia_passwd_change')
1071 win
.set_icon(common
.TINYERP_ICON
)
1072 win
.set_transient_for(self
.window
)
1074 server_widget
= dialog
.get_widget('ent_server2')
1075 old_pass_widget
= dialog
.get_widget('old_passwd')
1076 new_pass_widget
= dialog
.get_widget('new_passwd')
1077 new_pass2_widget
= dialog
.get_widget('new_passwd2')
1078 change_button
= dialog
.get_widget('but_server_change1')
1079 change_button
.connect_after('clicked', lambda a
,b
: _server_ask(b
, win
), server_widget
)
1081 host
= options
.options
['login.server']
1082 port
= options
.options
['login.port']
1083 protocol
= options
.options
['login.protocol']
1084 url
= '%s%s:%s' % (protocol
, host
, port
)
1085 server_widget
.set_text(url
)
1088 if res
== gtk
.RESPONSE_OK
:
1089 url
= server_widget
.get_text()
1090 old_passwd
= old_pass_widget
.get_text()
1091 new_passwd
= new_pass_widget
.get_text()
1092 new_passwd2
= new_pass2_widget
.get_text()
1093 if new_passwd
!= new_passwd2
:
1094 common
.warning(_("Confirmation password do not match " \
1095 "new password, operation cancelled!"),
1096 _("Validation Error."), parent
=win
)
1099 rpc
.session
.db_exec(url
, 'change_admin_password',
1100 old_passwd
, new_passwd
)
1102 if ('faultString' in e
and e
.faultString
=='AccessDenied:None') \
1103 or str(e
)=='AccessDenied':
1104 common
.warning(_("Could not change password database."),
1105 _('Bas password provided !'), parent
=win
)
1107 common
.warning(_("Error, password not changed."),
1109 self
.window
.present()
1112 def sig_db_dump(self
, widget
):
1113 url
, db_name
, passwd
= self
._choose
_db
_select
(_('Backup a database'))
1116 filename
= common
.file_selection(_('Save As...'),
1117 action
=gtk
.FILE_CHOOSER_ACTION_SAVE
, parent
=self
.window
, preview
=False)
1121 dump_b64
= rpc
.session
.db_exec(url
, 'dump', passwd
, db_name
)
1122 dump
= base64
.decodestring(dump_b64
)
1123 f
= file(filename
, 'wb')
1126 common
.message(_("Database backuped successfully !"), parent
=self
.window
)
1128 common
.warning(_("Couldn't backup database."), parent
=self
.window
)
1130 def _choose_db_select(self
, title
=_("Backup a database")):
1131 def refreshlist(widget
, db_widget
, label
, url
):
1132 res
= _refresh_dblist(db_widget
, url
)
1134 label
.set_label('<b>'+_('Could not connect to server !')+'</b>')
1138 label
.set_label('<b>'+_('No database found, you must create one !')+'</b>')
1146 def refreshlist_ask(widget
, server_widget
, db_widget
, label
, parent
=None):
1147 url
= _server_ask(server_widget
, parent
)
1150 refreshlist(widget
, db_widget
, label
, url
)
1153 dialog
= glade
.XML(common
.terp_path("terp.glade"), "win_db_select",
1154 gettext
.textdomain())
1155 win
= dialog
.get_widget('win_db_select')
1156 win
.set_icon(common
.TINYERP_ICON
)
1157 win
.set_default_response(gtk
.RESPONSE_OK
)
1158 win
.set_transient_for(self
.window
)
1161 pass_widget
= dialog
.get_widget('ent_passwd_select')
1162 server_widget
= dialog
.get_widget('ent_server_select')
1163 db_widget
= dialog
.get_widget('combo_db_select')
1164 label
= dialog
.get_widget('label_db_select')
1167 dialog
.get_widget('db_select_label').set_markup('<b>'+title
+'</b>')
1169 protocol
= options
.options
['login.protocol']
1170 url
= '%s%s:%s' % (protocol
, options
.options
['login.server'], options
.options
['login.port'])
1171 server_widget
.set_text(url
)
1173 liststore
= gtk
.ListStore(str)
1174 db_widget
.set_model(liststore
)
1176 refreshlist(None, db_widget
, label
, url
)
1177 change_button
= dialog
.get_widget('but_server_select')
1178 change_button
.connect_after('clicked', refreshlist_ask
, server_widget
, db_widget
, label
, win
)
1180 cell
= gtk
.CellRendererText()
1181 db_widget
.pack_start(cell
, True)
1182 db_widget
.add_attribute(cell
, 'text', 0)
1189 if res
== gtk
.RESPONSE_OK
:
1190 db
= db_widget
.get_active_text()
1191 url
= server_widget
.get_text()
1192 passwd
= pass_widget
.get_text()
1193 self
.window
.present()
1195 return (url
,db
,passwd
)
1197 def _choose_db_ent(self
):
1198 dialog
= glade
.XML(common
.terp_path("terp.glade"), "win_db_ent",
1199 gettext
.textdomain())
1200 win
= dialog
.get_widget('win_db_ent')
1201 win
.set_icon(common
.TINYERP_ICON
)
1202 win
.set_transient_for(self
.window
)
1205 db_widget
= dialog
.get_widget('ent_db')
1206 widget_pass
= dialog
.get_widget('ent_password')
1207 widget_url
= dialog
.get_widget('ent_server1')
1209 protocol
= options
.options
['login.protocol']
1210 url
= '%s%s:%s' % (protocol
, options
.options
['login.server'],
1211 options
.options
['login.port'])
1212 widget_url
.set_text(url
)
1214 change_button
= dialog
.get_widget('but_server_change')
1215 change_button
.connect_after('clicked', lambda a
,b
: _server_ask(b
, win
),
1223 if res
== gtk
.RESPONSE_OK
:
1224 db
= db_widget
.get_text()
1225 url
= widget_url
.get_text()
1226 passwd
= widget_pass
.get_text()
1227 self
.window
.present()
1229 return url
, db
, passwd
1232 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: