diff --git a/gajim/chat_control.py b/gajim/chat_control.py
index a00f6b81b..d21aaf192 100644
--- a/gajim/chat_control.py
+++ b/gajim/chat_control.py
@@ -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
@@ -95,60 +95,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 +111,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 +219,7 @@ class ChatControl(ChatControlBase):
# Enable encryption if needed
self.no_autonegotiation = False
-
+ self.add_actions()
self.update_ui()
self.set_lock_image()
@@ -298,6 +247,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 +379,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 +390,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 +736,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 +780,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 +811,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):
@@ -1272,10 +1247,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 +1429,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 +1496,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):
"""
diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py
index 23c5045b9..db05c10d6 100644
--- a/gajim/chat_control_base.py
+++ b/gajim/chat_control_base.py
@@ -44,7 +44,6 @@ from gajim import notify
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
@@ -256,28 +255,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 +324,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)
@@ -417,6 +408,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':
@@ -549,6 +562,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 +1297,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 +1309,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
diff --git a/gajim/command_system/implementation/standard.py b/gajim/command_system/implementation/standard.py
index 87b068e73..0d96cc30f 100644
--- a/gajim/command_system/implementation/standard.py
+++ b/gajim/command_system/implementation/standard.py
@@ -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):
diff --git a/gajim/common/config.py b/gajim/common/config.py
index 4da11fe47..c805f97ae 100644
--- a/gajim/common/config.py
+++ b/gajim/common/config.py
@@ -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.')],
diff --git a/gajim/common/connection.py b/gajim/common/connection.py
index bc4c65183..131049419 100644
--- a/gajim/common/connection.py
+++ b/gajim/common/connection.py
@@ -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
diff --git a/gajim/common/const.py b/gajim/common/const.py
index 3ab2706b5..baa92d679 100644
--- a/gajim/common/const.py
+++ b/gajim/common/const.py
@@ -30,8 +30,8 @@ class OptionType(IntEnum):
class AvatarSize(IntEnum):
ROSTER = 32
+ CHAT = 48
NOTIFICATION = 48
- CHAT = 52
PROFILE = 64
TOOLTIP = 125
VCARD = 200
diff --git a/gajim/config.py b/gajim/config.py
index 61ad624fd..12d1a199e 100644
--- a/gajim/config.py
+++ b/gajim/config.py
@@ -187,10 +187,6 @@ 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)
@@ -657,12 +653,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()
diff --git a/gajim/data/gui/chat_control.ui b/gajim/data/gui/chat_control.ui
index db4abefe6..d50f13956 100644
--- a/gajim/data/gui/chat_control.ui
+++ b/gajim/data/gui/chat_control.ui
@@ -374,7 +374,6 @@
True
False
vertical
- 5
-
+
True
False
- False
-
-
- True
- False
- gtk-missing-image
-
-
+ gtk-missing-image
False
False
+ 5
3
@@ -573,94 +573,16 @@
-
- True
- False
- vertical
- 6
+
+ 60
+ True
+ in
-
- 60
- True
- in
-
-
-
-
-
- True
- True
- 0
-
-
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
-
-
-
- False
- True
- 0
-
-
-
-
-
-
-
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- none
-
-
- True
- False
- gtk-dialog-authentication
- 1
-
-
-
-
-
- False
- False
- end
- 2
-
-
-
-
- False
- False
- 1
-
+
+
True
@@ -669,9 +591,48 @@
-
+
True
False
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ True
+ False
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+
+
+
+ False
+ True
+ 0
+
+
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- vertical
+
False
@@ -711,235 +662,15 @@
-
- True
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- True
- Add this contact to roster (Ctrl+D)
- Add this contact to roster (Ctrl+D)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-add
- 1
-
-
-
-
- False
- False
- 2
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- 1
-
-
-
-
- False
- True
- 3
-
-
-
-
- True
- True
- True
- none
-
-
- True
- False
- gtk-missing-image
- 1
-
-
-
-
- False
- True
- 4
-
-
-
-
- True
- True
- True
- none
-
-
- True
- False
- gtk-missing-image
- 1
-
-
-
-
- False
- True
- 5
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Invite contacts to the conversation (Ctrl+G)
- Invite contacts to the conversation (Ctrl+G)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-missing-image
- 1
-
-
-
-
- False
- True
- 6
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Show the contact's profile (Ctrl+I)
- Show the contact's profile (Ctrl+I)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-info
- 1
-
-
-
-
- False
- True
- 7
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Browse the chat history (Ctrl+H)
- Browse the chat history (Ctrl+H)
- none
-
-
- True
- False
- gtk-justify-fill
- 1
-
-
-
-
- False
- True
- 8
-
-
-
-
+
True
False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- vertical
- False
+ True
True
- 9
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Show advanced functions (Alt+D)
- Show advanced functions (Alt+D)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-execute
- 1
-
-
-
-
- False
- True
- 10
-
-
-
-
-
- False
- True
- 11
+ end
+ 2
@@ -1042,26 +773,70 @@ audio-mic-volume-low
False
True
- 12
+ end
+ 3
-
- True
- False
+
+ True
+ False
+ True
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ True
+ none
+
+
+ True
+ False
+ gtk-dialog-authentication
+ 1
+
+
+
- True
+ False
+ False
+ end
+ 4
+
+
+
+
+
+
+
+
+ False
True
- 13
+ end
+ 6
False
- True
- end
- 2
+ False
+ 3
diff --git a/gajim/data/gui/groupchat_control.ui b/gajim/data/gui/groupchat_control.ui
index 813f27d91..adcd77b26 100644
--- a/gajim/data/gui/groupchat_control.ui
+++ b/gajim/data/gui/groupchat_control.ui
@@ -2,6 +2,82 @@
+
True
@@ -13,83 +89,6 @@
7
vertical
5
-
-
- GroupChatControl-BannerEventBox
- True
- False
-
-
- True
- False
-
-
- True
- False
- gtk-missing-image
-
-
- False
- False
- 5
- 0
-
-
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- 5
- vertical
-
-
- GroupChatControl-BannerNameLabel
- True
- False
- <span weight="heavy" size="large">room jid</span>
- True
- True
- 0
-
-
- True
- True
- 0
-
-
-
-
- GroupChatControl-BannerLabel
- True
- False
- label
- True
- True
- 0
-
-
- True
- True
- 1
-
-
-
-
- True
- True
- 1
-
-
-
-
-
-
- False
- True
- 0
-
-
True
@@ -104,7 +103,68 @@
False
4
vertical
- 6
+
+
+ GroupChatControl-BannerEventBox
+ True
+ False
+
+
+ True
+ False
+
+
+ True
+ False
+ gtk-missing-image
+
+
+ False
+ False
+ 5
+ 0
+
+
+
+
+ GroupChatControl-BannerNameLabel
+ True
+ False
+ <span weight="heavy" size="large">room jid</span>
+ True
+ True
+ 0
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ False
+
+
+
+ True
+ True
+ end
+ 2
+
+
+
+
+
+
+ False
+ True
+ 0
+
+
200
@@ -115,11 +175,28 @@
+
True
True
- 0
+ 1
+
+
+
+
+ True
+ False
+
+
+
+ False
+ True
+ 2
@@ -138,12 +215,11 @@
True
False
- face-smile
+ face-smile-symbolic
@@ -153,7 +229,32 @@
-
+
+
+ False
+ True
+ 1
+
@@ -172,8 +273,7 @@
@@ -183,190 +283,19 @@
2
-
-
- False
- False
- 1
-
-
-
-
- 34
- True
- False
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Show a list of formattings
- none
-
-
- True
- False
- gtk-bold
- 1
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- True
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Change your nickname (Ctrl+N)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-edit
- 1
-
-
-
-
- False
- False
- 1
-
-
-
-
- True
- True
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Change the room's subject (Alt+T)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-properties
- 1
-
-
-
-
- False
- False
- 2
-
-
-
-
- True
- True
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- True
- Bookmark this room (Ctrl+B)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-add
- 1
-
-
-
-
- False
- False
- 3
-
-
-
-
- True
- True
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Browse the chat history (Ctrl+H)
- none
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- gtk-justify-fill
- 1
-
-
-
-
- False
- False
- 4
-
-
-
-
- True
- False
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
-
-
- False
- True
- 5
-
-
-
-
- True
- True
- False
- True
- GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- True
- Show advanced functions (Alt+D)
- none
-
-
- True
- False
- gtk-execute
- 1
-
-
-
-
- False
- False
- 6
-
+
True
False
+ True
True
True
- 7
+ 4
@@ -383,18 +312,22 @@
channel-secure-symbolic
+
False
True
- 8
+ end
+ 5
False
- True
- 2
+ False
+ 3
@@ -422,6 +355,9 @@
+
False
diff --git a/gajim/data/gui/message_window.ui b/gajim/data/gui/message_window.ui
index 0aab2b026..854cd7db3 100644
--- a/gajim/data/gui/message_window.ui
+++ b/gajim/data/gui/message_window.ui
@@ -1,5 +1,5 @@
-
+
@@ -28,10 +28,10 @@
70
True
False
- 0
True
end
9
+ 0
False
@@ -64,15 +64,35 @@
+
+ MessageWindow
False
480
440
+ False
True
True
- 2
True
diff --git a/gajim/data/gui/preferences_window.ui b/gajim/data/gui/preferences_window.ui
index 15a2156a9..101c4f255 100644
--- a/gajim/data/gui/preferences_window.ui
+++ b/gajim/data/gui/preferences_window.ui
@@ -426,23 +426,6 @@
2
-
-
- Ma_ke message windows compact
- True
- False
- Hide all buttons in chat windows
- True
- 0
- True
-
-
-
- 0
- 3
- 2
-
-
_Ignore rich content in incoming messages
@@ -457,7 +440,7 @@
0
- 4
+ 3
2
@@ -474,7 +457,7 @@
0
- 5
+ 4
2
@@ -490,7 +473,7 @@
0
- 6
+ 5
2
@@ -555,7 +538,7 @@
0
- 7
+ 6
2
diff --git a/gajim/data/gui/roster_window.ui b/gajim/data/gui/roster_window.ui
index e70381f9e..4601484bc 100644
--- a/gajim/data/gui/roster_window.ui
+++ b/gajim/data/gui/roster_window.ui
@@ -4,6 +4,7 @@
+ RosterWindow
85
200
False
@@ -115,4 +116,28 @@
+
diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css
index 83c6b9267..ce8663eff 100644
--- a/gajim/data/style/gajim.css
+++ b/gajim/data/style/gajim.css
@@ -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; }
diff --git a/gajim/emoticons.py b/gajim/emoticons.py
index f0e6c6ceb..2d39b60ee 100644
--- a/gajim/emoticons.py
+++ b/gajim/emoticons.py
@@ -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(' ')
diff --git a/gajim/gajim.py b/gajim/gajim.py
index ddaa8e7b3..2263ac531 100644
--- a/gajim/gajim.py
+++ b/gajim/gajim.py
@@ -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)
diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py
index 9c9fd90b1..b2c837bed 100644
--- a/gajim/groupchat_control.py
+++ b/gajim/groupchat_control.py
@@ -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 = '%s' % (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 = '%s' % 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)
diff --git a/gajim/gtkgui_helpers.py b/gajim/gtkgui_helpers.py
index c44b41e78..1db67d1bc 100644
--- a/gajim/gtkgui_helpers.py
+++ b/gajim/gtkgui_helpers.py
@@ -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:
diff --git a/gajim/gtkspell.py b/gajim/gtkspell.py
index bb75b8e1c..24d52e9d8 100644
--- a/gajim/gtkspell.py
+++ b/gajim/gtkspell.py
@@ -23,6 +23,7 @@ import gi
gi.require_version('GtkSpell', '3.0')
from gi.repository import GtkSpell
+
def ensure_attached(func):
def f(self, *args, **kwargs):
if self.spell:
diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py
index b07d9b45d..8436acf3d 100644
--- a/gajim/gui_menu_builder.py
+++ b/gajim/gui_menu_builder.py
@@ -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'):
diff --git a/gajim/message_control.py b/gajim/message_control.py
index 2246c6ef1..a1216ba88 100644
--- a/gajim/message_control.py
+++ b/gajim/message_control.py
@@ -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
diff --git a/gajim/message_textview.py b/gajim/message_textview.py
index bcaa81cdf..bd760587a 100644
--- a/gajim/message_textview.py
+++ b/gajim/message_textview.py
@@ -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)
@@ -64,6 +64,9 @@ class MessageTextView(Gtk.TextView):
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'] = ''
@@ -82,6 +85,30 @@ class MessageTextView(Gtk.TextView):
self.end_tags['strike'] = ''
self.connect_after('paste-clipboard', self.after_paste_clipboard)
+ self.connect('focus-in-event', self._on_focus_in)
+ self.connect('focus-out-event', self._on_focus_out)
+
+ start, end = _buffer.get_bounds()
+ _buffer.insert_with_tags(
+ start, self.PLACEHOLDER, self.placeholder_tag)
+
+ def _on_focus_in(self, *args):
+ buf = self.get_buffer()
+ start, end = buf.get_bounds()
+ text = buf.get_text(start, end, True)
+ if text == self.PLACEHOLDER:
+ buf.set_text('')
+
+ def _on_focus_out(self, *args):
+ buf = self.get_buffer()
+ start, end = buf.get_bounds()
+ text = buf.get_text(start, end, True)
+ if text == '':
+ buf.insert_with_tags(
+ start, self.PLACEHOLDER, self.placeholder_tag)
+
+ def remove_placeholder(self):
+ self._on_focus_in()
def after_paste_clipboard(self, textview):
buffer_ = textview.get_buffer()
diff --git a/gajim/message_window.py b/gajim/message_window.py
index d3b648583..b8c84b7f5 100644
--- a/gajim/message_window.py
+++ b/gajim/message_window.py
@@ -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
diff --git a/gajim/roster_window.py b/gajim/roster_window.py
index 6e8433d00..0d09827c0 100644
--- a/gajim/roster_window.py
+++ b/gajim/roster_window.py
@@ -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)
diff --git a/gajim/scrolled_window.py b/gajim/scrolled_window.py
deleted file mode 100644
index 9b7392275..000000000
--- a/gajim/scrolled_window.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding:utf-8 -*-
-# Copyright (C) 2015 Patrick Griffis
-# Copyright (C) 2014 Christian Hergert
-#
-# 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 .
-
-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