class DictMultiSelectionEntry(DictEntry): expand = False def create_widget(self): widget = Gtk.ScrolledWindow() widget.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) widget.set_shadow_type(Gtk.ShadowType.ETCHED_IN) widget.set_size_request(100, 100) model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) self.tree = TreeViewControl() self.tree.set_model(model) self.tree.set_search_column(1) self.tree.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out()) self.tree.set_headers_visible(False) selection = self.tree.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.connect('changed', self._changed) widget.add(self.tree) self.selection = self.definition['selection'] if self.definition.get('sort', True): self.selection.sort(key=operator.itemgetter(1)) for value, name in self.selection: name = str(name) model.append((value, name)) name_column = Gtk.TreeViewColumn() name_cell = Gtk.CellRendererText() name_column.pack_start(name_cell, expand=True) name_column.add_attribute(name_cell, 'text', 1) self.tree.append_column(name_column) return widget def get_value(self): model, paths = self.tree.get_selection().get_selected_rows() return [model[path][0] for path in paths] def set_value(self, value): value2path = {v: idx for idx, (v, _) in enumerate(self.selection)} selection = self.tree.get_selection() selection.handler_block_by_func(self._changed) try: selection.unselect_all() for v in value: if v in value2path: selection.select_path(value2path[v]) finally: selection.handler_unblock_by_func(self._changed) def _changed(self, selection): GLib.idle_add(self.parent_widget._focus_out) def set_readonly(self, readonly): selection = self.tree.get_selection() selection.set_select_function(lambda *a: not readonly)
class MultiSelection(Widget, SelectionMixin): expand = True def __init__(self, view, attrs): super(MultiSelection, self).__init__(view, attrs) if int(attrs.get('yexpand', self.expand)): self.widget = gtk.ScrolledWindow() self.widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.widget.set_shadow_type(gtk.SHADOW_ETCHED_IN) else: self.widget = gtk.VBox() self.widget.get_accessible().set_name(attrs.get('string', '')) self.model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) self.tree = TreeViewControl() self.tree.set_model(self.model) self.tree.set_search_column(1) self.tree.connect('focus-out-event', lambda *a: self._focus_out()) self.tree.set_headers_visible(False) selection = self.tree.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect('changed', self.changed) self.widget.add(self.tree) name_column = gtk.TreeViewColumn() name_cell = gtk.CellRendererText() name_column.pack_start(name_cell) name_column.add_attribute(name_cell, 'text', 1) self.tree.append_column(name_column) self.nullable_widget = False self.init_selection() self.id2path = {} def _color_widget(self): return self.tree def _readonly_set(self, readonly): super(MultiSelection, self)._readonly_set(readonly) set_widget_style(self.tree, not readonly) selection = self.tree.get_selection() selection.set_select_function(lambda *a: not readonly) @property def modified(self): if self.record and self.field: group = set(r.id for r in self.field.get_client(self.record)) value = set(self.get_value()) return value != group return False def changed(self, selection): def focus_out(): if self.widget.props.window: self._focus_out() # Must be deferred because it triggers a display of the form gobject.idle_add(focus_out) def get_value(self): model, paths = self.tree.get_selection().get_selected_rows() return [model[path][0] for path in paths] def set_value(self, record, field): field.set_client(record, self.get_value()) def display(self, record, field): selection = self.tree.get_selection() selection.handler_block_by_func(self.changed) try: # Remove select_function to allow update, # it will be set back in the super call selection.set_select_function(lambda *a: True) self.update_selection(record, field) self.model.clear() if field is None: return id2path = {} for idx, (value, name) in enumerate(self.selection): self.model.append((value, name)) id2path[value] = idx selection.unselect_all() group = field.get_client(record) for element in group: if (element not in group.record_removed and element not in group.record_deleted and element.id in id2path): selection.select_path(id2path[element.id]) super(MultiSelection, self).display(record, field) finally: selection.handler_unblock_by_func(self.changed)
class Email(NoModal): def __init__(self, name, record, prints, template=None): super().__init__() self.record = record self.dialog = Gtk.Dialog(transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.dialog) self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dialog.set_icon(TRYTON_ICON) self.dialog.set_default_size(*self.default_size()) self.dialog.connect('response', self.response) self.dialog.set_title(_('E-mail %s') % name) grid = Gtk.Grid(column_spacing=3, row_spacing=3, border_width=3) self.dialog.vbox.pack_start(grid, expand=True, fill=True, padding=0) label = Gtk.Label(set_underline(_("To:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 0, 1, 1) self.to = EmailEntry(hexpand=True, activates_default=True) widget_class(self.to, 'required', True) label.set_mnemonic_widget(self.to) grid.attach(self.to, 1, 0, 1, 1) label = Gtk.Label(set_underline(_("Cc:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 1, 1, 1) self.cc = EmailEntry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.cc) grid.attach(self.cc, 1, 1, 1, 1) label = Gtk.Label(set_underline(_("Bcc:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 2, 1, 1) self.bcc = EmailEntry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.bcc) grid.attach(self.bcc, 1, 2, 1, 1) label = Gtk.Label(set_underline(_("Subject:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 3, 1, 1) self.subject = Gtk.Entry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.subject) grid.attach(self.subject, 1, 3, 1, 1) self.body = Gtk.TextView() body_frame = Gtk.Frame() label = Gtk.Label(set_underline(_("Body")), use_underline=True, halign=Gtk.Align.END) label.set_mnemonic_widget(self.body) body_frame.set_label_widget(label) grid.attach(body_frame, 0, 4, 2, 1) body_box = Gtk.VBox(hexpand=True, vexpand=True) body_frame.add(body_box) register_format(self.body) body_toolbar = add_toolbar(self.body) body_box.pack_start(body_toolbar, expand=False, fill=True, padding=0) body_box.pack_start(self.body, expand=True, fill=True, padding=0) if GtkSpell and CONFIG['client.spellcheck']: checker = GtkSpell.Checker() checker.attach(self.body) language = os.environ.get('LANGUAGE', 'en') try: checker.set_language(language) except Exception: logger.error('Could not set spell checker for "%s"', language) checker.detach() attachments_box = Gtk.HBox() grid.attach(attachments_box, 0, 5, 2, 1) print_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) print_frame.set_label(_("Reports")) attachments_box.pack_start(print_frame, expand=True, fill=True, padding=0) print_box = Gtk.VBox() print_frame.add(print_box) print_flowbox = Gtk.FlowBox(selection_mode=Gtk.SelectionMode.NONE) print_box.pack_start(print_flowbox, expand=False, fill=False, padding=0) self.print_actions = {} for print_ in prints: print_check = Gtk.CheckButton.new_with_mnemonic( set_underline(print_['name'])) self.print_actions[print_['id']] = print_check print_flowbox.add(print_check) attachment_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) attachment_frame.set_label(_("Attachments")) attachments_box.pack_start(attachment_frame, expand=True, fill=True, padding=0) try: attachments = RPCExecute('model', 'ir.attachment', 'search_read', [ ('resource', '=', '%s,%s' % (record.model_name, record.id)), [ 'OR', ('data', '!=', None), ('file_id', '!=', None), ], ], 0, None, None, ['rec_name'], context=record.get_context()) except RPCException: logger.error('Could not fetch attachment for "%s"', record) attachments = [] scrolledwindow = Gtk.ScrolledWindow() if len(attachments) > 2: scrolledwindow.set_size_request(-1, 100) attachment_frame.add(scrolledwindow) scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.attachments = TreeViewControl() self.attachments.set_headers_visible(False) scrolledwindow.add(self.attachments) self.attachments.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) self.attachments.append_column( Gtk.TreeViewColumn("Name", Gtk.CellRendererText(), text=0)) model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT) for attachment in attachments: model.append((attachment['rec_name'], attachment['id'])) self.attachments.set_model(model) self.attachments.set_search_column(0) file_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) file_frame.set_label(_("Files")) attachments_box.pack_start(file_frame, expand=True, fill=True, padding=0) self.files = Gtk.VBox(spacing=6) file_frame.add(self.files) self._add_file_button() button_cancel = self.dialog.add_button(set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) button_cancel.set_image( IconFactory.get_image('tryton-cancel', Gtk.IconSize.BUTTON)) button_send = self.dialog.add_button(set_underline(_("Send")), Gtk.ResponseType.OK) button_send.set_image( IconFactory.get_image('tryton-send', Gtk.IconSize.BUTTON)) self.dialog.set_default_response(Gtk.ResponseType.OK) self._fill_with(template) self.dialog.show_all() self.register() def _add_file_button(self): tooltips = Tooltips() box = Gtk.HBox(spacing=3) self.files.pack_start(box, expand=False, fill=True, padding=0) file_ = Gtk.FileChooserButton(title=_("Select File")) box.pack_start(file_, expand=True, fill=True, padding=0) button = Gtk.Button() button.set_image( IconFactory.get_image('tryton-remove', Gtk.IconSize.BUTTON)) tooltips.set_tip(button, _("Remove File")) button.set_sensitive(False) box.pack_start(button, expand=False, fill=True, padding=0) box.show_all() file_.connect('file-set', self._file_set, button) button.connect('clicked', self._file_remove) def _file_set(self, file_, button): button.set_sensitive(True) self._add_file_button() def _file_remove(self, button): self.files.remove(button.get_parent()) def get_files(self): for box in self.files: file_ = list(box)[0] filename = file_.get_filename() if not filename: continue with open(filename, 'rb') as fp: data = fp.read() name = os.path.basename(filename) yield (name, data) def get_attachments(self): model, paths = self.attachments.get_selection().get_selected_rows() return [model[path][1] for path in paths] def _fill_with(self, template=None): try: if template: values = RPCExecute('model', 'ir.email.template', 'get', template, self.record.id) else: values = RPCExecute('model', 'ir.email.template', 'get_default', self.record.model_name, self.record.id) except RPCException: return self.to.set_text(', '.join(values.get('to', []))) self.cc.set_text(', '.join(values.get('cc', []))) self.bcc.set_text(', '.join(values.get('bcc', []))) self.subject.set_text(values.get('subject', '')) set_content(self.body, values.get('body', '')) print_ids = values.get('reports', []) for print_id, print_check in self.print_actions.items(): print_check.set_active(print_id in print_ids) def validate(self): valid = True if not self.subject.get_text(): valid = False widget_class(self.subject, 'invalid', True) self.subject.grab_focus() else: widget_class(self.subject, 'invalid', False) if not self.to.get_text(): valid = False widget_class(self.to, 'invalid', True) self.to.grab_focus() else: widget_class(self.to, 'invalid', False) return valid def response(self, dialog, response): if response == Gtk.ResponseType.OK: if not self.validate(): return to = self.to.get_text() cc = self.cc.get_text() bcc = self.bcc.get_text() subject = self.subject.get_text() body = get_content(self.body) files = list(self.get_files()) reports = [ id_ for id_, check in self.print_actions.items() if check.get_active() ] attachments = self.get_attachments() try: RPCExecute('model', 'ir.email', 'send', to, cc, bcc, subject, body, files, [self.record.model_name, self.record.id], reports, attachments) except RPCException: return self.destroy() def destroy(self): super().destroy() self.dialog.destroy()
class MultiSelection(Widget, SelectionMixin): expand = True def __init__(self, view, attrs): super(MultiSelection, self).__init__(view, attrs) self.widget = gtk.ScrolledWindow() self.widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.widget.set_shadow_type(gtk.SHADOW_ETCHED_IN) self.widget.get_accessible().set_name(attrs.get('string', '')) self.model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) self.tree = TreeViewControl() self.tree.set_model(self.model) self.tree.set_search_column(1) self.tree.connect('focus-out-event', lambda *a: self._focus_out()) self.tree.set_headers_visible(False) selection = self.tree.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect('changed', self.changed) self.widget.add(self.tree) name_column = gtk.TreeViewColumn() name_cell = gtk.CellRendererText() name_column.pack_start(name_cell) name_column.add_attribute(name_cell, 'text', 1) self.tree.append_column(name_column) self.nullable_widget = False self.init_selection() self.id2path = {} @property def modified(self): if self.record and self.field: group = set(r.id for r in self.field.get_client(self.record)) value = set(self.get_value()) return value != group return False def changed(self, selection): def focus_out(): if self.widget.props.window: self._focus_out() # Must be deferred because it triggers a display of the form gobject.idle_add(focus_out) def get_value(self): model, paths = self.tree.get_selection().get_selected_rows() return [model[path][0] for path in paths] def set_value(self, record, field): field.set_client(record, self.get_value()) def display(self, record, field): selection = self.tree.get_selection() selection.handler_block_by_func(self.changed) try: self.update_selection(record, field) super(MultiSelection, self).display(record, field) self.model.clear() if field is None: return id2path = {} for idx, (value, name) in enumerate(self.selection): self.model.append((value, name)) id2path[value] = idx selection.unselect_all() group = field.get_client(record) for element in group: if (element not in group.record_removed and element not in group.record_deleted and element.id in id2path): selection.select_path(id2path[element.id]) finally: selection.handler_unblock_by_func(self.changed)