Commit 970d6f8c authored by Philipp Hörist's avatar Philipp Hörist
Browse files

New style for ChatControl

- Move ActionBar into HeaderMenu
- Make Design of ChatControl look cleaner
- Hide the Roster in Groupchats per default
- Add Button to hide/show Roster in Groupchats
- Move Groupchat topic into popover
- Display Avatars on the right side of the ChatControl and status on the
left
- Add a default Avatar for contacts that have none
parent 398ad0ee
......@@ -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 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
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 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
# 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 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
# Enable encryption if needed
self.no_autonegotiation = False
self.add_actions()
self.update_ui()
self.set_lock_image()
......@@ -298,6 +247,107 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
# 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 @@ def _update_toolbar(self):
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 @@ def _update_toolbar(self):
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 @@ def close_jingle_content(self, jingle_type):
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 @@ def on_jingle_button_toggled(self, widget, jingle_type):
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 @@ def _show_lock_image(self, visible, enc_type='', authenticated=False):
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 @@ def show_avatar(self):
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 @@ def read_queue(self):
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 @@ def got_connected(self):
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):
"""
......
......@@ -44,7 +44,6 @@
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 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
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 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
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 @@ def add_window_actions(self):
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 @@ def on_msg_textview_populate_popup(self, textview, menu):
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 @@ def widget_set_visible(self, widget, state):
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 @@ def got_disconnected(self):
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
......@@ -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):
......
......@@ -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.')],
......
......@@ -2342,6 +2342,13 @@ def _nec_privacy_list_received(self, obj):
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
......
......@@ -30,8 +30,8 @@ class OptionType(IntEnum):
class AvatarSize(IntEnum):
ROSTER = 32
CHAT = 48
NOTIFICATION = 48
CHAT = 52
PROFILE = 64
TOOLTIP = 125
VCARD = 200
......
......@@ -187,10 +187,6 @@ def __init__(self):
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 @@ def on_show_roster_on_startup_changed(self, widget):
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()
......
This diff is collapsed.
This diff is collapsed.
<?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>