Commit 833e4fc4 authored by Philipp Hörist's avatar Philipp Hörist Committed by Philipp Hörist
Browse files

New Message Window

parent cb056712
......@@ -31,8 +31,8 @@
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import Pango
from gi.repository import GLib
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
from nbxmpp.protocol import NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER_5
......@@ -52,10 +52,12 @@
from gajim import message_control
from gajim import dialogs
from gajim.gtk.chatlist import ChatControlTab
from gajim.gtk.chatlist import GenericTab
from gajim.gtk.dialogs import ConfirmationDialog
from gajim.gtk.add_contact import AddNewContactWindow
from gajim.gtk.util import get_icon_name
from gajim.gtk.util import get_cursor
from gajim.gtk.util import load_icon
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
......@@ -93,9 +95,6 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
self.emoticons_button = self.xml.get_object('emoticons_button')
self.toggle_emoticons()
self.widget_set_visible(self.xml.get_object('banner_eventbox'),
app.config.get('hide_chat_banner'))
self.authentication_button = self.xml.get_object(
'authentication_button')
id_ = self.authentication_button.connect('clicked',
......@@ -115,8 +114,6 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
settings_menu = self.xml.get_object('settings_menu')
settings_menu.set_menu_model(self.control_menu)
self._audio_banner_image = self.xml.get_object('audio_banner_image')
self._video_banner_image = self.xml.get_object('video_banner_image')
self.audio_sid = None
self.audio_state = self.JINGLE_STATE_NULL
self.audio_available = False
......@@ -124,35 +121,14 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
self.video_state = self.JINGLE_STATE_NULL
self.video_available = False
self.update_toolbar()
self._pep_images = {}
self._pep_images['mood'] = self.xml.get_object('mood_image')
self._pep_images['activity'] = self.xml.get_object('activity_image')
self._pep_images['tune'] = self.xml.get_object('tune_image')
self._pep_images['geoloc'] = self.xml.get_object('location_image')
self.update_all_pep_types()
self.show_avatar()
# Hook up signals
widget = self.xml.get_object('avatar_eventbox')
widget.set_property('height-request', AvatarSize.CHAT)
id_ = widget.connect('button-press-event',
self.on_avatar_eventbox_button_press_event)
self.handlers[id_] = widget
# Tab
if self.TYPE_ID == message_control.TYPE_CHAT:
self.tab = ChatControlTab(self)
self.tab.update()
else:
self.tab = GenericTab(self)
widget = self.xml.get_object('location_eventbox')
id_ = widget.connect('button-release-event',
self.on_location_eventbox_button_release_event)
self.handlers[id_] = widget
id_ = widget.connect('enter-notify-event',
self.on_location_eventbox_enter_notify_event)
self.handlers[id_] = widget
id_ = widget.connect('leave-notify-event',
self.on_location_eventbox_leave_notify_event)
self.handlers[id_] = widget
self.update_toolbar()
for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'):
widget = self.xml.get_object(key + '_button')
......@@ -212,7 +188,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.update_ui(avatar=True)
self.set_lock_image()
self.encryption_menu = self.xml.get_object('encryption_menu')
......@@ -251,13 +227,15 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
app.plugin_manager.gui_extension_point('chat_control', self)
self.update_actions()
self.update_avatar()
def add_actions(self):
super().add_actions()
actions = [
('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
......@@ -405,28 +383,6 @@ def _update_toolbar(self):
self.video_available = False
self.audio_available = False
def update_all_pep_types(self):
for pep_type in self._pep_images:
self.update_pep(pep_type)
def update_pep(self, pep_type):
if isinstance(self.contact, GC_Contact):
return
if pep_type not in self._pep_images:
return
pep = self.contact.pep
img = self._pep_images[pep_type]
if pep_type in pep:
icon = gtkgui_helpers.get_pep_icon(pep[pep_type])
if isinstance(icon, str):
img.set_from_icon_name(icon, Gtk.IconSize.MENU)
else:
img.set_from_pixbuf(icon)
img.set_tooltip_markup(pep[pep_type].as_markup_text())
img.show()
else:
img.hide()
def _nec_pep_received(self, obj):
if obj.conn.name != self.account:
return
......@@ -435,10 +391,8 @@ def _nec_pep_received(self, obj):
if obj.pep_type == 'nick':
self.update_ui()
self.parent_win.redraw_tab(self)
self.tab.update()
self.parent_win.show_title()
else:
self.update_pep(obj.pep_type)
def _update_jingle(self, jingle_type):
if jingle_type not in ('audio', 'video'):
......@@ -569,68 +523,18 @@ def on_sound_hscale_value_changed(self, widget, value):
# Save volume to config
app.config.set('audio_output_volume', value)
def on_avatar_eventbox_button_press_event(self, widget, event):
"""
If right-clicked, show popup
"""
if event.button == 3: # right click
if self.TYPE_ID == message_control.TYPE_CHAT:
sha = app.contacts.get_avatar_sha(
self.account, self.contact.jid)
name = self.contact.get_shown_name()
else:
sha = self.gc_contact.avatar_sha
name = self.gc_contact.get_shown_name()
gui_menu_builder.show_save_as_menu(sha, name)
return True
def on_location_eventbox_button_release_event(self, widget, event):
if 'geoloc' in self.contact.pep:
location = self.contact.pep['geoloc'].data
if ('lat' in location) and ('lon' in location):
uri = 'https://www.openstreetmap.org/?' + \
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
'lon': location['lon']}
helpers.launch_browser_mailer('url', uri)
def on_location_eventbox_leave_notify_event(self, widget, event):
"""
Just moved the mouse so show the cursor
"""
cursor = get_cursor('LEFT_PTR')
self.parent_win.window.get_window().set_cursor(cursor)
def on_location_eventbox_enter_notify_event(self, widget, event):
cursor = get_cursor('HAND2')
self.parent_win.window.get_window().set_cursor(cursor)
def update_ui(self):
# The name banner is drawn here
def update_ui(self, avatar=False):
ChatControlBase.update_ui(self)
self.tab.update()
self.update_toolbar()
self.update_chatstate()
def _update_banner_state_image(self):
contact = app.contacts.get_contact_with_highest_priority(
self.account, self.contact.jid)
if not contact or self.resource:
# For transient contacts
contact = self.contact
show = contact.show
# Set banner image
icon = get_icon_name(show)
banner_status_img = self.xml.get_object('banner_status_image')
banner_status_img.set_from_icon_name(icon, Gtk.IconSize.DND)
def draw_banner_text(self):
"""
Draw the text in the fat line at the top of the window that houses the
name, jid
"""
def update_banner(self):
contact = self.contact
jid = contact.jid
# jid = contact.jid
banner_name_label = self.xml.get_object('banner_name_label')
banner_account_label = self.xml.get_object('banner_account_label')
name = contact.get_shown_name()
if self.resource:
......@@ -641,70 +545,93 @@ def draw_banner_text(self):
{'nickname': name, 'room_name': self.room_name}
name = i18n.direction_mark + GLib.markup_escape_text(name)
# Set Account Label
# We know our contacts nick, but if another contact has the same nick
# in another account we need to also display the account.
# except if we are talking to two different resources of the same contact
acct_info = ''
for account in app.contacts.get_accounts():
if account == self.account:
continue
if acct_info: # We already found a contact with same nick
break
for jid in app.contacts.get_jid_list(account):
other_contact_ = \
app.contacts.get_first_contact_from_jid(account, jid)
if other_contact_.get_shown_name() == \
self.contact.get_shown_name():
acct_info = i18n.direction_mark + ' (%s)' % \
GLib.markup_escape_text(
app.get_account_label(self.account))
break
#TODO
# for account in app.contacts.get_accounts():
# if account == self.account:
# continue
# if acct_info: # We already found a contact with same nick
# break
# for jid in app.contacts.get_jid_list(account):
# other_contact_ = \
# app.contacts.get_first_contact_from_jid(account, jid)
# if other_contact_.get_shown_name() == \
# self.contact.get_shown_name():
# acct_info = i18n.direction_mark + ' (%s)' % \
# GLib.markup_escape_text(
# app.get_account_label(self.account))
# break
# status = contact.status
# if status is not None:
# banner_name_label.set_ellipsize(Pango.EllipsizeMode.END)
# self.banner_status_label.set_ellipsize(Pango.EllipsizeMode.END)
# status_reduced = helpers.reduce_chars_newlines(status, max_lines=1)
# else:
# status_reduced = ''
# status_escaped = GLib.markup_escape_text(status_reduced)
# st = app.config.get('displayed_chat_state_notifications')
# if self.TYPE_ID == 'pm':
# cs = self.gc_contact.chatstate
# else:
# cs = app.contacts.get_combined_chatstate(
# self.account, self.contact.jid)
# if cs and st in ('composing_only', 'all'):
# if contact.show == 'offline':
# chatstate = ''
# elif st == 'all' or cs == 'composing':
# chatstate = helpers.get_uf_chatstate(cs)
# else:
# chatstate = ''
if len(app.contacts.get_accounts()) > 1:
acct_info = app.config.get_per('accounts', self.account, 'account_label') or self.account
banner_account_label.set_text(acct_info)
# Set Status Label
status = ''
if contact.status is not None:
status = helpers.reduce_chars_newlines(contact.status, max_lines=1)
status = GLib.markup_escape_text(status)
status = self.urlfinder.sub(self.make_href, status)
self.banner_status_label.set_tooltip_text(contact.status)
self.banner_status_label.set_text(status)
# Set Name Label
banner_name_label.set_text(name)
banner_name_label.set_tooltip_text('%s%s' % (name, acct_info))
def update_chatstate(self):
banner_chatstate = self.xml.get_object('banner_chatstate')
if not app.account_is_connected(self.account):
banner_chatstate.set_text('')
if not app.config.get('show_chatstate_in_banner'):
banner_chatstate.set_text('')
return
status = contact.status
if status is not None:
banner_name_label.set_ellipsize(Pango.EllipsizeMode.END)
self.banner_status_label.set_ellipsize(Pango.EllipsizeMode.END)
status_reduced = helpers.reduce_chars_newlines(status, max_lines=1)
else:
status_reduced = ''
status_escaped = GLib.markup_escape_text(status_reduced)
if self.contact.show == 'offline':
banner_chatstate.set_text('')
return
text = helpers.get_uf_chatstate(self.get_chatstate())
banner_chatstate.set_text(text)
self.tab.update_chatstate()
def get_chatstate(self):
if self.TYPE_ID == 'pm':
cs = self.gc_contact.chatstate
chatstate = self.gc_contact.chatstate
else:
cs = app.contacts.get_combined_chatstate(
chatstate = app.contacts.get_combined_chatstate(
self.account, self.contact.jid)
if app.config.get('show_chatstate_in_banner'):
chatstate = helpers.get_uf_chatstate(cs)
label_text = '<span>%s</span><span size="x-small" weight="light">%s %s</span>' \
% (name, acct_info, chatstate)
if acct_info:
acct_info = i18n.direction_mark + ' ' + acct_info
label_tooltip = '%s%s %s' % (name, acct_info, chatstate)
else:
label_text = '<span>%s</span><span size="x-small" weight="light">%s</span>' % \
(name, acct_info)
if acct_info:
acct_info = i18n.direction_mark + ' ' + acct_info
label_tooltip = '%s%s' % (name, acct_info)
if status_escaped:
status_text = self.urlfinder.sub(self.make_href, status_escaped)
status_text = '<span size="x-small" weight="light">%s</span>' % status_text
self.banner_status_label.set_tooltip_text(status)
self.banner_status_label.set_no_show_all(False)
self.banner_status_label.show()
else:
status_text = ''
self.banner_status_label.hide()
self.banner_status_label.set_no_show_all(True)
self.banner_status_label.set_markup(status_text)
# setup the label that holds name and jid
banner_name_label.set_markup(label_text)
banner_name_label.set_tooltip_text(label_tooltip)
return chatstate
def close_jingle_content(self, jingle_type):
sid = getattr(self, jingle_type + '_sid')
......@@ -867,6 +794,7 @@ def _message_sent(self, obj):
gtkgui_helpers.remove_css_class(
self.msg_textview, 'gajim-msg-correcting')
self.tab.update_last_active()
self.print_conversation(obj.message, self.contact.jid, tim=obj.timestamp,
encrypted=obj.encrypted, xep0184_id=xep0184_id, xhtml=obj.xhtml,
displaymarking=displaymarking, msg_stanza_id=id_,
......@@ -972,25 +900,10 @@ def _receipt_received(self, event):
self.conv_textview.show_xep0184_ack(event.receipt_id)
def get_tab_label(self):
unread = ''
if self.resource:
jid = self.contact.get_full_jid()
else:
jid = self.contact.jid
num_unread = len(app.events.get_events(self.account, jid,
['printed_' + self.type_id, self.type_id]))
if num_unread == 1 and not app.config.get('show_unread_tab_icon'):
unread = '*'
elif num_unread > 1:
unread = '[' + str(num_unread) + ']'
name = self.contact.get_shown_name()
if self.resource:
name += '/' + self.resource
label_str = GLib.markup_escape_text(name)
if num_unread: # if unread, text in the label becomes bold
label_str = '<b>' + unread + label_str + '</b>'
return label_str
return name
def get_tab_image(self, count_unread=True):
if self.resource:
......@@ -1146,15 +1059,7 @@ def _nec_chatstate_received(self, event):
if event.contact.jid != self.contact.jid:
return
self.draw_banner_text()
# update chatstate in tab for this chat
if event.contact.is_gc_contact:
chatstate = event.contact.chatstate
else:
chatstate = app.contacts.get_combined_chatstate(
self.account, self.contact.jid)
self.parent_win.redraw_tab(self, chatstate)
self.update_chatstate()
def _nec_caps_received(self, obj):
if obj.conn.name != self.account:
......@@ -1176,30 +1081,13 @@ def _nec_ping(self, obj):
elif obj.name == 'ping-error':
self.print_conversation(_('Error.'), 'status')
def show_avatar(self):
if not app.config.get('show_avatar_in_chat'):
return
scale = self.parent_win.window.get_scale_factor()
if self.TYPE_ID == message_control.TYPE_CHAT:
surface = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT, scale)
else:
surface = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT, scale)
image = self.xml.get_object('avatar_image')
if surface is None:
image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else:
image.set_from_surface(surface)
def _nec_update_avatar(self, obj):
if obj.account != self.account:
return
if obj.jid != self.contact.jid:
return
self.show_avatar()
self.update_avatar()
self.tab.update()
def _on_drag_data_received(self, widget, context, x, y, selection,
target_type, timestamp):
......@@ -1368,18 +1256,20 @@ def got_connected(self):
if contact:
self.contact = contact
self.draw_banner()
self.update_chatstate()
self.update_actions()
def got_disconnected(self):
ChatControlBase.got_disconnected(self)
self.update_actions()
self.update_chatstate()
def update_status_display(self, name, uf_show, status):
"""
Print the contact's status and update the status/GPG image
"""
self.update_ui()
self.parent_win.redraw_tab(self)
self.tab.update()
if status:
status = '- %s' % status
......@@ -1561,3 +1451,26 @@ def on_event_removed(self, event_list):
GLib.idle_add(self._info_bar_show_message)
break
i += 1
# Banner Methods
def update_avatar(self):
image = self.xml.get_object('avatar_image')
if not app.config.get('show_avatar_in_chat'):
image.hide()
return
scale = self.parent_win.window.get_scale_factor()
if self.TYPE_ID == message_control.TYPE_CHAT:
surface = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.TAB, scale)
else:
surface = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.TAB, scale)
if surface is None:
surface = load_icon('avatar-default',
size=AvatarSize.TAB,
scale=scale)
image.set_from_surface(surface)
......@@ -38,6 +38,7 @@
from gajim.common import ged
from gajim.common import i18n
from gajim.common.i18n import _
from gajim.common.contacts import GC_Contact
from gajim.common.connection_handlers_events import MessageOutgoingEvent
from gajim.common.const import StyleAttr
......@@ -121,8 +122,7 @@ def draw_banner(self):
Derived types MAY implement this.
"""
self.draw_banner_text()
self._update_banner_state_image()
self.update_banner()
app.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
self)
......@@ -134,7 +134,7 @@ def update_toolbar(self):
app.plugin_manager.gui_extension_point(
'chat_control_base_update_toolbar', self)
def draw_banner_text(self):
def update_banner(self):
"""
Derived types SHOULD implement this
"""
......@@ -175,7 +175,7 @@ def _nec_our_status(self, obj):
if not self.type_id == message_control.TYPE_GC:
self.got_connected()
if self.parent_win:
self.parent_win.redraw_tab(self)
self.tab.update()
def status_url_clicked(self, widget, url):
helpers.launch_browser_mailer('url', url)
......@@ -238,13 +238,6 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
MessageControl.__init__(self, type_id, parent_win, widget_name,
contact, acct, resource=resource)
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<>\)'\"\]]")
......@@ -293,7 +286,6 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget
vscrollbar = self.conv_scrolledwindow.get_vscrollbar()
id_ = vscrollbar.connect('button-release-event',
self._on_scrollbar_button_release)
......@@ -382,6 +374,22 @@ def __init__(self, type_id, parent_win, widget_name, contact, acct,
CommandTools.__init__(self)
def add_actions(self):
actions = [
('add-to-favorites-', self._on_add_to_favorites, True),
('remove-from-favorites-', self._on_remove_from_favorites, True),
('send-file-', self._on_send_file, False),
('send-file-httpupload-', self._on_send_httpupload, False),
('send-file-jingle-', self._on_send_jingle, False),
]
for action in actions:
action_name, func, enabled = action
act = Gio.SimpleAction.new(action_name + self.control_id, None)
act.connect("activate", func)
act.set_enabled(enabled)
self.parent_win.window.add_action(act)
action = Gio.SimpleAction.new_stateful(
"set-encryption-%s" % self.control_id,
GLib.VariantType.new("s"),
......@@ -389,25 +397,12 @@ def add_actions(self):
action.connect("change-state", self.change_encryption)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-%s' % self.control_id, None)
action.connect('activate', self._on_send_file)
action.set_enabled(False)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-httpupload-%s' % self.control_id, None)
action.connect('activate', self._on_send_httpupload)
action.set_enabled(False)
self.parent_win.window.add_action(action)
action = Gio.SimpleAction.new(
'send-file-jingle-%s' % self.control_id, None)
action.connect('activate', self._on_send_jingle)
action.set_enabled(False)
self.parent_win.window.add_action(action)
# Actions
def _on_add_to_favorites(self, _action, _param):
self.tab.is_favorite = True
def _on_remove_from_favorites(self, _action, _param):
self.tab.is_favorite = False
def change_encryption(self, action, param):