Exemplo n.º 1
0
class Action(SignalEvent):

    def __init__(self, attrs=None, context=None):
        super(Action, self).__init__()
        self.name = attrs['name']
        self.context = context or {}

        try:
            self.action = RPCExecute('model', 'ir.action.act_window', 'get',
                self.name)
        except RPCException:
            raise

        view_ids = None
        self.action['view_mode'] = None
        if self.action.get('views', []):
            view_ids = [x[0] for x in self.action['views']]
            self.action['view_mode'] = [x[1] for x in self.action['views']]
        elif self.action.get('view_id', False):
            view_ids = [self.action['view_id'][0]]

        if 'view_mode' in attrs:
            self.action['view_mode'] = attrs['view_mode']

        self.action.setdefault('pyson_domain', '[]')
        self.context.update(rpc.CONTEXT)
        self.context['_user'] = rpc._USER
        self.context.update(PYSONDecoder(self.context).decode(
            self.action.get('pyson_context', '{}')))

        eval_ctx = self.context.copy()
        self.context.update(PYSONDecoder(eval_ctx).decode(
            self.action.get('pyson_context', '{}')))

        self.domain = []
        self.update_domain([])

        search_context = self.context.copy()
        search_context['context'] = self.context
        search_context['_user'] = rpc._USER
        search_value = PYSONDecoder(search_context).decode(
            self.action['pyson_search_value'] or '{}')

        self.widget = gtk.Frame()
        self.widget.set_border_width(0)

        vbox = gtk.VBox(homogeneous=False, spacing=3)
        self.widget.add(vbox)

        self.title = gtk.Label()
        self.widget.set_label_widget(self.title)
        self.widget.set_label_align(0.0, 0.5)
        self.widget.show_all()

        self.screen = Screen(self.action['res_model'],
            mode=self.action['view_mode'], context=self.context,
            view_ids=view_ids, domain=self.domain,
            search_value=search_value, row_activate=self.row_activate)
        vbox.pack_start(self.screen.widget, expand=True, fill=True)
        name = self.screen.current_view.title
        self.screen.signal_connect(self, 'record-message',
            self._active_changed)

        if attrs.get('string'):
            self.title.set_text(attrs['string'])
        elif self.action.get('window_name'):
            self.title.set_text(self.action['name'])
        else:
            self.title.set_text(name)

        self.widget.set_size_request(int(attrs.get('width', -1)),
                int(attrs.get('height', -1)))

        self.screen.search_filter()

    def row_activate(self):
        if not self.screen.current_record:
            return

        if (self.screen.current_view.view_type == 'tree' and
                self.screen.current_view.treeview.keyword_open):
            GenericAction.exec_keyword('tree_open', {
                'model': self.screen.model_name,
                'id': self.screen.id_get(),
                'ids': [self.screen.id_get()],
                }, context=self.screen.context.copy(), warning=False)
        else:
            def callback(result):
                if result:
                    self.screen.current_record.save()
                else:
                    self.screen.current_record.cancel()
            WinForm(self.screen, callback)

    def set_value(self, mode, model_field):
        self.screen.current_view.set_value()
        return True

    def display(self):
        self.screen.search_filter(self.screen.screen_container.get_text())

    def _active_changed(self, *args):
        self.signal('active-changed')

    def _get_active(self):
        if self.screen and self.screen.current_record:
            return common.EvalEnvironment(self.screen.current_record)

    active = property(_get_active)

    def update_domain(self, actions):
        domain_ctx = self.context.copy()
        domain_ctx['context'] = domain_ctx
        domain_ctx['_user'] = rpc._USER
        for action in actions:
            if action.active:
                domain_ctx[action.name] = action.active
        new_domain = PYSONDecoder(domain_ctx).decode(
                self.action['pyson_domain'])
        if self.domain == new_domain:
            return
        del self.domain[:]
        self.domain.extend(new_domain)
        if hasattr(self, 'screen'):  # Catch early update
            # Using idle_add to prevent corruption of the event who triggered
            # the update.
            def display():
                if self.screen.widget.props.window:
                    self.display()
            gtk.idle_add(display)
Exemplo n.º 2
0
class Form(SignalEvent, TabContent):
    "Form"

    toolbar_def = [
        ('new', 'tryton-new', _('New'), _('Create a new record'),
            'sig_new'),
        ('save', 'tryton-save', _('Save'), _('Save this record'),
            'sig_save'),
        ('switch', 'tryton-fullscreen', _('Switch'), _('Switch view'),
            'sig_switch'),
        ('reload', 'tryton-refresh', _('_Reload'), _('Reload'),
            'sig_reload'),
        (None,) * 5,
        ('previous', 'tryton-go-previous', _('Previous'),
            _('Previous Record'), 'sig_previous'),
        ('next', 'tryton-go-next', _('Next'), _('Next Record'),
            'sig_next'),
        (None,) * 5,
        ('attach', 'tryton-attachment', _('Attachment(0)'),
            _('Add an attachment to the record'), 'sig_attach'),
    ]
    menu_def = [
        (_('_New'), 'tryton-new', 'sig_new', '<tryton>/Form/New'),
        (_('_Save'), 'tryton-save', 'sig_save', '<tryton>/Form/Save'),
        (_('_Switch View'), 'tryton-fullscreen', 'sig_switch',
            '<tryton>/Form/Switch View'),
        (_('_Reload/Undo'), 'tryton-refresh', 'sig_reload',
            '<tryton>/Form/Reload'),
        (_('_Duplicate'), 'tryton-copy', 'sig_copy',
            '<tryton>/Form/Duplicate'),
        (_('_Delete...'), 'tryton-delete', 'sig_remove',
            '<tryton>/Form/Delete'),
        (None,) * 4,
        (_('_Previous'), 'tryton-go-previous', 'sig_previous',
            '<tryton>/Form/Previous'),
        (_('_Next'), 'tryton-go-next', 'sig_next', '<tryton>/Form/Next'),
        (_('_Search'), 'tryton-find', 'sig_search', '<tryton>/Form/Search'),
        (_('View _Logs...'), None, 'sig_logs', None),
        (_('Show revisions...'), 'tryton-clock', 'revision', None),
        (None,) * 4,
        (_('_Close Tab'), 'tryton-close', 'sig_win_close',
            '<tryton>/Form/Close'),
        (None,) * 4,
        (_('A_ttachments...'), 'tryton-attachment', 'sig_attach',
            '<tryton>/Form/Attachments'),
        (_('_Actions...'), 'tryton-executable', 'sig_action',
            '<tryton>/Form/Actions'),
        (_('_Relate...'), 'tryton-go-jump', 'sig_relate',
            '<tryton>/Form/Relate'),
        (None,) * 4,
        (_('_Report...'), 'tryton-print-open', 'sig_print_open',
            '<tryton>/Form/Report'),
        (_('_E-Mail...'), 'tryton-print-email', 'sig_print_email',
            '<tryton>/Form/Email'),
        (_('_Print...'), 'tryton-print', 'sig_print',
            '<tryton>/Form/Print'),
        (None,) * 4,
        (_('_Export Data...'), 'tryton-save-as', 'sig_save_as',
            '<tryton>/Form/Export Data'),
        (_('_Import Data...'), None, 'sig_import',
            '<tryton>/Form/Import Data'),
    ]

    def __init__(self, model, res_id=False, domain=None, order=None, mode=None,
            view_ids=None, context=None, name=False, limit=None,
            search_value=None, tab_domain=None):
        super(Form, self).__init__()

        if not mode:
            mode = ['tree', 'form']
        if domain is None:
            domain = []
        if view_ids is None:
            view_ids = []

        self.model = model
        self.res_id = res_id
        self.domain = domain
        self.mode = mode
        self.context = context
        self.view_ids = view_ids
        self.dialogs = []

        self.screen = Screen(self.model, mode=mode, context=context,
            view_ids=view_ids, domain=domain, limit=limit, order=order,
            search_value=search_value, tab_domain=tab_domain)
        self.screen.widget.show()

        if not name:
            self.name = self.screen.current_view.title
        else:
            self.name = name

        if self.model not in common.MODELHISTORY:
            self.menu_def = self.menu_def[:]
            # Remove callback to revision
            self.menu_def[11] = (self.menu_def[11][:2] + (None,)
                + self.menu_def[11][3:])

        self.create_tabcontent()

        self.url_entry = url_entry = gtk.Entry()
        url_entry.show()
        url_entry.set_editable(False)
        style = url_entry.get_style()
        url_entry.modify_bg(gtk.STATE_ACTIVE,
            style.bg[gtk.STATE_INSENSITIVE])
        self.widget.pack_start(url_entry, False, False)

        access = common.MODELACCESS[self.model]
        for button, access_type in (
                ('new', 'create'),
                ('save', 'write'),
                ):
            self.buttons[button].props.sensitive = access[access_type]

        self.screen.signal_connect(self, 'record-message',
            self._record_message)

        self.screen.signal_connect(self, 'record-modified',
            lambda *a: gobject.idle_add(self._record_modified, *a))
        self.screen.signal_connect(self, 'record-saved', self._record_saved)
        self.screen.signal_connect(self, 'attachment-count',
                self._attachment_count)

        if res_id not in (None, False):
            if isinstance(res_id, (int, long)):
                res_id = [res_id]
            self.screen.load(res_id)
        else:
            if self.screen.current_view.view_type == 'form':
                self.sig_new(None, autosave=False)
            if self.screen.current_view.view_type \
                    in ('tree', 'graph', 'calendar'):
                self.screen.search_filter()

        self.update_revision()

    def get_toolbars(self):
        try:
            return RPCExecute('model', self.model, 'view_toolbar_get',
                context=self.screen.context)
        except RPCException:
            return {}

    def widget_get(self):
        return self.screen.widget

    def __eq__(self, value):
        if not value:
            return False
        if not isinstance(value, Form):
            return False
        return (self.model == value.model
            and self.res_id == value.res_id
            and self.domain == value.domain
            and self.mode == value.mode
            and self.view_ids == value.view_ids
            and self.screen.context == value.screen.context
            and self.name == value.name
            and self.screen.limit == value.screen.limit
            and self.screen.search_value == value.screen.search_value)

    def destroy(self):
        self.screen.destroy()

    def ids_get(self):
        return self.screen.ids_get()

    def id_get(self):
        return self.screen.id_get()

    def sig_attach(self, widget=None):
        record = self.screen.current_record
        if not record or record.id < 0:
            return
        Attachment(record,
            lambda: self.update_attachment_count(reload=True))

    def update_attachment_count(self, reload=False):
        record = self.screen.current_record
        if record:
            attachment_count = record.get_attachment_count(reload=reload)
        else:
            attachment_count = 0
        self._attachment_count(None, attachment_count)

    def _attachment_count(self, widget, signal_data):
        label = _('Attachment(%d)') % signal_data
        self.buttons['attach'].set_label(label)
        if signal_data:
            self.buttons['attach'].set_stock_id('tryton-attachment-hi')
        else:
            self.buttons['attach'].set_stock_id('tryton-attachment')
        record_id = self.id_get()
        self.buttons['attach'].props.sensitive = bool(
            record_id >= 0 and record_id is not False)

    def sig_switch(self, widget=None):
        if not self.modified_save():
            return
        self.screen.switch_view()

    def sig_logs(self, widget=None):
        obj_id = self.id_get()
        if obj_id < 0 or obj_id is False:
            self.message_info(_('You have to select one record!'))
            return False

        fields = [
            ('id', _('ID:')),
            ('create_uid.rec_name', _('Creation User:'******'create_date', _('Creation Date:')),
            ('write_uid.rec_name', _('Latest Modification by:')),
            ('write_date', _('Latest Modification Date:')),
        ]

        try:
            res = RPCExecute('model', self.model, 'read', [obj_id],
                [x[0] for x in fields], context=self.screen.context)
        except RPCException:
            return
        message_str = ''
        for line in res:
            for (key, val) in fields:
                value = str(line.get(key, False) or '/')
                if line.get(key, False) \
                        and key in ('create_date', 'write_date'):
                    date = timezoned_date(line[key])
                    value = common.datetime_strftime(date, '%X')
                message_str += val + ' ' + value + '\n'
        message_str += _('Model:') + ' ' + self.model
        message(message_str)
        return True

    def revision(self, widget=None):
        if not self.modified_save():
            return
        current_id = (self.screen.current_record.id
            if self.screen.current_record else None)
        try:
            revisions = RPCExecute('model', self.model, 'history_revisions',
                [r.id for r in self.screen.selected_records])
        except RPCException:
            return
        revision = self.screen.context.get('_datetime')
        format_ = self.screen.context.get('date_format', '%x')
        format_ += ' %X.%f'
        revision = Revision(revisions, revision, format_).run()
        # Prevent too old revision in form view
        if (self.screen.current_view.view_type == 'form'
                and revision
                and revision < revisions[-1][0]):
                revision = revisions[-1][0]
        if revision != self.screen.context.get('_datetime'):
            self.screen.clear()
            # Update root group context that will be propagated
            self.screen.group._context['_datetime'] = revision
            if self.screen.current_view.view_type != 'form':
                self.screen.search_filter(
                    self.screen.screen_container.get_text())
            else:
                # Test if record exist in revisions
                self.screen.load([current_id])
            self.screen.display(set_cursor=True)
            self.update_revision()

    def update_revision(self):
        revision = self.screen.context.get('_datetime')
        if revision:
            format_ = self.screen.context.get('date_format', '%x')
            format_ += ' %X.%f'
            revision = datetime_strftime(revision, format_)
            self.title.set_label('%s @ %s' % (self.name, revision))
        else:
            self.title.set_label(self.name)
        for button in ('new', 'save'):
            self.buttons[button].props.sensitive = not revision

    def sig_remove(self, widget=None):
        if not common.MODELACCESS[self.model]['delete']:
            return
        if self.screen.current_view.view_type == 'form':
            msg = _('Are you sure to remove this record?')
        else:
            msg = _('Are you sure to remove those records?')
        if sur(msg):
            if not self.screen.remove(delete=True, force_remove=True):
                self.message_info(_('Records not removed!'))
            else:
                self.message_info(_('Records removed!'), 'green')

    def sig_import(self, widget=None):
        WinImport(self.model, self.screen.context)

    def sig_save_as(self, widget=None):
        export = WinExport(self.model, self.ids_get(),
            context=self.screen.context)
        for name in self.screen.current_view.get_fields():
            export.sel_field(name)

    def sig_new(self, widget=None, autosave=True):
        if not common.MODELACCESS[self.model]['create']:
            return
        if autosave:
            if not self.modified_save():
                return
        self.screen.new()
        self.message_info('')
        self.activate_save()

    def sig_copy(self, widget=None):
        if not common.MODELACCESS[self.model]['create']:
            return
        if not self.modified_save():
            return
        self.screen.copy()
        self.message_info(_('Working now on the duplicated record(s)!'),
            'green')

    def sig_save(self, widget=None):
        if widget:
            # Called from button so we must save the tree state
            self.screen.save_tree_state()
        if not common.MODELACCESS[self.model]['write']:
            return
        if self.screen.save_current():
            self.message_info(_('Record saved!'), 'green')
            return True
        else:
            self.message_info(_('Invalid form!'))
            return False

    def sig_previous(self, widget=None):
        if not self.modified_save():
            return
        self.screen.display_prev()
        self.message_info('')
        self.activate_save()

    def sig_next(self, widget=None):
        if not self.modified_save():
            return
        self.screen.display_next()
        self.message_info('')
        self.activate_save()

    def sig_reload(self, test_modified=True):
        if test_modified:
            if not self.modified_save():
                return False
        else:
            self.screen.save_tree_state(store=False)
        self.screen.cancel_current()
        set_cursor = False
        record_id = (self.screen.current_record.id
            if self.screen.current_record else None)
        if self.screen.current_view.view_type != 'form':
            self.screen.search_filter(self.screen.screen_container.get_text())
            for record in self.screen.group:
                if record.id == record_id:
                    self.screen.current_record = record
                    set_cursor = True
                    break
        self.screen.display(set_cursor=set_cursor)
        self.message_info('')
        self.activate_save()
        return True

    def sig_action(self, widget):
        if self.buttons['action'].props.sensitive:
            self.buttons['action'].props.active = True

    def sig_print(self, widget):
        if self.buttons['print'].props.sensitive:
            self.buttons['print'].props.active = True

    def sig_print_open(self, widget):
        if self.buttons['open'].props.sensitive:
            self.buttons['open'].props.active = True

    def sig_print_email(self, widget):
        if self.buttons['email'].props.sensitive:
            self.buttons['email'].props.active = True

    def sig_relate(self, widget):
        if self.buttons['relate'].props.sensitive:
            self.buttons['relate'].props.active = True

    def sig_search(self, widget):
        search_container = self.screen.screen_container
        if hasattr(search_container, 'search_entry'):
            search_container.search_entry.grab_focus()

    def action_popup(self, widget):
        button, = widget.get_children()
        button.grab_focus()
        menu = widget._menu
        if not widget.props.active:
            menu.popdown()
            return

        def menu_position(menu):
            parent = widget.get_toplevel()
            parent_x, parent_y = parent.window.get_origin()
            widget_allocation = widget.get_allocation()
            return (
                widget_allocation.x + parent_x,
                widget_allocation.y + widget_allocation.height + parent_y,
                False
            )
        menu.show_all()
        menu.popup(None, None, menu_position, 0, 0)

    def message_info(self, message, color='red'):
        if message:
            self.info_label.set_label(message)
            self.eb_info.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(
                COLOR_SCHEMES.get(color, 'white')))
            self.eb_info.show_all()
        else:
            self.info_label.set_label('')
            self.eb_info.hide()

    def _record_message(self, screen, signal_data):
        name = '_'
        if signal_data[0]:
            name = str(signal_data[0])
        for button_id in ('print', 'relate', 'email', 'open', 'save',
                'attach'):
            button = self.buttons[button_id]
            can_be_sensitive = getattr(button, '_can_be_sensitive', True)
            button.props.sensitive = (bool(signal_data[0])
                and can_be_sensitive)
        button_switch = self.buttons['switch']
        button_switch.props.sensitive = self.screen.number_of_views > 1

        msg = name + ' / ' + str(signal_data[1])
        if signal_data[1] < signal_data[2]:
            msg += _(' of ') + str(signal_data[2])
        self.status_label.set_text(msg)
        self.message_info('')
        self.activate_save()
        self.url_entry.set_text(self.screen.get_url())

    def _record_modified(self, screen, signal_data):
        # As it is called via idle_add, the form could have been destroyed in
        # the meantime.
        if self.widget_get().props.window:
            self.activate_save()

    def _record_saved(self, screen, signal_data):
        self.activate_save()
        self.update_attachment_count()

    def modified_save(self):
        self.screen.save_tree_state()
        self.screen.current_view.set_value()
        if self.screen.modified():
            value = sur_3b(
                _('This record has been modified\n'
                    'do you want to save it ?'))
            if value == 'ok':
                return self.sig_save(None)
            if value == 'ko':
                return self.sig_reload(test_modified=False)
            return False
        return True

    def sig_close(self, widget=None):
        for dialog in self.dialogs[:]:
            dialog.destroy()
        return self.modified_save()

    def _action(self, action, atype):
        action = action.copy()
        if not self.screen.save_current():
            return
        record_id = (self.screen.current_record.id
            if self.screen.current_record else None)
        record_ids = [r.id for r in self.screen.selected_records]
        action = Action.evaluate(action, atype, self.screen.current_record)
        data = {
            'model': self.screen.model_name,
            'id': record_id,
            'ids': record_ids,
        }
        Action._exec_action(action, data, self.screen.context)

    def activate_save(self):
        self.buttons['save'].props.sensitive = self.screen.modified()

    def sig_win_close(self, widget):
        Main.get_main().sig_win_close(widget)

    def create_toolbar(self, toolbars):
        gtktoolbar = super(Form, self).create_toolbar(toolbars)

        attach_btn = self.buttons['attach']
        attach_btn.drag_dest_set(gtk.DEST_DEFAULT_ALL, [
                ('text/uri-list', 0, 0),
                ], gtk.gdk.ACTION_MOVE)
        attach_btn.connect('drag_data_received',
            self.attach_drag_data_received)

        iconstock = {
            'print': 'tryton-print',
            'action': 'tryton-executable',
            'relate': 'tryton-go-jump',
            'email': 'tryton-print-email',
            'open': 'tryton-print-open',
        }
        for action_type, special_action, action_name, tooltip in (
                ('action', 'action', _('Action'), _('Launch action')),
                ('relate', 'relate', _('Relate'), _('Open related records')),
                (None,) * 4,
                ('print', 'open', _('Report'), _('Open report')),
                ('print', 'email', _('E-Mail'), _('E-Mail report')),
                ('print', 'print', _('Print'), _('Print report')),
        ):
            if action_type is not None:
                tbutton = gtk.ToggleToolButton(iconstock.get(special_action))
                tbutton.set_label(action_name)
                tbutton._menu = self._create_popup_menu(tbutton,
                    action_type, toolbars[action_type], special_action)
                tbutton.connect('toggled', self.action_popup)
                self.tooltips.set_tip(tbutton, tooltip)
                self.buttons[special_action] = tbutton
                if action_type != 'action':
                    tbutton._can_be_sensitive = bool(
                        tbutton._menu.get_children())
            else:
                tbutton = gtk.SeparatorToolItem()
            gtktoolbar.insert(tbutton, -1)

        return gtktoolbar

    def _create_popup_menu(self, widget, keyword, actions, special_action):
        menu = gtk.Menu()
        menu.connect('deactivate', self._popup_menu_hide, widget)

        if keyword == 'action':
            widget.connect('toggled', self._update_action_popup, menu)

        for action in actions:
            new_action = action.copy()
            if special_action == 'print':
                new_action['direct_print'] = True
            elif special_action == 'email':
                new_action['email_print'] = True
            action_name = action['name']
            if '_' not in action_name:
                action_name = '_' + action_name
            menuitem = gtk.MenuItem(action_name)
            menuitem.set_use_underline(True)
            menuitem.connect('activate', self._popup_menu_selected, widget,
                new_action, keyword)
            menu.add(menuitem)
        return menu

    def _popup_menu_selected(self, menuitem, togglebutton, action, keyword):
        event = gtk.get_current_event()
        allow_similar = False
        if (event.state & gtk.gdk.CONTROL_MASK
                or event.state & gtk.gdk.MOD1_MASK):
            allow_similar = True
        with Window(hide_current=True, allow_similar=allow_similar):
            self._action(action, keyword)
        togglebutton.props.active = False

    def _popup_menu_hide(self, menuitem, togglebutton):
        togglebutton.props.active = False

    def _update_action_popup(self, tbutton, menu):
        for item in menu.get_children():
            if (getattr(item, '_update_action', False)
                    or isinstance(item, gtk.SeparatorMenuItem)):
                menu.remove(item)

        buttons = self.screen.get_buttons()
        if buttons:
            menu.add(gtk.SeparatorMenuItem())
        for button in buttons:
            menuitem = gtk.ImageMenuItem(button.attrs.get('icon'))
            menuitem.set_label('_' + button.attrs.get('string', _('Unknown')))
            menuitem.set_use_underline(True)
            menuitem.connect('activate',
                lambda m, attrs: self.screen.button(attrs), button.attrs)
            menuitem._update_action = True
            menu.add(menuitem)

        menu.add(gtk.SeparatorMenuItem())
        for plugin in plugins.MODULES:
            for name, func in plugin.get_plugins(self.model):
                menuitem = gtk.MenuItem('_' + name)
                menuitem.set_use_underline(True)
                menuitem.connect('activate', lambda m, func: func({
                            'model': self.model,
                            'ids': [r.id
                                for r in self.screen.selected_records],
                            'id': self.id_get(),
                            }), func)
                menuitem._update_action = True
                menu.add(menuitem)

    def set_cursor(self):
        if self.screen:
            self.screen.set_cursor(reset_view=False)

    def attach_drag_data_received(self, widget, context, x, y, selection, info,
            timestamp):
        record = self.screen.current_record
        if not record or record.id < 0:
            return
        win_attach = Attachment(record,
            lambda: self.update_attachment_count(reload=True))
        if info == 0:
            for uri in selection.data.splitlines():
                # Win32 cut&paste terminates the list with a NULL character
                if not uri or uri == '\0':
                    continue
                win_attach.add_uri(uri)
Exemplo n.º 3
0
class Form(SignalEvent, TabContent):
    "Form"

    toolbar_def = [
        ('new', 'tryton-new', _('New'), _('Create a new record'),
            'sig_new'),
        ('save', 'tryton-save', _('Save'), _('Save this record'),
            'sig_save'),
        ('switch', 'tryton-fullscreen', _('Switch'), _('Switch view'),
            'sig_switch'),
        ('reload', 'tryton-refresh', _('_Reload'), _('Reload'),
            'sig_reload'),
        (None,) * 5,
        ('previous', 'tryton-go-previous', _('Previous'),
            _('Previous Record'), 'sig_previous'),
        ('next', 'tryton-go-next', _('Next'), _('Next Record'),
            'sig_next'),
        (None,) * 5,
        ('attach', 'tryton-attachment', _('Attachment(0)'),
            _('Add an attachment to the record'), 'sig_attach'),
    ]
    menu_def = [
        (_('_New'), 'tryton-new', 'sig_new', '<tryton>/Form/New'),
        (_('_Save'), 'tryton-save', 'sig_save', '<tryton>/Form/Save'),
        (_('_Switch View'), 'tryton-fullscreen', 'sig_switch',
            '<tryton>/Form/Switch View'),
        (_('_Reload/Undo'), 'tryton-refresh', 'sig_reload',
            '<tryton>/Form/Reload'),
        (_('_Duplicate'), 'tryton-copy', 'sig_copy',
            '<tryton>/Form/Duplicate'),
        (_('_Delete...'), 'tryton-delete', 'sig_remove',
            '<tryton>/Form/Delete'),
        (None,) * 4,
        (_('_Previous'), 'tryton-go-previous', 'sig_previous',
            '<tryton>/Form/Previous'),
        (_('_Next'), 'tryton-go-next', 'sig_next', '<tryton>/Form/Next'),
        (_('_Search'), 'tryton-find', 'sig_search', '<tryton>/Form/Search'),
        (_('View _Logs...'), None, 'sig_logs', None),
        (None,) * 4,
        (_('_Close Tab'), 'tryton-close', 'sig_win_close',
            '<tryton>/Form/Close'),
        (None,) * 4,
        (_('A_ttachments...'), 'tryton-attachment', 'sig_attach',
            '<tryton>/Form/Attachments'),
        (_('_Actions...'), 'tryton-executable', 'sig_action',
            '<tryton>/Form/Actions'),
        (_('_Relate...'), 'tryton-go-jump', 'sig_relate',
            '<tryton>/Form/Relate'),
        (None,) * 4,
        (_('_Report...'), 'tryton-print-open', 'sig_print_open',
            '<tryton>/Form/Report'),
        (_('_E-Mail...'), 'tryton-print-email', 'sig_print_email',
            '<tryton>/Form/Email'),
        (_('_Print...'), 'tryton-print', 'sig_print',
            '<tryton>/Form/Print'),
        (None,) * 4,
        (_('_Export Data...'), 'tryton-save-as', 'sig_save_as',
            '<tryton>/Form/Export Data'),
        (_('_Import Data...'), None, 'sig_import',
            '<tryton>/Form/Import Data'),
    ]

    def __init__(self, model, res_id=False, domain=None, mode=None,
            view_ids=None, context=None, name=False, limit=None,
            auto_refresh=False, search_value=None, tab_domain=None):
        super(Form, self).__init__()

        if not mode:
            mode = ['tree', 'form']
        if domain is None:
            domain = []
        if view_ids is None:
            view_ids = []
        if context is None:
            context = {}

        self.model = model
        self.res_id = res_id
        self.domain = domain
        self.mode = mode
        self.context = context
        self.auto_refresh = auto_refresh
        self.view_ids = view_ids
        self.dialogs = []

        self.screen = Screen(self.model, mode=mode, context=self.context,
            view_ids=view_ids, domain=domain, limit=limit,
            readonly=bool(auto_refresh), search_value=search_value,
            tab_domain=tab_domain)
        self.screen.widget.show()

        if not name:
            self.name = self.screen.current_view.title
        else:
            self.name = name

        self.create_tabcontent()

        access = common.MODELACCESS[self.model]
        for button, access_type in (
                ('new', 'create'),
                ('save', 'write'),
                ):
            self.buttons[button].props.sensitive = access[access_type]

        self.screen.signal_connect(self, 'record-message',
            self._record_message)
        self.screen.signal_connect(self, 'record-modified',
            lambda *a: gobject.idle_add(self._record_modified, *a))
        self.screen.signal_connect(self, 'record-saved', self._record_saved)
        self.screen.signal_connect(self, 'attachment-count',
                self._attachment_count)

        if res_id not in (None, False):
            if isinstance(res_id, (int, long)):
                res_id = [res_id]
            self.screen.load(res_id)
        else:
            if self.screen.current_view.view_type == 'form':
                self.sig_new(None, autosave=False)
            if self.screen.current_view.view_type \
                    in ('tree', 'graph', 'calendar'):
                self.screen.search_filter()

        if auto_refresh and int(auto_refresh):
            gobject.timeout_add(int(auto_refresh) * 1000, self.sig_reload)

    def get_toolbars(self):
        try:
            return RPCExecute('model', self.model, 'view_toolbar_get',
                context=self.context)
        except RPCException:
            return {}

    def widget_get(self):
        return self.screen.widget

    def __eq__(self, value):
        if not value:
            return False
        if not isinstance(value, Form):
            return False
        return (self.model == value.model
            and self.res_id == value.res_id
            and self.domain == value.domain
            and self.mode == value.mode
            and self.view_ids == value.view_ids
            and self.context == value.context
            and self.name == value.name
            and self.screen.limit == value.screen.limit
            and self.auto_refresh == value.auto_refresh
            and self.screen.search_value == value.screen.search_value)

    def destroy(self):
        self.screen.destroy()
        self.screen = None
        self.widget = None
        #self.scrolledwindow.destroy()
        #self.scrolledwindow = None

    def sel_ids_get(self):
        return self.screen.sel_ids_get()

    def ids_get(self):
        return self.screen.ids_get()

    def id_get(self):
        return self.screen.id_get()

    def sig_attach(self, widget=None):
        record_id = self.id_get()
        if record_id is False or record_id < 0:
            return
        Attachment(self.model, record_id,
            lambda: self.update_attachment_count(reload=True))

    def update_attachment_count(self, reload=False):
        record = self.screen.current_record
        if record:
            attachment_count = record.get_attachment_count(reload=reload)
        else:
            attachment_count = 0
        self._attachment_count(None, attachment_count)

    def _attachment_count(self, widget, signal_data):
        label = _('Attachment(%d)') % signal_data
        self.buttons['attach'].set_label(label)
        if signal_data:
            self.buttons['attach'].set_stock_id('tryton-attachment-hi')
        else:
            self.buttons['attach'].set_stock_id('tryton-attachment')
        record_id = self.id_get()
        self.buttons['attach'].props.sensitive = bool(
            record_id >= 0 and record_id is not False)

    def sig_switch(self, widget=None):
        if not self.modified_save():
            return
        self.screen.switch_view()

    def sig_logs(self, widget=None):
        obj_id = self.id_get()
        if obj_id < 0 or obj_id is False:
            self.message_info(_('You have to select one record!'))
            return False

        fields = [
            ('id', _('ID:')),
            ('create_uid.rec_name', _('Creation User:'******'create_date', _('Creation Date:')),
            ('write_uid.rec_name', _('Latest Modification by:')),
            ('write_date', _('Latest Modification Date:')),
        ]

        try:
            res = RPCExecute('model', self.model, 'read', [obj_id],
                [x[0] for x in fields], context=self.context)
        except RPCException:
            return
        message_str = ''
        for line in res:
            for (key, val) in fields:
                value = str(line.get(key, False) or '/')
                if line.get(key, False) \
                        and key in ('create_date', 'write_date'):
                    display_format = date_format() + ' %H:%M:%S'
                    date = timezoned_date(line[key])
                    value = common.datetime_strftime(date, display_format)
                message_str += val + ' ' + value + '\n'
        message_str += _('Model:') + ' ' + self.model
        message(message_str)
        return True

    def sig_remove(self, widget=None):
        if not common.MODELACCESS[self.model]['delete']:
            return
        if self.screen.current_view.view_type == 'form':
            msg = _('Are you sure to remove this record?')
        else:
            msg = _('Are you sure to remove those records?')
        if sur(msg):
            if not self.screen.remove(delete=True, force_remove=True):
                self.message_info(_('Records not removed!'))
            else:
                self.message_info(_('Records removed!'), 'green')

    def sig_import(self, widget=None):
        while(self.screen.view_to_load):
            self.screen.load_view_to_load()
        fields = {}
        for name, field in self.screen.group.fields.iteritems():
            fields[name] = field.attrs
        WinImport(self.model)

    def sig_save_as(self, widget=None):
        while self.screen.view_to_load:
            self.screen.load_view_to_load()
        fields = {}
        for name, field in self.screen.group.fields.iteritems():
            fields[name] = field.attrs
        WinExport(self.model, self.ids_get(), context=self.context)

    def sig_new(self, widget=None, autosave=True):
        if not common.MODELACCESS[self.model]['create']:
            return
        if autosave:
            if not self.modified_save():
                return
        self.screen.new()
        self.message_info('')
        self.activate_save()

    def sig_copy(self, widget=None):
        if not common.MODELACCESS[self.model]['create']:
            return
        if not self.modified_save():
            return
        res_ids = self.sel_ids_get()
        try:
            new_ids = RPCExecute('model', self.model, 'copy', res_ids, {},
                context=self.context)
        except RPCException:
            return
        self.screen.load(new_ids)
        self.message_info(_('Working now on the duplicated record(s)!'),
            'green')

    def sig_save(self, widget=None):
        if not common.MODELACCESS[self.model]['write']:
            return
        if self.screen.save_current():
            self.message_info(_('Record saved!'), 'green')
            return True
        else:
            self.message_info(_('Invalid form!'))
            return False

    def sig_previous(self, widget=None):
        if not self.modified_save():
            return
        self.screen.display_prev()
        self.message_info('')
        self.activate_save()

    def sig_next(self, widget=None):
        if not self.modified_save():
            return
        self.screen.display_next()
        self.message_info('')
        self.activate_save()

    def sig_reload(self, test_modified=True):
        if not hasattr(self, 'screen'):
            return False
        if test_modified and self.screen.modified():
            res = sur_3b(_('This record has been modified\n'
                    'do you want to save it ?'))
            if res == 'ok':
                self.sig_save(None)
            elif res == 'ko':
                pass
            else:
                return False
        self.screen.cancel_current()
        set_cursor = False
        if self.screen.current_view.view_type != 'form':
            obj_id = self.id_get()
            self.screen.search_filter(self.screen.screen_container.get_text())
            for record in self.screen.group:
                if record.id == obj_id:
                    self.screen.current_record = record
                    set_cursor = True
                    break
        self.screen.display(set_cursor=set_cursor)
        self.message_info('')
        self.activate_save()
        return True

    def sig_action(self, widget):
        if self.buttons['action'].props.sensitive:
            self.buttons['action'].props.active = True

    def sig_print(self, widget):
        if self.buttons['print'].props.sensitive:
            self.buttons['print'].props.active = True

    def sig_print_open(self, widget):
        if self.buttons['open'].props.sensitive:
            self.buttons['open'].props.active = True

    def sig_print_email(self, widget):
        if self.buttons['email'].props.sensitive:
            self.buttons['email'].props.active = True

    def sig_relate(self, widget):
        if self.buttons['relate'].props.sensitive:
            self.buttons['relate'].props.active = True

    def sig_search(self, widget):
        search_container = self.screen.screen_container
        if hasattr(search_container, 'search_entry'):
            search_container.search_entry.grab_focus()

    def action_popup(self, widget):
        button, = widget.get_children()
        button.grab_focus()
        menu = widget._menu
        if not widget.props.active:
            menu.popdown()
            return

        def menu_position(menu):
            parent = widget.get_toplevel()
            parent_x, parent_y = parent.window.get_origin()
            widget_allocation = widget.get_allocation()
            return (
                widget_allocation.x + parent_x,
                widget_allocation.y + widget_allocation.height + parent_y,
                False
            )
        menu.show_all()
        menu.popup(None, None, menu_position, 0, 0)

    def message_info(self, message, color='red'):
        if message:
            self.info_label.set_label(message)
            self.eb_info.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(
                COLOR_SCHEMES.get(color, 'white')))
            self.eb_info.show_all()
        else:
            self.info_label.set_label('')
            self.eb_info.hide()

    def _record_message(self, screen, signal_data):
        name = '_'
        if signal_data[0]:
            name = str(signal_data[0])
        for button_id in ('print', 'action', 'relate', 'email', 'open', 'save',
                'attach'):
            button = self.buttons[button_id]
            can_be_sensitive = getattr(button, '_can_be_sensitive', True)
            button.props.sensitive = (bool(signal_data[0])
                and can_be_sensitive)
        button_switch = self.buttons['switch']
        button_switch.props.sensitive = self.screen.number_of_views > 1

        msg = name + ' / ' + str(signal_data[1])
        if signal_data[1] < signal_data[2]:
            msg += _(' of ') + str(signal_data[2])
        self.status_label.set_text(msg)
        self.message_info('')
        self.activate_save()

    def _record_modified(self, screen, signal_data):
        # As it is called via idle_add, the form could have been destroyed in
        # the meantime.
        if screen == self.screen:
            self.activate_save()

    def _record_saved(self, screen, signal_data):
        self.activate_save()
        self.update_attachment_count()

    def modified_save(self):
        self.screen.current_view.set_value()
        if self.screen.modified():
            value = sur_3b(
                _('This record has been modified\n'
                    'do you want to save it ?'))
            if value == 'ok':
                return self.sig_save(None)
            if value == 'ko':
                return self.sig_reload(test_modified=False)
            return False
        return True

    def sig_close(self, widget=None):
        for dialog in self.dialogs[:]:
            dialog.destroy()
        return self.modified_save()

    def _action(self, action, atype):
        action = action.copy()
        if not self.screen.save_current():
            return
        record_id = self.screen.id_get()
        record_ids = self.screen.sel_ids_get()
        action = Action.evaluate(action, atype, self.screen.current_record)
        data = {
            'model': self.screen.model_name,
            'id': record_id,
            'ids': record_ids,
        }
        Action._exec_action(action, data, self.context)

    def activate_save(self):
        self.buttons['save'].props.sensitive = self.screen.modified()

    def sig_win_close(self, widget):
        Main.get_main().sig_win_close(widget)

    def create_toolbar(self, toolbars):
        gtktoolbar = super(Form, self).create_toolbar(toolbars)

        iconstock = {
            'print': 'tryton-print',
            'action': 'tryton-executable',
            'relate': 'tryton-go-jump',
            'email': 'tryton-print-email',
            'open': 'tryton-print-open',
        }
        for action_type, special_action, action_name, tooltip in (
                ('action', 'action', _('Action'), _('Launch action')),
                ('relate', 'relate', _('Relate'), _('Open related records')),
                (None,) * 4,
                ('print', 'open', _('Report'), _('Open report')),
                ('print', 'email', _('E-Mail'), _('E-Mail report')),
                ('print', 'print', _('Print'), _('Print report')),
        ):
            if action_type is not None:
                tbutton = gtk.ToggleToolButton(iconstock.get(special_action))
                tbutton.set_label(action_name)
                tbutton._menu = self._create_popup_menu(tbutton,
                    action_type, toolbars[action_type], special_action)
                tbutton.connect('toggled', self.action_popup)
                self.tooltips.set_tip(tbutton, tooltip)
                self.buttons[special_action] = tbutton
                tbutton._can_be_sensitive = bool(tbutton._menu.get_children())
            else:
                tbutton = gtk.SeparatorToolItem()
            gtktoolbar.insert(tbutton, -1)

        return gtktoolbar

    def _create_popup_menu(self, widget, keyword, actions, special_action):
        menu = gtk.Menu()
        menu.connect('deactivate', self._popup_menu_hide, widget)

        for action in actions:
            new_action = action.copy()
            if special_action == 'print':
                new_action['direct_print'] = True
            elif special_action == 'email':
                new_action['email_print'] = True
            action_name = action['name']
            if '_' not in action_name:
                action_name = '_' + action_name
            menuitem = gtk.MenuItem(action_name)
            menuitem.set_use_underline(True)
            menuitem.connect('activate', self._popup_menu_selected, widget,
                new_action, keyword)
            menu.add(menuitem)
        if keyword == 'action':
            menu.add(gtk.SeparatorMenuItem())
            for plugin in plugins.MODULES:
                for name, func in plugin.get_plugins(self.model):
                    menuitem = gtk.MenuItem('_' + name)
                    menuitem.set_use_underline(True)
                    menuitem.connect('activate', lambda m, func: func({
                                'model': self.model,
                                'ids': self.id_get(),
                                'id': self.id_get(),
                                }), func)
                    menu.add(menuitem)
        return menu

    def _popup_menu_selected(self, menuitem, togglebutton, action, keyword):
        event = gtk.get_current_event()
        allow_similar = False
        if (event.state & gtk.gdk.CONTROL_MASK
                or event.state & gtk.gdk.MOD1_MASK):
            allow_similar = True
        with Window(hide_current=True, allow_similar=allow_similar):
            self._action(action, keyword)
        togglebutton.props.active = False

    def _popup_menu_hide(self, menuitem, togglebutton):
        togglebutton.props.active = False

    def set_cursor(self):
        if self.screen:
            self.screen.set_cursor(reset_view=False)