Merge branch 'newdesign' into 'master'

New Style for Chat Windows

See merge request gajim/gajim!144
This commit is contained in:
Philipp Hörist 2017-10-27 00:21:01 +02:00
commit 347f0a8aad
26 changed files with 1102 additions and 1367 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
@ -58,13 +58,6 @@ from gajim.common.exceptions import GajimGeneralException
from gajim.common.const import AvatarSize from gajim.common.const import AvatarSize
from gajim.command_system.implementation.hosts import ChatCommands from gajim.command_system.implementation.hosts import ChatCommands
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
from gajim.chat_control_base import ChatControlBase from gajim.chat_control_base import ChatControlBase
################################################################################ ################################################################################
@ -95,60 +88,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 +104,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 +212,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 +240,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 +372,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 +383,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 +729,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 +773,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 +804,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):
@ -1205,10 +1173,7 @@ class ChatControl(ChatControlBase):
self.handlers[i].disconnect(i) self.handlers[i].disconnect(i)
del self.handlers[i] del self.handlers[i]
self.conv_textview.del_handlers() self.conv_textview.del_handlers()
if app.config.get('use_speller') and HAS_GTK_SPELL: self.remove_speller()
spell_obj = gtkspell.get_from_text_view(self.msg_textview)
if spell_obj:
spell_obj.detach()
self.msg_textview.destroy() self.msg_textview.destroy()
# PluginSystem: calling shutdown of super class (ChatControlBase) to let # PluginSystem: calling shutdown of super class (ChatControlBase) to let
# it remove it's GUI extension points # it remove it's GUI extension points
@ -1272,9 +1237,17 @@ class ChatControl(ChatControlBase):
if not app.config.get('show_avatar_in_chat'): if not app.config.get('show_avatar_in_chat'):
return return
if self.TYPE_ID == message_control.TYPE_CHAT:
pixbuf = app.contacts.get_avatar( pixbuf = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT) 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')
if pixbuf is None:
image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else:
image.set_from_pixbuf(pixbuf) image.set_from_pixbuf(pixbuf)
def _nec_update_avatar(self, obj): def _nec_update_avatar(self, obj):
@ -1446,15 +1419,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 +1486,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

@ -35,16 +35,17 @@ from gi.repository import Pango
from gi.repository import GObject from gi.repository import GObject
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim.gtkgui_helpers import Color from gajim.gtkgui_helpers import Color
from gajim import message_control from gajim import message_control
from gajim import dialogs from gajim import dialogs
from gajim import history_window from gajim import history_window
from gajim import notify from gajim import notify
from gajim import gtkspell
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
@ -65,11 +66,6 @@ from gajim.command_system.implementation.middleware import CommandTools
from gajim.command_system.implementation import standard from gajim.command_system.implementation import standard
from gajim.command_system.implementation import execute from gajim.command_system.implementation import execute
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
################################################################################ ################################################################################
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
@ -256,15 +252,7 @@ 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
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)
id_ = widget.connect('clicked', self._on_history_menuitem_activate)
self.handlers[id_] = widget
# Create banner and connect signals # Create banner and connect signals
widget = self.xml.get_object('banner_eventbox') widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event', id_ = widget.connect('button-press-event',
@ -275,6 +263,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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')
if self.banner_status_label is not None:
id_ = self.banner_status_label.connect('populate_popup', id_ = self.banner_status_label.connect('populate_popup',
self.on_banner_label_populate_popup) self.on_banner_label_populate_popup)
self.handlers[id_] = self.banner_status_label self.handlers[id_] = self.banner_status_label
@ -332,9 +321,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)
@ -364,7 +352,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.set_emoticon_popover() self.set_emoticon_popover()
# Attach speller # Attach speller
if app.config.get('use_speller') and HAS_GTK_SPELL: self.spell = None
self.spell_handlers = []
self.set_speller() self.set_speller()
self.conv_textview.tv.show() self.conv_textview.tv.show()
@ -417,6 +406,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':
@ -467,24 +478,57 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
image.set_from_pixbuf(icon) image.set_from_pixbuf(icon)
def set_speller(self): def set_speller(self):
# now set the one the user selected if not gtkspell.HAS_GTK_SPELL or not app.config.get('use_speller'):
return
def _on_focus_in(*args):
if self.spell is None:
return
self.spell.attach(self.msg_textview)
def _on_focus_out(*args):
if self.spell is None:
return
if not self.msg_textview.has_text():
self.spell.detach()
lang = self.get_speller_language()
if not lang:
return
try:
self.spell = gtkspell.Spell(self.msg_textview, lang)
self.spell.connect('language_changed', self.on_language_changed)
handler_id = self.msg_textview.connect('focus-in-event',
_on_focus_in)
self.spell_handlers.append(handler_id)
handler_id = self.msg_textview.connect('focus-out-event',
_on_focus_out)
self.spell_handlers.append(handler_id)
except OSError:
dialogs.AspellDictError(lang)
app.config.set('use_speller', False)
def remove_speller(self):
if self.spell is None:
return
self.spell.detach()
for id_ in self.spell_handlers:
self.msg_textview.disconnect(id_)
self.spell_handlers.remove(id_)
self.spell = None
def get_speller_language(self):
per_type = 'contacts' per_type = 'contacts'
if self.type_id == message_control.TYPE_GC: if self.type_id == 'gc':
per_type = 'rooms' per_type = 'rooms'
lang = app.config.get_per(per_type, self.contact.jid, lang = app.config.get_per(
'speller_language') per_type, self.contact.jid, 'speller_language')
if not lang: if not lang:
# use the default one # use the default one
lang = app.config.get('speller_language') lang = app.config.get('speller_language')
if not lang: if not lang:
lang = app.LANG lang = app.LANG
if lang: return lang or None
try:
self.spell = gtkspell.Spell(self.msg_textview, lang)
self.msg_textview.lang = lang
self.spell.connect('language_changed', self.on_language_changed)
except (GObject.GError, RuntimeError, TypeError, OSError):
dialogs.AspellDictError(lang)
def on_language_changed(self, spell, lang): def on_language_changed(self, spell, lang):
per_type = 'contacts' per_type = 'contacts'
@ -492,9 +536,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
per_type = 'rooms' per_type = 'rooms'
if not app.config.get_per(per_type, self.contact.jid): if not app.config.get_per(per_type, self.contact.jid):
app.config.add_per(per_type, self.contact.jid) app.config.add_per(per_type, self.contact.jid)
app.config.set_per(per_type, self.contact.jid, 'speller_language', app.config.set_per(
lang) per_type, self.contact.jid, 'speller_language', lang)
self.msg_textview.lang = lang
def on_banner_label_populate_popup(self, label, menu): def on_banner_label_populate_popup(self, label, menu):
""" """
@ -549,6 +592,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 +1327,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 +1339,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

@ -50,12 +50,7 @@ from gajim import message_control
from gajim.chat_control_base import ChatControlBase from gajim.chat_control_base import ChatControlBase
from gajim import dataforms_widget from gajim import dataforms_widget
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim import gtkspell
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
from gajim.common import helpers from gajim.common import helpers
from gajim.common import app from gajim.common import app
@ -187,16 +182,12 @@ 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)
# use speller # use speller
if HAS_GTK_SPELL: if gtkspell.HAS_GTK_SPELL:
st = app.config.get('use_speller') st = app.config.get('use_speller')
self.xml.get_object('speller_checkbutton').set_active(st) self.xml.get_object('speller_checkbutton').set_active(st)
else: else:
@ -657,12 +648,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()
@ -670,23 +655,13 @@ class PreferencesWindow:
def apply_speller(self): def apply_speller(self):
for ctrl in self._get_all_controls(): for ctrl in self._get_all_controls():
if isinstance(ctrl, ChatControlBase): if isinstance(ctrl, ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
except (TypeError, RuntimeError, OSError):
spell_obj = None
if not spell_obj:
ctrl.set_speller() ctrl.set_speller()
def remove_speller(self): def remove_speller(self):
for ctrl in self._get_all_controls(): for ctrl in self._get_all_controls():
if isinstance(ctrl, ChatControlBase): if isinstance(ctrl, ChatControlBase):
try: if ctrl.spell is not None:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview) ctrl.remove_speller()
except (TypeError, RuntimeError):
spell_obj = None
if spell_obj:
spell_obj.detach()
def on_speller_checkbutton_toggled(self, widget): def on_speller_checkbutton_toggled(self, widget):
active = widget.get_active() active = widget.get_active()
@ -695,15 +670,10 @@ class PreferencesWindow:
lang = app.config.get('speller_language') lang = app.config.get('speller_language')
if not lang: if not lang:
lang = app.LANG lang = app.LANG
tv = Gtk.TextView()
try: available = gtkspell.test_language(lang)
gtkspell.Spell(tv, lang) if not available:
except (TypeError, RuntimeError, OSError): dialogs.AspellDictError(lang)
dialogs.ErrorDialog(
_('Dictionary for lang %s not available') % lang,
_('You have to install %s dictionary to use spellchecking, or '
'choose another language by setting the speller_language option.'
) % lang)
app.config.set('use_speller', False) app.config.set('use_speller', False)
widget.set_active(False) widget.set_active(False)
else: else:

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="can_focus">False</property>
<property name="margin_top">5</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="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="ypad">5</property>
<property name="stock">gtk-missing-image</property> <property name="stock">gtk-missing-image</property>
</object> </object>
</child>
</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="can_focus">False</property>
<property name="visible_window">False</property>
<child>
<object class="GtkImage" id="avatar_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="stock">gtk-missing-image</property> <property name="stock">gtk-missing-image</property>
</object> </object>
</child>
</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>
@ -572,12 +572,6 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox" id="vbox106">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child> <child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow"> <object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="height_request">60</property> <property name="height_request">60</property>
@ -586,11 +580,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>
@ -609,12 +620,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>
@ -623,55 +633,6 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="actions_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<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,237 +662,17 @@
</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="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="stock">gtk-add</property>
<property name="icon_size">1</property>
</object>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">False</property> <property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </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="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>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</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>
</child>
<child> <child>
<object class="GtkBox" id="audio_buttons_hbox"> <object class="GtkBox" id="audio_buttons_hbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -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="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="visible">True</property>
<property name="can_focus">False</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">True</property> <property name="fill">False</property>
<property name="position">13</property> <property name="pack_type">end</property>
<property name="position">4</property>
</packing> </packing>
</child> </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> </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="pack_type">end</property> <property name="pack_type">end</property>
<property name="position">2</property> <property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</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,6 +89,20 @@
<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="GtkPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">495</property>
<property name="position_set">True</property>
<signal name="notify" handler="on_hpaned_notify" swapped="no"/>
<child>
<object class="GtkBox" id="gc_textviews_vbox">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">4</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkEventBox" id="banner_eventbox"> <object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property> <property name="name">GroupChatControl-BannerEventBox</property>
@ -35,13 +125,6 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkLabel" id="banner_name_label"> <object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property> <property name="name">GroupChatControl-BannerNameLabel</property>
@ -53,32 +136,24 @@
<property name="xalign">0</property> <property name="xalign">0</property>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="banner_label"> <object class="GtkActionBar" id="banner_actionbar">
<property name="name">GroupChatControl-BannerLabel</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="label">label</property> <style>
<property name="use_markup">True</property> <class name="actionbar-no-border"/>
<property name="selectable">True</property> </style>
<property name="xalign">0</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">1</property> <property name="pack_type">end</property>
</packing> <property name="position">2</property>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -90,21 +165,6 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">495</property>
<property name="position_set">True</property>
<signal name="notify" handler="on_hpaned_notify" swapped="no"/>
<child>
<object class="GtkBox" id="gc_textviews_vbox">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">4</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<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> <child>
<object class="GtkBox" id="actions_hbox"> <placeholder/>
<property name="height_request">34</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton" 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="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

@ -42,6 +42,7 @@ from gajim import gtkgui_helpers
from gajim import vcard from gajim import vcard
from gajim import conversation_textview from gajim import conversation_textview
from gajim import dataforms_widget from gajim import dataforms_widget
from gajim import gtkspell
from random import randrange from random import randrange
from gajim.common import pep from gajim.common import pep
@ -50,12 +51,6 @@ from gajim.common import const
from gajim.options_dialog import OptionsDialog from gajim.options_dialog import OptionsDialog
from gajim.common.const import Option, OptionKind, OptionType from gajim.common.const import Option, OptionKind, OptionType
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
# those imports are not used in this file, but in files that 'import dialogs' # those imports are not used in this file, but in files that 'import dialogs'
# so they can do dialog.GajimThemesWindow() for example # so they can do dialog.GajimThemesWindow() for example
from gajim.filetransfers_window import FileTransfersWindow from gajim.filetransfers_window import FileTransfersWindow
@ -1486,11 +1481,11 @@ class FileChooserDialog(Gtk.FileChooserDialog):
class AspellDictError: class AspellDictError:
def __init__(self, lang): def __init__(self, lang):
ErrorDialog( ErrorDialog(
_('Dictionary for lang %s not available') % lang, _('Dictionary for lang "%s" not available') % lang,
_('You have to install %s dictionary to use spellchecking, or ' _('You have to install the dictionary "%s" to use spellchecking, '
'choose another language by setting the speller_language option.' 'or choose another language by setting the speller_language '
'\n\nHighlighting misspelled words feature will not be used') % lang) 'option.\n\n'
app.config.set('use_speller', False) 'Highlighting misspelled words feature will not be used') % lang)
class ConfirmationDialog(HigDialog): class ConfirmationDialog(HigDialog):
""" """
@ -3082,14 +3077,14 @@ class SingleMessageWindow:
else: else:
self.to_entry.set_text(to) self.to_entry.set_text(to)
if app.config.get('use_speller') and HAS_GTK_SPELL and action == 'send': if app.config.get('use_speller') and gtkspell.HAS_GTK_SPELL and action == 'send':
try: try:
lang = app.config.get('speller_language') lang = app.config.get('speller_language')
if not lang: if not lang:
lang = app.LANG lang = app.LANG
gtkspell.Spell(self.conversation_textview.tv, lang) self.spell = gtkspell.Spell(self.message_textview, lang)
gtkspell.Spell(self.message_textview, lang) self.spell.attach(self.message_textview)
except (GObject.GError, TypeError, RuntimeError, OSError): except OSError:
AspellDictError(lang) AspellDictError(lang)
self.prepare_widgets_for(self.action) self.prepare_widgets_for(self.action)

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

@ -19,9 +19,17 @@
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib
import gi import gi
gi.require_version('GtkSpell', '3.0') gi.require_version('GtkSpell', '3.0')
from gi.repository import GtkSpell try:
from gi.repository import GtkSpell
HAS_GTK_SPELL = True
except ImportError:
HAS_GTK_SPELL = False
from gajim.common import app
def ensure_attached(func): def ensure_attached(func):
def f(self, *args, **kwargs): def f(self, *args, **kwargs):
@ -47,12 +55,13 @@ class Spell(GObject.GObject):
if spell: if spell:
raise RuntimeError("Textview has already a Spell obj attached") raise RuntimeError("Textview has already a Spell obj attached")
self.spell = GtkSpell.Checker.new() self.spell = GtkSpell.Checker.new()
if not self.spell:
raise OSError("Unable to create spell object.") try:
if not self.spell.attach(textview): self.spell.set_language(language)
raise OSError("Unable to attach spell object.") except GLib.GError as error:
if not self.spell.set_language(language): if error.domain == 'gtkspell-error-quark':
raise OSError("Unable to set language: '%s'" % language) raise OSError("Unable to set language: '%s'" % language)
self.spell.connect('language-changed', self.on_language_changed) self.spell.connect('language-changed', self.on_language_changed)
else: else:
@ -73,12 +82,25 @@ class Spell(GObject.GObject):
def recheck_all(self): def recheck_all(self):
self.spell.recheck_all() self.spell.recheck_all()
@ensure_attached
def detach(self): def detach(self):
if self.spell is not None:
self.spell.detach() self.spell.detach()
self.spell = None
def attach(self, textview):
spell = GtkSpell.Checker.get_from_text_view(textview)
if spell is None:
print('attached')
self.spell.attach(textview)
GObject.type_register(Spell) GObject.type_register(Spell)
def get_from_text_view(textview):
return Spell(textview, create=False) def test_language(lang):
spell = GtkSpell.Checker.new()
try:
spell.set_language(lang)
except GLib.GError as error:
if error.domain == 'gtkspell-error-quark':
return False
return True

View file

@ -2858,18 +2858,6 @@ class Interface:
# get transports type from DB # get transports type from DB
app.transport_type = app.logger.get_transports_type() app.transport_type = app.logger.get_transports_type()
# test is dictionnary is present for speller
if app.config.get('use_speller'):
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
tv = Gtk.TextView()
try:
from gajim import gtkspell
spell = gtkspell.Spell(tv, lang)
except (ImportError, TypeError, RuntimeError, OSError, ValueError):
dialogs.AspellDictError(lang)
if app.config.get('soundplayer') == '': if app.config.get('soundplayer') == '':
# only on first time Gajim starts # only on first time Gajim starts
commands = ('paplay', 'aplay', 'play', 'ossplay') commands = ('paplay', 'aplay', 'play', 'ossplay')

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)
@ -57,13 +57,16 @@ class MessageTextView(Gtk.TextView):
self.undo_list = [] self.undo_list = []
# needed to know if we undid something # needed to know if we undid something
self.undo_pressed = False self.undo_pressed = False
self.lang = None # Lang used for spell checking
_buffer = self.get_buffer() _buffer = self.get_buffer()
self.begin_tags = {} self.begin_tags = {}
self.end_tags = {} self.end_tags = {}
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,33 @@ 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 has_text(self):
buf = self.get_buffer()
start, end = buf.get_bounds()
text = buf.get_text(start, end, True)
return text != self.PLACEHOLDER
def _on_focus_in(self, *args):
if not self.has_text():
self.get_buffer().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