Redesign XML Console

- Use modern GTK Widgets (HeaderBar, ActionBar, Switch)
- Add an option to filter Stream Management stanzas
- Add a generic Option Dialog that we can reuse later
This commit is contained in:
Philipp Hörist 2017-08-10 01:01:38 +02:00
parent e6f1d29471
commit 512e6d2c8e
3 changed files with 354 additions and 1 deletions

View File

@ -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 }

View File

@ -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)

View File

@ -3348,6 +3348,348 @@ class SingleMessageWindow:
self.window.destroy()
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)
headerbar = Gtk.HeaderBar()
headerbar.set_title(_('XML Console'))
headerbar.set_show_close_button(True)
self.set_titlebar(headerbar)
switch = Gtk.Switch()
switch.set_active(True)
switch.connect("notify::active", self.on_enable)
headerbar.pack_start(switch)
headerbar.set_decoration_layout(':minimize,close')
self.enabled = True
self.presence = True
self.message = True
self.iq = True
self.stream = True
self.incoming = True
self.outgoing = True
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()
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)
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.destroy()
def on_row_activated(self, listbox, row):
text = row.get_child().get_text()
input_text = None
if text == 'Presence':
input_text = (
'<presence>\n'
'<show></show>\n'
'<status></status>\n'
'<priority></priority>\n'
'</presence>')
elif text == 'Message':
input_text = (
'<message to="" type="">\n'
'<body></body>\n'
'</message>')
elif text == 'Iq':
input_text = (
'<iq to="" type="">\n'
'<query xmlns=""></query>\n'
'</iq>')
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_enable(self, switch, param):
self.enabled = switch.get_active()
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:
return
self.print_stanza(obj.stanza_str, 'incoming')
def _nec_stanza_sent(self, obj):
if obj.conn.name != self.account:
return
self.print_stanza(obj.stanza_str, 'outgoing')
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.scrolled)
buffer_ = self.textview.get_buffer()
end_iter = buffer_.get_end_iter()
type_ = kind
if stanza.startswith('<presence'):
type_ = 'presence'
elif stanza.startswith('<message'):
type_ = 'message'
elif stanza.startswith('<iq'):
type_ = 'iq'
elif stanza.startswith('<r') or stanza.startswith('<a'):
type_ = 'stream'
stanza = '<!-- {kind} {time} -->\n{stanza}\n\n'.format(
kind=kind.capitalize(),
time=time.strftime('%c'),
stanza=stanza.replace('><', '>\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'),
'remove': _('remove')}