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 time
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import GdkPixbuf
from gi.repository import Pango
from gi.repository import GLib
@ -58,13 +58,6 @@ from gajim.common.exceptions import GajimGeneralException
from gajim.common.const import AvatarSize
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
################################################################################
@ -95,60 +88,10 @@ class ChatControl(ChatControlBase):
self.last_recv_message_marks = 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.emoticons_button = self.xml.get_object('emoticons_button')
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'),
app.config.get('hide_chat_banner'))
@ -161,10 +104,9 @@ class ChatControl(ChatControlBase):
# Add lock image to show chat encryption
self.lock_image = self.xml.get_object('lock_image')
# Convert to GC icon
img = self.xml.get_object('convert_to_gc_button_image')
img.set_from_pixbuf(gtkgui_helpers.load_icon(
'muc_active').get_pixbuf())
# Menu for the HeaderBar
self.control_menu = gui_menu_builder.get_singlechat_menu(
self.control_id)
self._audio_banner_image = self.xml.get_object('audio_banner_image')
self._video_banner_image = self.xml.get_object('video_banner_image')
@ -270,7 +212,7 @@ class ChatControl(ChatControlBase):
# Enable encryption if needed
self.no_autonegotiation = False
self.add_actions()
self.update_ui()
self.set_lock_image()
@ -298,6 +240,107 @@ class ChatControl(ChatControlBase):
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
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):
"""
@ -329,14 +372,6 @@ class ChatControl(ChatControlBase):
self._formattings_button.set_tooltip_text(_('This contact does '
'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
if self.contact.supports(NS_JINGLE_ICE_UDP) and \
app.HAVE_FARSTREAM and self.contact.resource:
@ -348,63 +383,6 @@ class ChatControl(ChatControlBase):
self.video_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):
for pep_type in self._pep_images:
self.update_pep(pep_type)
@ -751,12 +729,12 @@ class ChatControl(ChatControlBase):
getattr(self, '_' + jingle_type + '_button').set_active(False)
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],
{True: 'active', False: 'inactive'}[widget.get_active()])
{True: 'active', False: 'inactive'}[state])
path_to_img = gtkgui_helpers.get_icon_path(img_name)
if widget.get_active():
if state:
if getattr(self, jingle_type + '_state') == \
self.JINGLE_STATE_NULL:
if jingle_type == 'video':
@ -795,12 +773,6 @@ class ChatControl(ChatControlBase):
img = getattr(self, '_' + jingle_type + '_button').get_property('image')
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):
loggable = self.session and self.session.is_loggable()
@ -832,10 +804,6 @@ class ChatControl(ChatControlBase):
self.authentication_button.set_tooltip_text(tooltip)
self.widget_set_visible(self.authentication_button, not visible)
context = self.msg_scrolledwindow.get_style_context()
if visible:
context.add_class('authentication')
else:
context.remove_class('authentication')
self.lock_image.set_sensitive(visible)
def _on_authentication_button_clicked(self, widget):
@ -1205,10 +1173,7 @@ class ChatControl(ChatControlBase):
self.handlers[i].disconnect(i)
del self.handlers[i]
self.conv_textview.del_handlers()
if app.config.get('use_speller') and HAS_GTK_SPELL:
spell_obj = gtkspell.get_from_text_view(self.msg_textview)
if spell_obj:
spell_obj.detach()
self.remove_speller()
self.msg_textview.destroy()
# PluginSystem: calling shutdown of super class (ChatControlBase) to let
# it remove it's GUI extension points
@ -1272,10 +1237,18 @@ class ChatControl(ChatControlBase):
if not app.config.get('show_avatar_in_chat'):
return
pixbuf = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT)
if self.TYPE_ID == message_control.TYPE_CHAT:
pixbuf = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT)
else:
pixbuf = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT)
image = self.xml.get_object('avatar_image')
image.set_from_pixbuf(pixbuf)
if pixbuf is None:
image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else:
image.set_from_pixbuf(pixbuf)
def _nec_update_avatar(self, obj):
if obj.account != self.account:
@ -1446,15 +1419,6 @@ class ChatControl(ChatControlBase):
elif typ == 'pm':
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):
"""
User wants to invite some friends to chat
@ -1522,21 +1486,11 @@ class ChatControl(ChatControlBase):
if contact:
self.contact = contact
self.draw_banner()
self.update_actions()
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)
self.update_actions()
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 GLib
from gi.repository import Gio
from gajim import gtkgui_helpers
from gajim.gtkgui_helpers import Color
from gajim import message_control
from gajim import dialogs
from gajim import history_window
from gajim import notify
from gajim import gtkspell
import re
from gajim import emoticons
from gajim.scrolled_window import ScrolledWindow
from gajim.common import events
from gajim.common import app
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 execute
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
################################################################################
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
@ -256,28 +252,21 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
MessageControl.__init__(self, type_id, parent_win, widget_name,
contact, acct, resource=resource)
widget = self.xml.get_object('history_button')
# 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
widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id_] = widget
if self.TYPE_ID != message_control.TYPE_GC:
# Create banner and connect signals
widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id_] = widget
self.urlfinder = re.compile(
r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
self.banner_status_label = self.xml.get_object('banner_label')
id_ = self.banner_status_label.connect('populate_popup',
self.on_banner_label_populate_popup)
self.handlers[id_] = self.banner_status_label
if self.banner_status_label is not None:
id_ = self.banner_status_label.connect('populate_popup',
self.on_banner_label_populate_popup)
self.handlers[id_] = self.banner_status_label
# Init DND
self.TARGET_TYPE_URI_LIST = 80
@ -332,9 +321,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.msg_scrolledwindow = ScrolledWindow()
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.set_property('shadow_type', Gtk.ShadowType.IN)
self.msg_scrolledwindow.add(self.msg_textview)
@ -364,8 +352,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.set_emoticon_popover()
# Attach speller
if app.config.get('use_speller') and HAS_GTK_SPELL:
self.set_speller()
self.spell = None
self.spell_handlers = []
self.set_speller()
self.conv_textview.tv.show()
# For XEP-0172
@ -417,6 +406,28 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
action.connect("change-state", self.change_encryption)
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):
encryption = param.get_string()
if encryption == 'disabled':
@ -467,24 +478,57 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
image.set_from_pixbuf(icon)
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'
if self.type_id == message_control.TYPE_GC:
if self.type_id == 'gc':
per_type = 'rooms'
lang = app.config.get_per(per_type, self.contact.jid,
'speller_language')
lang = app.config.get_per(
per_type, self.contact.jid, 'speller_language')
if not lang:
# use the default one
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
if lang:
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)
return lang or None
def on_language_changed(self, spell, lang):
per_type = 'contacts'
@ -492,9 +536,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
per_type = 'rooms'
if not app.config.get_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',
lang)
self.msg_textview.lang = lang
app.config.set_per(
per_type, self.contact.jid, 'speller_language', lang)
def on_banner_label_populate_popup(self, label, menu):
"""
@ -549,6 +592,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
menu.show_all()
def on_quote(self, widget, text):
self.msg_textview.remove_placeholder()
text = '>' + text.replace('\n', '\n>') + '\n'
message_buffer = self.msg_textview.get_buffer()
message_buffer.insert_at_cursor(text)
@ -1283,13 +1327,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
else:
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):
self.msg_textview.set_sensitive(True)
self.msg_textview.set_editable(True)
@ -1302,3 +1339,19 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.no_autonegotiation = False
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
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)
@doc(_("Show help on a given command or a list of available commands if -a is given"))
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_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.')],
'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_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.')],

View File

@ -2342,6 +2342,13 @@ class Connection(CommonConnection, ConnectionHandlers):
if rule['type'] == 'group':
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):
if not app.account_is_connected(self.name):
return

View File

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

View File

@ -50,12 +50,7 @@ from gajim import message_control
from gajim.chat_control_base import ChatControlBase
from gajim import dataforms_widget
from gajim import gui_menu_builder
try:
from gajim import gtkspell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
from gajim import gtkspell
from gajim.common import helpers
from gajim.common import app
@ -187,16 +182,12 @@ class PreferencesWindow:
else:
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
st = app.config.get('ignore_incoming_xhtml')
self.xml.get_object('xhtml_checkbutton').set_active(st)
# use speller
if HAS_GTK_SPELL:
if gtkspell.HAS_GTK_SPELL:
st = app.config.get('use_speller')
self.xml.get_object('speller_checkbutton').set_active(st)
else:
@ -657,12 +648,6 @@ class PreferencesWindow:
config_type = c_config.opt_show_roster_on_startup[active]
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):
self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
helpers.update_optional_features()
@ -670,23 +655,13 @@ class PreferencesWindow:
def apply_speller(self):
for ctrl in self._get_all_controls():
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):
for ctrl in self._get_all_controls():
if isinstance(ctrl, ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
except (TypeError, RuntimeError):
spell_obj = None
if spell_obj:
spell_obj.detach()
if ctrl.spell is not None:
ctrl.remove_speller()
def on_speller_checkbutton_toggled(self, widget):
active = widget.get_active()
@ -695,15 +670,10 @@ class PreferencesWindow:
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
tv = Gtk.TextView()
try:
gtkspell.Spell(tv, lang)
except (TypeError, RuntimeError, OSError):
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)
available = gtkspell.test_language(lang)
if not available:
dialogs.AspellDictError(lang)
app.config.set('use_speller', False)
widget.set_active(False)
else:

View File

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

View File

@ -2,6 +2,82 @@
<!-- Generated with glade 3.20.0 -->
<interface>
<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">
<property name="can_focus">True</property>
<child>
@ -13,83 +89,6 @@
<property name="margin_bottom">7</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="hbox3024">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="gc_banner_status_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="banner_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="banner_label">
<property name="name">GroupChatControl-BannerLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">label</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="hpaned">
<property name="visible">True</property>
@ -104,7 +103,68 @@
<property name="can_focus">False</property>
<property name="margin_right">4</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="hbox3024">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="gc_banner_status_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkActionBar" id="banner_actionbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<style>
<class name="actionbar-no-border"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property>
@ -115,11 +175,28 @@
<child>
<placeholder/>
</child>
<style>
<class name="scrolled-no-border"/>
</style>
</object>
<packing>
<property name="expand">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>
</child>
<child>
@ -138,12 +215,11 @@
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">face-smile</property>
<property name="icon_name">face-smile-symbolic</property>
</object>
</child>
<style>
<class name="msgtextview-button"/>
<class name="left"/>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
@ -153,7 +229,32 @@
</packing>
</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>
<object class="GtkButton" id="authentication_button">
@ -172,8 +273,7 @@
</object>
</child>
<style>
<class name="msgtextview-button"/>
<class name="right"/>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
@ -183,190 +283,19 @@
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="actions_hbox">
<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>
<placeholder/>
</child>
<child>
<object class="GtkComboBox" id="label_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">7</property>
<property name="position">4</property>
</packing>
</child>
<child>
@ -383,18 +312,22 @@
<property name="icon_name">channel-secure-symbolic</property>
</object>
</child>
<style>
<class name="chatcontrol-actionbar-button"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
@ -422,6 +355,9 @@
</child>
</object>
</child>
<style>
<class name="scrolled-no-border"/>
</style>
</object>
<packing>
<property name="resize">False</property>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkEventBox" id="chat_tab_ebox">
@ -28,10 +28,10 @@
<property name="width_request">70</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="use_markup">True</property>
<property name="ellipsize">end</property>
<property name="max_width_chars">9</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
@ -64,15 +64,35 @@
</object>
</child>
</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">
<property name="name">MessageWindow</property>
<property name="can_focus">False</property>
<property name="default_width">480</property>
<property name="default_height">440</property>
<property name="show_menubar">False</property>
<child>
<object class="GtkNotebook" id="notebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_top">2</property>
<property name="scrollable">True</property>
</object>
</child>

View File

@ -426,23 +426,6 @@
<property name="top_attach">2</property>
</packing>
</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>
<object class="GtkCheckButton" id="xhtml_checkbutton">
<property name="label" translatable="yes">_Ignore rich content in incoming messages</property>
@ -457,7 +440,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
<property name="top_attach">3</property>
<property name="width">2</property>
</packing>
</child>
@ -474,7 +457,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
<property name="top_attach">4</property>
<property name="width">2</property>
</packing>
</child>
@ -490,7 +473,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
<property name="top_attach">5</property>
<property name="width">2</property>
</packing>
</child>
@ -555,7 +538,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="top_attach">6</property>
<property name="width">2</property>
</packing>
</child>

View File

@ -4,6 +4,7 @@
<requires lib="gtk+" version="3.12"/>
<object class="GtkAccelGroup" id="accelgroup1"/>
<object class="GtkApplicationWindow" id="roster_window">
<property name="name">RosterWindow</property>
<property name="width_request">85</property>
<property name="height_request">200</property>
<property name="can_focus">False</property>
@ -115,4 +116,28 @@
</object>
</child>
</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>

View File

@ -1,28 +1,31 @@
/* Gajim Application CSS File */
.msgtextview-button {
.chatcontrol-actionbar-button {
padding: 0px 5px 0px 5px;
background-color: @theme_base_color;
border: 1px solid;
border-radius: 0px;
border-color: @borders;
border: none;
}
.msgtextview-button:hover, .msgtextview-button:checked {
color: @theme_base_color;
border-color: @borders;
text-shadow: none;
-gtk-icon-shadow: none;
box-shadow: none;
.scrolled-no-border {border: none}
.scrolled-no-border undershoot.top, undershoot.bottom { background-image: none; }
.actionbar-no-border box {border: none}
.actionbar-no-border button {
padding: 0px;
background-color: @theme_base_color;
border: none;
background-image: none;
}
#MessageWindow, #RosterWindow paned { background-color: @theme_base_color; }
.msgtextview-button.left { border-right: none; }
.msgtextview-button.right { border-left: none; }
.scrolledtextview { border:none; }
.scrolledtextview { border-left:none; }
.scrolledtextview.authentication { border-right:none; }
.chatcontrol-separator {margin-bottom: 6px;}
#SubjectPopover box { padding: 10px; }
/* VCardWindow */
.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 conversation_textview
from gajim import dataforms_widget
from gajim import gtkspell
from random import randrange
from gajim.common import pep
@ -50,12 +51,6 @@ from gajim.common import const
from gajim.options_dialog import OptionsDialog
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'
# so they can do dialog.GajimThemesWindow() for example
from gajim.filetransfers_window import FileTransfersWindow
@ -1486,11 +1481,11 @@ class FileChooserDialog(Gtk.FileChooserDialog):
class AspellDictError:
def __init__(self, lang):
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.'
'\n\nHighlighting misspelled words feature will not be used') % lang)
app.config.set('use_speller', False)
_('Dictionary for lang "%s" not available') % lang,
_('You have to install the dictionary "%s" to use spellchecking, '
'or choose another language by setting the speller_language '
'option.\n\n'
'Highlighting misspelled words feature will not be used') % lang)
class ConfirmationDialog(HigDialog):
"""
@ -3082,14 +3077,14 @@ class SingleMessageWindow:
else:
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:
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
gtkspell.Spell(self.conversation_textview.tv, lang)
gtkspell.Spell(self.message_textview, lang)
except (GObject.GError, TypeError, RuntimeError, OSError):
self.spell = gtkspell.Spell(self.message_textview, lang)
self.spell.attach(self.message_textview)
except OSError:
AspellDictError(lang)
self.prepare_widgets_for(self.action)

View File

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

View File

@ -212,8 +212,15 @@ class GajimApplication(Gtk.Application):
builder = Gtk.Builder()
builder.set_translation_domain(i18n.APP)
builder.add_from_file(path)
self.set_menubar(builder.get_object("menubar"))
self.set_app_menu(builder.get_object("appmenu"))
menubar = builder.get_object("menubar")
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):
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 Pango
from gi.repository import GLib
from gi.repository import Gio
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim import message_control
@ -251,15 +252,6 @@ class PrivateChatControl(ChatControl):
return
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):
self.contact = self.gc_contact.as_contact()
@ -274,20 +266,6 @@ class PrivateChatControl(ChatControl):
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):
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
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.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.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
if parent_win is not None:
# 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.add_actions()
widget = self.xml.get_object('list_treeview')
id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
@ -399,8 +339,6 @@ class GroupchatControl(ChatControlBase):
if not self.name:
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'),
app.config.get('hide_groupchat_banner'))
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)
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
hpaned_position = app.config.get('gc-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))
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,
self._nec_gc_presence_received)
app.ged.register_event_handler('gc-message-received', ged.GUI1,
@ -538,14 +496,199 @@ class GroupchatControl(ChatControlBase):
self.update_ui()
self.widget.show_all()
# PluginSystem: adding GUI extension point for this GroupchatControl
# instance object
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):
self.set_tooltip()
self.add_window_actions()
self.add_actions()
self.update_actions()
self.set_lock_image()
self._schedule_activity_timers()
@ -823,10 +966,6 @@ class GroupchatControl(ChatControlBase):
self.authentication_button.set_tooltip_text(tooltip)
self.widget_set_visible(self.authentication_button, not visible)
context = self.msg_scrolledwindow.get_style_context()
if visible:
context.add_class('authentication')
else:
context.remove_class('authentication')
self.lock_image.set_sensitive(visible)
def _on_authentication_button_clicked(self, widget):
@ -886,7 +1025,6 @@ class GroupchatControl(ChatControlBase):
room jid, subject
"""
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()
if self.is_continued:
name = self.get_continued_conversation_name()
@ -896,169 +1034,10 @@ class GroupchatControl(ChatControlBase):
self.name_label.set_markup(text)
if self.subject:
subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
subject = GLib.markup_escape_text(subject)
subject = GLib.markup_escape_text(self.subject)
subject_text = self.urlfinder.sub(self.make_href, subject)
subject_text = '<span %s>%s</span>' % (font_attrs_small,
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()
subject_text = '<span>%s</span>' % subject_text
self.subject_button.get_popover().set_text(subject_text)
def _nec_vcard_published(self, obj):
if obj.conn.name != self.account:
@ -1379,6 +1358,11 @@ class GroupchatControl(ChatControlBase):
else:
self.print_conversation(text)
if obj.subject == '':
self.subject_button.hide()
else:
self.subject_button.show()
def _nec_gc_config_changed_received(self, obj):
# statuscode is a list
# 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.set_sensitive(True)
change_nick_button = self.xml.get_object('change_nick_button')
change_nick_button.set_sensitive(True)
change_subject_button = self.xml.get_object('change_subject_button')
change_subject_button.set_sensitive(True)
self.update_actions()
def got_disconnected(self):
formattings_button = self.xml.get_object('formattings_button')
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.model.clear()
nick_list = app.contacts.get_nick_list(self.account, self.room_jid)
@ -1512,6 +1490,8 @@ class GroupchatControl(ChatControlBase):
if ar_to:
self.autorejoin = GLib.timeout_add_seconds(ar_to, self.rejoin)
self.update_actions()
def rejoin(self):
if not self.autorejoin:
return False
@ -1904,6 +1884,11 @@ class GroupchatControl(ChatControlBase):
st += ' (' + obj.status + ')'
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,
jid='', avatar_sha=None):
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,
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,
target_type, timestamp):
# Invite contact to groupchat
@ -2913,3 +2837,40 @@ class GroupchatControl(ChatControlBase):
self.grant_owner(widget, jid)
else:
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)
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)
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):
try:

View File

@ -19,9 +19,17 @@
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GLib
import gi
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 f(self, *args, **kwargs):
@ -47,12 +55,13 @@ class Spell(GObject.GObject):
if spell:
raise RuntimeError("Textview has already a Spell obj attached")
self.spell = GtkSpell.Checker.new()
if not self.spell:
raise OSError("Unable to create spell object.")
if not self.spell.attach(textview):
raise OSError("Unable to attach spell object.")
if not self.spell.set_language(language):
raise OSError("Unable to set language: '%s'" % language)
try:
self.spell.set_language(language)
except GLib.GError as error:
if error.domain == 'gtkspell-error-quark':
raise OSError("Unable to set language: '%s'" % language)
self.spell.connect('language-changed', self.on_language_changed)
else:
@ -73,12 +82,25 @@ class Spell(GObject.GObject):
def recheck_all(self):
self.spell.recheck_all()
@ensure_attached
def detach(self):
self.spell.detach()
self.spell = None
if self.spell is not None:
self.spell.detach()
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)
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
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') == '':
# only on first time Gajim starts
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):
if not app.connections[account].bookmarks:
return None
@ -708,7 +767,11 @@ def get_account_menu(account):
def build_accounts_menu():
menubar = app.app.get_menubar()
# 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()
accounts_list = sorted(app.contacts.get_accounts())
if not accounts_list:
@ -721,8 +784,8 @@ def build_accounts_menu():
acc, get_account_menu(acc))
else:
acc_menu = get_account_menu(accounts_list[0])
menubar.remove(0)
menubar.insert_submenu(0, 'Accounts', acc_menu)
menubar.remove(menu_position)
menubar.insert_submenu(menu_position, 'Accounts', acc_menu)
def build_bookmark_menu(account):
@ -731,8 +794,12 @@ def build_bookmark_menu(account):
if not bookmark_menu:
return
menu_position = 0
if os.name == 'nt':
menu_position = 1
# 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
if acc_menu.get_item_link(0, 'submenu'):

View File

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

View File

@ -24,7 +24,6 @@
import gc
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Pango
@ -37,6 +36,7 @@ class MessageTextView(Gtk.TextView):
chat/groupchat windows
"""
UNDO_LIMIT = 20
PLACEHOLDER = _('Write a message..')
def __init__(self):
Gtk.TextView.__init__(self)
@ -57,13 +57,16 @@ class MessageTextView(Gtk.TextView):
self.undo_list = []
# needed to know if we undid something
self.undo_pressed = False
self.lang = None # Lang used for spell checking
_buffer = self.get_buffer()
self.begin_tags = {}
self.end_tags = {}
self.color_tags = []
self.fonts_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'].set_property('weight', Pango.Weight.BOLD)
self.begin_tags['bold'] = '<strong>'
@ -82,6 +85,33 @@ class MessageTextView(Gtk.TextView):
self.end_tags['strike'] = '</span>'
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):
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.window = self.xml.get_object(self.widget_name)
self.window.set_application(app.app)
self.window.set_show_menubar(False)
self.notebook = self.xml.get_object('notebook')
self.parent_paned = None
@ -94,17 +93,26 @@ class MessageWindow(object):
if app.config.get('roster_on_the_right'):
child1 = self.parent_paned.get_child1()
self.parent_paned.remove(child1)
self.parent_paned.add(self.notebook)
self.parent_paned.pack1(self.notebook, resize=False,
shrink=True)
self.parent_paned.pack2(child1, resize=True, shrink=True)
self.parent_paned.pack1(self.notebook, resize=False)
self.parent_paned.pack2(child1)
else:
self.parent_paned.add(self.notebook)
self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
self.parent_paned.pack2(self.notebook)
self.window.lookup_action('show-roster').set_enabled(True)
orig_window.destroy()
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
# MessageWindowMgr._new_window we register handler that saves window
# 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.show_icon()
def set_header_menu(self, menu):
self.header_menu.set_menu_model(menu)
def change_account_name(self, old_name, new_name):
if old_name in self._controls:
self._controls[new_name] = self._controls[old_name]
@ -324,6 +335,7 @@ class MessageWindow(object):
self.notebook.show_all()
else:
self.window.show_all()
# NOTE: we do not call set_control_active(True) since we don't know
# whether the tab is the active one.
self.show_title()
@ -436,9 +448,6 @@ class MessageWindow(object):
elif chr(keyval) in st: # ALT + 1,2,3..
self.notebook.set_current_page(st.index(chr(keyval)))
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
control.emoticons_button.get_popover().show()
return True
@ -570,6 +579,7 @@ class MessageWindow(object):
ask any confirmation
"""
def close(ctrl):
self.remove_headermenu(self.notebook, ctrl)
if reason is not None: # We are leaving gc with a status message
ctrl.shutdown(reason)
else: # We are leaving gc without status message or it's a chat
@ -607,6 +617,7 @@ class MessageWindow(object):
def on_minimize(ctrl):
if method != self.CLOSE_COMMAND:
self.remove_headermenu(self.notebook, ctrl)
ctrl.minimize()
self.check_tabs()
return
@ -618,6 +629,13 @@ class MessageWindow(object):
else:
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):
if self.parent_paned:
# Do nothing in single window mode
@ -822,6 +840,8 @@ class MessageWindow(object):
def popup_menu(self, event):
menu = self.get_active_control().prepare_context_menu()
if menu is None:
return
# show the menu
menu.attach_to_widget(app.interface.roster.window, None)
menu.show_all()
@ -836,6 +856,7 @@ class MessageWindow(object):
new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
new_ctrl.set_control_active(True)
self.show_title(control = new_ctrl)
self.set_header_menu(new_ctrl.control_menu)
control = self.get_active_control()
if isinstance(control, ChatControlBase):
@ -879,6 +900,7 @@ class MessageWindow(object):
if isinstance(control, ChatControlBase):
# we forwarded it to message textview
control.msg_textview.remove_placeholder()
control.msg_textview.event(event)
control.msg_textview.grab_focus()
@ -1289,6 +1311,7 @@ class MessageWindowMgr(GObject.GObject):
gtkgui_helpers.resize_window(w.window,
app.config.get('roster_width'),
app.config.get('roster_height'))
self.hide_header_bar(self.parent_win)
self._windows = {}
@ -1298,8 +1321,16 @@ class MessageWindowMgr(GObject.GObject):
mw = self.create_window(ctrl.contact, ctrl.account,
ctrl.type_id)
ctrl.parent_win = mw
ctrl.add_actions()
ctrl.update_actions()
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):
if not app.config.get('remember_opened_chat_controls'):
return

View File

@ -5696,9 +5696,18 @@ class RosterWindow:
application.add_window(self.window)
self.add_actions()
self.hpaned = self.xml.get_object('roster_hpaned')
app.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
app.interface.msg_win_mgr.connect('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
if app.config.get('roster_window_skip_taskbar'):
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