class NextStage(Page): def __init__(self): Page.__init__(self) self.set_valign(Gtk.Align.FILL) self.complete = False self.title = _('Change Password') self._current_form = None self.show_all() def _on_is_valid(self, _widget, is_valid): self.complete = is_valid self.get_toplevel().update_page_complete() def set_form(self, form): if self._current_form is not None: self.remove(self._current_form) self._current_form.destroy() self._current_form = DataFormWidget(form) self._current_form.connect('is-valid', self._on_is_valid) self._current_form.validate() self.pack_start(self._current_form, True, True, 0) self._current_form.show_all() def get_submit_form(self): return self._current_form.get_submit_form()
class Form(Gtk.Box): type_ = Gtk.AssistantPageType.CUSTOM title = _('Search') complete = True def __init__(self): super().__init__(orientation=Gtk.Orientation.VERTICAL) self.set_spacing(18) self._dataform_widget = None self.show_all() @property def search_form(self): return self._dataform_widget.get_submit_form() def clear(self): self._show_form(None) def process_search_form(self, form): self._show_form(form) def _show_form(self, form): if self._dataform_widget is not None: self.remove(self._dataform_widget) self._dataform_widget.destroy() if form is None: return options = {'form-width': 350} form = dataforms.extend_form(node=form) self._dataform_widget = DataFormWidget(form, options=options) self._dataform_widget.connect('is-valid', self._on_is_valid) self._dataform_widget.validate() self._dataform_widget.show_all() self.add(self._dataform_widget) def _on_is_valid(self, _widget, is_valid): self.get_toplevel().set_stage_complete(is_valid) def get_submit_form(self): return self._dataform_widget.get_submit_form()
class FormDialog(Gtk.ApplicationWindow): def __init__(self, data): transient = app.app.get_active_window() Gtk.ApplicationWindow.__init__(self, title="Data Form Test") self.set_transient_for(transient) self.set_default_size(600, 400) self._account = data['account'] self._jid = data['jid'] self._form_widget = DataFormWidget( extend_form(node=nbxmpp.Node(node=data['form']))) box = Gtk.Box(orientation='vertical', spacing=12) box.add(self._form_widget) button = Gtk.Button(label=data['submit-text']) button.connect('clicked', self._on_send_clicked) button.set_halign(Gtk.Align.END) box.add(button) self.add(box) self.show_all() def _on_send_clicked(self, _button): form = self._form_widget.get_submit_form() app.nec.push_outgoing_event( MessageOutgoingEvent(None, account=self._account, jid=self._jid, form_node=form, is_loggable=False)) control = find_control(self._account, self._jid) if control is None: return control.add_status_message('Form has successfully been sent') self.destroy()
class SingleMessageWindow(Gtk.ApplicationWindow): """ SingleMessageWindow can send or show a received singled message depending on action argument which can be 'send' or 'receive' """ def __init__(self, account, to='', action='', from_whom='', subject='', message='', resource='', session=None, form_node=None): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_title(_('Send Single Message')) self.set_name('SendSingleMessageWindow') self.account = account self.action = action self.subject = subject self.message = message self.to = to self.from_whom = from_whom self.resource = resource self.session = session self._ui = get_builder('single_message_window.ui') self.message_tv_buffer = self._ui.message_textview.get_buffer() self.conversation_textview = ConversationTextview( account, used_in_history_window=True) self.conversation_textview.tv.show() self.conversation_textview.tv.set_left_margin(6) self.conversation_textview.tv.set_right_margin(6) self.conversation_textview.tv.set_top_margin(6) self.conversation_textview.tv.set_bottom_margin(6) self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer( ) self._ui.conversation_scrolledwindow.add(self.conversation_textview.tv) self.form_widget = None parent_box = self._ui.conversation_scrolledwindow.get_parent() if form_node: self.form_widget = DataFormWidget(form_node) self.form_widget.show_all() self._ui.conversation_scrolledwindow.hide() self._ui.message_label_received.hide() parent_box.add(self.form_widget) parent_box.child_set_property(self.form_widget, 'top-attach', 2) parent_box.child_set_property(self.form_widget, 'left-attach', 0) parent_box.child_set_property(self.form_widget, 'width', 2) self.action = 'form' self.message_tv_buffer.connect('changed', self.update_char_counter) if isinstance(to, list): jid = ', '.join([i[0].get_full_jid() for i in to]) self._ui.to_entry.set_text(jid) else: self._ui.to_entry.set_text(to) if app.config.get('use_speller') and app.is_installed( 'GSPELL') and action == 'send': lang = app.config.get('speller_language') gspell_lang = Gspell.language_lookup(lang) if gspell_lang is None: AspellDictError(lang) else: spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer( self._ui.message_textview.get_buffer()) spell_buffer.set_spell_checker(Gspell.Checker.new(gspell_lang)) spell_view = Gspell.TextView.get_from_gtk_text_view( self._ui.message_textview) spell_view.set_inline_spell_checking(True) spell_view.set_enable_language_menu(True) self.prepare_widgets_for(self.action) # set_text(None) raises TypeError exception if self.subject is None: self.subject = _('(No subject)') self._ui.subject_entry.set_text(self.subject) self._ui.subject_from_entry_label.set_text(self.subject) if to == '': liststore = get_completion_liststore(self._ui.to_entry) self.completion_dict = helpers.get_contact_dict_for_account( account) keys = sorted(self.completion_dict.keys()) for jid in keys: contact = self.completion_dict[jid] status_icon = get_icon_name(contact.show) liststore.append((status_icon, jid)) else: self.completion_dict = {} self._ui.to_entry.connect('changed', self.on_to_entry_changed) self._ui.connect_signals(self) # get window position and size from config resize_window(self._ui.single_message_window, app.config.get('single-msg-width'), app.config.get('single-msg-height')) move_window(self._ui.single_message_window, app.config.get('single-msg-x-position'), app.config.get('single-msg-y-position')) self._ui.single_message_window.show_all() def on_single_message_window_destroy(self, widget): c = app.contacts.get_contact_with_highest_priority( self.account, self.from_whom) if not c: # Groupchat is maybe already destroyed return if c.is_groupchat and self.from_whom not in \ app.interface.minimized_controls[self.account] and self.action == \ 'receive' and app.events.get_nb_roster_events(self.account, self.from_whom, types=['chat', 'normal']) == 0: app.interface.roster.remove_groupchat(self.from_whom, self.account) def set_cursor_to_end(self): end_iter = self.message_tv_buffer.get_end_iter() self.message_tv_buffer.place_cursor(end_iter) def save_pos(self): # save the window size and position x, y = self._ui.single_message_window.get_position() app.config.set('single-msg-x-position', x) app.config.set('single-msg-y-position', y) width, height = self._ui.single_message_window.get_size() app.config.set('single-msg-width', width) app.config.set('single-msg-height', height) def on_single_message_window_delete_event(self, window, ev): self.save_pos() def on_show_contact_info_button_clicked(self, widget): """ Ask for vCard """ entry = self._ui.to_entry.get_text().strip() keys = sorted(self.completion_dict.keys()) for key in keys: contact = self.completion_dict[key] if entry in key: entry = contact.jid break if entry in app.interface.instances[self.account]['infos']: app.interface.instances[ self.account]['infos'][entry].window.present() else: contact = app.contacts.create_contact(jid=entry, account=self.account) app.interface.instances[self.account]['infos'][entry] = \ vcard.VcardWindow(contact, self.account) # Remove xmpp page app.interface.instances[self.account]['infos'][entry].xml.\ get_object('information_notebook').remove_page(0) def on_to_entry_changed(self, widget): entry = self._ui.to_entry.get_text() is_empty = bool(not entry == '' and not ',' in entry) self._ui.show_contact_info_button.set_sensitive(is_empty) def prepare_widgets_for(self, action): if len(app.connections) > 1: if action == 'send': title = _('Single Message using account %s') % self.account else: title = _('Single Message in account %s') % self.account else: title = _('Single Message') if action == 'send': # prepare UI for Sending title = _('Send %s') % title self._ui.send_button.show() self._ui.send_and_close_button.show() self._ui.reply_button.hide() self._ui.close_button.hide() self._ui.send_grid.show() self._ui.received_grid.hide() if self.message: # we come from a reply? self._ui.show_contact_info_button.set_sensitive(True) self._ui.message_textview.grab_focus() self.message_tv_buffer.set_text(self.message) GLib.idle_add(self.set_cursor_to_end) else: # we write a new message (not from reply) if self.to: # do we already have jid? self._ui.subject_entry.grab_focus() elif action == 'receive': # prepare UI for Receiving title = _('Received %s') % title self._ui.reply_button.show() self._ui.close_button.show() self._ui.send_button.hide() self._ui.send_and_close_button.hide() self._ui.reply_button.grab_focus() self._ui.received_grid.show() self._ui.send_grid.hide() if self.message: self.conversation_textview.print_real_text(self.message) fjid = self.from_whom if self.resource: fjid += '/' + self.resource # Full jid of sender (with resource) self._ui.from_entry_label.set_text(fjid) elif action == 'form': # prepare UI for Receiving title = self.form_widget.title title = _('Form: %s') % title self._ui.send_button.hide() self._ui.send_and_close_button.hide() self._ui.reply_button.show() self._ui.close_button.show() self._ui.send_grid.hide() self._ui.received_grid.show() fjid = self.from_whom if self.resource: fjid += '/' + self.resource # Full jid of sender (with resource) self._ui.from_entry_label.set_text(fjid) self._ui.single_message_window.set_title(title) def on_close_button_clicked(self, widget): self.save_pos() self._ui.single_message_window.destroy() def update_char_counter(self, widget): characters_no = self.message_tv_buffer.get_char_count() self._ui.count_chars_label.set_text( _('Characters typed: %s') % str(characters_no)) def send_single_message(self): if not app.account_is_connected(self.account): # if offline or connecting ErrorDialog( _('Connection not available'), _('Please make sure you are connected with "%s".') % self.account) return True if isinstance(self.to, list): sender_list = [] for i in self.to: if i[0].resource: sender_list.append(i[0].jid + '/' + i[0].resource) else: sender_list.append(i[0].jid) else: sender_list = [ j.strip() for j in self._ui.to_entry.get_text().split(',') ] subject = self._ui.subject_entry.get_text() begin, end = self.message_tv_buffer.get_bounds() message = self.message_tv_buffer.get_text(begin, end, True) if self.form_widget: form_node = self.form_widget.get_submit_form() else: form_node = None recipient_list = [] for to_whom_jid in sender_list: if to_whom_jid in self.completion_dict: to_whom_jid = self.completion_dict[to_whom_jid].jid try: to_whom_jid = helpers.parse_jid(to_whom_jid) except helpers.InvalidFormat: ErrorDialog( _('Invalid XMPP Address'), _('It is not possible to send a message to %s, this ' 'XMPP Address is not valid.') % to_whom_jid) return True if '/announce/' in to_whom_jid: con = app.connections[self.account] con.get_module('Announce').set_announce( to_whom_jid, subject, message) continue recipient_list.append(to_whom_jid) app.nec.push_outgoing_event( MessageOutgoingEvent(None, account=self.account, jid=recipient_list, message=message, type_='normal', subject=subject, form_node=form_node)) self._ui.subject_entry.set_text('') # we sent ok, clear the subject self.message_tv_buffer.set_text('') # we sent ok, clear the textview def on_send_button_clicked(self, widget): self.send_single_message() def on_reply_button_clicked(self, widget): # we create a new blank window to send and we preset RE: and to jid self.subject = _('RE: %s') % self.subject self.message = _('%s wrote:\n') % self.from_whom + self.message # add > at the begining of each line self.message = self.message.replace('\n', '\n> ') + '\n\n' self._ui.single_message_window.destroy() SingleMessageWindow(self.account, to=self.from_whom, action='send', from_whom=self.from_whom, subject=self.subject, message=self.message, session=self.session) def on_send_and_close_button_clicked(self, widget): if self.send_single_message(): return self.save_pos() self._ui.single_message_window.destroy() def on_single_message_window_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: # ESCAPE self.save_pos() self._ui.single_message_window.destroy()
class GroupchatConfig(Gtk.ApplicationWindow): def __init__(self, account, jid, own_affiliation, form=None): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_position(Gtk.WindowPosition.CENTER) self.set_show_menubar(False) self.set_title(_('Group Chat Configuration')) self._destroyed = False self.account = account self.jid = jid self._own_affiliation = own_affiliation self._ui = get_builder('groupchat_config.ui') self.add(self._ui.grid) # Activate Add button only for Admins and Owners if self._own_affiliation in ('admin', 'owner'): self._ui.add_button.set_sensitive(True) self._ui.add_button.set_tooltip_text('') visible = muc_caps_cache.supports(jid, nbxmpp.NS_REGISTER) self._ui.reserved_name_column.set_visible(visible) self._ui.info_button.set_sensitive(False) self._form = form self._affiliations = {} self._new_affiliations = {} con = app.connections[self.account] for affiliation in ('owner', 'admin', 'member', 'outcast'): con.get_module('MUC').get_affiliation( self.jid, affiliation, callback=self._on_affiliations_received, user_data=affiliation) if form is not None: self._ui.stack.set_visible_child_name('config') self._data_form_widget = DataFormWidget(form) self._data_form_widget.connect('is-valid', self._on_is_valid) self._data_form_widget.validate() self._ui.config_grid.add(self._data_form_widget) else: self._ui.stack.get_child_by_name('config').hide() self._ui.stack.get_child_by_name('config').set_no_show_all(True) self._ui.stack.set_visible_child_name('affiliation') self._ui.connect_signals(self) self.connect('delete-event', self._cancel) self.connect('destroy', self._on_destroy) self.connect('key-press-event', self._on_key_press) self.show_all() self._ui.stack.notify('visible-child-name') def _on_is_valid(self, _widget, is_valid): self._ui.ok_button.set_sensitive(is_valid) def _get_current_treeview(self): page_name = self._ui.stack.get_visible_child_name() return getattr(self._ui, '%s_treeview' % page_name) def _on_add(self, *args): page_name = self._ui.stack.get_visible_child_name() if page_name == 'outcast': affiliation_edit, jid_edit = self._allowed_to_edit('outcast') text = None affiliation = 'outcast' else: affiliation_edit, jid_edit = self._allowed_to_edit('member') text = _('Member') affiliation = 'member' treeview = self._get_current_treeview() iter_ = treeview.get_model().append([None, None, None, affiliation, text, affiliation_edit, jid_edit]) # Scroll to added row path = treeview.get_model().get_path(iter_) treeview.scroll_to_cell(path, None, False, 0, 0) treeview.get_selection().unselect_all() treeview.get_selection().select_path(path) def _on_remove(self, *args): treeview = self._get_current_treeview() model, paths = treeview.get_selection().get_selected_rows() owner_count = self._get_owner_count() references = [] for path in paths: if model[path][MUCUser.AFFILIATION] == 'owner': if owner_count < 2: # There must be at least one owner ErrorDialog(_('Error'), _('A Group Chat needs at least one Owner')) return owner_count -= 1 references.append(Gtk.TreeRowReference.new(model, path)) for ref in references: iter_ = model.get_iter(ref.get_path()) model.remove(iter_) def _on_jid_edited(self, _renderer, path, new_text): old_text = self._ui.affiliation_store[path][MUCUser.JID] if new_text == old_text: return if self._jid_exists(new_text): self._raise_error() return self._ui.affiliation_store[path][MUCUser.JID] = new_text def _on_outcast_jid_edited(self, _renderer, path, new_text): old_text = self._ui.outcast_store[path][MUCUser.JID] if new_text == old_text: return if self._jid_exists(new_text): self._raise_error() return self._ui.outcast_store[path][MUCUser.JID] = new_text self._ui.outcast_store[path][MUCUser.AFFILIATION] = 'outcast' def _on_nick_edited(self, _renderer, path, new_text): self._ui.affiliation_store[path][MUCUser.NICK] = new_text def _on_reason_edited(self, _renderer, path, new_text): self._ui.outcast_store[path][MUCUser.REASON] = new_text def _on_affiliation_changed(self, cell_renderer_combo, path_string, new_iter): combo_store = cell_renderer_combo.get_property('model') affiliation_text = combo_store.get_value(new_iter, 0) affiliation = combo_store.get_value(new_iter, 1) store = self._ui.affiliation_treeview.get_model() store[path_string][MUCUser.AFFILIATION] = affiliation store[path_string][MUCUser.AFFILIATION_TEXT] = affiliation_text def _on_selection_changed(self, tree_selection): sensitive = bool(tree_selection.count_selected_rows()) selected_affiliations = self._get_selected_affiliations(tree_selection) self._set_remove_button_state(sensitive, selected_affiliations) def _jid_exists(self, jid): stores = [self._ui.affiliation_store, self._ui.outcast_store] for store in stores: for row in store: if row[MUCUser.JID] == jid: return True return False @staticmethod def _get_selected_affiliations(tree_selection): model, paths = tree_selection.get_selected_rows() selected_affiliations = set() for path in paths: selected_affiliations.add(model[path][MUCUser.AFFILIATION]) return selected_affiliations def _on_switch_page(self, stack, _pspec): page_name = stack.get_visible_child_name() self._set_button_box_state(page_name) if page_name == 'config': return treeview = getattr(self._ui, '%s_treeview' % page_name) sensitive = bool(treeview.get_selection().count_selected_rows()) selected_affiliations = self._get_selected_affiliations( treeview.get_selection()) self._set_remove_button_state(sensitive, selected_affiliations) def _set_button_box_state(self, page_name): affiliation = self._own_affiliation in ('admin', 'owner') page = page_name != 'config' self._ui.treeview_buttonbox.set_visible(affiliation and page) self._ui.info_button.set_sensitive(page_name == 'outcast') def _set_remove_button_state(self, sensitive, selected_affiliations): if self._own_affiliation not in ('admin', 'owner'): self._ui.remove_button.set_sensitive(False) return self._ui.remove_button.set_tooltip_text('') if not sensitive: self._ui.remove_button.set_sensitive(False) return if self._own_affiliation == 'owner': self._ui.remove_button.set_sensitive(True) return if set(['owner', 'admin']).intersection(selected_affiliations): self._ui.remove_button.set_sensitive(False) self._ui.remove_button.set_tooltip_text( _('You are not allowed to modify the affiliation ' 'of Admins and Owners')) return self._ui.remove_button.set_sensitive(True) def _get_owner_count(self): owner_count = 0 for row in self._ui.affiliation_store: if row[MUCUser.AFFILIATION] == 'owner': owner_count += 1 return owner_count def _allowed_to_edit(self, affiliation): if self._own_affiliation == 'owner': return True, True if self._own_affiliation == 'admin': if affiliation in ('admin', 'owner'): return False, False return False, True return False, False def _on_ok(self, *args): if self._own_affiliation in ('admin', 'owner'): self._set_affiliations() if self._form is not None and self._own_affiliation == 'owner': form = self._data_form_widget.get_submit_form() con = app.connections[self.account] con.get_module('MUC').set_config(self.jid, form) self.destroy() def _get_diff(self): stores = [self._ui.affiliation_store, self._ui.outcast_store] self._new_affiliations = {} for store in stores: for row in store: if not row[MUCUser.JID]: # Ignore empty JID field continue attr = 'nick' if row[MUCUser.AFFILIATION] == 'outcast': attr = 'reason' self._new_affiliations[row[MUCUser.JID]] = { 'affiliation': row[MUCUser.AFFILIATION], attr: row[MUCUser.NICK_OR_REASON]} old_jids = set(self._affiliations.keys()) new_jids = set(self._new_affiliations.keys()) remove = old_jids - new_jids add = new_jids - old_jids modified = new_jids - remove - add return add, remove, modified def _on_key_press(self, _widget, event): if event.keyval == Gdk.KEY_Escape: self._on_cancel() def _on_cancel(self, *args): self._cancel() self.destroy() def _cancel(self, *args): if self._form and self._own_affiliation == 'owner': con = app.connections[self.account] con.get_module('MUC').cancel_config(self.jid) def _on_destroy(self, *args): self._destroyed = True def _set_affiliations(self): add, remove, modified = self._get_diff() diff_dict = {} for jid in add: diff_dict[jid] = self._new_affiliations[jid] for jid in remove: diff_dict[jid] = {'affiliation': 'none'} for jid in modified: if self._new_affiliations[jid] == self._affiliations[jid]: # Not modified continue diff_dict[jid] = self._new_affiliations[jid] if self._new_affiliations[jid]['affiliation'] == 'outcast': # New affiliation is outcast, check if the reason changed. # In case the affiliation was 'admin', 'owner' or 'member' # before, there is no reason. new_reason = self._new_affiliations[jid]['reason'] old_reason = self._affiliations[jid].get('reason') if new_reason == old_reason: diff_dict[jid].pop('reason', None) else: # New affiliation is not outcast, check if the nick has changed. # In case the affiliation was 'outcast' there is no nick. new_nick = self._new_affiliations[jid]['nick'] old_nick = self._affiliations[jid].get('nick') if new_nick == old_nick: diff_dict[jid].pop('nick', None) if not diff_dict: # No changes were made return con = app.connections[self.account] con.get_module('MUC').set_affiliation(self.jid, diff_dict) @ensure_not_destroyed def _on_affiliations_received(self, result, affiliation): if is_error_result(result): log.info('Error while requesting %s affiliations: %s', affiliation, result) return if affiliation == 'outcast': self._ui.stack.get_child_by_name('outcast').show() for jid, attrs in result.users.items(): affiliation_edit, jid_edit = self._allowed_to_edit(affiliation) if affiliation == 'outcast': reason = attrs.get('reason') self._ui.outcast_store.append( [str(jid), reason, None, affiliation, None, affiliation_edit, jid_edit]) self._affiliations[jid] = {'affiliation': affiliation, 'reason': reason} else: nick = attrs.get('nick') role = attrs.get('role') self._ui.affiliation_store.append( [str(jid), nick, role, affiliation, _(affiliation.capitalize()), affiliation_edit, jid_edit]) self._affiliations[jid] = {'affiliation': affiliation, 'nick': nick} if role is not None: self._ui.role_column.set_visible(True) @staticmethod def _raise_error(): ErrorDialog(_('Error'), _('An entry with this XMPP Address already exists'))
class ServiceRegistration(Gtk.Assistant): def __init__(self, account, agent): Gtk.Assistant.__init__(self) self._con = app.connections[account] self._agent = agent self._account = account self._data_form_widget = None self._is_form = None self.set_application(app.app) self.set_resizable(True) self.set_position(Gtk.WindowPosition.CENTER) self.set_default_size(600, 400) self.get_style_context().add_class('dialog-margin') self._add_page(RequestPage()) self._add_page(FormPage()) self._add_page(SendingPage()) self._add_page(SuccessfulPage()) self._add_page(ErrorPage()) self.connect('prepare', self._on_page_change) self.connect('cancel', self._on_cancel) self.connect('close', self._on_cancel) self._remove_sidebar() self.show_all() def _add_page(self, page): self.append_page(page) self.set_page_type(page, page.type_) self.set_page_title(page, page.title) self.set_page_complete(page, page.complete) def _remove_sidebar(self): main_box = self.get_children()[0] sidebar = main_box.get_children()[0] main_box.remove(sidebar) def _on_page_change(self, assistant, page): if self.get_current_page() == Page.REQUEST: self._con.get_module('Register').get_register_form( self._agent, self._on_get_success, self._on_error) elif self.get_current_page() == Page.SENDING: self._register() self.commit() pass def _on_get_success(self, form, is_form): log.info('Show Form page') self._is_form = is_form if is_form: dataform = dataforms.ExtendForm(node=form) self._data_form_widget = DataFormWidget(dataform) else: from gajim import config self._data_form_widget = config.FakeDataForm(form) page = self.get_nth_page(Page.FORM) page.pack_start(self._data_form_widget, True, True, 0) self._data_form_widget.show_all() self.set_current_page(Page.FORM) def _on_error(self, error_text): log.info('Show Error page') page = self.get_nth_page(Page.ERROR) page.set_text(error_text) self.set_current_page(Page.ERROR) def _on_cancel(self, widget): self.destroy() def _register(self): log.info('Show Sending page') if self._is_form: form = self._data_form_widget.get_submit_form() else: form = self._data_form_widget.get_infos() if 'instructions' in form: del form['instructions'] if 'registered' in form: del form['registered'] self._con.get_module('Register').register_agent( self._agent, form, self._is_form, self._on_register_success, self._on_error) def _on_register_success(self): log.info('Show Success page') self.set_current_page(Page.SUCCESS)