New style for ChatControl

- Move ActionBar into HeaderMenu
- Make Design of ChatControl look cleaner
- Hide the Roster in Groupchats per default
- Add Button to hide/show Roster in Groupchats
- Move Groupchat topic into popover
- Display Avatars on the right side of the ChatControl and status on the
left
- Add a default Avatar for contacts that have none
This commit is contained in:
Philipp Hörist 2017-10-03 13:08:06 +02:00
parent 398ad0eed8
commit 970d6f8c3f
24 changed files with 994 additions and 1266 deletions

View File

@ -30,7 +30,7 @@
import os import os
import time import time
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gio
from gi.repository import GdkPixbuf from gi.repository import GdkPixbuf
from gi.repository import Pango from gi.repository import Pango
from gi.repository import GLib from gi.repository import GLib
@ -95,60 +95,10 @@ class ChatControl(ChatControlBase):
self.last_recv_message_marks = None self.last_recv_message_marks = None
self.last_message_timestamp = None self.last_message_timestamp = None
# for muc use:
# widget = self.xml.get_object('muc_window_actions_button')
self.actions_button = self.xml.get_object('message_window_actions_button')
id_ = self.actions_button.connect('clicked',
self.on_actions_button_clicked)
self.handlers[id_] = self.actions_button
self._formattings_button = self.xml.get_object('formattings_button') self._formattings_button = self.xml.get_object('formattings_button')
self.emoticons_button = self.xml.get_object('emoticons_button') self.emoticons_button = self.xml.get_object('emoticons_button')
self.toggle_emoticons() self.toggle_emoticons()
self._add_to_roster_button = self.xml.get_object(
'add_to_roster_button')
id_ = self._add_to_roster_button.connect('clicked',
self._on_add_to_roster_menuitem_activate)
self.handlers[id_] = self._add_to_roster_button
self._audio_button = self.xml.get_object('audio_togglebutton')
id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
self.handlers[id_] = self._audio_button
# add a special img
gtkgui_helpers.add_image_to_button(self._audio_button,
'gajim-mic_inactive')
self._video_button = self.xml.get_object('video_togglebutton')
id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
self.handlers[id_] = self._video_button
# add a special img
gtkgui_helpers.add_image_to_button(self._video_button,
'gajim-cam_inactive')
self._send_file_button = self.xml.get_object('send_file_button')
# add a special img for send file button
pixbuf = gtkgui_helpers.get_icon_pixmap('document-send', quiet=True)
img = Gtk.Image.new_from_pixbuf(pixbuf)
self._send_file_button.set_image(img)
id_ = self._send_file_button.connect('clicked',
self._on_send_file_menuitem_activate)
self.handlers[id_] = self._send_file_button
self._convert_to_gc_button = self.xml.get_object(
'convert_to_gc_button')
id_ = self._convert_to_gc_button.connect('clicked',
self._on_convert_to_gc_menuitem_activate)
self.handlers[id_] = self._convert_to_gc_button
self._contact_information_button = self.xml.get_object(
'contact_information_button')
id_ = self._contact_information_button.connect('clicked',
self._on_contact_information_menuitem_activate)
self.handlers[id_] = self._contact_information_button
compact_view = app.config.get('compact_view')
self.chat_buttons_set_visible(compact_view)
self.widget_set_visible(self.xml.get_object('banner_eventbox'), self.widget_set_visible(self.xml.get_object('banner_eventbox'),
app.config.get('hide_chat_banner')) app.config.get('hide_chat_banner'))
@ -161,10 +111,9 @@ class ChatControl(ChatControlBase):
# Add lock image to show chat encryption # Add lock image to show chat encryption
self.lock_image = self.xml.get_object('lock_image') self.lock_image = self.xml.get_object('lock_image')
# Convert to GC icon # Menu for the HeaderBar
img = self.xml.get_object('convert_to_gc_button_image') self.control_menu = gui_menu_builder.get_singlechat_menu(
img.set_from_pixbuf(gtkgui_helpers.load_icon( self.control_id)
'muc_active').get_pixbuf())
self._audio_banner_image = self.xml.get_object('audio_banner_image') self._audio_banner_image = self.xml.get_object('audio_banner_image')
self._video_banner_image = self.xml.get_object('video_banner_image') self._video_banner_image = self.xml.get_object('video_banner_image')
@ -270,7 +219,7 @@ class ChatControl(ChatControlBase):
# Enable encryption if needed # Enable encryption if needed
self.no_autonegotiation = False self.no_autonegotiation = False
self.add_actions()
self.update_ui() self.update_ui()
self.set_lock_image() self.set_lock_image()
@ -298,6 +247,107 @@ class ChatControl(ChatControlBase):
# PluginSystem: adding GUI extension point for this ChatControl # PluginSystem: adding GUI extension point for this ChatControl
# instance object # instance object
app.plugin_manager.gui_extension_point('chat_control', self) app.plugin_manager.gui_extension_point('chat_control', self)
self.update_actions()
def add_actions(self):
actions = [
('send-file-', self._on_send_file),
('invite-contacts-', self._on_invite_contacts),
('add-to-roster-', self._on_add_to_roster),
('information-', self._on_information),
]
for action in actions:
action_name, func = action
act = Gio.SimpleAction.new(action_name + self.control_id, None)
act.connect("activate", func)
self.parent_win.window.add_action(act)
act = Gio.SimpleAction.new_stateful(
'toggle-audio-' + self.control_id, None,
GLib.Variant.new_boolean(False))
act.connect('change-state', self._on_audio)
self.parent_win.window.add_action(act)
act = Gio.SimpleAction.new_stateful(
'toggle-video-' + self.control_id,
None, GLib.Variant.new_boolean(False))
act.connect('change-state', self._on_video)
self.parent_win.window.add_action(act)
def update_actions(self):
win = self.parent_win.window
online = app.account_is_connected(self.account)
# Add to roster
if not isinstance(self.contact, GC_Contact) \
and _('Not in Roster') in self.contact.groups and \
app.connections[self.account].roster_supported and online:
win.lookup_action(
'add-to-roster-' + self.control_id).set_enabled(True)
else:
win.lookup_action(
'add-to-roster-' + self.control_id).set_enabled(False)
# Audio
win.lookup_action('toggle-audio-' + self.control_id).set_enabled(
online and self.audio_available)
# Video
win.lookup_action('toggle-video-' + self.control_id).set_enabled(
online and self.video_available)
# Send file
if ((self.contact.supports(NS_FILE) or \
self.contact.supports(NS_JINGLE_FILE_TRANSFER_5)) and \
(self.type_id == 'chat' or self.gc_contact.resource)) and \
self.contact.show != 'offline' and online:
win.lookup_action('send-file-' + self.control_id).set_enabled(
True)
else:
win.lookup_action('send-file-' + self.control_id).set_enabled(
False)
# Convert to GC
if app.config.get_per('accounts', self.account, 'is_zeroconf'):
win.lookup_action(
'invite-contacts-' + self.control_id).set_enabled(False)
else:
if self.contact.supports(NS_MUC) and online:
win.lookup_action(
'invite-contacts-' + self.control_id).set_enabled(True)
else:
win.lookup_action(
'invite-contacts-' + self.control_id).set_enabled(False)
# Information
win.lookup_action(
'information-' + self.control_id).set_enabled(online)
def _on_send_file(self, action, param):
super()._on_send_file()
def _on_add_to_roster(self, action, param):
dialogs.AddNewContactWindow(self.account, self.contact.jid)
def _on_information(self, action, param):
app.interface.roster.on_info(None, self.contact, self.account)
def _on_invite_contacts(self, action, param):
"""
User wants to invite some friends to chat
"""
dialogs.TransformChatToMUC(self.account, [self.contact.jid])
def _on_audio(self, action, param):
action.set_state(param)
state = param.get_boolean()
self.on_jingle_button_toggled(state, 'audio')
def _on_video(self, action, param):
action.set_state(param)
state = param.get_boolean()
self.on_jingle_button_toggled(state, 'video')
def subscribe_events(self): def subscribe_events(self):
""" """
@ -329,14 +379,6 @@ class ChatControl(ChatControlBase):
self._formattings_button.set_tooltip_text(_('This contact does ' self._formattings_button.set_tooltip_text(_('This contact does '
'not support HTML')) 'not support HTML'))
# Add to roster
if not isinstance(self.contact, GC_Contact) \
and _('Not in Roster') in self.contact.groups and \
app.connections[self.account].roster_supported:
self._add_to_roster_button.show()
else:
self._add_to_roster_button.hide()
# Jingle detection # Jingle detection
if self.contact.supports(NS_JINGLE_ICE_UDP) and \ if self.contact.supports(NS_JINGLE_ICE_UDP) and \
app.HAVE_FARSTREAM and self.contact.resource: app.HAVE_FARSTREAM and self.contact.resource:
@ -348,63 +390,6 @@ class ChatControl(ChatControlBase):
self.video_available = False self.video_available = False
self.audio_available = False self.audio_available = False
# Audio buttons
self._audio_button.set_sensitive(self.audio_available)
# Video buttons
self._video_button.set_sensitive(self.video_available)
# change tooltip text for audio and video buttons if farstream is
# not installed
audio_tooltip_text = _('Toggle audio session') + '\n'
video_tooltip_text = _('Toggle video session') + '\n'
if not app.HAVE_FARSTREAM:
ext_text = _('Feature not available, see Help->Features')
self._audio_button.set_tooltip_text(audio_tooltip_text + ext_text)
self._video_button.set_tooltip_text(video_tooltip_text + ext_text)
elif not self.audio_available :
ext_text =_('Feature not supported by remote client')
self._audio_button.set_tooltip_text(audio_tooltip_text + ext_text)
self._video_button.set_tooltip_text(video_tooltip_text + ext_text)
else:
self._audio_button.set_tooltip_text(audio_tooltip_text[:-1])
self._video_button.set_tooltip_text(video_tooltip_text[:-1])
# Send file
if ((self.contact.supports(NS_FILE) or \
self.contact.supports(NS_JINGLE_FILE_TRANSFER_5)) and \
(self.type_id == 'chat' or self.gc_contact.resource)) and \
self.contact.show != 'offline':
self._send_file_button.set_sensitive(True)
self._send_file_button.set_tooltip_text(_('Send files'))
else:
self._send_file_button.set_sensitive(False)
if not (self.contact.supports(NS_FILE) or self.contact.supports(
NS_JINGLE_FILE_TRANSFER_5)):
self._send_file_button.set_tooltip_text(_(
"This contact does not support file transfer."))
else:
self._send_file_button.set_tooltip_text(
_("You need to know the real JID of the contact to send "
"them a file."))
# Convert to GC
if app.config.get_per('accounts', self.account, 'is_zeroconf'):
self._convert_to_gc_button.set_no_show_all(True)
self._convert_to_gc_button.hide()
else:
if self.contact.supports(NS_MUC):
self._convert_to_gc_button.set_sensitive(True)
else:
self._convert_to_gc_button.set_sensitive(False)
# Information
if app.account_is_disconnected(self.account):
self._contact_information_button.set_sensitive(False)
else:
self._contact_information_button.set_sensitive(True)
def update_all_pep_types(self): def update_all_pep_types(self):
for pep_type in self._pep_images: for pep_type in self._pep_images:
self.update_pep(pep_type) self.update_pep(pep_type)
@ -751,12 +736,12 @@ class ChatControl(ChatControlBase):
getattr(self, '_' + jingle_type + '_button').set_active(False) getattr(self, '_' + jingle_type + '_button').set_active(False)
getattr(self, 'update_' + jingle_type)() getattr(self, 'update_' + jingle_type)()
def on_jingle_button_toggled(self, widget, jingle_type): def on_jingle_button_toggled(self, state, jingle_type):
img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type], img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type],
{True: 'active', False: 'inactive'}[widget.get_active()]) {True: 'active', False: 'inactive'}[state])
path_to_img = gtkgui_helpers.get_icon_path(img_name) path_to_img = gtkgui_helpers.get_icon_path(img_name)
if widget.get_active(): if state:
if getattr(self, jingle_type + '_state') == \ if getattr(self, jingle_type + '_state') == \
self.JINGLE_STATE_NULL: self.JINGLE_STATE_NULL:
if jingle_type == 'video': if jingle_type == 'video':
@ -795,12 +780,6 @@ class ChatControl(ChatControlBase):
img = getattr(self, '_' + jingle_type + '_button').get_property('image') img = getattr(self, '_' + jingle_type + '_button').get_property('image')
img.set_from_file(path_to_img) img.set_from_file(path_to_img)
def on_audio_button_toggled(self, widget):
self.on_jingle_button_toggled(widget, 'audio')
def on_video_button_toggled(self, widget):
self.on_jingle_button_toggled(widget, 'video')
def set_lock_image(self): def set_lock_image(self):
loggable = self.session and self.session.is_loggable() loggable = self.session and self.session.is_loggable()
@ -832,10 +811,6 @@ class ChatControl(ChatControlBase):
self.authentication_button.set_tooltip_text(tooltip) self.authentication_button.set_tooltip_text(tooltip)
self.widget_set_visible(self.authentication_button, not visible) self.widget_set_visible(self.authentication_button, not visible)
context = self.msg_scrolledwindow.get_style_context() context = self.msg_scrolledwindow.get_style_context()
if visible:
context.add_class('authentication')
else:
context.remove_class('authentication')
self.lock_image.set_sensitive(visible) self.lock_image.set_sensitive(visible)
def _on_authentication_button_clicked(self, widget): def _on_authentication_button_clicked(self, widget):
@ -1272,10 +1247,18 @@ class ChatControl(ChatControlBase):
if not app.config.get('show_avatar_in_chat'): if not app.config.get('show_avatar_in_chat'):
return return
pixbuf = app.contacts.get_avatar( if self.TYPE_ID == message_control.TYPE_CHAT:
self.account, self.contact.jid, AvatarSize.CHAT) pixbuf = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT)
else:
pixbuf = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT)
image = self.xml.get_object('avatar_image') image = self.xml.get_object('avatar_image')
image.set_from_pixbuf(pixbuf) if pixbuf is None:
image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else:
image.set_from_pixbuf(pixbuf)
def _nec_update_avatar(self, obj): def _nec_update_avatar(self, obj):
if obj.account != self.account: if obj.account != self.account:
@ -1446,15 +1429,6 @@ class ChatControl(ChatControlBase):
elif typ == 'pm': elif typ == 'pm':
control.remove_contact(nick) control.remove_contact(nick)
def _on_send_file_menuitem_activate(self, widget):
self._on_send_file()
def _on_add_to_roster_menuitem_activate(self, widget):
dialogs.AddNewContactWindow(self.account, self.contact.jid)
def _on_contact_information_menuitem_activate(self, widget):
app.interface.roster.on_info(widget, self.contact, self.account)
def _on_convert_to_gc_menuitem_activate(self, widget): def _on_convert_to_gc_menuitem_activate(self, widget):
""" """
User wants to invite some friends to chat User wants to invite some friends to chat
@ -1522,21 +1496,11 @@ class ChatControl(ChatControlBase):
if contact: if contact:
self.contact = contact self.contact = contact
self.draw_banner() self.draw_banner()
self.update_actions()
def got_disconnected(self): def got_disconnected(self):
# Add to roster
self._add_to_roster_button.hide()
# Audio button
self._audio_button.set_sensitive(False)
# Video button
self._video_button.set_sensitive(False)
# Send file button
self._send_file_button.set_tooltip_text('')
self._send_file_button.set_sensitive(False)
# Convert to GC button
self._convert_to_gc_button.set_sensitive(False)
ChatControlBase.got_disconnected(self) ChatControlBase.got_disconnected(self)
self.update_actions()
def update_status_display(self, name, uf_show, status): def update_status_display(self, name, uf_show, status):
""" """

View File

@ -44,7 +44,6 @@ from gajim import notify
import re import re
from gajim import emoticons from gajim import emoticons
from gajim.scrolled_window import ScrolledWindow
from gajim.common import events from gajim.common import events
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
@ -256,28 +255,21 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
MessageControl.__init__(self, type_id, parent_win, widget_name, MessageControl.__init__(self, type_id, parent_win, widget_name,
contact, acct, resource=resource) contact, acct, resource=resource)
widget = self.xml.get_object('history_button') if self.TYPE_ID != message_control.TYPE_GC:
# set document-open-recent icon for history button # Create banner and connect signals
if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'): widget = self.xml.get_object('banner_eventbox')
img = self.xml.get_object('history_image') id_ = widget.connect('button-press-event',
img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU) self._on_banner_eventbox_button_press_event)
self.handlers[id_] = widget
id_ = widget.connect('clicked', self._on_history_menuitem_activate)
self.handlers[id_] = widget
# Create banner and connect signals
widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id_] = widget
self.urlfinder = re.compile( self.urlfinder = re.compile(
r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
self.banner_status_label = self.xml.get_object('banner_label') self.banner_status_label = self.xml.get_object('banner_label')
id_ = self.banner_status_label.connect('populate_popup', if self.banner_status_label is not None:
self.on_banner_label_populate_popup) id_ = self.banner_status_label.connect('populate_popup',
self.handlers[id_] = self.banner_status_label self.on_banner_label_populate_popup)
self.handlers[id_] = self.banner_status_label
# Init DND # Init DND
self.TARGET_TYPE_URI_LIST = 80 self.TARGET_TYPE_URI_LIST = 80
@ -332,9 +324,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.msg_scrolledwindow = ScrolledWindow() self.msg_scrolledwindow = ScrolledWindow()
self.msg_scrolledwindow.set_max_content_height(100) self.msg_scrolledwindow.set_max_content_height(100)
self.msg_scrolledwindow.set_min_content_height(23) self.msg_scrolledwindow.set_propagate_natural_height(True)
self.msg_scrolledwindow.get_style_context().add_class('scrolledtextview') self.msg_scrolledwindow.get_style_context().add_class('scrolledtextview')
self.msg_scrolledwindow.set_property('shadow_type', Gtk.ShadowType.IN) self.msg_scrolledwindow.set_property('shadow_type', Gtk.ShadowType.IN)
self.msg_scrolledwindow.add(self.msg_textview) self.msg_scrolledwindow.add(self.msg_textview)
@ -417,6 +408,28 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
action.connect("change-state", self.change_encryption) action.connect("change-state", self.change_encryption)
self.parent_win.window.add_action(action) self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'browse-history-%s' % self.control_id, GLib.VariantType.new('s'))
action.connect('activate', self._on_history)
self.parent_win.window.add_action(action)
# Actions
def _on_history(self, action, param):
"""
When history menuitem is pressed: call history window
"""
jid = param.get_string()
if jid == 'none':
jid = self.contact.jid
if 'logs' in app.interface.instances:
app.interface.instances['logs'].window.present()
app.interface.instances['logs'].open_history(jid, self.account)
else:
app.interface.instances['logs'] = \
history_window.HistoryWindow(jid, self.account)
def change_encryption(self, action, param): def change_encryption(self, action, param):
encryption = param.get_string() encryption = param.get_string()
if encryption == 'disabled': if encryption == 'disabled':
@ -549,6 +562,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
menu.show_all() menu.show_all()
def on_quote(self, widget, text): def on_quote(self, widget, text):
self.msg_textview.remove_placeholder()
text = '>' + text.replace('\n', '\n>') + '\n' text = '>' + text.replace('\n', '\n>') + '\n'
message_buffer = self.msg_textview.get_buffer() message_buffer = self.msg_textview.get_buffer()
message_buffer.insert_at_cursor(text) message_buffer.insert_at_cursor(text)
@ -1283,13 +1297,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
else: else:
widget.show_all() widget.show_all()
def chat_buttons_set_visible(self, state):
"""
Toggle chat buttons
"""
MessageControl.chat_buttons_set_visible(self, state)
self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
def got_connected(self): def got_connected(self):
self.msg_textview.set_sensitive(True) self.msg_textview.set_sensitive(True)
self.msg_textview.set_editable(True) self.msg_textview.set_editable(True)
@ -1302,3 +1309,19 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.no_autonegotiation = False self.no_autonegotiation = False
self.update_toolbar() self.update_toolbar()
class ScrolledWindow(Gtk.ScrolledWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def do_get_preferred_height(self):
min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self)
child = self.get_child()
if natural_height and self.get_max_content_height() > -1 and child:
_, child_nat_height = child.get_preferred_height()
if natural_height > child_nat_height:
if child_nat_height < 26:
return 26, 26
return min_height, natural_height

View File

@ -41,12 +41,6 @@ class StandardCommonCommands(CommandContainer):
AUTOMATIC = True AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command
@doc(_("Hide the chat buttons"))
def compact(self):
new_status = not self.hide_chat_buttons
self.chat_buttons_set_visible(new_status)
@command(overlap=True) @command(overlap=True)
@doc(_("Show help on a given command or a list of available commands if -a is given")) @doc(_("Show help on a given command or a list of available commands if -a is given"))
def help(self, command=None, all=False): def help(self, command=None, all=False):

View File

@ -264,7 +264,6 @@ class Config:
'show_roster_on_startup':[opt_str, 'always', _('Show roster on startup.\n\'always\' - Always show roster.\n\'never\' - Never show roster.\n\'last_state\' - Restore the last state roster.')], 'show_roster_on_startup':[opt_str, 'always', _('Show roster on startup.\n\'always\' - Always show roster.\n\'never\' - Never show roster.\n\'last_state\' - Restore the last state roster.')],
'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')], 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')], 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')], 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],

View File

@ -2342,6 +2342,13 @@ class Connection(CommonConnection, ConnectionHandlers):
if rule['type'] == 'group': if rule['type'] == 'group':
roster.draw_group(rule['value'], self.name) roster.draw_group(rule['value'], self.name)
def bookmarks_available(self):
if self.private_storage_supported:
return True
if self.pubsub_publish_options_supported:
return True
return False
def _request_bookmarks_xml(self): def _request_bookmarks_xml(self):
if not app.account_is_connected(self.name): if not app.account_is_connected(self.name):
return return

View File

@ -30,8 +30,8 @@ class OptionType(IntEnum):
class AvatarSize(IntEnum): class AvatarSize(IntEnum):
ROSTER = 32 ROSTER = 32
CHAT = 48
NOTIFICATION = 48 NOTIFICATION = 48
CHAT = 52
PROFILE = 64 PROFILE = 64
TOOLTIP = 125 TOOLTIP = 125
VCARD = 200 VCARD = 200

View File

@ -187,10 +187,6 @@ class PreferencesWindow:
else: else:
show_roster_combobox.set_active(0) show_roster_combobox.set_active(0)
# Compact View
st = app.config.get('compact_view')
self.xml.get_object('compact_view_checkbutton').set_active(st)
# Ignore XHTML # Ignore XHTML
st = app.config.get('ignore_incoming_xhtml') st = app.config.get('ignore_incoming_xhtml')
self.xml.get_object('xhtml_checkbutton').set_active(st) self.xml.get_object('xhtml_checkbutton').set_active(st)
@ -657,12 +653,6 @@ class PreferencesWindow:
config_type = c_config.opt_show_roster_on_startup[active] config_type = c_config.opt_show_roster_on_startup[active]
app.config.set('show_roster_on_startup', config_type) app.config.set('show_roster_on_startup', config_type)
def on_compact_view_checkbutton_toggled(self, widget):
active = widget.get_active()
for ctrl in self._get_all_controls():
ctrl.chat_buttons_set_visible(active)
app.config.set('compact_view', active)
def on_xhtml_checkbutton_toggled(self, widget): def on_xhtml_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml') self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
helpers.update_optional_features() helpers.update_optional_features()

View File

@ -374,7 +374,6 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">5</property>
<child> <child>
<object class="GtkEventBox" id="banner_eventbox"> <object class="GtkEventBox" id="banner_eventbox">
<property name="name">ChatControl-BannerEventBox</property> <property name="name">ChatControl-BannerEventBox</property>
@ -385,16 +384,23 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkImage" id="banner_status_image"> <object class="GtkEventBox" id="avatar_eventbox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="ypad">5</property> <property name="margin_top">5</property>
<property name="stock">gtk-missing-image</property> <property name="margin_bottom">5</property>
<property name="visible_window">False</property>
<child>
<object class="GtkImage" id="avatar_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
@ -545,21 +551,15 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEventBox" id="avatar_eventbox"> <object class="GtkImage" id="banner_status_image">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="visible_window">False</property> <property name="stock">gtk-missing-image</property>
<child>
<object class="GtkImage" id="avatar_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="padding">5</property>
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
@ -573,94 +573,16 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox" id="vbox106"> <object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="visible">True</property> <property name="height_request">60</property>
<property name="can_focus">False</property> <property name="can_focus">True</property>
<property name="orientation">vertical</property> <property name="shadow_type">in</property>
<property name="spacing">6</property>
<child> <child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow"> <placeholder/>
<property name="height_request">60</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkMenuButton" id="emoticons_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">face-smile</property>
</object>
</child>
<style>
<class name="msgtextview-button"/>
<class name="left"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="authentication_button">
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="lock_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon_size">1</property>
</object>
</child>
<style>
<class name="msgtextview-button"/>
<class name="right"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child> </child>
<style>
<class name="scrolled-no-border"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@ -669,9 +591,48 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox" id="actions_hbox"> <object class="GtkSeparator">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<style>
<class name="chatcontrol-separator"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkMenuButton" id="emoticons_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">face-smile-symbolic</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkMenuButton" id="formattings_button"> <object class="GtkMenuButton" id="formattings_button">
<property name="visible">True</property> <property name="visible">True</property>
@ -686,23 +647,13 @@
<object class="GtkImage" id="image10"> <object class="GtkImage" id="image10">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-bold</property> <property name="icon_name">format-text-bold-symbolic</property>
<property name="icon_size">1</property> <property name="icon_size">1</property>
</object> </object>
</child> </child>
</object> <style>
<packing> <class name="chatcontrol-actionbar-button"/>
<property name="expand">False</property> </style>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="vseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -711,235 +662,15 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="add_to_roster_button"> <object class="GtkComboBox" id="label_selector">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Add this contact to roster (Ctrl+D)</property>
<property name="tooltip_text" translatable="yes">Add this contact to roster (Ctrl+D)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-add</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="send_file_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="audio_togglebutton">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="audio_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="video_togglebutton">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="video_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="convert_to_gc_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
<property name="tooltip_text" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="convert_to_gc_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkButton" id="contact_information_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Show the contact&amp;apos;s profile (Ctrl+I)</property>
<property name="tooltip_text" translatable="yes">Show the contact's profile (Ctrl+I)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-info</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkButton" id="history_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Browse the chat history (Ctrl+H)</property>
<property name="tooltip_text" translatable="yes">Browse the chat history (Ctrl+H)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="history_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-justify-fill</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="vseparator3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">9</property> <property name="pack_type">end</property>
</packing> <property name="position">2</property>
</child>
<child>
<object class="GtkButton" id="message_window_actions_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Show advanced functions (Alt+D)</property>
<property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-execute</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">10</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="encryption_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Choose an encryption</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">channel-secure-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1042,26 +773,70 @@ audio-mic-volume-low</property>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">12</property> <property name="pack_type">end</property>
<property name="position">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkComboBox" id="label_selector"> <object class="GtkButton" id="authentication_button">
<property name="visible">True</property> <property name="can_focus">True</property>
<property name="can_focus">False</property> <property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="lock_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon_size">1</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkMenuButton" id="encryption_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Choose an encryption</property>
<property name="relief">none</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">channel-secure-symbolic</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">13</property> <property name="pack_type">end</property>
<property name="position">6</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">False</property>
<property name="pack_type">end</property> <property name="position">3</property>
<property name="position">2</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@ -2,6 +2,82 @@
<!-- Generated with glade 3.20.0 --> <!-- Generated with glade 3.20.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="formattings_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCheckMenuItem" id="bold">
<property name="name">bold</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Bold</property>
<signal name="activate" handler="on_formatting_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="italic">
<property name="name">italic</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Italic</property>
<signal name="activate" handler="on_formatting_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="underline">
<property name="name">underline</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Underline</property>
<signal name="activate" handler="on_formatting_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="strike">
<property name="name">strike</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Strike</property>
<signal name="activate" handler="on_formatting_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="color">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Color</property>
<signal name="activate" handler="on_color_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="font">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Font</property>
<signal name="activate" handler="on_font_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="clear_formatting">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Clear formatting</property>
<signal name="activate" handler="on_clear_formatting_menuitem_activate" swapped="no"/>
</object>
</child>
</object>
<object class="GtkBox" id="groupchat_control_hbox"> <object class="GtkBox" id="groupchat_control_hbox">
<property name="can_focus">True</property> <property name="can_focus">True</property>
<child> <child>
@ -13,83 +89,6 @@
<property name="margin_bottom">7</property> <property name="margin_bottom">7</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="hbox3024">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="gc_banner_status_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="banner_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="banner_label">
<property name="name">GroupChatControl-BannerLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">label</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkPaned" id="hpaned"> <object class="GtkPaned" id="hpaned">
<property name="visible">True</property> <property name="visible">True</property>
@ -104,7 +103,68 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_right">4</property> <property name="margin_right">4</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">6</property> <child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="hbox3024">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="gc_banner_status_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkActionBar" id="banner_actionbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<style>
<class name="actionbar-no-border"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow"> <object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property> <property name="width_request">200</property>
@ -115,11 +175,28 @@
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
<style>
<class name="scrolled-no-border"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<style>
<class name="chatcontrol-separator"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -138,12 +215,11 @@
<object class="GtkImage"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">face-smile</property> <property name="icon_name">face-smile-symbolic</property>
</object> </object>
</child> </child>
<style> <style>
<class name="msgtextview-button"/> <class name="chatcontrol-actionbar-button"/>
<class name="left"/>
</style> </style>
</object> </object>
<packing> <packing>
@ -153,7 +229,32 @@
</packing> </packing>
</child> </child>
<child> <child>
<placeholder/> <object class="GtkMenuButton" id="formattings_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="relief">none</property>
<property name="popup">formattings_menu</property>
<child>
<object class="GtkImage" id="image10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">format-text-bold-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="authentication_button"> <object class="GtkButton" id="authentication_button">
@ -172,8 +273,7 @@
</object> </object>
</child> </child>
<style> <style>
<class name="msgtextview-button"/> <class name="chatcontrol-actionbar-button"/>
<class name="right"/>
</style> </style>
</object> </object>
<packing> <packing>
@ -183,190 +283,19 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="actions_hbox">
<property name="height_request">34</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child> <child>
<object class="GtkButton" id="formattings_button"> <placeholder/>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show a list of formattings</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-bold</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="change_nick_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Change your nickname (Ctrl+N)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-edit</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="change_subject_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Change the room's subject (Alt+T)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-properties</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="bookmark_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Bookmark this room (Ctrl+B)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-add</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="history_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Browse the chat history (Ctrl+H)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="history_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-justify-fill</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="vseparator4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="muc_window_actions_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="image1344">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-execute</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkComboBox" id="label_selector"> <object class="GtkComboBox" id="label_selector">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="no_show_all">True</property>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">7</property> <property name="position">4</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -383,18 +312,22 @@
<property name="icon_name">channel-secure-symbolic</property> <property name="icon_name">channel-secure-symbolic</property>
</object> </object>
</child> </child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">8</property> <property name="pack_type">end</property>
<property name="position">5</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">False</property>
<property name="position">2</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -422,6 +355,9 @@
</child> </child>
</object> </object>
</child> </child>
<style>
<class name="scrolled-no-border"/>
</style>
</object> </object>
<packing> <packing>
<property name="resize">False</property> <property name="resize">False</property>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 --> <!-- Generated with glade 3.20.0 -->
<interface> <interface>
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<object class="GtkEventBox" id="chat_tab_ebox"> <object class="GtkEventBox" id="chat_tab_ebox">
@ -28,10 +28,10 @@
<property name="width_request">70</property> <property name="width_request">70</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="use_markup">True</property> <property name="use_markup">True</property>
<property name="ellipsize">end</property> <property name="ellipsize">end</property>
<property name="max_width_chars">9</property> <property name="max_width_chars">9</property>
<property name="xalign">0</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -64,15 +64,35 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkHeaderBar" id="headerbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton" id="header_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
</object>
<object class="GtkApplicationWindow" id="message_window"> <object class="GtkApplicationWindow" id="message_window">
<property name="name">MessageWindow</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="default_width">480</property> <property name="default_width">480</property>
<property name="default_height">440</property> <property name="default_height">440</property>
<property name="show_menubar">False</property>
<child> <child>
<object class="GtkNotebook" id="notebook"> <object class="GtkNotebook" id="notebook">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="margin_top">2</property>
<property name="scrollable">True</property> <property name="scrollable">True</property>
</object> </object>
</child> </child>

View File

@ -426,23 +426,6 @@
<property name="top_attach">2</property> <property name="top_attach">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkCheckButton" id="compact_view_checkbutton">
<property name="label" translatable="yes">Ma_ke message windows compact</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Hide all buttons in chat windows</property>
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_compact_view_checkbutton_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="width">2</property>
</packing>
</child>
<child> <child>
<object class="GtkCheckButton" id="xhtml_checkbutton"> <object class="GtkCheckButton" id="xhtml_checkbutton">
<property name="label" translatable="yes">_Ignore rich content in incoming messages</property> <property name="label" translatable="yes">_Ignore rich content in incoming messages</property>
@ -457,7 +440,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">4</property> <property name="top_attach">3</property>
<property name="width">2</property> <property name="width">2</property>
</packing> </packing>
</child> </child>
@ -474,7 +457,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">5</property> <property name="top_attach">4</property>
<property name="width">2</property> <property name="width">2</property>
</packing> </packing>
</child> </child>
@ -490,7 +473,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">6</property> <property name="top_attach">5</property>
<property name="width">2</property> <property name="width">2</property>
</packing> </packing>
</child> </child>
@ -555,7 +538,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">7</property> <property name="top_attach">6</property>
<property name="width">2</property> <property name="width">2</property>
</packing> </packing>
</child> </child>

View File

@ -4,6 +4,7 @@
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.12"/>
<object class="GtkAccelGroup" id="accelgroup1"/> <object class="GtkAccelGroup" id="accelgroup1"/>
<object class="GtkApplicationWindow" id="roster_window"> <object class="GtkApplicationWindow" id="roster_window">
<property name="name">RosterWindow</property>
<property name="width_request">85</property> <property name="width_request">85</property>
<property name="height_request">200</property> <property name="height_request">200</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -115,4 +116,28 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkHeaderBar" id="headerbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title">Gajim</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton" id="header_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="no_show_all">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">open-menu-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</interface> </interface>

View File

@ -1,28 +1,31 @@
/* Gajim Application CSS File */ /* Gajim Application CSS File */
.msgtextview-button {
.chatcontrol-actionbar-button {
padding: 0px 5px 0px 5px; padding: 0px 5px 0px 5px;
background-color: @theme_base_color; background-color: @theme_base_color;
border: 1px solid; border: none;
border-radius: 0px;
border-color: @borders;
} }
.msgtextview-button:hover, .msgtextview-button:checked { .scrolled-no-border {border: none}
color: @theme_base_color; .scrolled-no-border undershoot.top, undershoot.bottom { background-image: none; }
border-color: @borders;
text-shadow: none; .actionbar-no-border box {border: none}
-gtk-icon-shadow: none;
box-shadow: none; .actionbar-no-border button {
padding: 0px;
background-color: @theme_base_color;
border: none;
background-image: none; background-image: none;
} }
#MessageWindow, #RosterWindow paned { background-color: @theme_base_color; }
.msgtextview-button.left { border-right: none; } .scrolledtextview { border:none; }
.msgtextview-button.right { border-left: none; }
.scrolledtextview { border-left:none; } .chatcontrol-separator {margin-bottom: 6px;}
.scrolledtextview.authentication { border-right:none; }
#SubjectPopover box { padding: 10px; }
/* VCardWindow */ /* VCardWindow */
.VCard-GtkLinkButton { padding-left: 5px; border-left: none; } .VCard-GtkLinkButton { padding-left: 5px; border-left: none; }

View File

@ -289,6 +289,7 @@ class EmoticonPopover(Gtk.Popover):
self.append_emoticon(child.get_child().get_text()) self.append_emoticon(child.get_child().get_text())
def append_emoticon(self, pix): def append_emoticon(self, pix):
self.text_widget.remove_placeholder()
buffer_ = self.text_widget.get_buffer() buffer_ = self.text_widget.get_buffer()
if buffer_.get_char_count(): if buffer_.get_char_count():
buffer_.insert_at_cursor(' ') buffer_.insert_at_cursor(' ')

View File

@ -212,8 +212,15 @@ class GajimApplication(Gtk.Application):
builder = Gtk.Builder() builder = Gtk.Builder()
builder.set_translation_domain(i18n.APP) builder.set_translation_domain(i18n.APP)
builder.add_from_file(path) builder.add_from_file(path)
self.set_menubar(builder.get_object("menubar")) menubar = builder.get_object("menubar")
self.set_app_menu(builder.get_object("appmenu")) appmenu = builder.get_object("appmenu")
if os.name != 'nt':
self.set_app_menu(appmenu)
else:
# Dont set Application Menu for Windows
# Add it to the menubar instead
menubar.prepend_submenu('Gajim', appmenu)
self.set_menubar(menubar)
def do_activate(self): def do_activate(self):
Gtk.Application.do_activate(self) Gtk.Application.do_activate(self)

View File

@ -36,6 +36,7 @@ from gi.repository import Gdk
from gi.repository import GdkPixbuf from gi.repository import GdkPixbuf
from gi.repository import Pango from gi.repository import Pango
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim import message_control from gajim import message_control
@ -251,15 +252,6 @@ class PrivateChatControl(ChatControl):
return return
self.show_avatar() self.show_avatar()
def show_avatar(self):
if not app.config.get('show_avatar_in_chat'):
return
pixbuf = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT)
image = self.xml.get_object('avatar_image')
image.set_from_pixbuf(pixbuf)
def update_contact(self): def update_contact(self):
self.contact = self.gc_contact.as_contact() self.contact = self.gc_contact.as_contact()
@ -274,20 +266,6 @@ class PrivateChatControl(ChatControl):
self.session.negotiate_e2e(False) self.session.negotiate_e2e(False)
def prepare_context_menu(self, hide_buttonbar_items=False):
"""
Set compact view menuitem active state sets active and sensitivity state
for history_menuitem (False for tranasports) and file_transfer_menuitem
and hide()/show() for add_to_roster_menuitem
"""
menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
use_multiple_contacts=False, show_start_chat=False,
show_encryption=True, control=self,
show_buttonbar_items=not hide_buttonbar_items,
gc_contact=self.gc_contact,
is_anonymous=self.room_ctrl.is_anonymous)
return menu
def got_disconnected(self): def got_disconnected(self):
ChatControl.got_disconnected(self) ChatControl.got_disconnected(self)
@ -317,56 +295,18 @@ class GroupchatControl(ChatControlBase):
# Keep error dialog instance to be sure to have only once at a time # Keep error dialog instance to be sure to have only once at a time
self.error_dialog = None self.error_dialog = None
self.actions_button = self.xml.get_object('muc_window_actions_button')
id_ = self.actions_button.connect('clicked',
self.on_actions_button_clicked)
self.handlers[id_] = self.actions_button
self.emoticons_button = self.xml.get_object('emoticons_button') self.emoticons_button = self.xml.get_object('emoticons_button')
self.toggle_emoticons() self.toggle_emoticons()
widget = self.xml.get_object('change_nick_button')
widget.set_sensitive(False)
id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
self.handlers[id_] = widget
widget = self.xml.get_object('change_subject_button')
widget.set_sensitive(False)
id_ = widget.connect('clicked',
self._on_change_subject_menuitem_activate)
self.handlers[id_] = widget
formattings_button = self.xml.get_object('formattings_button') formattings_button = self.xml.get_object('formattings_button')
formattings_button.set_sensitive(False) formattings_button.set_sensitive(False)
widget = self.xml.get_object('bookmark_button')
for bm in app.connections[self.account].bookmarks:
if bm['jid'] == self.contact.jid:
widget.hide()
break
else:
id_ = widget.connect('clicked',
self._on_bookmark_room_menuitem_activate)
self.handlers[id_] = widget
if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
img = self.xml.get_object('image7')
img.set_from_icon_name('bookmark-new', Gtk.IconSize.MENU)
widget.set_sensitive(
app.connections[self.account].private_storage_supported or \
(app.connections[self.account].pep_supported and \
app.connections[self.account].pubsub_publish_options_supported))
widget.show()
if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
img = self.xml.get_object('history_image')
img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
self.current_tooltip = None self.current_tooltip = None
if parent_win is not None: if parent_win is not None:
# On AutoJoin with minimize Groupchats are created without parent # On AutoJoin with minimize Groupchats are created without parent
# Tooltip Window has to be created with parent # Tooltip Window and Actions have to be created with parent
self.set_tooltip() self.set_tooltip()
self.add_actions()
widget = self.xml.get_object('list_treeview') widget = self.xml.get_object('list_treeview')
id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
@ -399,8 +339,6 @@ class GroupchatControl(ChatControlBase):
if not self.name: if not self.name:
self.name = self.room_jid.split('@')[0] self.name = self.room_jid.split('@')[0]
compact_view = app.config.get('compact_view')
self.chat_buttons_set_visible(compact_view)
self.widget_set_visible(self.xml.get_object('banner_eventbox'), self.widget_set_visible(self.xml.get_object('banner_eventbox'),
app.config.get('hide_groupchat_banner')) app.config.get('hide_groupchat_banner'))
self.widget_set_visible(self.xml.get_object('list_scrolledwindow'), self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
@ -444,6 +382,10 @@ class GroupchatControl(ChatControlBase):
id_ = self.hpaned.connect('notify', self.on_hpaned_notify) id_ = self.hpaned.connect('notify', self.on_hpaned_notify)
self.handlers[id_] = self.hpaned self.handlers[id_] = self.hpaned
# Hide the Roster per default
self.hpaned.get_child2().set_no_show_all(True)
self.hpaned.get_child2().hide()
# set the position of the current hpaned # set the position of the current hpaned
hpaned_position = app.config.get('gc-hpaned-position') hpaned_position = app.config.get('gc-hpaned-position')
self.hpaned.set_position(hpaned_position) self.hpaned.set_position(hpaned_position)
@ -516,6 +458,22 @@ class GroupchatControl(ChatControlBase):
gui_menu_builder.get_encryption_menu(self.control_id, self.type_id)) gui_menu_builder.get_encryption_menu(self.control_id, self.type_id))
self.set_encryption_menu_icon() self.set_encryption_menu_icon()
# Banner
self.banner_actionbar = self.xml.get_object('banner_actionbar')
self.hide_roster_button = Gtk.Button.new_from_icon_name(
'go-previous-symbolic', Gtk.IconSize.MENU)
self.hide_roster_button.connect('clicked',
lambda *args: self.show_roster())
self.subject_button = Gtk.MenuButton()
self.subject_button.set_image(Gtk.Image.new_from_icon_name(
'go-down-symbolic', Gtk.IconSize.MENU))
self.subject_button.set_popover(SubjectPopover())
self.subject_button.set_no_show_all(True)
self.banner_actionbar.pack_end(self.hide_roster_button)
self.banner_actionbar.pack_start(self.subject_button)
self.control_menu = gui_menu_builder.get_groupchat_menu(self.control_id)
app.ged.register_event_handler('gc-presence-received', ged.GUI1, app.ged.register_event_handler('gc-presence-received', ged.GUI1,
self._nec_gc_presence_received) self._nec_gc_presence_received)
app.ged.register_event_handler('gc-message-received', ged.GUI1, app.ged.register_event_handler('gc-message-received', ged.GUI1,
@ -538,14 +496,199 @@ class GroupchatControl(ChatControlBase):
self.update_ui() self.update_ui()
self.widget.show_all() self.widget.show_all()
# PluginSystem: adding GUI extension point for this GroupchatControl # PluginSystem: adding GUI extension point for this GroupchatControl
# instance object # instance object
app.plugin_manager.gui_extension_point('groupchat_control', self) app.plugin_manager.gui_extension_point('groupchat_control', self)
def add_actions(self):
actions = [
('change-subject-', self._on_change_subject),
('change-nick-', self._on_change_nick),
('disconnect-', self._on_disconnect),
('destroy-', self._on_destroy_room),
('configure-', self._on_configure_room),
('bookmark-', self._on_bookmark_room),
('request-voice-', self._on_request_voice),
]
for action in actions:
action_name, func = action
act = Gio.SimpleAction.new(action_name + self.control_id, None)
act.connect("activate", func)
self.parent_win.window.add_action(act)
non_minimized_gc = app.config.get_per(
'accounts', self.account, 'non_minimized_gc').split()
value = self.contact.jid not in non_minimized_gc
act = Gio.SimpleAction.new_stateful(
'minimize-' + self.control_id, None,
GLib.Variant.new_boolean(value))
act.connect('change-state', self._on_minimize)
self.parent_win.window.add_action(act)
value = app.config.get_per(
'rooms', self.contact.jid, 'notify_on_all_messages')
act = Gio.SimpleAction.new_stateful(
'notify-on-message-' + self.control_id,
None, GLib.Variant.new_boolean(value))
act.connect('change-state', self._on_notify_on_all_messages)
self.parent_win.window.add_action(act)
def update_actions(self):
if self.parent_win is None:
return
win = self.parent_win.window
contact = app.contacts.get_gc_contact(
self.account, self.room_jid, self.nick)
online = app.gc_connected[self.account][self.room_jid]
# Destroy Room
win.lookup_action('destroy-' + self.control_id).set_enabled(
online and contact.affiliation == 'owner')
# Configure Room
win.lookup_action('configure-' + self.control_id).set_enabled(
online and contact.affiliation in ('admin', 'owner'))
# Bookmarks
con = app.connections[self.account]
bookmark_support = con.bookmarks_available()
not_bookmarked = True
for bm in con.bookmarks:
if bm['jid'] == self.room_jid:
not_bookmarked = False
break
win.lookup_action('bookmark-' + self.control_id).set_enabled(
online and bookmark_support and not_bookmarked)
# Request Voice
role = self.get_role(self.nick)
win.lookup_action('request-voice-' + self.control_id).set_enabled(
online and role == 'visitor')
# Change Subject
# Get this from Room Disco
win.lookup_action('change-subject-' + self.control_id).set_enabled(
online)
# Change Nick
win.lookup_action('change-nick-' + self.control_id).set_enabled(
online)
# Actions
def _on_change_subject(self, action, param):
def on_ok(subject):
# Note, we don't update self.subject since we don't know whether it
# will work yet
app.connections[self.account].send_gc_subject(
self.room_jid, subject)
dialogs.InputTextDialog(_('Changing Subject'),
_('Please specify the new subject:'), input_str=self.subject,
ok_handler=on_ok, transient_for=self.parent_win.window)
def _on_change_nick(self, action, param):
if 'change_nick_dialog' in app.interface.instances:
app.interface.instances['change_nick_dialog'].dialog.present()
else:
title = _('Changing Nickname')
prompt = _('Please specify the new nickname you want to use:')
app.interface.instances['change_nick_dialog'] = \
dialogs.ChangeNickDialog(self.account, self.room_jid, title,
prompt, change_nick=True, transient_for=self.parent_win.window)
def _on_disconnect(self, action, param):
self.force_non_minimizable = True
self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND)
self.force_non_minimizable = False
def _on_destroy_room(self, action, param):
def on_ok(reason, jid):
if jid:
# Test jid
try:
jid = helpers.parse_jid(jid)
except Exception:
dialogs.ErrorDialog(_('Invalid group chat JID'),
_('The group chat JID has not allowed characters.'))
return
app.connections[self.account].destroy_gc_room(
self.room_jid, reason, jid)
# Ask for a reason
dialogs.DoubleInputDialog(_('Destroying %s') % '\u200E' + \
self.room_jid, _('You are going to remove this room permanently.'
'\nYou may specify a reason below:'),
_('You may also enter an alternate venue:'), ok_handler=on_ok,
transient_for=self.parent_win.window)
def _on_configure_room(self, action, param):
c = app.contacts.get_gc_contact(
self.account, self.room_jid, self.nick)
if c.affiliation == 'owner':
app.connections[self.account].request_gc_config(self.room_jid)
elif c.affiliation == 'admin':
if self.room_jid not in app.interface.instances[self.account][
'gc_config']:
app.interface.instances[self.account]['gc_config'][
self.room_jid] = config.GroupchatConfigWindow(self.account,
self.room_jid)
def _on_bookmark_room(self, action, param):
"""
Bookmark the room, without autojoin and not minimized
"""
password = app.gc_passwords.get(self.room_jid, '')
app.interface.add_gc_bookmark(
self.account, self.name, self.room_jid,
'0', '0', password, self.nick)
def _on_request_voice(self, action, param):
"""
Request voice in the current room
"""
app.connections[self.account].request_voice(self.room_jid)
def _on_minimize(self, action, param):
"""
When a grouchat is minimized, unparent the tab, put it in roster etc
"""
action.set_state(param)
non_minimized_gc = app.config.get_per(
'accounts', self.account, 'non_minimized_gc').split()
minimize = param.get_boolean()
if minimize:
non_minimized_gc.remove(self.contact.jid)
else:
non_minimized_gc.append(self.contact.jid)
app.config.set_per('accounts', self.account,
'non_minimized_gc', ' '.join(non_minimized_gc))
def _on_notify_on_all_messages(self, action, param):
action.set_state(param)
app.config.set_per('rooms', self.contact.jid,
'notify_on_all_messages', param.get_boolean())
def show_roster(self):
new_state = not self.hpaned.get_child2().is_visible()
image = self.hide_roster_button.get_image()
if new_state:
self.hpaned.get_child2().show()
image.set_from_icon_name('go-next-symbolic', Gtk.IconSize.MENU)
else:
self.hpaned.get_child2().hide()
image.set_from_icon_name('go-previous-symbolic', Gtk.IconSize.MENU)
def on_groupchat_maximize(self): def on_groupchat_maximize(self):
self.set_tooltip() self.set_tooltip()
self.add_window_actions() self.add_window_actions()
self.add_actions()
self.update_actions()
self.set_lock_image() self.set_lock_image()
self._schedule_activity_timers() self._schedule_activity_timers()
@ -823,10 +966,6 @@ class GroupchatControl(ChatControlBase):
self.authentication_button.set_tooltip_text(tooltip) self.authentication_button.set_tooltip_text(tooltip)
self.widget_set_visible(self.authentication_button, not visible) self.widget_set_visible(self.authentication_button, not visible)
context = self.msg_scrolledwindow.get_style_context() context = self.msg_scrolledwindow.get_style_context()
if visible:
context.add_class('authentication')
else:
context.remove_class('authentication')
self.lock_image.set_sensitive(visible) self.lock_image.set_sensitive(visible)
def _on_authentication_button_clicked(self, widget): def _on_authentication_button_clicked(self, widget):
@ -886,7 +1025,6 @@ class GroupchatControl(ChatControlBase):
room jid, subject room jid, subject
""" """
self.name_label.set_ellipsize(Pango.EllipsizeMode.END) self.name_label.set_ellipsize(Pango.EllipsizeMode.END)
self.banner_status_label.set_ellipsize(Pango.EllipsizeMode.END)
font_attrs, font_attrs_small = self.get_font_attrs() font_attrs, font_attrs_small = self.get_font_attrs()
if self.is_continued: if self.is_continued:
name = self.get_continued_conversation_name() name = self.get_continued_conversation_name()
@ -896,169 +1034,10 @@ class GroupchatControl(ChatControlBase):
self.name_label.set_markup(text) self.name_label.set_markup(text)
if self.subject: if self.subject:
subject = helpers.reduce_chars_newlines(self.subject, max_lines=2) subject = GLib.markup_escape_text(self.subject)
subject = GLib.markup_escape_text(subject)
subject_text = self.urlfinder.sub(self.make_href, subject) subject_text = self.urlfinder.sub(self.make_href, subject)
subject_text = '<span %s>%s</span>' % (font_attrs_small, subject_text = '<span>%s</span>' % subject_text
subject_text) self.subject_button.get_popover().set_text(subject_text)
# tooltip must always hold ALL the subject
self.event_box.set_tooltip_text(self.subject)
self.banner_status_label.set_no_show_all(False)
self.banner_status_label.show()
else:
subject_text = ''
self.event_box.set_has_tooltip(False)
self.banner_status_label.hide()
self.banner_status_label.set_no_show_all(True)
self.banner_status_label.set_markup(subject_text)
def prepare_context_menu(self, hide_buttonbar_items=False):
"""
Set sensitivity state for configure_room
"""
xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
menu = xml.get_object('gc_control_popup_menu')
bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
change_nick_menuitem = xml.get_object('change_nick_menuitem')
configure_room_menuitem = xml.get_object('configure_room_menuitem')
destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
change_subject_menuitem = xml.get_object('change_subject_menuitem')
history_menuitem = xml.get_object('history_menuitem')
disconnect_menuitem = xml.get_object('disconnect_menuitem')
minimize_menuitem = xml.get_object('minimize_menuitem')
notify_menuitem = xml.get_object('notify_menuitem')
request_voice_menuitem = xml.get_object('request_voice_menuitem')
bookmark_separator = xml.get_object('bookmark_separator')
separatormenuitem2 = xml.get_object('separatormenuitem2')
request_voice_separator = xml.get_object('request_voice_separator')
if hide_buttonbar_items:
change_nick_menuitem.hide()
change_subject_menuitem.hide()
bookmark_room_menuitem.hide()
history_menuitem.hide()
bookmark_separator.hide()
separatormenuitem2.hide()
else:
change_nick_menuitem.show()
change_subject_menuitem.show()
bookmark_room_menuitem.show()
history_menuitem.show()
bookmark_separator.show()
separatormenuitem2.show()
for bm in app.connections[self.account].bookmarks:
if bm['jid'] == self.room_jid:
bookmark_room_menuitem.hide()
bookmark_separator.hide()
break
ag = Gtk.accel_groups_from_object(self.parent_win.window)[0]
change_nick_menuitem.add_accelerator('activate', ag, Gdk.KEY_n,
Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE)
change_subject_menuitem.add_accelerator('activate', ag,
Gdk.KEY_t, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.VISIBLE)
bookmark_room_menuitem.add_accelerator('activate', ag, Gdk.KEY_b,
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
history_menuitem.add_accelerator('activate', ag, Gdk.KEY_h,
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
if self.contact.jid not in app.config.get_per('accounts', self.account,
'non_minimized_gc').split(' '):
minimize_menuitem.set_active(True)
notify_menuitem.set_active(app.config.get_per('rooms', self.contact.jid,
'notify_on_all_messages'))
conn = app.connections[self.account]
if not conn.private_storage_supported and (not conn.pep_supported or \
not conn.pubsub_publish_options_supported):
bookmark_room_menuitem.set_sensitive(False)
if app.gc_connected[self.account][self.room_jid]:
c = app.contacts.get_gc_contact(self.account, self.room_jid,
self.nick)
if c.affiliation not in ('owner', 'admin'):
configure_room_menuitem.set_sensitive(False)
else:
configure_room_menuitem.set_sensitive(True)
if c.affiliation != 'owner':
destroy_room_menuitem.set_sensitive(False)
else:
destroy_room_menuitem.set_sensitive(True)
change_subject_menuitem.set_sensitive(True)
change_nick_menuitem.set_sensitive(True)
if c.role == 'visitor':
request_voice_menuitem.set_sensitive(True)
else:
request_voice_menuitem.set_sensitive(False)
else:
# We are not connected to this groupchat, disable unusable menuitems
configure_room_menuitem.set_sensitive(False)
destroy_room_menuitem.set_sensitive(False)
change_subject_menuitem.set_sensitive(False)
change_nick_menuitem.set_sensitive(False)
request_voice_menuitem.set_sensitive(False)
# connect the menuitems to their respective functions
id_ = bookmark_room_menuitem.connect('activate',
self._on_bookmark_room_menuitem_activate)
self.handlers[id_] = bookmark_room_menuitem
id_ = change_nick_menuitem.connect('activate',
self._on_change_nick_menuitem_activate)
self.handlers[id_] = change_nick_menuitem
id_ = configure_room_menuitem.connect('activate',
self._on_configure_room_menuitem_activate)
self.handlers[id_] = configure_room_menuitem
id_ = destroy_room_menuitem.connect('activate',
self._on_destroy_room_menuitem_activate)
self.handlers[id_] = destroy_room_menuitem
id_ = change_subject_menuitem.connect('activate',
self._on_change_subject_menuitem_activate)
self.handlers[id_] = change_subject_menuitem
id_ = history_menuitem.connect('activate',
self._on_history_menuitem_activate)
self.handlers[id_] = history_menuitem
id_ = disconnect_menuitem.connect('activate',
self._on_disconnect_menuitem_activate)
self.handlers[id_] = disconnect_menuitem
id_ = request_voice_menuitem.connect('activate',
self._on_request_voice_menuitem_activate)
self.handlers[id_] = request_voice_menuitem
id_ = minimize_menuitem.connect('toggled',
self.on_minimize_menuitem_toggled)
self.handlers[id_] = minimize_menuitem
id_ = notify_menuitem.connect('toggled',
self.on_notify_menuitem_toggled)
self.handlers[id_] = notify_menuitem
menu.connect('selection-done', self.destroy_menu,
change_nick_menuitem, change_subject_menuitem,
bookmark_room_menuitem, history_menuitem)
return menu
def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
bookmark_room_menuitem, history_menuitem):
# destroy accelerators
ag = Gtk.accel_groups_from_object(self.parent_win.window)[0]
change_nick_menuitem.remove_accelerator(ag, Gdk.KEY_n,
Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)
change_subject_menuitem.remove_accelerator(ag, Gdk.KEY_t,
Gdk.ModifierType.MOD1_MASK)
bookmark_room_menuitem.remove_accelerator(ag, Gdk.KEY_b,
Gdk.ModifierType.CONTROL_MASK)
history_menuitem.remove_accelerator(ag, Gdk.KEY_h,
Gdk.ModifierType.CONTROL_MASK)
# destroy menu
menu.destroy()
def _nec_vcard_published(self, obj): def _nec_vcard_published(self, obj):
if obj.conn.name != self.account: if obj.conn.name != self.account:
@ -1379,6 +1358,11 @@ class GroupchatControl(ChatControlBase):
else: else:
self.print_conversation(text) self.print_conversation(text)
if obj.subject == '':
self.subject_button.hide()
else:
self.subject_button.show()
def _nec_gc_config_changed_received(self, obj): def _nec_gc_config_changed_received(self, obj):
# statuscode is a list # statuscode is a list
# http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
@ -1467,18 +1451,12 @@ class GroupchatControl(ChatControlBase):
formattings_button = self.xml.get_object('formattings_button') formattings_button = self.xml.get_object('formattings_button')
formattings_button.set_sensitive(True) formattings_button.set_sensitive(True)
change_nick_button = self.xml.get_object('change_nick_button')
change_nick_button.set_sensitive(True) self.update_actions()
change_subject_button = self.xml.get_object('change_subject_button')
change_subject_button.set_sensitive(True)
def got_disconnected(self): def got_disconnected(self):
formattings_button = self.xml.get_object('formattings_button') formattings_button = self.xml.get_object('formattings_button')
formattings_button.set_sensitive(False) formattings_button.set_sensitive(False)
change_nick_button = self.xml.get_object('change_nick_button')
change_nick_button.set_sensitive(False)
change_subject_button = self.xml.get_object('change_subject_button')
change_subject_button.set_sensitive(False)
self.list_treeview.set_model(None) self.list_treeview.set_model(None)
self.model.clear() self.model.clear()
nick_list = app.contacts.get_nick_list(self.account, self.room_jid) nick_list = app.contacts.get_nick_list(self.account, self.room_jid)
@ -1512,6 +1490,8 @@ class GroupchatControl(ChatControlBase):
if ar_to: if ar_to:
self.autorejoin = GLib.timeout_add_seconds(ar_to, self.rejoin) self.autorejoin = GLib.timeout_add_seconds(ar_to, self.rejoin)
self.update_actions()
def rejoin(self): def rejoin(self):
if not self.autorejoin: if not self.autorejoin:
return False return False
@ -1904,6 +1884,11 @@ class GroupchatControl(ChatControlBase):
st += ' (' + obj.status + ')' st += ' (' + obj.status + ')'
self.print_conversation(st, graphics=False) self.print_conversation(st, graphics=False)
# Update Actions
if obj.status_code:
if '110' in obj.status_code:
self.update_actions()
def add_contact_to_roster(self, nick, show, role, affiliation, status, def add_contact_to_roster(self, nick, show, role, affiliation, status,
jid='', avatar_sha=None): jid='', avatar_sha=None):
role_name = helpers.get_uf_role(role, plural=True) role_name = helpers.get_uf_role(role, plural=True)
@ -2262,67 +2247,6 @@ class GroupchatControl(ChatControlBase):
_('Please specify the new subject:'), input_str=self.subject, _('Please specify the new subject:'), input_str=self.subject,
ok_handler=on_ok, transient_for=self.parent_win.window) ok_handler=on_ok, transient_for=self.parent_win.window)
def _on_disconnect_menuitem_activate(self, widget):
self.force_non_minimizable = True
self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND)
self.force_non_minimizable = False
def _on_change_nick_menuitem_activate(self, widget):
if 'change_nick_dialog' in app.interface.instances:
app.interface.instances['change_nick_dialog'].dialog.present()
else:
title = _('Changing Nickname')
prompt = _('Please specify the new nickname you want to use:')
app.interface.instances['change_nick_dialog'] = \
dialogs.ChangeNickDialog(self.account, self.room_jid, title,
prompt, change_nick=True, transient_for=self.parent_win.window)
def _on_configure_room_menuitem_activate(self, widget):
c = app.contacts.get_gc_contact(self.account, self.room_jid,
self.nick)
if c.affiliation == 'owner':
app.connections[self.account].request_gc_config(self.room_jid)
elif c.affiliation == 'admin':
if self.room_jid not in app.interface.instances[self.account][
'gc_config']:
app.interface.instances[self.account]['gc_config'][
self.room_jid] = config.GroupchatConfigWindow(self.account,
self.room_jid)
def _on_destroy_room_menuitem_activate(self, widget):
def on_ok(reason, jid):
if jid:
# Test jid
try:
jid = helpers.parse_jid(jid)
except Exception:
dialogs.ErrorDialog(_('Invalid group chat JID'),
_('The group chat JID has not allowed characters.'))
return
app.connections[self.account].destroy_gc_room(self.room_jid,
reason, jid)
# Ask for a reason
dialogs.DoubleInputDialog(_('Destroying %s') % '\u200E' + \
self.room_jid, _('You are going to remove this room permanently.'
'\nYou may specify a reason below:'),
_('You may also enter an alternate venue:'), ok_handler=on_ok,
transient_for=self.parent_win.window)
def _on_bookmark_room_menuitem_activate(self, widget):
"""
Bookmark the room, without autojoin and not minimized
"""
password = app.gc_passwords.get(self.room_jid, '')
app.interface.add_gc_bookmark(self.account, self.name, self.room_jid,\
'0', '0', password, self.nick)
def _on_request_voice_menuitem_activate(self, widget):
"""
Request voice in the current room
"""
app.connections[self.account].request_voice(self.room_jid)
def _on_drag_data_received(self, widget, context, x, y, selection, def _on_drag_data_received(self, widget, context, x, y, selection,
target_type, timestamp): target_type, timestamp):
# Invite contact to groupchat # Invite contact to groupchat
@ -2913,3 +2837,40 @@ class GroupchatControl(ChatControlBase):
self.grant_owner(widget, jid) self.grant_owner(widget, jid)
else: else:
self.revoke_owner(widget, jid) self.revoke_owner(widget, jid)
class SubjectPopover(Gtk.Popover):
def __init__(self):
Gtk.Popover.__init__(self)
self.set_name('SubjectPopover')
scrolledwindow = Gtk.ScrolledWindow()
scrolledwindow.set_max_content_height(250)
scrolledwindow.set_propagate_natural_height(True)
scrolledwindow.set_propagate_natural_width(True)
scrolledwindow.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
self.label = Gtk.Label()
self.label.set_line_wrap(True)
self.label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
self.label.set_max_width_chars(80)
scrolledwindow.add(self.label)
box = Gtk.Box()
box.add(scrolledwindow)
box.show_all()
self.add(box)
self.connect_after('show', self._after_show)
def set_text(self, text):
self.label.set_markup(text)
def _after_show(self, *args):
# Gtk Bug: If we set selectable True, on show
# everything inside the Label is selected.
# So we switch after show to False and again to True
self.label.set_selectable(False)
self.label.set_selectable(True)

View File

@ -57,6 +57,7 @@ class Color:
BLACK = Gdk.RGBA(red=0, green=0, blue=0, alpha=1) BLACK = Gdk.RGBA(red=0, green=0, blue=0, alpha=1)
GREEN = Gdk.RGBA(red=115/255, green=210/255, blue=22/255, alpha=1) GREEN = Gdk.RGBA(red=115/255, green=210/255, blue=22/255, alpha=1)
RED = Gdk.RGBA(red=204/255, green=0, blue=0, alpha=1) RED = Gdk.RGBA(red=204/255, green=0, blue=0, alpha=1)
GREY = Gdk.RGBA(red=195/255, green=195/255, blue=192/255, alpha=1)
def get_icon_pixmap(icon_name, size=16, color=None, quiet=False): def get_icon_pixmap(icon_name, size=16, color=None, quiet=False):
try: try:

View File

@ -23,6 +23,7 @@ import gi
gi.require_version('GtkSpell', '3.0') gi.require_version('GtkSpell', '3.0')
from gi.repository import GtkSpell from gi.repository import GtkSpell
def ensure_attached(func): def ensure_attached(func):
def f(self, *args, **kwargs): def f(self, *args, **kwargs):
if self.spell: if self.spell:

View File

@ -606,6 +606,65 @@ Build dynamic Application Menus
''' '''
def get_singlechat_menu(control_id):
singlechat_menu = [
('win.send-file-', _('Send File...')),
('win.invite-contacts-', _('Invite Contacts')),
('win.add-to-roster-', _('Add to Roster')),
('win.toggle-audio-', _('Audio Session')),
('win.toggle-video-', _('Video Session')),
('win.information-', _('Information')),
('win.browse-history-', _('History')),
]
def build_menu(preset):
menu = Gio.Menu()
for item in preset:
action_name, label = item
if action_name == 'win.browse-history-':
menu.append(label, action_name + control_id + '::none')
else:
menu.append(label, action_name + control_id)
return menu
return build_menu(singlechat_menu)
def get_groupchat_menu(control_id):
groupchat_menu = [
(_('Manage Room'), [
('win.change-subject-', _('Change Subject')),
('win.configure-', _('Configure Room')),
('win.destroy-', _('Destroy Room')),
]),
('win.change-nick-', _('Change Nick')),
('win.bookmark-', _('Bookmark Room')),
('win.request-voice-', _('Request Voice')),
('win.notify-on-message-', _('Notify on all messages')),
('win.minimize-', _('Minimize on close')),
('win.browse-history-', _('History')),
('win.disconnect-', _('Disconnect')),
]
def build_menu(preset):
menu = Gio.Menu()
for item in preset:
if isinstance(item[1], str):
action_name, label = item
if action_name == 'win.browse-history-':
menu.append(label, action_name + control_id + '::none')
else:
menu.append(label, action_name + control_id)
else:
label, sub_menu = item
# This is a submenu
submenu = build_menu(sub_menu)
menu.append_submenu(label, submenu)
return menu
return build_menu(groupchat_menu)
def get_bookmarks_menu(account, rebuild=False): def get_bookmarks_menu(account, rebuild=False):
if not app.connections[account].bookmarks: if not app.connections[account].bookmarks:
return None return None
@ -708,7 +767,11 @@ def get_account_menu(account):
def build_accounts_menu(): def build_accounts_menu():
menubar = app.app.get_menubar() menubar = app.app.get_menubar()
# Accounts Submenu # Accounts Submenu
acc_menu = menubar.get_item_link(0, 'submenu') menu_position = 0
if os.name == 'nt':
menu_position = 1
acc_menu = menubar.get_item_link(menu_position, 'submenu')
acc_menu.remove_all() acc_menu.remove_all()
accounts_list = sorted(app.contacts.get_accounts()) accounts_list = sorted(app.contacts.get_accounts())
if not accounts_list: if not accounts_list:
@ -721,8 +784,8 @@ def build_accounts_menu():
acc, get_account_menu(acc)) acc, get_account_menu(acc))
else: else:
acc_menu = get_account_menu(accounts_list[0]) acc_menu = get_account_menu(accounts_list[0])
menubar.remove(0) menubar.remove(menu_position)
menubar.insert_submenu(0, 'Accounts', acc_menu) menubar.insert_submenu(menu_position, 'Accounts', acc_menu)
def build_bookmark_menu(account): def build_bookmark_menu(account):
@ -731,8 +794,12 @@ def build_bookmark_menu(account):
if not bookmark_menu: if not bookmark_menu:
return return
menu_position = 0
if os.name == 'nt':
menu_position = 1
# Accounts Submenu # Accounts Submenu
acc_menu = menubar.get_item_link(0, 'submenu') acc_menu = menubar.get_item_link(menu_position, 'submenu')
# We have more than one Account active # We have more than one Account active
if acc_menu.get_item_link(0, 'submenu'): if acc_menu.get_item_link(0, 'submenu'):

View File

@ -57,7 +57,6 @@ class MessageControl(object):
self.widget_name = widget_name self.widget_name = widget_name
self.contact = contact self.contact = contact
self.account = account self.account = account
self.hide_chat_buttons = False
self.resource = resource self.resource = resource
# control_id is a unique id for the control, # control_id is a unique id for the control,
# its used as action name for actions that belong to a control # its used as action name for actions that belong to a control
@ -175,12 +174,6 @@ class MessageControl(object):
""" """
return None return None
def chat_buttons_set_visible(self, state):
"""
Derived classes MAY implement this
"""
self.hide_chat_buttons = state
def got_connected(self): def got_connected(self):
pass pass

View File

@ -24,7 +24,6 @@
import gc import gc
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Pango from gi.repository import Pango
@ -37,6 +36,7 @@ class MessageTextView(Gtk.TextView):
chat/groupchat windows chat/groupchat windows
""" """
UNDO_LIMIT = 20 UNDO_LIMIT = 20
PLACEHOLDER = _('Write a message..')
def __init__(self): def __init__(self):
Gtk.TextView.__init__(self) Gtk.TextView.__init__(self)
@ -64,6 +64,9 @@ class MessageTextView(Gtk.TextView):
self.color_tags = [] self.color_tags = []
self.fonts_tags = [] self.fonts_tags = []
self.other_tags = {} self.other_tags = {}
self.placeholder_tag = _buffer.create_tag('placeholder')
self.placeholder_tag.set_property('foreground_rgba',
gtkgui_helpers.Color.GREY)
self.other_tags['bold'] = _buffer.create_tag('bold') self.other_tags['bold'] = _buffer.create_tag('bold')
self.other_tags['bold'].set_property('weight', Pango.Weight.BOLD) self.other_tags['bold'].set_property('weight', Pango.Weight.BOLD)
self.begin_tags['bold'] = '<strong>' self.begin_tags['bold'] = '<strong>'
@ -82,6 +85,30 @@ class MessageTextView(Gtk.TextView):
self.end_tags['strike'] = '</span>' self.end_tags['strike'] = '</span>'
self.connect_after('paste-clipboard', self.after_paste_clipboard) self.connect_after('paste-clipboard', self.after_paste_clipboard)
self.connect('focus-in-event', self._on_focus_in)
self.connect('focus-out-event', self._on_focus_out)
start, end = _buffer.get_bounds()
_buffer.insert_with_tags(
start, self.PLACEHOLDER, self.placeholder_tag)
def _on_focus_in(self, *args):
buf = self.get_buffer()
start, end = buf.get_bounds()
text = buf.get_text(start, end, True)
if text == self.PLACEHOLDER:
buf.set_text('')
def _on_focus_out(self, *args):
buf = self.get_buffer()
start, end = buf.get_bounds()
text = buf.get_text(start, end, True)
if text == '':
buf.insert_with_tags(
start, self.PLACEHOLDER, self.placeholder_tag)
def remove_placeholder(self):
self._on_focus_in()
def after_paste_clipboard(self, textview): def after_paste_clipboard(self, textview):
buffer_ = textview.get_buffer() buffer_ = textview.get_buffer()

View File

@ -81,7 +81,6 @@ class MessageWindow(object):
self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name) self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name)
self.window = self.xml.get_object(self.widget_name) self.window = self.xml.get_object(self.widget_name)
self.window.set_application(app.app) self.window.set_application(app.app)
self.window.set_show_menubar(False)
self.notebook = self.xml.get_object('notebook') self.notebook = self.xml.get_object('notebook')
self.parent_paned = None self.parent_paned = None
@ -94,17 +93,26 @@ class MessageWindow(object):
if app.config.get('roster_on_the_right'): if app.config.get('roster_on_the_right'):
child1 = self.parent_paned.get_child1() child1 = self.parent_paned.get_child1()
self.parent_paned.remove(child1) self.parent_paned.remove(child1)
self.parent_paned.add(self.notebook) self.parent_paned.pack1(self.notebook, resize=False)
self.parent_paned.pack1(self.notebook, resize=False, self.parent_paned.pack2(child1)
shrink=True)
self.parent_paned.pack2(child1, resize=True, shrink=True)
else: else:
self.parent_paned.add(self.notebook) self.parent_paned.pack2(self.notebook)
self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
self.window.lookup_action('show-roster').set_enabled(True) self.window.lookup_action('show-roster').set_enabled(True)
orig_window.destroy() orig_window.destroy()
del orig_window del orig_window
# Set headermenu
# single-window mode: show the header menu on the roster window
# all other modes: add the headerbar to the new window
# A headerbar has to be set before the window calls show()
if parent_window:
self.header_menu = app.interface.roster.header_menu
self.header_menu.show()
else:
self.header_menu = self.xml.get_object('header_menu')
headerbar = self.xml.get_object('headerbar')
self.window.set_titlebar(headerbar)
# NOTE: we use 'connect_after' here because in # NOTE: we use 'connect_after' here because in
# MessageWindowMgr._new_window we register handler that saves window # MessageWindowMgr._new_window we register handler that saves window
# state when closing it, and it should be called before # state when closing it, and it should be called before
@ -162,6 +170,9 @@ class MessageWindow(object):
self.notebook.set_show_border(app.config.get('tabs_border')) self.notebook.set_show_border(app.config.get('tabs_border'))
self.show_icon() self.show_icon()
def set_header_menu(self, menu):
self.header_menu.set_menu_model(menu)
def change_account_name(self, old_name, new_name): def change_account_name(self, old_name, new_name):
if old_name in self._controls: if old_name in self._controls:
self._controls[new_name] = self._controls[old_name] self._controls[new_name] = self._controls[old_name]
@ -324,6 +335,7 @@ class MessageWindow(object):
self.notebook.show_all() self.notebook.show_all()
else: else:
self.window.show_all() self.window.show_all()
# NOTE: we do not call set_control_active(True) since we don't know # NOTE: we do not call set_control_active(True) since we don't know
# whether the tab is the active one. # whether the tab is the active one.
self.show_title() self.show_title()
@ -436,9 +448,6 @@ class MessageWindow(object):
elif chr(keyval) in st: # ALT + 1,2,3.. elif chr(keyval) in st: # ALT + 1,2,3..
self.notebook.set_current_page(st.index(chr(keyval))) self.notebook.set_current_page(st.index(chr(keyval)))
return True return True
elif keyval == Gdk.KEY_c: # ALT + C toggles chat buttons
control.chat_buttons_set_visible(not control.hide_chat_buttons)
return True
elif keyval == Gdk.KEY_m: # ALT + M show emoticons menu elif keyval == Gdk.KEY_m: # ALT + M show emoticons menu
control.emoticons_button.get_popover().show() control.emoticons_button.get_popover().show()
return True return True
@ -570,6 +579,7 @@ class MessageWindow(object):
ask any confirmation ask any confirmation
""" """
def close(ctrl): def close(ctrl):
self.remove_headermenu(self.notebook, ctrl)
if reason is not None: # We are leaving gc with a status message if reason is not None: # We are leaving gc with a status message
ctrl.shutdown(reason) ctrl.shutdown(reason)
else: # We are leaving gc without status message or it's a chat else: # We are leaving gc without status message or it's a chat
@ -607,6 +617,7 @@ class MessageWindow(object):
def on_minimize(ctrl): def on_minimize(ctrl):
if method != self.CLOSE_COMMAND: if method != self.CLOSE_COMMAND:
self.remove_headermenu(self.notebook, ctrl)
ctrl.minimize() ctrl.minimize()
self.check_tabs() self.check_tabs()
return return
@ -618,6 +629,13 @@ class MessageWindow(object):
else: else:
ctrl.allow_shutdown(method, on_yes, on_no, on_minimize) ctrl.allow_shutdown(method, on_yes, on_no, on_minimize)
def remove_headermenu(self, notebook, ctrl):
page_num = notebook.page_num(ctrl.widget)
if page_num == notebook.get_current_page():
self.set_header_menu(None)
elif notebook.get_n_pages() == 1:
self.set_header_menu(None)
def check_tabs(self): def check_tabs(self):
if self.parent_paned: if self.parent_paned:
# Do nothing in single window mode # Do nothing in single window mode
@ -822,6 +840,8 @@ class MessageWindow(object):
def popup_menu(self, event): def popup_menu(self, event):
menu = self.get_active_control().prepare_context_menu() menu = self.get_active_control().prepare_context_menu()
if menu is None:
return
# show the menu # show the menu
menu.attach_to_widget(app.interface.roster.window, None) menu.attach_to_widget(app.interface.roster.window, None)
menu.show_all() menu.show_all()
@ -836,6 +856,7 @@ class MessageWindow(object):
new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
new_ctrl.set_control_active(True) new_ctrl.set_control_active(True)
self.show_title(control = new_ctrl) self.show_title(control = new_ctrl)
self.set_header_menu(new_ctrl.control_menu)
control = self.get_active_control() control = self.get_active_control()
if isinstance(control, ChatControlBase): if isinstance(control, ChatControlBase):
@ -879,6 +900,7 @@ class MessageWindow(object):
if isinstance(control, ChatControlBase): if isinstance(control, ChatControlBase):
# we forwarded it to message textview # we forwarded it to message textview
control.msg_textview.remove_placeholder()
control.msg_textview.event(event) control.msg_textview.event(event)
control.msg_textview.grab_focus() control.msg_textview.grab_focus()
@ -1289,6 +1311,7 @@ class MessageWindowMgr(GObject.GObject):
gtkgui_helpers.resize_window(w.window, gtkgui_helpers.resize_window(w.window,
app.config.get('roster_width'), app.config.get('roster_width'),
app.config.get('roster_height')) app.config.get('roster_height'))
self.hide_header_bar(self.parent_win)
self._windows = {} self._windows = {}
@ -1298,8 +1321,16 @@ class MessageWindowMgr(GObject.GObject):
mw = self.create_window(ctrl.contact, ctrl.account, mw = self.create_window(ctrl.contact, ctrl.account,
ctrl.type_id) ctrl.type_id)
ctrl.parent_win = mw ctrl.parent_win = mw
ctrl.add_actions()
ctrl.update_actions()
mw.new_tab(ctrl) mw.new_tab(ctrl)
@staticmethod
def hide_header_bar(parent_win):
header_bar = parent_win.get_titlebar()
for child in header_bar.get_children():
child.hide()
def save_opened_controls(self): def save_opened_controls(self):
if not app.config.get('remember_opened_chat_controls'): if not app.config.get('remember_opened_chat_controls'):
return return

View File

@ -5696,9 +5696,18 @@ class RosterWindow:
application.add_window(self.window) application.add_window(self.window)
self.add_actions() self.add_actions()
self.hpaned = self.xml.get_object('roster_hpaned') self.hpaned = self.xml.get_object('roster_hpaned')
app.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) app.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
app.interface.msg_win_mgr.connect('window-delete', app.interface.msg_win_mgr.connect('window-delete',
self.on_message_window_delete) self.on_message_window_delete)
# Set headermenu but hide it.
# MessageWindow will show it if we are in single-window mode
headerbar = self.xml.get_object('headerbar')
self.window.set_titlebar(headerbar)
self.header_menu = self.xml.get_object('header_menu')
self.header_menu.hide()
self.advanced_menus = [] # We keep them to destroy them self.advanced_menus = [] # We keep them to destroy them
if app.config.get('roster_window_skip_taskbar'): if app.config.get('roster_window_skip_taskbar'):
self.window.set_property('skip-taskbar-hint', True) self.window.set_property('skip-taskbar-hint', True)

View File

@ -1,89 +0,0 @@
# -*- coding:utf-8 -*-
# Copyright (C) 2015 Patrick Griffis <tingping@tingping.se>
# Copyright (C) 2014 Christian Hergert <christian@hergert.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import GObject, Gtk
class ScrolledWindow(Gtk.ScrolledWindow):
"""
ScrolledWindow that sets a max size for the child to grow into.
Taken from the Gnome Builder project:
https://git.gnome.org/browse/gnome-builder/tree/contrib/egg/egg-scrolled-window.c
"""
__gtype_name__ = "EggScrolledWindow"
max_content_height = GObject.Property(type=int, default=-1, nick="Max Content Height",
blurb="The maximum height request that can be made")
max_content_width = GObject.Property(type=int, default=-1, nick="Max Content Width",
blurb="The maximum width request that can be made")
min_content_height = GObject.Property(type=int, default=-1, nick="Min Content Height",
blurb="The minimum height request that can be made")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.connect_after("notify::max-content-height", lambda obj, param: self.queue_resize())
self.connect_after("notify::max-content-width", lambda obj, param: self.queue_resize())
def set_min_content_height(self, value):
self.min_content_height = value
def set_max_content_height(self, value):
self.max_content_height = value
def set_max_content_width(self, value):
self.max_content_width = value
def get_max_content_height(self):
return self.max_content_height
def get_max_content_width(self):
return self.max_content_width
def do_get_preferred_height(self):
min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self)
child = self.get_child()
if natural_height and self.max_content_height > -1 and child:
style = self.get_style_context()
border = style.get_border(style.get_state())
additional = border.top + border.bottom
child_min_height, child_nat_height = child.get_preferred_height()
if child_nat_height > natural_height and self.max_content_height > natural_height:
natural_height = min(self.max_content_height, child_nat_height) + additional
elif natural_height > child_nat_height:
if child_nat_height < self.min_content_height:
return self.min_content_height, self.min_content_height
min_height, natural_height = child_min_height + additional, child_nat_height + additional
return min_height, natural_height
def do_get_preferred_width(self):
min_width, natural_width = Gtk.ScrolledWindow.do_get_preferred_width(self)
child = self.get_child()
if natural_width and self.max_content_width > -1 and child:
style = self.get_style_context()
border = style.get_border(style.get_state())
additional = border.left + border.right + 1
child_min_width, child_nat_width = child.get_preferred_width()
if child_nat_width > natural_width and self.max_content_width > natural_width:
natural_width = min(self.max_content_width, child_nat_width) + additional
return min_width, natural_width