When there is no trailing '/', it adds None which is not supported by urllib.unquote. issue4589 review12981002
1555 lines
60 KiB
Python
1555 lines
60 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
import os
|
|
import sys
|
|
import gettext
|
|
from urlparse import urlparse, parse_qsl
|
|
import urllib
|
|
import gobject
|
|
import gtk
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
import webbrowser
|
|
import threading
|
|
|
|
import tryton.rpc as rpc
|
|
from tryton.common import RPCExecute, RPCException, RPCContextReload
|
|
from tryton.config import CONFIG, TRYTON_ICON, get_config_dir
|
|
import tryton.common as common
|
|
from tryton.pyson import PYSONDecoder
|
|
from tryton.jsonrpc import object_hook
|
|
from tryton.action import Action
|
|
from tryton.exceptions import TrytonServerError, TrytonError, \
|
|
TrytonServerUnavailable
|
|
from tryton.gui.window import Window
|
|
from tryton.gui.window.preference import Preference
|
|
from tryton.gui.window import Limit
|
|
from tryton.gui.window import Email
|
|
from tryton.gui.window.dblogin import DBLogin
|
|
from tryton.gui.window.dbcreate import DBCreate
|
|
from tryton.gui.window.dbdumpdrop import DBBackupDrop
|
|
from tryton.gui.window.tips import Tips
|
|
from tryton.gui.window.about import About
|
|
from tryton.gui.window.shortcuts import Shortcuts
|
|
from tryton.gui.window.dbrestore import DBRestore
|
|
from tryton.common.cellrendererclickablepixbuf import \
|
|
CellRendererClickablePixbuf
|
|
import tryton.translate as translate
|
|
import tryton.plugins
|
|
from tryton.common.placeholder_entry import PlaceholderEntry
|
|
import pango
|
|
import time
|
|
try:
|
|
import gtk_osxapplication
|
|
except ImportError:
|
|
gtk_osxapplication = None
|
|
try:
|
|
import gtkspell
|
|
except ImportError:
|
|
gtkspell = None
|
|
|
|
_ = gettext.gettext
|
|
|
|
|
|
_MAIN = []
|
|
TAB_SIZE = 120
|
|
|
|
|
|
class Main(object):
|
|
window = None
|
|
tryton_client = None
|
|
|
|
def __init__(self, tryton_client):
|
|
super(Main, self).__init__()
|
|
Main.tryton_client = tryton_client
|
|
|
|
self.window = gtk.Window()
|
|
self._width = int(CONFIG['client.default_width'])
|
|
self._height = int(CONFIG['client.default_height'])
|
|
if CONFIG['client.maximize']:
|
|
self.window.maximize()
|
|
self.window.set_default_size(self._width, self._height)
|
|
self.window.set_resizable(True)
|
|
self.set_title()
|
|
self.window.set_icon(TRYTON_ICON)
|
|
self.window.connect("destroy", Main.sig_quit)
|
|
self.window.connect("delete_event", self.sig_close)
|
|
self.window.connect('configure_event', self.sig_configure)
|
|
self.window.connect('window_state_event', self.sig_window_state)
|
|
|
|
self.accel_group = gtk.AccelGroup()
|
|
self.window.add_accel_group(self.accel_group)
|
|
|
|
self.macapp = None
|
|
if gtk_osxapplication is not None:
|
|
self.macapp = gtk_osxapplication.OSXApplication()
|
|
self.macapp.connect("NSApplicationBlockTermination",
|
|
self.sig_close)
|
|
|
|
gtk.accel_map_add_entry('<tryton>/File/Connect', gtk.keysyms.O,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/File/Quit', gtk.keysyms.Q,
|
|
gtk.gdk.CONTROL_MASK)
|
|
if sys.platform != 'darwin':
|
|
gtk.accel_map_add_entry('<tryton>/User/Menu Reload', gtk.keysyms.T,
|
|
gtk.gdk.MOD1_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/User/Menu Toggle', gtk.keysyms.T,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/User/Global Search', gtk.keysyms.K,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/User/Home', gtk.keysyms.H,
|
|
gtk.gdk.CONTROL_MASK)
|
|
|
|
gtk.accel_map_add_entry('<tryton>/Form/New', gtk.keysyms.N,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Save', gtk.keysyms.S,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Duplicate', gtk.keysyms.D,
|
|
gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Delete', gtk.keysyms.D,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Next', gtk.keysyms.Page_Down,
|
|
0)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Previous', gtk.keysyms.Page_Up,
|
|
0)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Switch View', gtk.keysyms.L,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Close', gtk.keysyms.W,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Previous Tab',
|
|
gtk.keysyms.Page_Up, gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Next Tab',
|
|
gtk.keysyms.Page_Down, gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Reload', gtk.keysyms.R,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Actions', gtk.keysyms.E,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Report', gtk.keysyms.P,
|
|
gtk.gdk.CONTROL_MASK)
|
|
gtk.accel_map_add_entry('<tryton>/Form/Search', gtk.keysyms.F,
|
|
gtk.gdk.CONTROL_MASK)
|
|
|
|
gtk.accel_map_load(os.path.join(get_config_dir(), 'accel.map'))
|
|
|
|
self.tooltips = common.Tooltips()
|
|
|
|
self.vbox = gtk.VBox()
|
|
self.window.add(self.vbox)
|
|
|
|
self.menubar = None
|
|
self.global_search_entry = None
|
|
self.menuitem_user = None
|
|
self.menuitem_favorite = None
|
|
|
|
self.buttons = {}
|
|
|
|
self.pane = gtk.HPaned()
|
|
self.pane.connect('button-press-event',
|
|
self.on_paned_button_press_event)
|
|
|
|
self.menu_screen = None
|
|
self.menu_expander = gtk.Expander()
|
|
self.menu_expander.connect('notify::expanded', self.menu_expanded)
|
|
if self.menu_expander.get_direction() == gtk.TEXT_DIR_RTL:
|
|
self.menu_expander.set_direction(gtk.TEXT_DIR_LTR)
|
|
else:
|
|
self.menu_expander.set_direction(gtk.TEXT_DIR_RTL)
|
|
self.menu_expander.set_expanded(CONFIG['menu.expanded'])
|
|
self.pane.add(self.menu_expander)
|
|
|
|
self.vbox.pack_start(self.pane, True, True)
|
|
|
|
self.notebook = gtk.Notebook()
|
|
self.notebook.popup_enable()
|
|
self.notebook.set_scrollable(True)
|
|
self.notebook.connect_after('switch-page', self._sig_page_changt)
|
|
|
|
self.pane.add2(self.notebook)
|
|
|
|
self.set_menubar()
|
|
|
|
self.window.show_all()
|
|
|
|
self.pages = []
|
|
self.previous_pages = {}
|
|
self.current_page = 0
|
|
self.last_page = 0
|
|
self.dialogs = []
|
|
|
|
if CONFIG['client.modepda']:
|
|
self.radiomenuitem_pda.set_active(True)
|
|
else:
|
|
self.radiomenuitem_normal.set_active(True)
|
|
|
|
settings = gtk.settings_get_default()
|
|
# Due to a bug in old version of pyGTk gtk-button-images can
|
|
# not be set when there is no buttons
|
|
gtk.Button()
|
|
try:
|
|
settings.set_property('gtk-button-images', True)
|
|
except TypeError:
|
|
pass
|
|
try:
|
|
settings.set_property('gtk-can-change-accels',
|
|
CONFIG['client.can_change_accelerators'])
|
|
except TypeError:
|
|
pass
|
|
|
|
# Register plugins
|
|
tryton.plugins.register()
|
|
|
|
if self.macapp is not None:
|
|
self.macapp.ready()
|
|
|
|
_MAIN.append(self)
|
|
|
|
def set_menubar(self):
|
|
if self.menubar:
|
|
self.menubar.destroy()
|
|
menubar = gtk.MenuBar()
|
|
self.menubar = menubar
|
|
|
|
self.vbox.pack_start(menubar, False, True)
|
|
self.vbox.reorder_child(menubar, 0)
|
|
|
|
menuitem_file = gtk.MenuItem(_('_File'))
|
|
menubar.add(menuitem_file)
|
|
|
|
menu_file = self._set_menu_file()
|
|
menuitem_file.set_submenu(menu_file)
|
|
menu_file.set_accel_group(self.accel_group)
|
|
menu_file.set_accel_path('<tryton>/File')
|
|
|
|
menuitem_user = gtk.MenuItem(_('_User'))
|
|
if self.menuitem_user:
|
|
menuitem_user.set_sensitive(
|
|
self.menuitem_user.get_property('sensitive'))
|
|
else:
|
|
menuitem_user.set_sensitive(False)
|
|
self.menuitem_user = menuitem_user
|
|
menubar.add(menuitem_user)
|
|
|
|
menu_user = self._set_menu_user()
|
|
menuitem_user.set_submenu(menu_user)
|
|
menu_user.set_accel_group(self.accel_group)
|
|
menu_user.set_accel_path('<tryton>/User')
|
|
|
|
menuitem_options = gtk.MenuItem(_('_Options'))
|
|
menubar.add(menuitem_options)
|
|
|
|
menu_options = self._set_menu_options()
|
|
menuitem_options.set_submenu(menu_options)
|
|
menu_options.set_accel_group(self.accel_group)
|
|
menu_options.set_accel_path('<tryton>/Options')
|
|
|
|
menuitem_favorite = gtk.MenuItem(_('Fa_vorites'))
|
|
if self.menuitem_favorite:
|
|
menuitem_favorite.set_sensitive(
|
|
self.menuitem_favorite.get_property('sensitive'))
|
|
else:
|
|
menuitem_favorite.set_sensitive(False)
|
|
self.menuitem_favorite = menuitem_favorite
|
|
menubar.add(menuitem_favorite)
|
|
menuitem_favorite.set_accel_path('<tryton>/Favorites')
|
|
|
|
def favorite_activate(widget):
|
|
if (not menuitem_favorite.get_submenu()
|
|
or not menuitem_favorite.get_submenu().get_children()):
|
|
self.favorite_set()
|
|
menuitem_favorite.connect('select', favorite_activate)
|
|
|
|
menuitem_help = gtk.MenuItem(_('_Help'))
|
|
menubar.add(menuitem_help)
|
|
|
|
menu_help = self._set_menu_help()
|
|
menuitem_help.set_submenu(menu_help)
|
|
menu_help.set_accel_group(self.accel_group)
|
|
menu_help.set_accel_path('<tryton>/Help')
|
|
|
|
if self.macapp is not None:
|
|
self.menubar.set_no_show_all(True)
|
|
self.macapp.set_menu_bar(self.menubar)
|
|
self.macapp.insert_app_menu_item(self.aboutitem, 0)
|
|
menuitem_file.show_all()
|
|
menuitem_user.show_all()
|
|
menuitem_options.show_all()
|
|
menuitem_favorite.show_all()
|
|
menuitem_help.show_all()
|
|
else:
|
|
self.menubar.show_all()
|
|
|
|
def set_global_search(self):
|
|
self.global_search_entry = PlaceholderEntry()
|
|
self.global_search_entry.set_placeholder_text(_('Search'))
|
|
global_search_completion = gtk.EntryCompletion()
|
|
global_search_completion.set_match_func(lambda *a: True)
|
|
global_search_completion.set_model(gtk.ListStore(
|
|
gtk.gdk.Pixbuf, str, str, int))
|
|
pixbuf_cell = gtk.CellRendererPixbuf()
|
|
global_search_completion.pack_start(pixbuf_cell, False)
|
|
global_search_completion.add_attribute(pixbuf_cell, 'pixbuf', 0)
|
|
text_cell = gtk.CellRendererText()
|
|
global_search_completion.pack_start(text_cell)
|
|
global_search_completion.add_attribute(text_cell, "markup", 1)
|
|
global_search_completion.props.popup_set_width = True
|
|
self.global_search_entry.set_completion(global_search_completion)
|
|
self.global_search_entry.set_icon_from_stock(gtk.ENTRY_ICON_PRIMARY,
|
|
'gtk-find')
|
|
|
|
def match_selected(completion, model, iter_):
|
|
model, record_id = model.get(iter_, 2, 3)
|
|
if model == self.menu_screen.model_name:
|
|
Action.exec_keyword('tree_open', {
|
|
'model': model,
|
|
'id': record_id,
|
|
'ids': [record_id],
|
|
}, context=self.menu_screen.context.copy())
|
|
else:
|
|
Window.create(False, model, res_id=record_id,
|
|
mode=['form', 'tree'])
|
|
self.global_search_entry.set_text('')
|
|
return True
|
|
|
|
global_search_completion.connect('match-selected', match_selected)
|
|
|
|
def update(widget, search_text, callback=None):
|
|
def end():
|
|
if callback:
|
|
callback()
|
|
return False
|
|
if search_text != widget.get_text().decode('utf-8'):
|
|
return end()
|
|
gmodel = global_search_completion.get_model()
|
|
if not search_text or not gmodel:
|
|
gmodel.clear()
|
|
gmodel.search_text = search_text
|
|
return end()
|
|
if getattr(gmodel, 'search_text', None) == search_text:
|
|
return end()
|
|
|
|
def set_result(result):
|
|
try:
|
|
result = result()
|
|
except RPCException:
|
|
result = []
|
|
if search_text != widget.get_text().decode('utf-8'):
|
|
if callback:
|
|
callback()
|
|
return False
|
|
gmodel.clear()
|
|
for r in result:
|
|
_, model, model_name, record_id, record_name, icon = r
|
|
if icon:
|
|
text = common.to_xml(record_name)
|
|
common.ICONFACTORY.register_icon(icon)
|
|
pixbuf = widget.render_icon(stock_id=icon,
|
|
size=gtk.ICON_SIZE_BUTTON, detail=None)
|
|
else:
|
|
text = '<b>%s:</b>\n %s' % (
|
|
common.to_xml(model_name),
|
|
common.to_xml(record_name))
|
|
pixbuf = None
|
|
gmodel.append([pixbuf, text, model, record_id])
|
|
gmodel.search_text = search_text
|
|
# Force display of popup
|
|
widget.emit('changed')
|
|
end()
|
|
|
|
RPCExecute('model', 'ir.model', 'global_search', search_text,
|
|
CONFIG['client.limit'], self.menu_screen.model_name,
|
|
context=self.menu_screen.context, callback=set_result)
|
|
return False
|
|
|
|
def changed(widget):
|
|
search_text = widget.get_text().decode('utf-8')
|
|
gobject.timeout_add(300, update, widget, search_text)
|
|
|
|
def activate(widget):
|
|
def message():
|
|
gmodel = global_search_completion.get_model()
|
|
if not len(gmodel):
|
|
common.message(_('No result found.'))
|
|
else:
|
|
widget.emit('changed')
|
|
search_text = widget.get_text().decode('utf-8')
|
|
update(widget, search_text, message)
|
|
|
|
self.global_search_entry.connect('changed', changed)
|
|
self.global_search_entry.connect('activate', activate)
|
|
|
|
def show_global_search(self):
|
|
self.pane.get_child1().set_expanded(True)
|
|
self.global_search_entry.grab_focus()
|
|
|
|
def set_title(self, value=''):
|
|
title = 'Tryton'
|
|
if value:
|
|
title += ' - ' + value
|
|
self.window.set_title(title)
|
|
|
|
def _set_menu_file(self):
|
|
menu_file = gtk.Menu()
|
|
|
|
imagemenuitem_connect = gtk.ImageMenuItem(_('_Connect...'),
|
|
self.accel_group)
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-connect', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_connect.set_image(image)
|
|
imagemenuitem_connect.connect('activate', self.sig_login)
|
|
imagemenuitem_connect.set_accel_path('<tryton>/File/Connect')
|
|
menu_file.add(imagemenuitem_connect)
|
|
|
|
imagemenuitem_disconnect = gtk.ImageMenuItem(_('_Disconnect'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-disconnect', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_disconnect.set_image(image)
|
|
imagemenuitem_disconnect.connect('activate', self.sig_logout)
|
|
imagemenuitem_disconnect.set_accel_path('<tryton>/File/Disconnect')
|
|
menu_file.add(imagemenuitem_disconnect)
|
|
|
|
menu_file.add(gtk.SeparatorMenuItem())
|
|
|
|
imagemenuitem_database = gtk.ImageMenuItem(_('Data_base'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-system-file-manager', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_database.set_image(image)
|
|
menu_file.add(imagemenuitem_database)
|
|
|
|
menu_database = gtk.Menu()
|
|
menu_database.set_accel_group(self.accel_group)
|
|
menu_database.set_accel_path('<tryton>/File/Database')
|
|
imagemenuitem_database.set_submenu(menu_database)
|
|
|
|
imagemenuitem_db_new = gtk.ImageMenuItem(_('_New Database...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-folder-new', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_db_new.set_image(image)
|
|
imagemenuitem_db_new.connect('activate', self.sig_db_new)
|
|
imagemenuitem_db_new.set_accel_path(
|
|
'<tryton>/File/Database/New Database')
|
|
menu_database.add(imagemenuitem_db_new)
|
|
|
|
imagemenuitem_db_restore = gtk.ImageMenuItem(_('_Restore Database...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-folder-saved-search', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_db_restore.set_image(image)
|
|
imagemenuitem_db_restore.connect('activate', self.sig_db_restore)
|
|
imagemenuitem_db_restore.set_accel_path(
|
|
'<tryton>/File/Database/Restore Database')
|
|
menu_database.add(imagemenuitem_db_restore)
|
|
|
|
imagemenuitem_db_dump = gtk.ImageMenuItem(_('_Backup Database...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-save-as', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_db_dump.set_image(image)
|
|
imagemenuitem_db_dump.connect('activate', self.sig_db_dump)
|
|
imagemenuitem_db_dump.set_accel_path(
|
|
'<tryton>/File/Database/Backup Database')
|
|
menu_database.add(imagemenuitem_db_dump)
|
|
|
|
imagemenuitem_db_drop = gtk.ImageMenuItem(_('Dro_p Database...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-delete', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_db_drop.set_image(image)
|
|
imagemenuitem_db_drop.connect('activate', self.sig_db_drop)
|
|
imagemenuitem_db_drop.set_accel_path(
|
|
'<tryton>/File/Database/Drop Database')
|
|
menu_database.add(imagemenuitem_db_drop)
|
|
|
|
imagemenuitem_close = gtk.ImageMenuItem(_('_Quit...'),
|
|
self.accel_group)
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-log-out', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_close.set_image(image)
|
|
imagemenuitem_close.connect('activate', self.sig_close)
|
|
imagemenuitem_close.set_accel_path('<tryton>/File/Quit')
|
|
if self.macapp is None:
|
|
menu_file.add(gtk.SeparatorMenuItem())
|
|
menu_file.add(imagemenuitem_close)
|
|
return menu_file
|
|
|
|
def _set_menu_user(self):
|
|
menu_user = gtk.Menu()
|
|
|
|
imagemenuitem_preference = gtk.ImageMenuItem(_('_Preferences...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-preferences-system-session',
|
|
gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_preference.set_image(image)
|
|
imagemenuitem_preference.connect('activate', self.sig_user_preferences)
|
|
imagemenuitem_preference.set_accel_path('<tryton>/User/Preferences')
|
|
menu_user.add(imagemenuitem_preference)
|
|
|
|
menu_user.add(gtk.SeparatorMenuItem())
|
|
|
|
imagemenuitem_menu = gtk.ImageMenuItem(_('_Menu Reload'),
|
|
self.accel_group)
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-start-here', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_menu.set_image(image)
|
|
imagemenuitem_menu.connect('activate', lambda *a: self.sig_win_menu())
|
|
imagemenuitem_menu.set_accel_path('<tryton>/User/Menu Reload')
|
|
menu_user.add(imagemenuitem_menu)
|
|
|
|
imagemenuitem_menu_toggle = gtk.ImageMenuItem(_('_Menu Toggle'),
|
|
self.accel_group)
|
|
imagemenuitem_menu_toggle.connect('activate',
|
|
lambda *a: self.menu_toggle())
|
|
imagemenuitem_menu_toggle.set_accel_path('<tryton>/User/Menu Toggle')
|
|
menu_user.add(imagemenuitem_menu_toggle)
|
|
|
|
imagemenuitem_global_search = gtk.ImageMenuItem(_('_Global Search'),
|
|
self.accel_group)
|
|
image = gtk.Image()
|
|
image.set_from_stock('gtk-find', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_global_search.set_image(image)
|
|
imagemenuitem_global_search.connect('activate', lambda *a:
|
|
self.show_global_search())
|
|
imagemenuitem_global_search.set_accel_path(
|
|
'<tryton>/User/Global Search')
|
|
menu_user.add(imagemenuitem_global_search)
|
|
return menu_user
|
|
|
|
def _set_menu_options(self):
|
|
menu_options = gtk.Menu()
|
|
|
|
menuitem_toolbar = gtk.MenuItem(_('_Toolbar'))
|
|
menu_options.add(menuitem_toolbar)
|
|
|
|
menu_toolbar = gtk.Menu()
|
|
menu_toolbar.set_accel_group(self.accel_group)
|
|
menu_toolbar.set_accel_path('<tryton>/Options/Toolbar')
|
|
menuitem_toolbar.set_submenu(menu_toolbar)
|
|
|
|
radiomenuitem_default = gtk.RadioMenuItem(label=_('_Default'))
|
|
radiomenuitem_default.connect('activate',
|
|
lambda x: self.sig_toolbar('default'))
|
|
radiomenuitem_default.set_accel_path(
|
|
'<tryton>/Options/Toolbar/Default')
|
|
menu_toolbar.add(radiomenuitem_default)
|
|
if (CONFIG['client.toolbar'] or 'both') == 'default':
|
|
radiomenuitem_default.set_active(True)
|
|
|
|
radiomenuitem_both = gtk.RadioMenuItem(group=radiomenuitem_default,
|
|
label=_('_Text and Icons'))
|
|
radiomenuitem_both.connect('activate',
|
|
lambda x: self.sig_toolbar('both'))
|
|
radiomenuitem_both.set_accel_path(
|
|
'<tryton>/Options/Toolbar/Text and Icons')
|
|
menu_toolbar.add(radiomenuitem_both)
|
|
if (CONFIG['client.toolbar'] or 'both') == 'both':
|
|
radiomenuitem_both.set_active(True)
|
|
|
|
radiomenuitem_icons = gtk.RadioMenuItem(group=radiomenuitem_default,
|
|
label=_('_Icons'))
|
|
radiomenuitem_icons.connect('activate',
|
|
lambda x: self.sig_toolbar('icons'))
|
|
radiomenuitem_icons.set_accel_path('<tryton>/Options/Toolbar/Icons')
|
|
menu_toolbar.add(radiomenuitem_icons)
|
|
if (CONFIG['client.toolbar'] or 'both') == 'icons':
|
|
radiomenuitem_icons.set_active(True)
|
|
|
|
radiomenuitem_text = gtk.RadioMenuItem(group=radiomenuitem_default,
|
|
label=_('_Text'))
|
|
radiomenuitem_text.connect('activate',
|
|
lambda x: self.sig_toolbar('text'))
|
|
radiomenuitem_text.set_accel_path('<tryton>/Options/Toolbar/Text')
|
|
menu_toolbar.add(radiomenuitem_text)
|
|
if (CONFIG['client.toolbar'] or 'both') == 'text':
|
|
radiomenuitem_text.set_active(True)
|
|
|
|
# Menubar accelerators
|
|
menuitem_menubar = gtk.MenuItem(_('_Menubar'))
|
|
menu_options.add(menuitem_menubar)
|
|
|
|
menu_menubar = gtk.Menu()
|
|
menu_menubar.set_accel_group(self.accel_group)
|
|
menu_menubar.set_accel_path('<tryton>/Options/Menubar')
|
|
menuitem_menubar.set_submenu(menu_menubar)
|
|
|
|
checkmenuitem_accel = gtk.CheckMenuItem(_('Change Accelerators'))
|
|
checkmenuitem_accel.connect('activate',
|
|
lambda menuitem: self.sig_accel_change(menuitem.get_active()))
|
|
checkmenuitem_accel.set_accel_path('<tryton>/Options/Menubar/Accel')
|
|
menu_menubar.add(checkmenuitem_accel)
|
|
if CONFIG['client.can_change_accelerators']:
|
|
checkmenuitem_accel.set_active(True)
|
|
|
|
menuitem_mode = gtk.MenuItem(_('_Mode'))
|
|
menu_options.add(menuitem_mode)
|
|
|
|
menu_mode = gtk.Menu()
|
|
menu_mode.set_accel_group(self.accel_group)
|
|
menu_mode.set_accel_path('<tryton>/Options/Mode')
|
|
menuitem_mode.set_submenu(menu_mode)
|
|
|
|
radiomenuitem_normal = gtk.RadioMenuItem(label=_('_Normal'))
|
|
self.radiomenuitem_normal = radiomenuitem_normal
|
|
radiomenuitem_normal.connect('activate',
|
|
lambda x: self.sig_mode_change(False))
|
|
radiomenuitem_normal.set_accel_path('<tryton>/Options/Mode/Normal')
|
|
menu_mode.add(radiomenuitem_normal)
|
|
|
|
radiomenuitem_pda = gtk.RadioMenuItem(group=radiomenuitem_normal,
|
|
label=_('_PDA'))
|
|
self.radiomenuitem_pda = radiomenuitem_pda
|
|
radiomenuitem_pda.connect('activate',
|
|
lambda x: self.sig_mode_change(True))
|
|
radiomenuitem_pda.set_accel_path('<tryton>/Options/Mode/PDA')
|
|
menu_mode.add(radiomenuitem_pda)
|
|
|
|
menuitem_form = gtk.MenuItem(_('_Form'))
|
|
menu_options.add(menuitem_form)
|
|
|
|
menu_form = gtk.Menu()
|
|
menu_form.set_accel_group(self.accel_group)
|
|
menu_form.set_accel_path('<tryton>/Options/Form')
|
|
menuitem_form.set_submenu(menu_form)
|
|
|
|
checkmenuitem_save_width_height = gtk.CheckMenuItem(
|
|
_('Save Width/Height'))
|
|
checkmenuitem_save_width_height.connect('activate',
|
|
lambda menuitem: CONFIG.__setitem__('client.save_width_height',
|
|
menuitem.get_active()))
|
|
checkmenuitem_save_width_height.set_accel_path(
|
|
'<tryton>/Options/Form/Save Width Height')
|
|
menu_form.add(checkmenuitem_save_width_height)
|
|
if CONFIG['client.save_width_height']:
|
|
checkmenuitem_save_width_height.set_active(True)
|
|
|
|
checkmenuitem_save_tree_state = gtk.CheckMenuItem(
|
|
_('Save Tree State'))
|
|
checkmenuitem_save_tree_state.connect('activate',
|
|
lambda menuitem: CONFIG.__setitem__(
|
|
'client.save_tree_state',
|
|
menuitem.get_active()))
|
|
checkmenuitem_save_tree_state.set_accel_path(
|
|
'<tryton>/Options/Form/Save Tree State')
|
|
menu_form.add(checkmenuitem_save_tree_state)
|
|
if CONFIG['client.save_tree_state']:
|
|
checkmenuitem_save_tree_state.set_active(True)
|
|
|
|
if gtkspell:
|
|
checkmenuitem_spellcheck = gtk.CheckMenuItem(_('Spell Checking'))
|
|
checkmenuitem_spellcheck.connect('activate',
|
|
lambda menuitem: CONFIG.__setitem__('client.spellcheck',
|
|
menuitem.get_active()))
|
|
checkmenuitem_spellcheck.set_accel_path(
|
|
'<tryton>/Options/Form/Spell Checking')
|
|
menu_form.add(checkmenuitem_spellcheck)
|
|
if CONFIG['client.spellcheck']:
|
|
checkmenuitem_spellcheck.set_active(True)
|
|
|
|
menuitem_tab = gtk.MenuItem(_('Tabs Position'))
|
|
menu_form.add(menuitem_tab)
|
|
|
|
menu_tab = gtk.Menu()
|
|
menu_tab.set_accel_group(self.accel_group)
|
|
menu_tab.set_accel_path('<tryton>/Options/Tabs Position')
|
|
menuitem_tab.set_submenu(menu_tab)
|
|
|
|
radiomenuitem_top = gtk.RadioMenuItem(label=_('Top'))
|
|
radiomenuitem_top.connect('activate',
|
|
lambda x: CONFIG.__setitem__('client.form_tab', 'top'))
|
|
radiomenuitem_top.set_accel_path('<tryton>/Options/Tabs Position/Top')
|
|
menu_tab.add(radiomenuitem_top)
|
|
if (CONFIG['client.form_tab'] or 'left') == 'top':
|
|
radiomenuitem_top.set_active(True)
|
|
|
|
radiomenuitem_left = gtk.RadioMenuItem(group=radiomenuitem_top,
|
|
label=_('Left'))
|
|
radiomenuitem_left.connect('activate',
|
|
lambda x: CONFIG.__setitem__('client.form_tab', 'left'))
|
|
radiomenuitem_left.set_accel_path(
|
|
'<tryton>/Options/Tabs Position/Left')
|
|
menu_tab.add(radiomenuitem_left)
|
|
if (CONFIG['client.form_tab'] or 'left') == 'left':
|
|
radiomenuitem_left.set_active(True)
|
|
|
|
radiomenuitem_right = gtk.RadioMenuItem(group=radiomenuitem_top,
|
|
label=_('Right'))
|
|
radiomenuitem_right.connect('activate',
|
|
lambda x: CONFIG.__setitem__('client.form_tab', 'right'))
|
|
radiomenuitem_right.set_accel_path(
|
|
'<tryton>/Options/Tabs Position/Right')
|
|
menu_tab.add(radiomenuitem_right)
|
|
if (CONFIG['client.form_tab'] or 'left') == 'right':
|
|
radiomenuitem_right.set_active(True)
|
|
|
|
radiomenuitem_bottom = gtk.RadioMenuItem(group=radiomenuitem_top,
|
|
label=_('Bottom'))
|
|
radiomenuitem_bottom.connect('activate',
|
|
lambda x: CONFIG.__setitem__('client.form_tab', 'bottom'))
|
|
radiomenuitem_bottom.set_accel_path(
|
|
'<tryton>/Options/Tabs Position/Bottom')
|
|
menu_tab.add(radiomenuitem_bottom)
|
|
if (CONFIG['client.form_tab'] or 'left') == 'bottom':
|
|
radiomenuitem_bottom.set_active(True)
|
|
|
|
imagemenuitem_win_prev = gtk.ImageMenuItem(_('_Previous Tab'),
|
|
self.accel_group)
|
|
imagemenuitem_win_prev.connect('activate', self.sig_win_prev)
|
|
imagemenuitem_win_prev.set_accel_path('<tryton>/Form/Previous Tab')
|
|
menu_form.add(imagemenuitem_win_prev)
|
|
|
|
imagemenuitem_win_next = gtk.ImageMenuItem(_('_Next Tab'),
|
|
self.accel_group)
|
|
imagemenuitem_win_next.connect('activate', self.sig_win_next)
|
|
imagemenuitem_win_next.set_accel_path('<tryton>/Form/Next Tab')
|
|
menu_form.add(imagemenuitem_win_next)
|
|
|
|
menuitem_limit = gtk.MenuItem(_('Search Limit...'))
|
|
self.menuitem_limit = menuitem_limit
|
|
menuitem_limit.connect('activate', self.sig_limit)
|
|
menuitem_limit.set_accel_path('<tryton>/Options/Search Limit')
|
|
menu_options.add(menuitem_limit)
|
|
|
|
menuitem_email = gtk.MenuItem(_('_Email...'))
|
|
self.menuitem_email = menuitem_email
|
|
menuitem_email.connect('activate', self.sig_email)
|
|
menuitem_email.set_accel_path('<tryton>/Options/Email')
|
|
menu_options.add(menuitem_email)
|
|
|
|
menu_options.add(gtk.SeparatorMenuItem())
|
|
|
|
imagemenuitem_opt_save = gtk.ImageMenuItem(_('_Save Options'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-save', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_opt_save.set_image(image)
|
|
imagemenuitem_opt_save.connect('activate', lambda x: CONFIG.save())
|
|
imagemenuitem_opt_save.set_accel_path('<tryton>/Options/Save Options')
|
|
menu_options.add(imagemenuitem_opt_save)
|
|
return menu_options
|
|
|
|
def _set_menu_help(self):
|
|
menu_help = gtk.Menu()
|
|
|
|
imagemenuitem_tips = gtk.ImageMenuItem(_('_Tips...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-information', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_tips.set_image(image)
|
|
imagemenuitem_tips.connect('activate', self.sig_tips)
|
|
imagemenuitem_tips.set_accel_path('<tryton>/Help/Tips')
|
|
menu_help.add(imagemenuitem_tips)
|
|
|
|
imagemenuitem_shortcuts = gtk.ImageMenuItem(
|
|
_('_Keyboard Shortcuts...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('tryton-help', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_shortcuts.set_image(image)
|
|
imagemenuitem_shortcuts.connect('activate', self.sig_shortcuts)
|
|
imagemenuitem_shortcuts.set_accel_path(
|
|
'<tryton>/Help/Keyboard Shortcuts')
|
|
menu_help.add(imagemenuitem_shortcuts)
|
|
|
|
imagemenuitem_about = gtk.ImageMenuItem(_('_About...'))
|
|
image = gtk.Image()
|
|
image.set_from_stock('gtk-about', gtk.ICON_SIZE_MENU)
|
|
imagemenuitem_about.set_image(image)
|
|
imagemenuitem_about.connect('activate', self.sig_about)
|
|
imagemenuitem_about.set_accel_path('<tryton>/Help/About')
|
|
self.aboutitem = imagemenuitem_about
|
|
if self.macapp is None:
|
|
menu_help.add(gtk.SeparatorMenuItem())
|
|
menu_help.add(imagemenuitem_about)
|
|
|
|
return menu_help
|
|
|
|
@staticmethod
|
|
def get_main():
|
|
return _MAIN[0]
|
|
|
|
def favorite_set(self):
|
|
if not self.menu_screen:
|
|
return False
|
|
|
|
def _action_favorite(widget, id_):
|
|
Action.exec_keyword('tree_open', {
|
|
'model': self.menu_screen.model_name,
|
|
'id': id_,
|
|
'ids': [id_],
|
|
})
|
|
|
|
def _manage_favorites(widget):
|
|
Window.create(False, self.menu_screen.model_name + '.favorite',
|
|
False, mode=['tree', 'form'])
|
|
try:
|
|
favorites = RPCExecute('model',
|
|
self.menu_screen.model_name + '.favorite', 'get')
|
|
except RPCException:
|
|
favorites = []
|
|
menu = self.menuitem_favorite.get_submenu()
|
|
if not menu:
|
|
menu = gtk.Menu()
|
|
for id_, name, icon in favorites:
|
|
if icon:
|
|
common.ICONFACTORY.register_icon(icon)
|
|
menuitem = gtk.ImageMenuItem(name)
|
|
image = gtk.Image()
|
|
image.set_from_stock(icon, gtk.ICON_SIZE_MENU)
|
|
menuitem.set_image(image)
|
|
else:
|
|
menuitem = gtk.MenuItem(name)
|
|
menuitem.connect('activate', _action_favorite, id_)
|
|
menu.add(menuitem)
|
|
menu.add(gtk.SeparatorMenuItem())
|
|
manage_favorites = gtk.MenuItem(_('Manage Favorites'))
|
|
manage_favorites.connect('activate', _manage_favorites)
|
|
menu.add(manage_favorites)
|
|
menu.show_all()
|
|
self.menuitem_favorite.set_submenu(menu)
|
|
return True
|
|
|
|
def favorite_unset(self):
|
|
had_submenu = self.menuitem_favorite.get_submenu()
|
|
self.menuitem_favorite.remove_submenu()
|
|
# Set a submenu to get keyboard shortcut working
|
|
self.menuitem_favorite.set_submenu(gtk.Menu())
|
|
|
|
if self.macapp and had_submenu:
|
|
# As the select event is not managed by the mac menu,
|
|
# it is done using a timeout
|
|
gobject.timeout_add(1000, lambda: not self.favorite_set())
|
|
|
|
def sig_accel_change(self, value):
|
|
CONFIG['client.can_change_accelerators'] = value
|
|
return self.sig_accel()
|
|
|
|
def sig_accel(self):
|
|
menubar = CONFIG['client.can_change_accelerators']
|
|
settings = gtk.settings_get_default()
|
|
if menubar:
|
|
settings.set_property('gtk-can-change-accels', True)
|
|
else:
|
|
settings.set_property('gtk-can-change-accels', False)
|
|
|
|
def sig_mode_change(self, pda_mode=False):
|
|
CONFIG['client.modepda'] = pda_mode
|
|
return
|
|
|
|
def sig_toolbar(self, option):
|
|
CONFIG['client.toolbar'] = option
|
|
if option == 'default':
|
|
barstyle = False
|
|
elif option == 'both':
|
|
barstyle = gtk.TOOLBAR_BOTH
|
|
elif option == 'text':
|
|
barstyle = gtk.TOOLBAR_TEXT
|
|
elif option == 'icons':
|
|
barstyle = gtk.TOOLBAR_ICONS
|
|
for page_idx in range(self.notebook.get_n_pages()):
|
|
page = self.get_page(page_idx)
|
|
page.toolbar.set_style(barstyle)
|
|
|
|
def sig_limit(self, widget):
|
|
Limit().run()
|
|
|
|
def sig_email(self, widget):
|
|
Email().run()
|
|
|
|
def sig_win_next(self, widget):
|
|
page = self.notebook.get_current_page()
|
|
if page == len(self.pages) - 1:
|
|
page = -1
|
|
self.notebook.set_current_page(page + 1)
|
|
|
|
def sig_win_prev(self, widget):
|
|
page = self.notebook.get_current_page()
|
|
self.notebook.set_current_page(page - 1)
|
|
|
|
def get_preferences(self):
|
|
def _set_preferences(prefs):
|
|
try:
|
|
prefs = prefs()
|
|
except RPCException:
|
|
prefs = {}
|
|
threads = []
|
|
for target in (
|
|
common.ICONFACTORY.load_icons,
|
|
common.MODELACCESS.load_models,
|
|
common.MODELHISTORY.load_history,
|
|
common.VIEW_SEARCH.load_searches,
|
|
):
|
|
t = threading.Thread(target=target)
|
|
threads.append(t)
|
|
t.start()
|
|
for t in threads:
|
|
t.join()
|
|
if prefs and 'language_direction' in prefs:
|
|
translate.set_language_direction(prefs['language_direction'])
|
|
CONFIG['client.language_direction'] = \
|
|
prefs['language_direction']
|
|
self.sig_win_menu(prefs=prefs)
|
|
for action_id in prefs.get('actions', []):
|
|
Action.execute(action_id, {})
|
|
self.set_title(prefs.get('status_bar', ''))
|
|
if prefs and 'language' in prefs:
|
|
translate.setlang(prefs['language'], prefs.get('locale'))
|
|
if CONFIG['client.lang'] != prefs['language']:
|
|
self.set_menubar()
|
|
self.favorite_unset()
|
|
CONFIG['client.lang'] = prefs['language']
|
|
CONFIG.save()
|
|
|
|
def _get_preferences():
|
|
RPCExecute('model', 'res.user', 'get_preferences', False,
|
|
callback=_set_preferences)
|
|
|
|
RPCContextReload(_get_preferences)
|
|
|
|
def sig_user_preferences(self, widget):
|
|
if not self.close_pages():
|
|
return False
|
|
Preference(rpc._USER, self.get_preferences)
|
|
|
|
def sig_win_close(self, widget=None):
|
|
self._sig_remove_book(widget,
|
|
self.notebook.get_nth_page(self.notebook.get_current_page()))
|
|
|
|
def sig_login(self, widget=None, res=None):
|
|
if not self.sig_logout(widget, disconnect=False):
|
|
return
|
|
if not res:
|
|
try:
|
|
res = DBLogin().run()
|
|
except TrytonError, exception:
|
|
if exception.faultCode == 'QueryCanceled':
|
|
return False
|
|
except TrytonServerError, exception:
|
|
common.process_exception(exception)
|
|
return
|
|
try:
|
|
log_response = rpc.login(*res)
|
|
except TrytonServerError, exception:
|
|
common.process_exception(exception)
|
|
return
|
|
if log_response > 0:
|
|
self.get_preferences()
|
|
elif log_response == -1:
|
|
common.message(_('Connection error!\n'
|
|
'Unable to connect to the server!'))
|
|
elif log_response == -2:
|
|
common.message(_('Connection error!\n'
|
|
'Bad username or password!'))
|
|
return self.sig_login()
|
|
self.favorite_unset()
|
|
self.menuitem_favorite.set_sensitive(True)
|
|
self.menuitem_user.set_sensitive(True)
|
|
if CONFIG.arguments:
|
|
url = CONFIG.arguments.pop()
|
|
self.open_url(url)
|
|
return True
|
|
|
|
def close_pages(self):
|
|
if self.notebook.get_n_pages():
|
|
if not common.sur(
|
|
_('The following action requires to close all tabs.\n'
|
|
'Do you want to continue?')):
|
|
return False
|
|
res = True
|
|
while res:
|
|
wid = self.get_page()
|
|
if wid:
|
|
if not wid.sig_close():
|
|
return False
|
|
res = self._win_del()
|
|
else:
|
|
res = False
|
|
if self.menu_screen:
|
|
self.menu_screen.save_tree_state()
|
|
self.menu_screen.destroy()
|
|
self.menu_screen = None
|
|
self.menu_expander_clear()
|
|
return True
|
|
|
|
def sig_logout(self, widget=None, disconnect=True):
|
|
try:
|
|
if not self.close_pages():
|
|
return False
|
|
except TrytonServerUnavailable:
|
|
pass
|
|
self.set_title()
|
|
self.favorite_unset()
|
|
self.menuitem_favorite.set_sensitive(False)
|
|
self.menuitem_user.set_sensitive(False)
|
|
if disconnect:
|
|
rpc.logout()
|
|
return True
|
|
|
|
def sig_tips(self, *args):
|
|
Tips()
|
|
|
|
def sig_about(self, widget):
|
|
About()
|
|
|
|
def sig_shortcuts(self, widget):
|
|
Shortcuts().run()
|
|
|
|
def menu_toggle(self):
|
|
expander = self.pane.get_child1()
|
|
if expander:
|
|
expander.set_expanded(not expander.get_expanded())
|
|
|
|
@property
|
|
def menu_expander_size(self):
|
|
return self.menu_expander.style_get_property('expander-size')
|
|
|
|
def menu_expanded(self, expander, *args):
|
|
expanded = expander.get_expanded()
|
|
CONFIG['menu.expanded'] = expanded
|
|
if expanded:
|
|
self.pane.set_position(int(CONFIG['menu.pane']))
|
|
if self.menu_screen:
|
|
self.menu_screen.set_cursor()
|
|
else:
|
|
CONFIG['menu.pane'] = self.pane.get_position()
|
|
self.pane.set_position(self.menu_expander_size)
|
|
self.notebook.grab_focus()
|
|
|
|
def menu_expander_clear(self):
|
|
self.menu_expander.hide()
|
|
if self.menu_expander.get_child():
|
|
self.menu_expander.remove(self.menu_expander.get_child())
|
|
expanded = self.menu_expander.get_expanded()
|
|
CONFIG['menu.expanded'] = expanded
|
|
if expanded:
|
|
CONFIG['menu.pane'] = self.pane.get_position()
|
|
|
|
def on_paned_button_press_event(self, paned, event):
|
|
expander = self.pane.get_child1()
|
|
if expander:
|
|
return not expander.get_expanded()
|
|
return False
|
|
|
|
def sig_win_menu(self, prefs=None):
|
|
from tryton.gui.window.view_form.screen import Screen
|
|
|
|
if not prefs:
|
|
try:
|
|
prefs = RPCExecute('model', 'res.user', 'get_preferences',
|
|
False)
|
|
except RPCException:
|
|
return False
|
|
|
|
vbox = gtk.VBox()
|
|
|
|
self.set_global_search()
|
|
vbox.pack_start(self.global_search_entry, False, False)
|
|
vbox.show_all()
|
|
|
|
if self.menu_screen:
|
|
self.menu_screen.save_tree_state()
|
|
self.menu_screen = None
|
|
self.menu_expander_clear()
|
|
action = PYSONDecoder().decode(prefs['pyson_menu'])
|
|
view_ids = False
|
|
if action.get('views', []):
|
|
view_ids = [x[0] for x in action['views']]
|
|
elif action.get('view_id', False):
|
|
view_ids = [action['view_id'][0]]
|
|
ctx = rpc.CONTEXT.copy()
|
|
decoder = PYSONDecoder(ctx)
|
|
action_ctx = decoder.decode(action.get('pyson_context') or '{}')
|
|
domain = decoder.decode(action['pyson_domain'])
|
|
screen = Screen(action['res_model'], mode=['tree'], view_ids=view_ids,
|
|
domain=domain, context=action_ctx, readonly=True)
|
|
# Use alternate view to not show search box
|
|
screen.screen_container.alternate_view = True
|
|
screen.switch_view(view_type=screen.current_view.view_type)
|
|
|
|
vbox.pack_start(screen.screen_container.alternate_viewport, True, True)
|
|
treeview = screen.current_view.treeview
|
|
treeview.set_headers_visible(False)
|
|
|
|
self.menu_expander.add(vbox)
|
|
self.menu_expander.show()
|
|
|
|
# Favorite column
|
|
column = gtk.TreeViewColumn()
|
|
column.name = None
|
|
column._type = None
|
|
favorite_renderer = CellRendererClickablePixbuf()
|
|
column.pack_start(favorite_renderer, expand=False)
|
|
|
|
def favorite_setter(column, cell, store, iter_):
|
|
menu = store.get_value(iter_, 0)
|
|
favorite = menu.value.get('favorite')
|
|
if favorite:
|
|
stock_id = 'tryton-star'
|
|
elif favorite is False:
|
|
stock_id = 'tryton-unstar'
|
|
else:
|
|
stock_id = ''
|
|
pixbuf = treeview.render_icon(stock_id=stock_id,
|
|
size=gtk.ICON_SIZE_MENU, detail=None)
|
|
cell.set_property('pixbuf', pixbuf)
|
|
column.set_cell_data_func(favorite_renderer, favorite_setter)
|
|
|
|
def toggle_favorite(renderer, path, treeview):
|
|
if treeview.props.window:
|
|
self.toggle_favorite(renderer, path, treeview)
|
|
favorite_renderer.connect('clicked',
|
|
lambda *a: gobject.idle_add(toggle_favorite, *a), treeview)
|
|
# Unset fixed height mode to add column
|
|
treeview.set_fixed_height_mode(False)
|
|
treeview.append_column(column)
|
|
|
|
screen.search_filter()
|
|
screen.display(set_cursor=True)
|
|
self.menu_screen = screen
|
|
|
|
def toggle_favorite(self, renderer, path, treeview):
|
|
store = treeview.get_model()
|
|
iter_ = store.get_iter(path)
|
|
menu = store.get_value(iter_, 0)
|
|
favorite = menu.value.get('favorite')
|
|
if favorite:
|
|
value = False
|
|
method = 'unset'
|
|
elif favorite is False:
|
|
value = True
|
|
method = 'set'
|
|
else:
|
|
return
|
|
try:
|
|
RPCExecute('model', self.menu_screen.model_name + '.favorite',
|
|
method, menu.id)
|
|
except RPCException:
|
|
return
|
|
menu.value['favorite'] = value
|
|
store.emit('row-changed', path, iter_)
|
|
self.favorite_unset()
|
|
|
|
@classmethod
|
|
def sig_quit(cls, widget=None):
|
|
rpc.logout()
|
|
CONFIG['client.default_width'] = Main.get_main()._width
|
|
CONFIG['client.default_height'] = Main.get_main()._height
|
|
CONFIG.save()
|
|
gtk.accel_map_save(os.path.join(get_config_dir(), 'accel.map'))
|
|
|
|
cls.tryton_client.quit_mainloop()
|
|
sys.exit()
|
|
|
|
def sig_close(self, widget, event=None):
|
|
if not self.sig_logout(widget):
|
|
return True
|
|
Main.sig_quit()
|
|
|
|
def sig_configure(self, widget, event):
|
|
if hasattr(event, 'width') \
|
|
and hasattr(event, 'height'):
|
|
self._width = int(event.width)
|
|
self._height = int(event.height)
|
|
return False
|
|
|
|
def sig_window_state(self, widget, event):
|
|
CONFIG['client.maximize'] = (event.new_window_state ==
|
|
gtk.gdk.WINDOW_STATE_MAXIMIZED)
|
|
return False
|
|
|
|
def win_add(self, page, hide_current=False, allow_similar=True):
|
|
if not allow_similar:
|
|
for other_page in self.pages:
|
|
if page == other_page:
|
|
current_page = self.notebook.get_current_page()
|
|
page_num = self.notebook.page_num(other_page.widget)
|
|
other_page.widget.props.visible = True
|
|
self.notebook.set_current_page(page_num)
|
|
# In order to focus the page
|
|
if current_page == page_num:
|
|
self._sig_page_changt(self.notebook, None, page_num)
|
|
return
|
|
previous_page_id = self.notebook.get_current_page()
|
|
previous_widget = self.notebook.get_nth_page(previous_page_id)
|
|
if previous_widget and hide_current:
|
|
prev_tab_label = self.notebook.get_tab_label(previous_widget)
|
|
prev_tab_label.set_size_request(TAB_SIZE / 4, -1)
|
|
close_button = prev_tab_label.get_children()[-1]
|
|
close_button.hide()
|
|
page_id = previous_page_id + 1
|
|
else:
|
|
page_id = -1
|
|
self.previous_pages[page] = previous_widget
|
|
self.pages.append(page)
|
|
hbox = gtk.HBox(spacing=3)
|
|
icon_w, icon_h = gtk.icon_size_lookup(gtk.ICON_SIZE_SMALL_TOOLBAR)
|
|
if page.icon is not None:
|
|
common.ICONFACTORY.register_icon(page.icon)
|
|
image = gtk.Image()
|
|
image.set_from_stock(page.icon, gtk.ICON_SIZE_SMALL_TOOLBAR)
|
|
hbox.pack_start(image, expand=False, fill=False)
|
|
noise_size = 2 * icon_w + 3
|
|
else:
|
|
noise_size = icon_w + 3
|
|
name = page.name
|
|
label = gtk.Label(name)
|
|
self.tooltips.set_tip(label, page.name)
|
|
self.tooltips.enable()
|
|
label.set_alignment(0.0, 0.5)
|
|
hbox.pack_start(label, expand=True, fill=True)
|
|
layout = label.get_layout()
|
|
w, h = layout.get_size()
|
|
if (w // pango.SCALE) > TAB_SIZE - noise_size:
|
|
label2 = gtk.Label('...')
|
|
self.tooltips.set_tip(label2, page.name)
|
|
hbox.pack_start(label2, expand=False, fill=False)
|
|
|
|
button = gtk.Button()
|
|
img = gtk.Image()
|
|
img.set_from_stock('tryton-close', gtk.ICON_SIZE_MENU)
|
|
width, height = img.size_request()
|
|
button.set_relief(gtk.RELIEF_NONE)
|
|
button.unset_flags(gtk.CAN_FOCUS)
|
|
button.add(img)
|
|
self.tooltips.set_tip(button, _('Close Tab'))
|
|
button.connect('clicked', self._sig_remove_book, page.widget)
|
|
hbox.pack_start(button, expand=False, fill=False)
|
|
x, y = gtk.icon_size_lookup_for_settings(button.get_settings(),
|
|
gtk.ICON_SIZE_MENU)
|
|
button.set_size_request(x, y)
|
|
|
|
hbox.show_all()
|
|
hbox.set_size_request(TAB_SIZE, -1)
|
|
label_menu = gtk.Label(page.name)
|
|
label_menu.set_alignment(0.0, 0.5)
|
|
self.notebook.insert_page_menu(page.widget, hbox, label_menu, page_id)
|
|
self.notebook.set_tab_reorderable(page.widget, True)
|
|
self.notebook.set_current_page(page_id)
|
|
|
|
def _sig_remove_book(self, widget, page_widget):
|
|
for page in self.pages:
|
|
if page.widget == page_widget:
|
|
if not page.widget.props.sensitive:
|
|
return
|
|
page_num = self.notebook.page_num(page.widget)
|
|
self.notebook.set_current_page(page_num)
|
|
res = page.sig_close()
|
|
if not res:
|
|
return
|
|
self._win_del(page_widget)
|
|
|
|
def _win_del(self, page_widget=None):
|
|
if page_widget:
|
|
page_id = self.notebook.page_num(page_widget)
|
|
else:
|
|
page_id = int(self.notebook.get_current_page())
|
|
page_widget = self.notebook.get_nth_page(page_id)
|
|
if page_id != -1:
|
|
page = None
|
|
for i in range(len(self.pages)):
|
|
if self.pages[i].widget == page_widget:
|
|
page = self.pages.pop(i)
|
|
page.signal_unconnect(self)
|
|
break
|
|
self.notebook.remove_page(page_id)
|
|
|
|
next_page_id = -1
|
|
to_pop = []
|
|
for i in self.previous_pages:
|
|
if self.previous_pages[i] == page_widget:
|
|
to_pop.append(i)
|
|
if i.widget == page_widget:
|
|
if self.previous_pages[i]:
|
|
next_page_id = self.notebook.page_num(
|
|
self.previous_pages[i])
|
|
to_pop.append(i)
|
|
to_pop.reverse()
|
|
for i in to_pop:
|
|
self.previous_pages.pop(i)
|
|
|
|
if hasattr(page, 'destroy'):
|
|
page.destroy()
|
|
del page
|
|
|
|
current_widget = self.notebook.get_nth_page(next_page_id)
|
|
if current_widget:
|
|
current_widget.props.visible = True
|
|
self.notebook.set_current_page(next_page_id)
|
|
if not self.pages and self.menu_screen:
|
|
self.menu_screen.set_cursor()
|
|
return self.notebook.get_current_page() != -1
|
|
|
|
def get_page(self, page_id=None):
|
|
if page_id is None:
|
|
page_id = self.notebook.get_current_page()
|
|
if page_id == -1:
|
|
return None
|
|
page_widget = self.notebook.get_nth_page(page_id)
|
|
for page in self.pages:
|
|
if page.widget == page_widget:
|
|
return page
|
|
return None
|
|
|
|
def _sig_page_changt(self, notebook, page, page_num):
|
|
self.last_page = self.current_page
|
|
last_form = self.get_page(self.current_page)
|
|
tab_label = notebook.get_tab_label(notebook.get_nth_page(page_num))
|
|
tab_label.set_size_request(TAB_SIZE, -1)
|
|
close_button = tab_label.get_children()[-1]
|
|
close_button.show()
|
|
if last_form:
|
|
for dialog in last_form.dialogs:
|
|
dialog.hide()
|
|
|
|
self.current_page = self.notebook.get_current_page()
|
|
current_form = self.get_page(self.current_page)
|
|
|
|
def set_cursor():
|
|
if self.current_page == self.notebook.get_current_page():
|
|
current_form.set_cursor()
|
|
# Using idle_add because the gtk.TreeView grabs the focus at the
|
|
# end of the event
|
|
gobject.idle_add(set_cursor)
|
|
for dialog in current_form.dialogs:
|
|
dialog.show()
|
|
|
|
def sig_db_new(self, widget):
|
|
if not self.sig_logout(widget):
|
|
return False
|
|
dia = DBCreate(CONFIG['login.server'], int(CONFIG['login.port']),
|
|
sig_login=self.sig_login)
|
|
res = dia.run()
|
|
if res:
|
|
CONFIG.save()
|
|
return res
|
|
|
|
def sig_db_drop(self, widget=None):
|
|
if not self.sig_logout(widget):
|
|
return False
|
|
url, dbname, passwd = DBBackupDrop(function='drop').run()
|
|
if not dbname:
|
|
rpc.logout()
|
|
return
|
|
|
|
host, port = url.rsplit(':', 1)
|
|
sure = common.sur_3b(_("You are going to delete a Tryton "
|
|
"database.\nAre you really sure to proceed?"))
|
|
if sure == "ko" or sure == "cancel":
|
|
return
|
|
rpcprogress = common.RPCProgress('db_exec', (host, int(port), 'drop',
|
|
dbname, passwd))
|
|
try:
|
|
rpcprogress.run(False)
|
|
except TrytonServerError, exception:
|
|
if exception.faultCode == "AccessDenied":
|
|
common.warning(_("Wrong Tryton Server Password\n"
|
|
"Please try again."),
|
|
_('Access denied!'))
|
|
self.sig_db_drop()
|
|
else:
|
|
common.warning(_('Database drop failed with error message:\n')
|
|
+ str(exception.faultCode), _('Database drop failed!'))
|
|
return
|
|
common.message(_("Database dropped successfully!"))
|
|
|
|
def sig_db_restore(self, widget=None):
|
|
if not self.sig_logout(widget):
|
|
return False
|
|
filename = common.file_selection(_('Open Backup File to Restore...'),
|
|
preview=False)
|
|
if not filename:
|
|
return
|
|
dialog = DBRestore(filename=filename)
|
|
url, dbname, passwd, update = dialog.run()
|
|
if dbname:
|
|
with open(filename, 'rb') as file_p:
|
|
data = file_p.read()
|
|
host, port = url.rsplit(':', 1)
|
|
rpcprogress = common.RPCProgress('db_exec', (host, int(port),
|
|
'restore', dbname, passwd, buffer(data), update))
|
|
try:
|
|
res = rpcprogress.run(False)
|
|
except TrytonServerError, exception:
|
|
if exception.faultCode == \
|
|
"Couldn't restore database with password":
|
|
common.warning(_("It is not possible to restore a "
|
|
"password protected database.\n"
|
|
"Backup and restore needed to be proceed "
|
|
"manual."),
|
|
_('Database is password protected!'))
|
|
elif exception.faultCode == "AccessDenied":
|
|
common.warning(_("Wrong Tryton Server Password.\n"
|
|
"Please try again."),
|
|
_('Access denied!'))
|
|
self.sig_db_restore()
|
|
else:
|
|
common.warning(_('Database restore failed with '
|
|
'error message:\n') + str(exception.faultCode),
|
|
_('Database restore failed!'))
|
|
return
|
|
if res:
|
|
common.message(_("Database restored successfully!"))
|
|
else:
|
|
common.message(_('Database restore failed!'))
|
|
|
|
def sig_db_dump(self, widget=None):
|
|
if not self.sig_logout(widget):
|
|
return False
|
|
dialog = DBBackupDrop(function='backup')
|
|
url, dbname, passwd = dialog.run()
|
|
|
|
if not (dbname and url and passwd):
|
|
rpc.logout()
|
|
return
|
|
|
|
host, port = url.rsplit(':', 1)
|
|
rpcprogress = common.RPCProgress('db_exec', (host, int(port), 'dump',
|
|
dbname, passwd))
|
|
try:
|
|
dump = rpcprogress.run(False)
|
|
except TrytonServerError, exception:
|
|
if exception.faultCode == "Couldn't dump database with password":
|
|
common.warning(_("It is not possible to dump a password "
|
|
"protected Database.\nBackup and restore "
|
|
"needed to be proceed manual."),
|
|
_('Database is password protected!'))
|
|
elif exception.faultCode == "AccessDenied":
|
|
common.warning(_("Wrong Tryton Server Password.\n"
|
|
"Please try again."),
|
|
_('Access denied!'))
|
|
self.sig_db_dump()
|
|
else:
|
|
common.warning(_('Database dump failed with '
|
|
'error message:\n') + str(exception.faultCode),
|
|
_('Database dump failed!'))
|
|
rpc.logout()
|
|
return
|
|
|
|
filename = common.file_selection(_('Save As...'),
|
|
action=gtk.FILE_CHOOSER_ACTION_SAVE, preview=False,
|
|
filename=dbname + '-' + time.strftime('%Y%m%d%H%M') + '.dump')
|
|
|
|
if filename:
|
|
with open(filename, 'wb') as file_:
|
|
file_.write(dump)
|
|
common.message(_("Database backuped successfully!"))
|
|
else:
|
|
rpc.logout()
|
|
|
|
def _open_url(self, url):
|
|
urlp = urlparse(url)
|
|
if not urlp.scheme == 'tryton':
|
|
return
|
|
urlp = urlparse('http' + url[6:])
|
|
hostname, port = map(urllib.unquote,
|
|
(urlp.netloc.split(':', 1) + [CONFIG.defaults['login.port']])[:2])
|
|
database, path = map(urllib.unquote,
|
|
(urlp.path[1:].split('/', 1) + [''])[:2])
|
|
if (not path or
|
|
hostname != rpc._HOST or
|
|
int(port) != rpc._PORT or
|
|
database != rpc._DATABASE):
|
|
return
|
|
type_, path = (path.split('/', 1) + [''])[:2]
|
|
params = {}
|
|
if urlp.params:
|
|
try:
|
|
params.update(dict(parse_qsl(urlp.params,
|
|
strict_parsing=True)))
|
|
except ValueError:
|
|
return
|
|
|
|
def open_model(path):
|
|
model, path = (path.split('/', 1) + [''])[:2]
|
|
if not model:
|
|
return
|
|
res_id = False
|
|
mode = None
|
|
try:
|
|
view_ids = json.loads(params.get('views', 'false'))
|
|
limit = json.loads(params.get('limit', 'null'))
|
|
name = json.loads(params.get('window_name', 'false'))
|
|
search_value = json.loads(params.get('search_value', '{}'),
|
|
object_hook=object_hook)
|
|
domain = json.loads(params.get('domain', '[]'),
|
|
object_hook=object_hook)
|
|
context = json.loads(params.get('context', '{}'),
|
|
object_hook=object_hook)
|
|
except ValueError:
|
|
return
|
|
if path:
|
|
try:
|
|
res_id = int(path)
|
|
except ValueError:
|
|
return
|
|
mode = ['form', 'tree']
|
|
try:
|
|
Window.create(view_ids, model, res_id=res_id, domain=domain,
|
|
context=context, mode=mode, name=name, limit=limit,
|
|
search_value=search_value)
|
|
except Exception:
|
|
# Prevent crashing the client
|
|
return
|
|
|
|
def open_wizard(wizard):
|
|
if not wizard:
|
|
return
|
|
try:
|
|
data = json.loads(params.get('data', '{}'),
|
|
object_hook=object_hook)
|
|
direct_print = json.loads(params.get('direct_print', 'false'))
|
|
email_print = json.loads(params.get('email_print', 'false'))
|
|
email = json.loads(params.get('email', 'null'))
|
|
name = json.loads(params.get('name', 'false'))
|
|
window = json.loads(params.get('window', 'false'))
|
|
context = json.loads(params.get('context', '{}'),
|
|
object_hook=object_hook)
|
|
except ValueError:
|
|
return
|
|
try:
|
|
Window.create_wizard(wizard, data, direct_print=direct_print,
|
|
email_print=email_print, email=email, name=name,
|
|
context=context, window=window)
|
|
except Exception:
|
|
# Prevent crashing the client
|
|
return
|
|
|
|
def open_report(report):
|
|
if not report:
|
|
return
|
|
try:
|
|
data = json.loads(params.get('data'), object_hook=object_hook)
|
|
direct_print = json.loads(params.get('direct_print', 'false'))
|
|
email_print = json.loads(params.get('email_print', 'false'))
|
|
email = json.loads(params.get('email', 'null'))
|
|
context = json.loads(params.get('context', '{}'),
|
|
object_hook=object_hook)
|
|
except ValueError:
|
|
return
|
|
try:
|
|
Action.exec_report(report, data, direct_print=direct_print,
|
|
email_print=email_print, email=email, context=context)
|
|
except Exception:
|
|
# Prevent crashing the client
|
|
return
|
|
|
|
def open_url():
|
|
try:
|
|
url = json.loads(params.get('url', 'false'))
|
|
except ValueError:
|
|
return
|
|
if url:
|
|
webbrowser.open(url, new=2)
|
|
|
|
if type_ == 'model':
|
|
open_model(path)
|
|
elif type_ == 'wizard':
|
|
open_wizard(path)
|
|
elif type_ == 'report':
|
|
open_report(path)
|
|
elif type_ == 'url':
|
|
open_url()
|
|
|
|
self.window.present()
|
|
|
|
def open_url(self, url):
|
|
def idle_open_url():
|
|
with gtk.gdk.lock:
|
|
self._open_url(url)
|
|
return False
|
|
gobject.idle_add(idle_open_url)
|