diff --git a/data/style/gajim.css b/data/style/gajim.css index afa75bbf0..1b241ad6d 100644 --- a/data/style/gajim.css +++ b/data/style/gajim.css @@ -29,3 +29,14 @@ popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; } #ServerInfoGrid > list > row { padding: 10px 20px 10px 10px; } #ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; } #ServerInfoGrid > list > row.activatable:active { box-shadow: none; } + +/* Generic Options Dialog */ +#OptionsDialog list > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } +#OptionsDialog list > row:last-child { border-bottom: 0px} +#OptionsDialog list > row { padding: 10px; } +#OptionsDialog list > row.activatable:active { box-shadow: none; } + +/* Generic Popover Menu with Buttons */ +.PopoverButtonListbox { margin-top: 10px; margin-bottom: 10px; } +.PopoverButtonListbox > row { padding: 10px 20px 10px 20px; } +.PopoverButtonListbox row.activatable:active { box-shadow: none; background-color: @theme_selected_bg_color } diff --git a/gajim/app_actions.py b/gajim/app_actions.py index f3b980f18..5e2c947c9 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -175,7 +175,7 @@ class AppActions(): def on_xml_console(self, action, param): account = param.get_string() if 'xml_console' in gajim.interface.instances[account]: - gajim.interface.instances[account]['xml_console'].window.present() + gajim.interface.instances[account]['xml_console'].present() else: gajim.interface.instances[account]['xml_console'] = \ dialogs.XMLConsoleWindow(account) diff --git a/gajim/dialogs.py b/gajim/dialogs.py index aed265cb5..f3e1d5e2c 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -3347,139 +3347,256 @@ class SingleMessageWindow: self.save_pos() self.window.destroy() -class XMLConsoleWindow: + +class XMLConsoleWindow(Gtk.Window): def __init__(self, account): + Gtk.Window.__init__(self) self.account = account + self.set_default_size(600, 600) + self.connect("destroy", self.on_destroy) - self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui') - self.window = self.xml.get_object('xml_console_window') - self.input_textview = self.xml.get_object('input_textview') - self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview') - self.input_tv_buffer = self.input_textview.get_buffer() - self.parent = self.stanzas_log_textview.get_parent() - buffer_ = self.stanzas_log_textview.get_buffer() + headerbar = Gtk.HeaderBar() + headerbar.set_title(_('XML Console')) + headerbar.set_show_close_button(True) + self.set_titlebar(headerbar) - self.tagIn = buffer_.create_tag('incoming') - color = gajim.config.get('inmsgcolor') - self.tagIn.set_property('foreground', color) - self.tagInPresence = buffer_.create_tag('incoming_presence') - self.tagInPresence.set_property('foreground', color) - self.tagInMessage = buffer_.create_tag('incoming_message') - self.tagInMessage.set_property('foreground', color) - self.tagInIq = buffer_.create_tag('incoming_iq') - self.tagInIq.set_property('foreground', color) + switch = Gtk.Switch() + switch.set_active(True) + switch.connect("notify::active", self.on_enable) + headerbar.pack_start(switch) - self.tagOut = buffer_.create_tag('outgoing') - color = gajim.config.get('outmsgcolor') - self.tagOut.set_property('foreground', color) - self.tagOutPresence = buffer_.create_tag('outgoing_presence') - self.tagOutPresence.set_property('foreground', color) - self.tagOutMessage = buffer_.create_tag('outgoing_message') - self.tagOutMessage.set_property('foreground', color) - self.tagOutIq = buffer_.create_tag('outgoing_iq') - self.tagOutIq.set_property('foreground', color) - buffer_.create_tag('') # Default tag + headerbar.set_decoration_layout(':minimize,close') self.enabled = True - self.xml.get_object('enable_checkbutton').set_active(True) + self.presence = True + self.message = True + self.iq = True + self.stream = True + self.incoming = True + self.outgoing = True - if len(gajim.connections) > 1: - title = _('XML Console for %s') % self.account - else: - title = _('XML Console') + self.textview = Gtk.TextView() + self.textview.set_size_request(-1, 400) + self.textview.set_editable(False) + self.textview.set_cursor_visible(False) + self.textview.set_hexpand(True) + + self.scrolled = Gtk.ScrolledWindow() + self.scrolled.add(self.textview) + + self.input = Gtk.TextView() + self.input.show() + self.input.set_vexpand(True) + + self.scrolled_input = Gtk.ScrolledWindow() + self.scrolled_input.set_size_request(-1, 150) + self.scrolled_input.add(self.input) + self.scrolled_input.set_no_show_all(True) + + self.paned = Gtk.VPaned() + self.paned.set_vexpand(True) + self.paned.pack1(self.scrolled, True, False) + self.paned.pack2(self.scrolled_input, False, False) + self.paned.set_position(self.paned.get_property('max-position')) + + self.actionbar = Gtk.ActionBar() + + icon = gtkgui_helpers.get_icon_pixmap('edit-clear-all-symbolic') + image = Gtk.Image() + image.set_from_pixbuf(icon) + button = Gtk.Button() + button.set_tooltip_text(_('Clear')) + button.connect('clicked', self.on_clear) + button.set_image(image) + self.actionbar.pack_start(button) + + icon = gtkgui_helpers.get_icon_pixmap('applications-system-symbolic') + image = Gtk.Image() + image.set_from_pixbuf(icon) + button = Gtk.Button() + button.set_tooltip_text(_('Filter')) + button.connect('clicked', self.on_filter_options) + button.set_image(image) + self.actionbar.pack_start(button) + + icon = gtkgui_helpers.get_icon_pixmap('document-edit-symbolic') + image = Gtk.Image() + image.set_from_pixbuf(icon) + button = Gtk.ToggleButton() + button.set_tooltip_text(_('XML Input')) + button.set_active(False) + button.connect('toggled', self.on_input) + button.set_image(image) + self.actionbar.pack_start(button) + + icon = gtkgui_helpers.get_icon_pixmap('emblem-ok-symbolic') + image = Gtk.Image() + image.set_from_pixbuf(icon) + button = Gtk.Button() + button.set_tooltip_text(_('Send')) + button.connect('clicked', self.on_send) + button.set_image(image) + self.actionbar.pack_end(button) + + listbox = Gtk.ListBox() + context = listbox.get_style_context() + context.add_class('PopoverButtonListbox') + + label = Gtk.Label(label='Message') + listbox.add(label) + + label = Gtk.Label(label='Presence') + listbox.add(label) + + label = Gtk.Label(label='Iq') + listbox.add(label) + listbox.show_all() + listbox.set_selection_mode(Gtk.SelectionMode.NONE) + listbox.connect('row-activated', self.on_row_activated) + + popover = Gtk.Popover() + popover.add(listbox) + + self.menubutton = Gtk.MenuButton() + self.menubutton.set_direction(Gtk.ArrowType.UP) + self.menubutton.set_popover(popover) + self.menubutton.set_tooltip_text(_('Presets')) + self.menubutton.set_no_show_all(True) + + self.actionbar.pack_start(self.menubutton) + + box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + box.add(self.paned) + box.add(self.actionbar) + + self.add(box) + + self.connect('key_press_event', self.on_key_press_event) + + self.create_tags() + self.show_all() - self.window.set_title(title) - self.window.show_all() gajim.ged.register_event_handler('stanza-received', ged.GUI1, self._nec_stanza_received) gajim.ged.register_event_handler('stanza-sent', ged.GUI1, self._nec_stanza_sent) - self.xml.connect_signals(self) + def create_tags(self): + buffer_ = self.textview.get_buffer() + in_color = gajim.config.get('inmsgcolor') + out_color = gajim.config.get('outmsgcolor') + + tags = ['presence', 'message', 'stream', 'iq'] + + tag = buffer_.create_tag('incoming') + tag.set_property('foreground', in_color) + tag = buffer_.create_tag('outgoing') + tag.set_property('foreground', out_color) + + for tag_name in tags: + buffer_.create_tag(tag_name) def on_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: - self.window.destroy() + self.destroy() - def on_xml_console_window_destroy(self, widget): + def on_row_activated(self, listbox, row): + text = row.get_child().get_text() + input_text = None + if text == 'Presence': + input_text = ( + '\n' + '\n' + '\n' + '\n' + '') + elif text == 'Message': + input_text = ( + '\n' + '\n' + '') + elif text == 'Iq': + input_text = ( + '\n' + '\n' + '') + + if input_text is not None: + buffer_ = self.input.get_buffer() + buffer_.set_text(input_text) + self.input.grab_focus() + + def on_send(self, *args): + if gajim.connections[self.account].connected <= 1: + # if offline or connecting + ErrorDialog(_('Connection not available'), + _('Please make sure you are connected with "%s".') % \ + self.account) + return + buffer_ = self.input.get_buffer() + begin_iter, end_iter = buffer_.get_bounds() + stanza = buffer_.get_text(begin_iter, end_iter, True) + if stanza: + gajim.connections[self.account].send_stanza(stanza) + buffer_.set_text('') + + def on_input(self, button, *args): + if button.get_active(): + self.paned.get_child2().show() + self.menubutton.show() + self.input.grab_focus() + else: + self.paned.get_child2().hide() + self.menubutton.hide() + + def on_filter_options(self, *args): + options = [ + SwitchOption('Presence', self.presence, + self.on_option, + 'presence'), + SwitchOption('Message', self.message, + self.on_option, + 'message'), + SwitchOption('Iq', self.iq, + self.on_option, + 'iq'), + SwitchOption('Stream\nManagement', self.stream, + self.on_option, + 'stream'), + SwitchOption('In', self.incoming, + self.on_option, + 'incoming'), + SwitchOption('Out', self.outgoing, + self.on_option, + 'outgoing')] + + OptionsDialog(self, 'Filter', options) + + def on_clear(self, *args): + buffer_ = self.textview.get_buffer().set_text('') + + def on_destroy(self, *args): del gajim.interface.instances[self.account]['xml_console'] gajim.ged.remove_event_handler('stanza-received', ged.GUI1, self._nec_stanza_received) gajim.ged.remove_event_handler('stanza-sent', ged.GUI1, self._nec_stanza_sent) - def on_clear_button_clicked(self, widget): - buffer_ = self.stanzas_log_textview.get_buffer() - buffer_.set_text('') + def on_enable(self, switch, param): + self.enabled = switch.get_active() - def on_enable_checkbutton_toggled(self, widget): - self.enabled = widget.get_active() - - def on_in_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagIn.set_property('invisible', active) - self.tagInPresence.set_property('invisible', active) - self.tagInMessage.set_property('invisible', active) - self.tagInIq.set_property('invisible', active) - - def on_presence_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInPresence.set_property('invisible', active) - self.tagOutPresence.set_property('invisible', active) - - def on_out_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagOut.set_property('invisible', active) - self.tagOutPresence.set_property('invisible', active) - self.tagOutMessage.set_property('invisible', active) - self.tagOutIq.set_property('invisible', active) - - def on_message_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInMessage.set_property('invisible', active) - self.tagOutMessage.set_property('invisible', active) - - def on_iq_stanza_checkbutton_toggled(self, widget): - active = widget.get_active() - self.tagInIq.set_property('invisible', active) - self.tagOutIq.set_property('invisible', active) - - def print_stanza(self, stanza, kind): - # kind must be 'incoming' or 'outgoing' - if not self.enabled: - return - if not stanza: - return - - at_the_end = gtkgui_helpers.at_the_end(self.parent) - - buffer = self.stanzas_log_textview.get_buffer() - end_iter = buffer.get_end_iter() - - type_ = '' - if stanza[1:9] == 'presence': - type_ = 'presence' - elif stanza[1:8] == 'message': - type_ = 'message' - elif stanza[1:3] == 'iq': - type_ = 'iq' - - if type_: - type_ = kind + '_' + type_ - else: - type_ = kind # 'incoming' or 'outgoing' - - if kind == 'incoming': - buffer.insert_with_tags_by_name(end_iter, '\n' % \ - time.strftime('%c'), type_) - elif kind == 'outgoing': - buffer.insert_with_tags_by_name(end_iter, '\n' % \ - time.strftime('%c'), type_) - end_iter = buffer.get_end_iter() - buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') \ - + '\n\n', type_) - if at_the_end: - GLib.idle_add(gtkgui_helpers.scroll_to_end, self.parent) + def on_option(self, switch, param, *user_data): + kind = user_data[0] + setattr(self, kind, switch.get_active()) + value = not switch.get_active() + table = self.textview.get_buffer().get_tag_table() + tag = table.lookup(kind) + if kind in ('incoming', 'outgoing'): + if value: + tag.set_priority(table.get_size() - 1) + else: + tag.set_priority(0) + tag.set_property('invisible', value) def _nec_stanza_received(self, obj): if obj.conn.name != self.account: @@ -3491,36 +3608,87 @@ class XMLConsoleWindow: return self.print_stanza(obj.stanza_str, 'outgoing') - def on_send_button_clicked(self, widget): - if gajim.connections[self.account].connected <= 1: - # if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % \ - self.account) + def print_stanza(self, stanza, kind): + # kind must be 'incoming' or 'outgoing' + if not self.enabled: + return + if not stanza: return - begin_iter, end_iter = self.input_tv_buffer.get_bounds() - stanza = self.input_tv_buffer.get_text(begin_iter, end_iter, True) - if stanza: - gajim.connections[self.account].send_stanza(stanza) - self.input_tv_buffer.set_text('') # we sent ok, clear the textview - def on_presence_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '' - '') + at_the_end = gtkgui_helpers.at_the_end(self.scrolled) - def on_iq_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + buffer_ = self.textview.get_buffer() + end_iter = buffer_.get_end_iter() - def on_message_button_clicked(self, widget): - self.input_tv_buffer.set_text( - '') + type_ = kind + if stanza.startswith('<', '>\n<')) + buffer_.insert_with_tags_by_name(end_iter, stanza, type_, kind) + + if at_the_end: + GLib.idle_add(gtkgui_helpers.scroll_to_end, self.scrolled) + + +class OptionsDialog(Gtk.Dialog): + def __init__(self, parent, title, options): + Gtk.Dialog.__init__(self, title, parent, + Gtk.DialogFlags.DESTROY_WITH_PARENT) + self.set_name('OptionsDialog') + self.set_resizable(False) + self.set_default_size(250, -1) + + self.remove(self.get_content_area()) + + listbox = Gtk.ListBox() + listbox.set_hexpand(True) + listbox.set_selection_mode(Gtk.SelectionMode.NONE) + + for option in options: + listbox.add(option) + + self.add(listbox) + + self.show_all() + listbox.connect('row-activated', self.on_row_activated) + + def on_row_activated(self, listbox, row): + row.get_child().set_switch_state() + + +class SwitchOption(Gtk.Grid): + def __init__(self, label, state, callback, *user_data): + Gtk.Grid.__init__(self) + self.set_column_spacing(6) + + label = Gtk.Label(label=label) + label.set_hexpand(True) + label.set_halign(Gtk.Align.START) + + self.switch = Gtk.Switch() + self.switch.set_active(state) + self.switch.connect("notify::active", callback, *user_data) + self.switch.set_hexpand(True) + self.switch.set_halign(Gtk.Align.END) + + self.add(label) + self.add(self.switch) + self.show_all() + + def set_switch_state(self): + state = self.switch.get_active() + self.switch.set_active(not state) + #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'),