class Form(SignalEvent, TabContent): "Form" def __init__(self, model, res_id=None, name='', **attributes): super(Form, self).__init__(**attributes) self.model = model self.res_id = res_id self.mode = attributes.get('mode') self.view_ids = attributes.get('view_ids') self.dialogs = [] self.screen = Screen(self.model, **attributes) self.screen.widget.show() self.name = name self.create_tabcontent() self.set_buttons_sensitive() self.screen.signal_connect(self, 'record-message', self._record_message) self.screen.signal_connect( self, 'record-modified', lambda *a: GLib.idle_add(self._record_modified, *a)) self.screen.signal_connect(self, 'record-saved', self._record_saved) self.screen.signal_connect( self, 'resources', lambda screen, resources: self.update_resources(resources)) if res_id not in (None, False): if isinstance(res_id, int): 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 compare(self, model, attributes): if not attributes: return False return (self.model == model and self.res_id == attributes.get('res_id') and self.attributes.get('domain') == attributes.get('domain') and self.attributes.get('view_ids') == attributes.get('view_ids') and (attributes.get('view_ids') or (self.attributes.get('mode') or ['tree', 'form']) == (attributes.get('mode') or ['tree', 'form'])) and self.screen.local_context == attributes.get('context') and self.attributes.get('search_value') == (attributes.get('search_value'))) def __hash__(self): return id(self) def destroy(self): super(Form, self).destroy() self.screen.signal_unconnect(self) self.screen.destroy() def sig_attach(self, widget=None): def window(widget): return Attachment(record, lambda: self.refresh_resources(reload=True)) def add_file(widget): filenames = common.file_selection(_("Select"), multi=True) if filenames: attachment = window(widget) for filename in filenames: attachment.add_file(filename) def activate(widget, callback): callback() button = self.buttons['attach'] if widget != button: if button.props.sensitive: button.props.active = True return record = self.screen.current_record menu = button._menu = Gtk.Menu() for name, callback in Attachment.get_attachments(record): item = Gtk.MenuItem(label=name) item.connect('activate', activate, callback) menu.add(item) menu.add(Gtk.SeparatorMenuItem()) add_item = Gtk.MenuItem(label=_("Add...")) add_item.connect('activate', add_file) menu.add(add_item) manage_item = Gtk.MenuItem(label=_("Manage...")) manage_item.connect('activate', window) menu.add(manage_item) menu.show_all() menu.connect('deactivate', self._popup_menu_hide, button) self.action_popup(button) def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.refresh_resources(reload=True)) def refresh_resources(self, reload=False): record = self.screen.current_record self.update_resources( record.get_resources(reload=reload) if record else None) def update_resources(self, resources): if not resources: resources = {} record = self.screen.current_record sensitive = record.id >= 0 if record else False def update(name, label, icon, badge): button = self.buttons[name] button.set_label(label) image = common.IconFactory.get_image(icon, Gtk.IconSize.LARGE_TOOLBAR, badge=badge) image.show() button.set_icon_widget(image) button.props.sensitive = sensitive attachment_count = resources.get('attachment_count', 0) badge = 1 if attachment_count else None label = _("Attachment (%s)") % attachment_count update('attach', label, 'tryton-attach', badge) note_count = resources.get('note_count', 0) note_unread = resources.get('note_unread', 0) if note_unread: badge = 2 elif note_count: badge = 1 else: badge = None label = _("Note (%d/%d)") % (note_unread, note_count) update('note', label, 'tryton-note', badge) def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.message_info(_('You have to select one record.'), Gtk.MessageType.INFO) return False fields = [ ('id', _('ID:')), ('create_uid.rec_name', _('Created by:')), ('create_date', _('Created at:')), ('write_uid.rec_name', _('Edited by:')), ('write_date', _('Edited at:')), ] try: data = RPCExecute('model', self.model, 'read', [current_record.id], [x[0] for x in fields], context=self.screen.context)[0] except RPCException: return date_format = self.screen.context.get('date_format', '%x') datetime_format = date_format + ' %H:%M:%S.%f' message_str = '' for (key, label) in fields: value = data keys = key.split('.') name = keys.pop(-1) for key in keys: value = value.get(key + '.', {}) value = (value or {}).get(name, '/') if isinstance(value, datetime.datetime): value = timezoned_date(value).strftime(datetime_format) message_str += '%s %s\n' % (label, value) message_str += _('Model:') + ' ' + self.model message(message_str) return True def sig_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_ += ' %H:%M:%S.%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): tooltips = common.Tooltips() revision = self.screen.context.get('_datetime') if revision: format_ = self.screen.context.get('date_format', '%x') format_ += ' %H:%M:%S.%f' revision_label = ' @ %s' % revision.strftime(format_) label = common.ellipsize(self.name, 80 - len(revision_label)) + revision_label tooltip = self.name + revision_label else: label = common.ellipsize(self.name, 80) tooltip = self.name self.title.set_markup(label) tooltips.set_tip(self.title, tooltip) self.set_buttons_sensitive(revision) def set_buttons_sensitive(self, revision=None): if not revision: access = common.MODELACCESS[self.model] for name, sensitive in [ ('new', access['create']), ('save', access['create'] or access['write']), ('remove', access['delete']), ('copy', access['create']), ('import', access['create']), ]: if name in self.buttons: self.buttons[name].props.sensitive = sensitive if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = sensitive else: for name in ['new', 'save', 'remove', 'copy', 'import']: if name in self.buttons: self.buttons[name].props.sensitive = False if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = False 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.'), Gtk.MessageType.ERROR) else: self.message_info(_('Records removed.'), Gtk.MessageType.INFO) self.screen.count_tab_domain() def sig_import(self, widget=None): WinImport(self.title.get_text(), self.model, self.screen.context) def sig_export(self, widget=None): if not self.modified_save(): return export = WinExport(self.title.get_text(), self.model, [r.id for r in self.screen.selected_records], context=self.screen.context) for name in self.screen.current_view.get_fields(): type = self.screen.group.fields[name].attrs['type'] if type == 'selection': export.sel_field(name + '.translated') elif type == 'reference': export.sel_field(name + '.translated') export.sel_field(name + '/rec_name') else: export.sel_field(name) def do_export(self, widget, export): if not self.modified_save(): return ids = [r.id for r in self.screen.selected_records] fields = [f['name'] for f in export['export_fields.']] data = RPCExecute('model', self.model, 'export_data', ids, fields, context=self.screen.context) delimiter = ',' if os.name == 'nt' and ',' == locale.localeconv()['decimal_point']: delimiter = ';' fileno, fname = tempfile.mkstemp('.csv', common.slugify(export['name']) + '_') with open(fname, 'w') as fp: writer = csv.writer(fp, delimiter=delimiter) writer.writerow(fields) for row in data: writer.writerow(WinExport.format_row(row)) os.close(fileno) common.file_open(fname, 'csv') 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 if self.screen.copy(): self.message_info(_('Working now on the duplicated record(s).'), Gtk.MessageType.INFO) self.screen.count_tab_domain() 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'] or common.MODELACCESS[self.model]['create']): return if self.screen.save_current(): self.message_info(_('Record saved.'), Gtk.MessageType.INFO) self.screen.count_tab_domain() return True else: self.message_info(self.screen.invalid_message(), Gtk.MessageType.ERROR) 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() self.screen.count_tab_domain() 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_copy_url(self, widget): if self.buttons['copy_url'].props.sensitive: self.buttons['copy_url'].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, x, y, user_data): widget_allocation = widget.get_allocation() x, y = widget.get_window().get_root_coords(widget_allocation.x, widget_allocation.y) return (x, y + widget_allocation.height, False) menu.show_all() if hasattr(menu, 'popup_at_widget'): menu.popup_at_widget(widget, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, Gtk.get_current_event()) else: menu.popup(None, None, menu_position, None, 0, Gtk.get_current_event_time()) 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) if button_id in {'print', 'relate', 'email', 'open'}: action_type = button_id if button_id in {'email', 'open'}: action_type = 'print' can_be_sensitive |= any( b.attrs.get('keyword', 'action') == action_type for b in screen.get_buttons()) 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 self.widget_get().props.window: self.activate_save() def _record_saved(self, screen, signal_data): self.activate_save() self.refresh_resources() 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 reversed(self.dialogs[:]): dialog.destroy() return self.modified_save() def _action(self, action, atype): if not self.modified_save(): return action = action.copy() 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.execute(action, data, context=self.screen.local_context) def activate_save(self): self.buttons['save'].props.sensitive = self.screen.modified() def sig_win_close(self, widget): 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.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/uri-list', 0, 0), Gtk.TargetEntry.new('text/plain', 0, 0), ], Gdk.DragAction.MOVE | Gdk.DragAction.COPY) attach_btn.connect('drag_data_received', self.attach_drag_data_received) iconstock = { 'print': 'tryton-print', 'action': 'tryton-launch', 'relate': 'tryton-link', 'email': 'tryton-email', 'open': 'tryton-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() tbutton.set_icon_widget( common.IconFactory.get_image(iconstock.get(special_action), Gtk.IconSize.LARGE_TOOLBAR)) 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) exports = toolbars['exports'] if exports: tbutton = self.buttons['open'] tbutton._can_be_sensitive = True menu = tbutton._menu if menu.get_children(): menu.add(Gtk.SeparatorMenuItem()) for export in exports: menuitem = Gtk.MenuItem(set_underline(export['name'])) menuitem.set_use_underline(True) menuitem.connect('activate', self.do_export, export) menu.add(menuitem) gtktoolbar.insert(Gtk.SeparatorToolItem(), -1) url_button = Gtk.ToggleToolButton() url_button.set_icon_widget( common.IconFactory.get_image('tryton-public', Gtk.IconSize.LARGE_TOOLBAR)) url_button.set_label(_('_Copy URL')) url_button.set_use_underline(True) self.tooltips.set_tip(url_button, _('Copy URL into clipboard')) url_button._menu = url_menu = Gtk.Menu() url_menuitem = Gtk.MenuItem() url_menuitem.connect('activate', self.url_copy) url_menu.add(url_menuitem) url_menu.show_all() url_menu.connect('deactivate', self._popup_menu_hide, url_button) url_button.connect('toggled', self.url_set, url_menuitem) url_button.connect('toggled', self.action_popup) self.buttons['copy_url'] = url_button gtktoolbar.insert(url_button, -1) return gtktoolbar def _create_popup_menu(self, widget, keyword, actions, special_action): menu = Gtk.Menu() menu.connect('deactivate', self._popup_menu_hide, widget) widget.connect('toggled', self._update_popup_menu, menu, keyword) 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 menuitem = Gtk.MenuItem(label=set_underline(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 & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.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_popup_menu(self, tbutton, menu, keyword): for item in menu.get_children(): if getattr(item, '_update_action', False): menu.remove(item) buttons = [ b for b in self.screen.get_buttons() if keyword == b.attrs.get('keyword', 'action') ] if buttons and menu.get_children(): separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for button in buttons: menuitem = Gtk.MenuItem(label=set_underline( button.attrs.get('string', _('Unknown'))), use_underline=True) menuitem.connect('activate', lambda m, attrs: self.screen.button(attrs), button.attrs) menuitem._update_action = True menu.add(menuitem) kw_plugins = [] for plugin in plugins.MODULES: for plugin_spec in plugin.get_plugins(self.model): name, func = plugin_spec[:2] try: plugin_keyword = plugin_spec[2] except IndexError: plugin_keyword = 'action' if keyword != plugin_keyword: continue kw_plugins.append((name, func)) if kw_plugins: separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for name, func in kw_plugins: menuitem = Gtk.MenuItem(label=set_underline(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.screen.current_record.id if self.screen.current_record else None), }), func) menuitem._update_action = True menu.add(menuitem) def url_copy(self, menuitem): url = self.screen.get_url(self.name) for selection in [ Gdk.Atom.intern('PRIMARY', True), Gdk.Atom.intern('CLIPBOARD', True), ]: clipboard = Gtk.Clipboard.get(selection) clipboard.set_text(url, -1) def url_set(self, button, menuitem): url = self.screen.get_url(self.name) size = 80 if len(url) > size: url = url[:size // 2] + '...' + url[-size // 2:] menuitem.set_label(url) 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.refresh_resources(reload=True)) if info == 0: if selection.get_uris(): for uri in selection.get_uris(): # Win32 cut&paste terminates the list with a NULL character if not uri or uri == '\0': continue win_attach.add_uri(uri) else: win_attach.add_uri(selection.get_text())
class Form(SignalEvent, TabContent): "Form" def __init__(self, model, res_id=None, name='', **attributes): super(Form, self).__init__() self.model = model self.res_id = res_id self.mode = attributes.get('mode') self.view_ids = attributes.get('view_ids') self.dialogs = [] self.screen = Screen(self.model, **attributes) self.screen.widget.show() self.name = name self.create_tabcontent() self.set_buttons_sensitive() 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) self.screen.signal_connect(self, 'unread-note', self._unread_note) 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.screen.domain == value.screen.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): super(Form, self).destroy() self.screen.signal_unconnect(self) self.screen.destroy() 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 = self.screen.current_record self.buttons['attach'].props.sensitive = bool( record.id >= 0 if record else False) def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.update_unread_note(reload=True)) def update_unread_note(self, reload=False): record = self.screen.current_record if record: unread = record.get_unread_note(reload=reload) else: unread = 0 self._unread_note(None, unread) def _unread_note(self, widget, signal_data): label = _('Note(%d)') % signal_data self.buttons['note'].set_label(label) if signal_data: self.buttons['note'].set_stock_id('tryton-note-hi') else: self.buttons['note'].set_stock_id('tryton-note') record = self.screen.current_record if not record or record.id < 0: sensitive = False else: sensitive = True self.buttons['note'].props.sensitive = sensitive def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.message_info(_('You have to select one record.'), gtk.MESSAGE_INFO) 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', [current_record.id], [x[0] for x in fields], context=self.screen.context) except RPCException: return date_format = self.screen.context.get('date_format', '%x') datetime_format = date_format + ' %H:%M:%S.%f' 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, datetime_format) message_str += val + ' ' + value + '\n' message_str += _('Model:') + ' ' + self.model message(message_str) return True def sig_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_ += ' %H:%M:%S.%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_ += ' %H:%M:%S.%f' revision = datetime_strftime(revision, format_) self.title.set_label('%s @ %s' % (self.name, revision)) else: self.title.set_label(self.name) self.set_buttons_sensitive(revision) def set_buttons_sensitive(self, revision=None): if not revision: access = common.MODELACCESS[self.model] for name, sensitive in [ ('new', access['create']), ('save', access['create'] or access['write']), ('remove', access['delete']), ('copy', access['create']), ('import', access['create']), ]: if name in self.buttons: self.buttons[name].props.sensitive = sensitive if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = sensitive else: for name in ['new', 'save', 'remove', 'copy', 'import']: if name in self.buttons: self.buttons[name].props.sensitive = False if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = False 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.'), gtk.MESSAGE_ERROR) else: self.message_info(_('Records removed.'), gtk.MESSAGE_INFO) self.screen.count_tab_domain() def sig_import(self, widget=None): WinImport(self.model, self.screen.context) def sig_export(self, widget=None): export = WinExport(self.model, [r.id for r in self.screen.selected_records], 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 if self.screen.copy(): self.message_info(_('Working now on the duplicated record(s).'), gtk.MESSAGE_INFO) self.screen.count_tab_domain() 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'] or common.MODELACCESS[self.model]['create']): return if self.screen.save_current(): self.message_info(_('Record saved.'), gtk.MESSAGE_INFO) self.screen.count_tab_domain() return True else: self.message_info(self.screen.invalid_message(), gtk.MESSAGE_ERROR) 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() self.screen.count_tab_domain() 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_copy_url(self, widget): if self.buttons['copy_url'].props.sensitive: self.buttons['copy_url'].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, data): widget_allocation = widget.get_allocation() if hasattr(widget.window, 'get_root_coords'): x, y = widget.window.get_root_coords(widget_allocation.x, widget_allocation.y) else: x, y = widget.window.get_origin() x += widget_allocation.x y += widget_allocation.y return (x, y + widget_allocation.height, False) menu.show_all() menu.popup(None, None, menu_position, 0, gtk.get_current_event_time(), None) 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() 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'] target_entry = gtk.TargetEntry.new('text/uri-list', 0, 0) attach_btn.drag_dest_set(gtk.DEST_DEFAULT_ALL, [ target_entry, ], gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY) 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) gtktoolbar.insert(gtk.SeparatorToolItem(), -1) url_button = gtk.ToggleToolButton('tryton-web-browser') url_button.set_label(_('_Copy URL')) url_button.set_use_underline(True) self.tooltips.set_tip(url_button, _('Copy URL into clipboard')) url_button._menu = url_menu = gtk.Menu() url_menuitem = gtk.MenuItem() url_menuitem.connect('activate', self.url_copy) url_menu.add(url_menuitem) url_menu.show_all() url_menu.connect('deactivate', self._popup_menu_hide, url_button) url_button.connect('toggled', self.url_set, url_menuitem) url_button.connect('toggled', self.action_popup) self.buttons['copy_url'] = url_button gtktoolbar.insert(url_button, -1) return gtktoolbar def _create_popup_menu(self, widget, keyword, actions, special_action): menu = gtk.Menu() menu.connect('deactivate', self._popup_menu_hide, widget) widget.connect('toggled', self._update_popup, menu, special_action) 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_popup(self, tbutton, menu, keyword): assert keyword in ['print', 'action', 'relate', 'email', 'open'] for item in menu.get_children(): if (getattr(item, '_update_action', False) or isinstance(item, gtk.SeparatorMenuItem)): menu.remove(item) buttons = [ button for button in self.screen.get_buttons() if keyword in button.attrs.get('keywords', 'action').split(',') ] if buttons: menu.add(gtk.SeparatorMenuItem()) for button in buttons: menuitem = gtk.ImageMenuItem() menuitem.set_label('_' + button.attrs.get('string', _('Unknown'))) menuitem.set_use_underline(True) if button.attrs.get('icon'): icon = gtk.Image() icon.set_from_stock(button.attrs['icon'], gtk.ICON_SIZE_MENU) menuitem.set_image(icon) menuitem.connect('activate', lambda m, attrs: self.screen.button(attrs), button.attrs) menuitem._update_action = True 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': [r.id for r in self.screen.selected_records], 'id': (self.screen.current_record.id if self.screen.current_record else None), }), func) menuitem._update_action = True menu.add(menuitem) def url_copy(self, menuitem): url = self.screen.get_url(self.name) for selection in [gtk.CLIPBOARD_PRIMARY, gtk.CLIPBOARD_CLIPBOARD]: clipboard = gtk.clipboard_get(selection) clipboard.set_text(url, -1) def url_set(self, button, menuitem): url = self.screen.get_url(self.name) size = 80 if len(url) > size: url = url[:size // 2] + '...' + url[-size // 2:] menuitem.set_label(url) 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)
class Form(TabContent): "Form" def __init__(self, model, res_id=None, name='', **attributes): super(Form, self).__init__(**attributes) self.model = model self.res_id = res_id self.mode = attributes.get('mode') self.view_ids = attributes.get('view_ids') self.dialogs = [] if not name: name = common.MODELNAME.get(model) self.name = name loading_ids = res_id not in (None, False) if loading_ids: attributes.pop('tab_domain', None) self.screen = Screen(self.model, breadcrumb=[self.name], **attributes) self.screen.widget.show() self.screen.windows.append(self) self.create_tabcontent() self.set_buttons_sensitive() self.attachment_screen = None if loading_ids: if isinstance(res_id, int): 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() self.activate_save() def get_toolbars(self): try: return RPCExecute('model', self.model, 'view_toolbar_get', context=self.screen.context) except RPCException: return {} def create_tabcontent(self): super().create_tabcontent() self.attachment_preview = Gtk.Viewport() self.attachment_preview.set_shadow_type(Gtk.ShadowType.NONE) self.attachment_preview.show() scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.add(self.attachment_preview) scrolledwindow.set_size_request(300, -1) self.main.pack2(scrolledwindow, resize=False, shrink=True) def widget_get(self): return self.screen.widget def compare(self, model, attributes): if not attributes: return False return (self.model == model and self.res_id == attributes.get('res_id') and self.attributes.get('domain') == attributes.get('domain') and self.attributes.get('view_ids') == attributes.get('view_ids') and (attributes.get('view_ids') or (self.attributes.get('mode') or ['tree', 'form']) == (attributes.get('mode') or ['tree', 'form'])) and self.screen.local_context == attributes.get('context') and self.attributes.get('search_value') == (attributes.get('search_value')) and self.attributes.get('tab_domain') == (attributes.get('tab_domain'))) def __hash__(self): return id(self) def destroy(self): self.screen.destroy() def sig_attach(self, widget=None): def window(widget): return Attachment(record, lambda: self.refresh_resources(reload=True)) def add_file(widget): filenames = common.file_selection(_("Select"), multi=True) if filenames: attachment = window(widget) for filename in filenames: attachment.add_file(filename) def preview(widget): children = self.attachment_preview.get_children() for child in children: self.attachment_preview.remove(child) if widget.get_active(): self.attachment_preview.add(self._attachment_preview_widget()) self.attachment_preview.get_parent().show() self.refresh_attachment_preview() else: self.attachment_screen = None self.attachment_preview.get_parent().hide() def activate(widget, callback): callback() button = self.buttons['attach'] if widget != button: if button.props.sensitive: button.props.active = True return record = self.screen.current_record menu = button._menu = Gtk.Menu() for name, callback in Attachment.get_attachments(record): item = Gtk.MenuItem(label=name) item.connect('activate', activate, callback) menu.add(item) menu.add(Gtk.SeparatorMenuItem()) add_item = Gtk.MenuItem(label=_("Add...")) add_item.connect('activate', add_file) menu.add(add_item) preview_item = Gtk.CheckMenuItem(label=_("Preview")) preview_item.set_active(bool(self.attachment_preview.get_children())) preview_item.connect('toggled', preview) menu.add(preview_item) manage_item = Gtk.MenuItem(label=_("Manage...")) manage_item.connect('activate', window) menu.add(manage_item) menu.show_all() menu.connect('deactivate', self._popup_menu_hide, button) self.action_popup(button) def _attachment_preview_widget(self): vbox = Gtk.VBox(homogeneous=False, spacing=2) vbox.set_margin_start(4) hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.set_halign(Gtk.Align.CENTER) vbox.pack_start(hbox, expand=False, fill=True, padding=0) hbox.set_border_width(2) tooltips = common.Tooltips() but_prev = Gtk.Button() tooltips.set_tip(but_prev, _("Previous")) but_prev.add( common.IconFactory.get_image('tryton-back', Gtk.IconSize.SMALL_TOOLBAR)) but_prev.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_prev, expand=False, fill=False, padding=0) label = Gtk.Label(label='(0,0)') hbox.pack_start(label, expand=False, fill=False, padding=0) but_next = Gtk.Button() tooltips.set_tip(but_next, _("Next")) but_next.add( common.IconFactory.get_image('tryton-forward', Gtk.IconSize.SMALL_TOOLBAR)) but_next.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_next, expand=False, fill=False, padding=0) vbox.show_all() self.attachment_screen = screen = Screen('ir.attachment', readonly=True, mode=['form'], context={ 'preview': True, }) screen.widget.show() but_prev.connect('clicked', lambda *a: screen.display_prev()) but_next.connect('clicked', lambda *a: screen.display_next()) class Preview(): def record_message(self, position, length, *args): label.set_text('(%s/%s)' % (position or '_', length)) but_prev.set_sensitive(position and position > 1) but_next.set_sensitive(position and position < length) screen.windows.append(Preview()) vbox.pack_start(screen.widget, expand=True, fill=True, padding=0) return vbox def refresh_attachment_preview(self, force=False): if not self.attachment_screen: return record = self.screen.current_record if not record: return resource = '%s,%s' % (record.model_name, record.id) domain = [ ('resource', '=', resource), ('type', '=', 'data'), ] if self.attachment_screen.domain != domain or force: self.attachment_screen.domain = domain self.attachment_screen.search_filter() def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.refresh_resources(reload=True)) def refresh_resources(self, reload=False): record = self.screen.current_record self.update_resources( record.get_resources(reload=reload) if record else None) if reload: self.refresh_attachment_preview(True) def update_resources(self, resources): if not resources: resources = {} record = self.screen.current_record sensitive = record.id >= 0 if record else False def update(name, label, icon, badge): button = self.buttons[name] button.set_label(label) image = common.IconFactory.get_image(icon, Gtk.IconSize.LARGE_TOOLBAR, badge=badge) image.show() button.set_icon_widget(image) button.props.sensitive = sensitive attachment_count = resources.get('attachment_count', 0) badge = 1 if attachment_count else None label = _("Attachment (%s)") % attachment_count update('attach', label, 'tryton-attach', badge) note_count = resources.get('note_count', 0) note_unread = resources.get('note_unread', 0) if note_unread: badge = 2 elif note_count: badge = 1 else: badge = None label = _("Note (%d/%d)") % (note_unread, note_count) update('note', label, 'tryton-note', badge) def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.message_info(_('You have to select one record.'), Gtk.MessageType.INFO) return False fields = [ ('id', _('ID:')), ('create_uid.rec_name', _('Created by:')), ('create_date', _('Created at:')), ('write_uid.rec_name', _('Edited by:')), ('write_date', _('Edited at:')), ] try: data = RPCExecute('model', self.model, 'read', [current_record.id], [x[0] for x in fields], context=self.screen.context)[0] except RPCException: return date_format = self.screen.context.get('date_format', '%x') datetime_format = date_format + ' %H:%M:%S.%f' message_str = '' for (key, label) in fields: value = data keys = key.split('.') name = keys.pop(-1) for key in keys: value = value.get(key + '.', {}) value = (value or {}).get(name, '/') if isinstance(value, datetime.datetime): value = timezoned_date(value).strftime(datetime_format) message_str += '%s %s\n' % (label, value) message_str += _('Model:') + ' ' + self.model message(message_str) return True def sig_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_ += ' %H:%M:%S.%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): tooltips = common.Tooltips() revision = self.screen.context.get('_datetime') if revision: format_ = self.screen.context.get('date_format', '%x') format_ += ' %H:%M:%S.%f' revision_label = ' @ %s' % revision.strftime(format_) label = common.ellipsize(self.name, 80 - len(revision_label)) + revision_label tooltip = self.name + revision_label else: label = common.ellipsize(self.name, 80) tooltip = self.name self.title.set_text(label) tooltips.set_tip(self.title, tooltip) self.set_buttons_sensitive(revision) def set_buttons_sensitive(self, revision=None): if not revision: access = common.MODELACCESS[self.model] for name, sensitive in [ ('new', access['create']), ('save', access['create'] or access['write']), ('remove', access['delete']), ('copy', access['create']), ('import', access['create']), ]: if name in self.buttons: self.buttons[name].props.sensitive = sensitive if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = sensitive else: for name in ['new', 'save', 'remove', 'copy', 'import']: if name in self.buttons: self.buttons[name].props.sensitive = False if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = False def sig_remove(self, widget=None): if (not common.MODELACCESS[self.model]['delete'] or not self.screen.deletable): 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.'), Gtk.MessageType.ERROR) else: self.message_info(_('Records removed.'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) def sig_import(self, widget=None): WinImport(self.title.get_text(), self.model, self.screen.context) def sig_export(self, widget=None): if not self.modified_save(): return export = WinExport(self.title.get_text(), self.screen) for name in self.screen.current_view.get_fields(): type = self.screen.group.fields[name].attrs['type'] if type == 'selection': export.sel_field(name + '.translated') elif type == 'reference': export.sel_field(name + '.translated') export.sel_field(name + '/rec_name') else: export.sel_field(name) def do_export(self, widget, export): if not self.modified_save(): return if (self.screen.current_view and self.screen.current_view.view_type == 'tree' and self.screen.current_view.children_field): ids = [r.id for r in self.screen.listed_records] paths = self.screen.listed_paths else: ids = [r.id for r in self.screen.selected_records] paths = self.screen.selected_paths fields = [f['name'] for f in export['export_fields.']] data = RPCExecute('model', self.model, 'export_data', ids, fields, context=self.screen.context) delimiter = ',' if os.name == 'nt' and ',' == locale.localeconv()['decimal_point']: delimiter = ';' fileno, fname = tempfile.mkstemp('.csv', common.slugify(export['name']) + '_') with open(fname, 'w') as fp: writer = csv.writer(fp, delimiter=delimiter) writer.writerow(fields) for row, path in zip_longest(data, paths or []): indent = len(path) - 1 if path else 0 if row: writer.writerow(WinExport.format_row(row, indent=indent)) os.close(fileno) common.file_open(fname, 'csv') 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 if self.screen.copy(): self.message_info(_('Working now on the duplicated record(s).'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) 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'] or common.MODELACCESS[self.model]['create'] or self.screen.writable): return if self.screen.save_current(): self.message_info(_('Record saved.'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) return True else: self.message_info(self.screen.invalid_message(), Gtk.MessageType.ERROR) 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.set_buttons_sensitive() self.activate_save() self.screen.count_tab_domain() 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_email(self, widget): def is_report(action): return action['type'] == 'ir.action.report' if self.buttons['email'].props.sensitive: if not self.modified_save(): return record = self.screen.current_record if not record or record.id < 0: return toolbars = self.get_toolbars() title = self.title.get_text() prints = filter(is_report, toolbars['print']) emails = {e['name']: e['id'] for e in toolbars['emails']} template = selection_(_("Template"), emails, alwaysask=True) if template: template = template[1] Email('%s: %s' % (title, record.rec_name()), record, prints, template=template) def sig_relate(self, widget): if self.buttons['relate'].props.sensitive: self.buttons['relate'].props.active = True def sig_copy_url(self, widget): if self.buttons['copy_url'].props.sensitive: self.buttons['copy_url'].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 popup(menu, widget) def record_message(self, position, size, max_size, record_id): name = str(position) if position else '_' selected = len(self.screen.selected_records) if selected > 1: name += '#%i' % selected for button_id in ('print', 'relate', 'email', 'open', 'save', 'attach'): button = self.buttons[button_id] can_be_sensitive = getattr(button, '_can_be_sensitive', True) if button_id in {'print', 'relate', 'email', 'open'}: action_type = button_id if button_id == 'open': action_type = 'print' can_be_sensitive |= any( b.attrs.get('keyword', 'action') == action_type for b in self.screen.get_buttons()) elif button_id == 'save': can_be_sensitive &= not self.screen.readonly button.props.sensitive = bool(position) and can_be_sensitive button_switch = self.buttons['switch'] button_switch.props.sensitive = self.screen.number_of_views > 1 menu_delete = self.menu_buttons['remove'] menu_delete.props.sensitive = self.screen.deletable menu_save = self.menu_buttons['save'] menu_save.props.sensitive = not self.screen.readonly if size < max_size: msg = "%s@%s/%s" % (name, common.humanize(size), common.humanize(max_size)) if max_size >= self.screen.count_limit: msg += "+" else: msg = "%s/%s" % (name, common.humanize(size)) self.status_label.set_text(msg) self.message_info() self.activate_save() self.refresh_attachment_preview() def record_modified(self): def _record_modified(): # 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() GLib.idle_add(_record_modified) def record_saved(self): self.activate_save() self.refresh_resources() 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': record_id = self.screen.current_record.id if self.sig_reload(test_modified=False): if record_id < 0: return None elif self.screen.current_record: return record_id == self.screen.current_record.id return False return True def sig_close(self, widget=None): for dialog in reversed(self.dialogs[:]): dialog.destroy() modified_save = self.modified_save() return True if modified_save is None else modified_save def _action(self, action, atype): if not self.modified_save(): return action = action.copy() record_id = (self.screen.current_record.id if self.screen.current_record else None) if action.get('records') == 'listed': record_ids = [r.id for r in self.screen.listed_records] record_paths = self.screen.listed_paths else: record_ids = [r.id for r in self.screen.selected_records] record_paths = self.screen.selected_paths data = { 'model': self.screen.model_name, 'model_context': (self.screen.context_screen.model_name if self.screen.context_screen else None), 'id': record_id, 'ids': record_ids, 'paths': record_paths, } Action.execute(action, data, context=self.screen.local_context) def activate_save(self): self.buttons['save'].props.sensitive = self.screen.modified() def sig_win_close(self, widget): 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.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/uri-list', 0, 0), Gtk.TargetEntry.new('text/plain', 0, 0), ], Gdk.DragAction.MOVE | Gdk.DragAction.COPY) attach_btn.connect('drag_data_received', self.attach_drag_data_received) pos = gtktoolbar.get_item_index(self.buttons['email']) iconstock = { 'print': 'tryton-print', 'action': 'tryton-launch', 'relate': 'tryton-link', 'open': 'tryton-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', 'print', _('Print'), _('Print report')), ): if action_type is not None: tbutton = Gtk.ToggleToolButton() tbutton.set_icon_widget( common.IconFactory.get_image(iconstock.get(special_action), Gtk.IconSize.LARGE_TOOLBAR)) 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, pos) pos += 1 exports = toolbars['exports'] if exports: tbutton = self.buttons['open'] tbutton._can_be_sensitive = True menu = tbutton._menu if menu.get_children(): menu.add(Gtk.SeparatorMenuItem()) for export in exports: menuitem = Gtk.MenuItem(set_underline(export['name'])) menuitem.set_use_underline(True) menuitem.connect('activate', self.do_export, export) menu.add(menuitem) last_item = gtktoolbar.get_nth_item(gtktoolbar.get_n_items() - 1) if not isinstance(last_item, Gtk.SeparatorToolItem): gtktoolbar.insert(Gtk.SeparatorToolItem(), -1) url_button = Gtk.ToggleToolButton() url_button.set_icon_widget( common.IconFactory.get_image('tryton-public', Gtk.IconSize.LARGE_TOOLBAR)) url_button.set_label(_('_Copy URL')) url_button.set_use_underline(True) self.tooltips.set_tip(url_button, _('Copy URL into clipboard')) url_button._menu = url_menu = Gtk.Menu() url_menuitem = Gtk.MenuItem() url_menuitem.connect('activate', self.url_copy) url_menu.add(url_menuitem) url_menu.show_all() url_menu.connect('deactivate', self._popup_menu_hide, url_button) url_button.connect('toggled', self.url_set, url_menuitem) url_button.connect('toggled', self.action_popup) self.buttons['copy_url'] = url_button gtktoolbar.insert(url_button, -1) return gtktoolbar def _create_popup_menu(self, widget, keyword, actions, special_action): menu = Gtk.Menu() menu.connect('deactivate', self._popup_menu_hide, widget) widget.connect('toggled', self._update_popup_menu, menu, keyword) for action in actions: new_action = action.copy() if special_action == 'print': new_action['direct_print'] = True menuitem = Gtk.MenuItem(label=set_underline(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 & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.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_popup_menu(self, tbutton, menu, keyword): for item in menu.get_children(): if getattr(item, '_update_action', False): menu.remove(item) buttons = [ b for b in self.screen.get_buttons() if keyword == b.attrs.get('keyword', 'action') ] if buttons and menu.get_children(): separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for button in buttons: menuitem = Gtk.MenuItem(label=set_underline( button.attrs.get('string', _('Unknown'))), use_underline=True) menuitem.connect('activate', lambda m, attrs: self.screen.button(attrs), button.attrs) menuitem._update_action = True menu.add(menuitem) kw_plugins = [] for plugin in plugins.MODULES: for plugin_spec in plugin.get_plugins(self.model): name, func = plugin_spec[:2] try: plugin_keyword = plugin_spec[2] except IndexError: plugin_keyword = 'action' if keyword != plugin_keyword: continue kw_plugins.append((name, func)) if kw_plugins: separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for name, func in kw_plugins: menuitem = Gtk.MenuItem(label=set_underline(name)) menuitem.set_use_underline(True) menuitem.connect( 'activate', lambda m, func: func({ 'model': self.screen.model_name, 'model_context': (self.screen.context_screen.model_name if self.screen.context_screen else None), 'id': (self.screen.current_record.id if self.screen.current_record else None), 'ids': [r.id for r in self.screen.selected_records], 'paths': self.screen.selected_paths, }), func) menuitem._update_action = True menu.add(menuitem) def url_copy(self, menuitem): url = self.screen.get_url(self.name) for selection in [ Gdk.Atom.intern('PRIMARY', True), Gdk.Atom.intern('CLIPBOARD', True), ]: clipboard = Gtk.Clipboard.get(selection) clipboard.set_text(url, -1) def url_set(self, button, menuitem): url = self.screen.get_url(self.name) size = 80 if len(url) > size: url = url[:size // 2] + '...' + url[-size // 2:] menuitem.set_label(url) 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.refresh_resources(reload=True)) if info == 0: if selection.get_uris(): for uri in selection.get_uris(): # Win32 cut&paste terminates the list with a NULL character if not uri or uri == '\0': continue win_attach.add_uri(uri) else: win_attach.add_uri(selection.get_text())
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'), ('note', 'tryton-note', _('Note(0)'), _('Add a note to the record'), 'sig_note'), ] 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'), (_('_Notes...'), 'tryton-note', 'sig_note', '<tryton>/Form/Notes'), (_('_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, context_model=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, context_model=context_model) 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) self.set_buttons_sensitive() 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) self.screen.signal_connect(self, 'unread-note', self._unread_note) 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 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 = self.screen.current_record self.buttons['attach'].props.sensitive = bool( record.id >= 0 if record else False) def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.update_unread_note(reload=True)) def update_unread_note(self, reload=False): record = self.screen.current_record if record: unread = record.get_unread_note(reload=reload) else: unread = 0 self._unread_note(None, unread) def _unread_note(self, widget, signal_data): label = _('Note(%d)') % signal_data self.buttons['note'].set_label(label) if signal_data: self.buttons['note'].set_stock_id('tryton-note-hi') else: self.buttons['note'].set_stock_id('tryton-note') record = self.screen.current_record if not record or record.id < 0: sensitive = False else: sensitive = True self.buttons['note'].props.sensitive = sensitive def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.message_info(_('You have to select one record.'), gtk.MESSAGE_INFO) 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', [current_record.id], [x[0] for x in fields], context=self.screen.context) except RPCException: return date_format = self.screen.context.get('date_format', '%x') datetime_format = date_format + ' %X.%f' 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, datetime_format) 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) self.set_buttons_sensitive(revision) def set_buttons_sensitive(self, revision=None): if not revision: access = common.MODELACCESS[self.model] for button, access_type in [('new', 'create'), ('save', 'write')]: self.buttons[button].props.sensitive = access[access_type] else: for button in ['new', 'save']: self.buttons[button].props.sensitive = False 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.'), gtk.MESSAGE_ERROR) else: self.message_info(_('Records removed.'), gtk.MESSAGE_INFO) def sig_import(self, widget=None): WinImport(self.model, self.screen.context) def sig_save_as(self, widget=None): export = WinExport(self.model, [r.id for r in self.screen.selected_records], 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 if self.screen.copy(): self.message_info(_('Working now on the duplicated record(s).'), gtk.MESSAGE_INFO) 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.'), gtk.MESSAGE_INFO) return True else: self.message_info(self.screen.invalid_message(), gtk.MESSAGE_ERROR) 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 _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.screen.current_record.id if self.screen.current_record else None), }), 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)
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'), ('note', 'tryton-note', _('Note(0)'), _('Add a note to the record'), 'sig_note'), ] 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'), (_('_Notes...'), 'tryton-note', 'sig_note', '<tryton>/Form/Notes'), (_('_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, context_model=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, context_model=context_model) 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) self.set_buttons_sensitive() 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) self.screen.signal_connect(self, 'unread-note', self._unread_note) 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 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 = self.screen.current_record self.buttons['attach'].props.sensitive = bool( record.id >= 0 if record else False) def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.update_unread_note(reload=True)) def update_unread_note(self, reload=False): record = self.screen.current_record if record: unread = record.get_unread_note(reload=reload) else: unread = 0 self._unread_note(None, unread) def _unread_note(self, widget, signal_data): label = _('Note(%d)') % signal_data self.buttons['note'].set_label(label) if signal_data: self.buttons['note'].set_stock_id('tryton-note-hi') else: self.buttons['note'].set_stock_id('tryton-note') record = self.screen.current_record if not record or record.id < 0: sensitive = False else: sensitive = True self.buttons['note'].props.sensitive = sensitive def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.message_info( _('You have to select one record.'), gtk.MESSAGE_INFO) 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', [current_record.id], [x[0] for x in fields], context=self.screen.context) except RPCException: return date_format = self.screen.context.get('date_format', '%x') datetime_format = date_format + ' %X.%f' 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, datetime_format) 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) self.set_buttons_sensitive(revision) def set_buttons_sensitive(self, revision=None): if not revision: access = common.MODELACCESS[self.model] self.buttons['new'].props.sensitive = access['create'] self.buttons['save'].props.sensitive = ( access['create'] or access['write']) else: for button in ['new', 'save']: self.buttons[button].props.sensitive = False 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.'), gtk.MESSAGE_ERROR) else: self.message_info(_('Records removed.'), gtk.MESSAGE_INFO) def sig_import(self, widget=None): WinImport(self.model, self.screen.context) def sig_save_as(self, widget=None): export = WinExport(self.model, [r.id for r in self.screen.selected_records], 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 if self.screen.copy(): self.message_info(_('Working now on the duplicated record(s).'), gtk.MESSAGE_INFO) 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'] or common.MODELACCESS[self.model]['create']): return if self.screen.save_current(): self.message_info(_('Record saved.'), gtk.MESSAGE_INFO) return True else: self.message_info(self.screen.invalid_message(), gtk.MESSAGE_ERROR) 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 _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) widget.connect('toggled', self._update_popup, menu, special_action) 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_popup(self, tbutton, menu, keyword): assert keyword in ['print','action','relate','email','open'] for item in menu.get_children(): if (getattr(item, '_update_action', False) or isinstance(item, gtk.SeparatorMenuItem)): menu.remove(item) buttons = [button for button in self.screen.get_buttons() if keyword in button.attrs.get('keywords', 'action').split(',')] 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) 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': [r.id for r in self.screen.selected_records], 'id': (self.screen.current_record.id if self.screen.current_record else None), }), 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)