Refactor GC Roster tooltip

This commit is contained in:
lovetox 2016-11-13 21:06:37 +01:00
parent ec0a11fe1a
commit 7a278d9123
3 changed files with 289 additions and 181 deletions

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkGrid" id="tooltip_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="nick">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="wrap">True</property>
<property name="max_width_chars">40</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="jid_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Jabber ID:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkImage" id="avatar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
<property name="height">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="resource_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Resource:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="affiliation">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="user_show">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="fillelement">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="vexpand">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="resource">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="jid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
</object>
</interface>

View File

@ -360,6 +360,12 @@ class GroupchatControl(ChatControlBase):
img = self.xml.get_object('history_image')
img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
widget = self.xml.get_object('list_treeview')
widget.set_has_tooltip(True)
widget.set_tooltip_window(tooltips.GCTooltip(self.parent_win.window))
self.current_tooltip = None
id_ = widget.connect('query-tooltip', self.query_tooltip)
self.handlers[id_] = widget
id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
self.handlers[id_] = widget
@ -379,14 +385,6 @@ class GroupchatControl(ChatControlBase):
self.on_list_treeview_key_press_event)
self.handlers[id_] = widget
id_ = widget.connect('motion_notify_event',
self.on_list_treeview_motion_notify_event)
self.handlers[id_] = widget
id_ = widget.connect('leave_notify_event',
self.on_list_treeview_leave_notify_event)
self.handlers[id_] = widget
self.room_jid = self.contact.jid
self.nick = contact.name
self.new_nick = ''
@ -419,8 +417,6 @@ class GroupchatControl(ChatControlBase):
self.subject = ''
self.tooltip = tooltips.GCTooltip()
# nickname coloring
self.gc_count_nicknames_colors = -1
self.gc_custom_colors = {}
@ -530,6 +526,46 @@ class GroupchatControl(ChatControlBase):
# instance object
gajim.plugin_manager.gui_extension_point('groupchat_control', self)
def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
try:
row = self.list_treeview.get_path_at_pos(x_pos, y_pos)[0]
except TypeError:
return False
if not row:
return False
iter_ = None
try:
iter_ = self.model.get_iter(row)
except Exception:
return False
typ = self.model[iter_][C_TYPE]
nick = self.model[iter_][C_NICK]
if typ != 'contact':
return False
if self.current_tooltip != row:
# If the row changes we hide the current tooltip
self.current_tooltip = row
return False
tooltip = widget.get_tooltip_window()
if tooltip.row == row:
# We already populated the window with the row data
return True
tooltip.row = row
contact = gajim.contacts.get_gc_contact(
self.account, self.room_jid, nick)
if not contact:
return False
tooltip.populate(contact)
return True
def fill_column(self, col):
for rend in self.renderers_list:
col.pack_start(rend[1], rend[2])
@ -2638,8 +2674,6 @@ class GroupchatControl(ChatControlBase):
"""
Popup user's group's or agent menu
"""
# hide tooltip, no matter the button is pressed
self.tooltip.hide_tooltip()
try:
pos = widget.get_path_at_pos(int(event.x), int(event.y))
path, x = pos[0], pos[2]
@ -2697,55 +2731,6 @@ class GroupchatControl(ChatControlBase):
add = gc_refer_to_nick_char + ' '
message_buffer.insert_at_cursor(start + nick + add)
def on_list_treeview_motion_notify_event(self, widget, event):
props = widget.get_path_at_pos(int(event.x), int(event.y))
if self.tooltip.timeout > 0 or self.tooltip.shown:
if not props or self.tooltip.id != props[0]:
self.tooltip.hide_tooltip()
if props:
[row, col, x, y] = props
iter_ = None
try:
iter_ = self.model.get_iter(row)
except Exception:
self.tooltip.hide_tooltip()
return
typ = self.model[iter_][C_TYPE]
if typ == 'contact':
account = self.account
if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
self.tooltip.id = row
nick = self.model[iter_][C_NICK]
self.tooltip.timeout = GLib.timeout_add(500,
self.show_tooltip, gajim.contacts.get_gc_contact(
account, self.room_jid, nick))
def on_list_treeview_leave_notify_event(self, widget, event):
props = widget.get_path_at_pos(int(event.x), int(event.y))
if self.tooltip.timeout > 0 or self.tooltip.shown:
if not props or self.tooltip.id == props[0]:
self.tooltip.hide_tooltip()
def show_tooltip(self, contact):
self.tooltip.timeout = 0
if not self.list_treeview.get_window():
# control has been destroyed since tooltip was requested
return
w = self.list_treeview.get_window()
device = w.get_display().get_device_manager().get_client_pointer()
pointer = w.get_device_position(device)
props = self.list_treeview.get_path_at_pos(pointer[1], pointer[2])
# check if the current pointer is at the same path
# as it was before setting the timeout
if props and self.tooltip.id == props[0]:
rect = self.list_treeview.get_cell_area(props[0], props[1])
position = w.get_origin()[1:]
self.tooltip.show_tooltip(contact, rect.height,
position[1] + rect.y)
else:
self.tooltip.hide_tooltip()
def grant_voice(self, widget, nick):
"""
Grant voice privilege to a user

View File

@ -188,50 +188,6 @@ class BaseTooltip:
self.check_last_time = None
self.shown = False
@staticmethod
def colorize_status(status):
"""
Colorize the status message inside the tooltip by it's
semantics. Color palette is the Tango.
"""
formatted = "<span foreground='%s'>%s</span>"
color = None
if status.startswith(Q_("?user status:Available")):
color = gajim.config.get('tooltip_status_online_color')
elif status.startswith(_("Free for Chat")):
color = gajim.config.get('tooltip_status_free_for_chat_color')
elif status.startswith(_("Away")):
color = gajim.config.get('tooltip_status_away_color')
elif status.startswith(_("Busy")):
color = gajim.config.get('tooltip_status_busy_color')
elif status.startswith(_("Not Available")):
color = gajim.config.get('tooltip_status_na_color')
elif status.startswith(_("Offline")):
color = gajim.config.get('tooltip_status_offline_color')
if color:
status = formatted % (color, status)
return status
@staticmethod
def colorize_affiliation(affiliation):
"""
Color the affiliation of a MUC participant inside the tooltip by
it's semantics. Color palette is the Tango.
"""
formatted = "<span foreground='%s'>%s</span>"
color = None
if affiliation.startswith(Q_("?Group Chat Contact Affiliation:None")):
color = gajim.config.get('tooltip_affiliation_none_color')
elif affiliation.startswith(_("Member")):
color = gajim.config.get('tooltip_affiliation_member_color')
elif affiliation.startswith(_("Administrator")):
color = gajim.config.get('tooltip_affiliation_administrator_color')
elif affiliation.startswith(_("Owner")):
color = gajim.config.get('tooltip_affiliation_owner_color')
if color:
affiliation = formatted % (color, affiliation)
return affiliation
class StatusTable:
"""
Contains methods for creating status table. This is used in Roster and
@ -356,68 +312,76 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
self.hbox.add(self.table)
self.hbox.show_all()
class GCTooltip(BaseTooltip):
"""
Tooltip that is shown in the GC treeview
"""
class GCTooltip(Gtk.Window):
# pylint: disable=E1101
def __init__(self, parent):
super().__init__(self, type=Gtk.WindowType.POPUP, transient_for=parent)
self.row = None
self.set_title('tooltip')
self.set_border_width(3)
self.set_resizable(False)
self.set_name('gtk-tooltips')
self.set_type_hint(Gdk.WindowTypeHint.TOOLTIP)
def __init__(self):
self.account = None
self.text_label = Gtk.Label()
self.text_label.set_line_wrap(True)
self.text_label.set_halign(Gtk.Align.START)
self.text_label.set_valign(Gtk.Align.START)
self.text_label.set_selectable(False)
self.avatar_image = Gtk.Image()
self.xml = gtkgui_helpers.get_gtk_builder('tooltip_gc_contact.ui')
for name in ('nick', 'status', 'jid', 'user_show', 'fillelement',
'resource', 'affiliation', 'avatar', 'resource_label',
'jid_label', 'tooltip_grid'):
setattr(self, name, self.xml.get_object(name))
BaseTooltip.__init__(self)
self.add(self.tooltip_grid)
self.tooltip_grid.show()
def clear_tooltip(self):
"""
Hide all Elements of the Tooltip Grid
"""
for child in self.tooltip_grid.get_children():
child.hide()
def populate(self, contact):
if not contact:
return
self.create_window()
vcard_table = Gtk.Grid()
vcard_table.insert_row(0)
vcard_table.insert_row(0)
vcard_table.insert_row(0)
vcard_table.insert_column(0)
vcard_table.set_property('column-spacing', 2)
vcard_current_row = 1
properties = []
"""
Populate the Tooltip Grid with data of from the contact
"""
self.clear_tooltip()
nick_markup = '<b>' + GLib.markup_escape_text(contact.get_shown_name())\
+ '</b>'
properties.append((nick_markup, None))
self.nick.set_text(contact.get_shown_name())
self.nick.show()
if contact.status: # status message
# Status Message
if contact.status:
status = contact.status.strip()
if status != '':
# escape markup entities
status = helpers.reduce_chars_newlines(status, 300, 5)
status = '<i>' + GLib.markup_escape_text(status) + '</i>'
properties.append((status, None))
self.status.set_text(status)
self.status.show()
# Status
show = helpers.get_uf_show(contact.show)
show = self.colorize_status(show)
properties.append((show, None))
self.user_show.set_markup(colorize_status(show))
self.user_show.show()
# JID
if contact.jid.strip():
properties.append((_('Jabber ID: '), '\u200E' + "<b>%s</b>" % \
contact.jid))
self.jid.set_text(contact.jid)
self.jid.show()
self.jid_label.show()
# Resource
if hasattr(contact, 'resource') and contact.resource.strip():
properties.append((_('Resource: '), GLib.markup_escape_text(
contact.resource)))
self.resource.set_text(contact.resource)
self.resource.show()
self.resource_label.show()
# Affiliation
if contact.affiliation != 'none':
uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
uf_affiliation = \
_('%(owner_or_admin_or_member)s of this group chat') % \
{'owner_or_admin_or_member': uf_affiliation}
_('%(owner_or_admin_or_member)s of this group chat') \
% {'owner_or_admin_or_member': uf_affiliation}
uf_affiliation = self.colorize_affiliation(uf_affiliation)
properties.append((uf_affiliation, None))
self.affiliation.set_markup(uf_affiliation)
self.affiliation.show()
# Add avatar
# Avatar
puny_name = helpers.sanitize_filename(contact.name)
puny_room = helpers.sanitize_filename(contact.room_jid)
file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH,
@ -426,39 +390,29 @@ class GCTooltip(BaseTooltip):
with open(file_, 'rb') as file_data:
pix = gtkgui_helpers.get_pixbuf_from_data(file_data.read())
pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
self.avatar_image.set_from_pixbuf(pix)
else:
self.avatar_image.set_from_pixbuf(None)
while properties:
property_ = properties.pop(0)
vcard_current_row += 1
label = Gtk.Label()
if not properties:
label.set_vexpand(True)
label.set_halign(Gtk.Align.START)
label.set_valign(Gtk.Align.START)
if property_[1]:
label.set_markup(property_[0])
vcard_table.attach(label, 1, vcard_current_row, 1, 1)
label = Gtk.Label()
if not properties:
label.set_vexpand(True)
label.set_halign(Gtk.Align.START)
label.set_valign(Gtk.Align.START)
label.set_markup(property_[1])
label.set_line_wrap(True)
vcard_table.attach(label, 2, vcard_current_row, 1, 1)
else:
label.set_markup(property_[0])
label.set_line_wrap(True)
vcard_table.attach(label, 1, vcard_current_row, 2, 1)
self.avatar.set_from_pixbuf(pix)
self.avatar.show()
self.fillelement.show()
self.avatar_image.set_halign(Gtk.Align.START)
self.avatar_image.set_valign(Gtk.Align.START)
vcard_table.attach(self.avatar_image, 3, 2, 1, vcard_current_row - 1)
gajim.plugin_manager.gui_extension_point('gc_tooltip_populate',
self, contact, vcard_table)
self.win.add(vcard_table)
@staticmethod
def colorize_affiliation(affiliation):
"""
Color the affiliation of a MUC participant inside the tooltip by
it's semantics. Color palette is the Tango.
"""
formatted = "<span foreground='%s'>%s</span>"
color = None
if affiliation.startswith(Q_("?Group Chat Contact Affiliation:None")):
color = gajim.config.get('tooltip_affiliation_none_color')
elif affiliation.startswith(_("Member")):
color = gajim.config.get('tooltip_affiliation_member_color')
elif affiliation.startswith(_("Administrator")):
color = gajim.config.get('tooltip_affiliation_administrator_color')
elif affiliation.startswith(_("Owner")):
color = gajim.config.get('tooltip_affiliation_owner_color')
if color:
affiliation = formatted % (color, affiliation)
return affiliation
class RosterTooltip(NotificationAreaTooltip):
"""
@ -602,7 +556,7 @@ class RosterTooltip(NotificationAreaTooltip):
show = _('Connected')
else:
show = _('Disconnected')
show = self.colorize_status(show)
show = colorize_status(show)
if contact.status:
status = contact.status.strip()
@ -816,3 +770,27 @@ class FileTransfersTooltip(BaseTooltip):
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, 0, 0)
self.win.add(ft_table)
def colorize_status(status):
"""
Colorize the status message inside the tooltip by it's
semantics. Color palette is the Tango.
"""
formatted = "<span foreground='%s'>%s</span>"
color = None
if status.startswith(Q_("?user status:Available")):
color = gajim.config.get('tooltip_status_online_color')
elif status.startswith(_("Free for Chat")):
color = gajim.config.get('tooltip_status_free_for_chat_color')
elif status.startswith(_("Away")):
color = gajim.config.get('tooltip_status_away_color')
elif status.startswith(_("Busy")):
color = gajim.config.get('tooltip_status_busy_color')
elif status.startswith(_("Not Available")):
color = gajim.config.get('tooltip_status_na_color')
elif status.startswith(_("Offline")):
color = gajim.config.get('tooltip_status_offline_color')
if color:
status = formatted % (color, status)
return status