Refactor Roster Tooltip

This commit is contained in:
lovetox 2016-11-17 03:29:18 +01:00
parent ec99e93a7c
commit 3296c23e32
5 changed files with 762 additions and 352 deletions

View File

@ -366,12 +366,9 @@
<signal name="button-press-event" handler="on_roster_treeview_button_press_event" swapped="no"/>
<signal name="button-release-event" handler="on_roster_treeview_button_release_event" swapped="no"/>
<signal name="key-press-event" handler="on_roster_treeview_key_press_event" swapped="no"/>
<signal name="leave-notify-event" handler="on_roster_treeview_leave_notify_event" swapped="no"/>
<signal name="motion-notify-event" handler="on_roster_treeview_motion_notify_event" swapped="no"/>
<signal name="row-activated" handler="on_roster_treeview_row_activated" swapped="no"/>
<signal name="row-collapsed" handler="on_roster_treeview_row_collapsed" swapped="no"/>
<signal name="row-expanded" handler="on_roster_treeview_row_expanded" swapped="no"/>
<signal name="scroll-event" handler="on_roster_treeview_scroll_event" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1"/>
</child>

View File

@ -0,0 +1,370 @@
<?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="halign">start</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="use_markup">True</property>
<property name="xalign">0</property>
</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="jid_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Jabber ID:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkImage" id="avatar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</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">14</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="resource_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Resource:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="user_show">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</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="resource">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="jid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Status:</property>
<property name="wrap">True</property>
<property name="max_width_chars">40</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="idle_since_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Idle since:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="idle_for_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Idle for:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="idle_since">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="idle_for">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">13</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="mood_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Mood:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="activity_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Activity:</property>
<property name="lines">2</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="tune_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Tune:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="location_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Location:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="mood">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="activity">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="tune">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="location">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pgp_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">OpenPGP:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pgp">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="sub_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Subscription:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="sub">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="wrap">True</property>
<property name="max_width_chars">30</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</interface>

View File

@ -562,11 +562,14 @@ class Interface:
def handle_event_last_status_time(self, obj):
# ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
if obj.seconds < 0:
# Ann error occured
return
account = obj.conn.name
c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
tooltip_window = self.roster.tree.get_tooltip_window()
if obj.seconds < 0:
if tooltip_window:
tooltip_window.update_last_time(c, True)
return
if c: # c can be none if it's a gc contact
if obj.status:
c.status = obj.status
@ -576,8 +579,10 @@ class Interface:
c.last_status_time = last_time
else:
c.last_activity_time = last_time
if self.roster.tooltip.id and self.roster.tooltip.win:
self.roster.tooltip.update_last_time(last_time)
# Set last time on roster tooltip
if tooltip_window:
tooltip_window.update_last_time(c)
def handle_event_gc_config(self, obj):
#('GC_CONFIG', account, (jid, form_node)) config is a dict

View File

@ -2063,25 +2063,6 @@ class RosterWindow:
vb.hide()
vb.set_no_show_all(True)
def show_tooltip(self, contact):
self.tooltip.timeout = 0
device = self.tree.get_window().get_display().get_device_manager().\
get_client_pointer()
pointer = self.tree.get_window().get_device_position(device)
props = self.tree.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]:
# bounding rectangle of coordinates for the cell within the treeview
rect = self.tree.get_cell_area(props[0], props[1])
# position of the treeview on the screen
position = self.tree.get_window().get_origin()
self.tooltip.show_tooltip(contact, rect.height, position[2] + \
rect.y)
else:
self.tooltip.hide_tooltip()
def authorize(self, widget, jid, account):
"""
Authorize a contact (by re-sending auth menuitem)
@ -2443,7 +2424,6 @@ class RosterWindow:
if not gajim.config.get('quit_on_roster_x_button') and (
(gajim.interface.systray_enabled and gajim.config.get('trayicon') != \
'on_event') or gajim.config.get('allow_hide_roster')):
self.tooltip.hide_tooltip()
if gajim.config.get('save-roster-position'):
x, y = self.window.get_position()
gajim.config.set('roster_x-position', x)
@ -2894,119 +2874,6 @@ class RosterWindow:
return
info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
def on_roster_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 on_roster_treeview_motion_notify_event(self, widget, event):
model = widget.get_model()
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 = props[0]
titer = None
try:
titer = model.get_iter(row)
except Exception:
self.tooltip.hide_tooltip()
return
if model[titer][C_TYPE] in ('contact', 'self_contact'):
# we're on a contact entry in the roster
if (not self.tooltip.shown and self.tooltip.timeout == 0) or \
self.tooltip.id != props[0]:
account = model[titer][C_ACCOUNT]
jid = model[titer][C_JID]
self.tooltip.id = row
contacts = gajim.contacts.get_contacts(account, jid)
connected_contacts = []
for c in contacts:
if c.show not in ('offline', 'error'):
connected_contacts.append(c)
if not connected_contacts:
# no connected contacts, show the ofline one
connected_contacts = contacts
self.tooltip.account = account
self.tooltip.timeout = GLib.timeout_add(500,
self.show_tooltip, connected_contacts)
elif model[titer][C_TYPE] == 'groupchat':
if (not self.tooltip.shown and self.tooltip.timeout == 0) or \
self.tooltip.id != props[0]:
account = model[titer][C_ACCOUNT]
jid = model[titer][C_JID]
self.tooltip.id = row
contact = gajim.contacts.get_contacts(account, jid)
self.tooltip.account = account
self.tooltip.timeout = GLib.timeout_add(500,
self.show_tooltip, contact)
elif model[titer][C_TYPE] == 'account':
# we're on an account entry in the roster
if (not self.tooltip.shown and self.tooltip.timeout == 0) or \
self.tooltip.id != props[0]:
account = model[titer][C_ACCOUNT]
if account == 'all':
self.tooltip.id = row
self.tooltip.account = None
self.tooltip.timeout = GLib.timeout_add(500,
self.show_tooltip, [])
return
jid = gajim.get_jid_from_account(account)
contacts = []
connection = gajim.connections[account]
# get our current contact info
nbr_on, nbr_total = gajim.\
contacts.get_nb_online_total_contacts(
accounts=[account])
account_name = account
if gajim.account_is_connected(account):
account_name += ' (%s/%s)' % (repr(nbr_on),
repr(nbr_total))
contact = gajim.contacts.create_self_contact(jid=jid,
account=account, name=account_name,
show=connection.get_status(), status=connection.status,
resource=connection.server_resource,
priority=connection.priority)
if gajim.connections[account].gpg:
contact.keyID = gajim.config.get_per('accounts',
connection.name, 'keyid')
contacts.append(contact)
# if we're online ...
if connection.connection:
roster = connection.connection.getRoster()
# in threadless connection when no roster stanza is sent
# 'roster' is None
if roster and roster.getItem(jid):
resources = roster.getResources(jid)
# ...get the contact info for our other online
# resources
for resource in resources:
# Check if we already have this resource
found = False
for contact_ in contacts:
if contact_.resource == resource:
found = True
break
if found:
continue
show = roster.getShow(jid+'/'+resource)
if not show:
show = 'online'
contact = gajim.contacts.create_self_contact(
jid=jid, account=account, show=show,
status=roster.getStatus(
jid + '/' + resource),
priority=roster.getPriority(
jid + '/' + resource), resource=resource)
contacts.append(contact)
self.tooltip.id = row
self.tooltip.account = None
self.tooltip.timeout = GLib.timeout_add(500,
self.show_tooltip, contacts)
def on_agent_logging(self, widget, jid, state, account):
"""
When an agent is requested to log in or off
@ -3449,15 +3316,10 @@ class RosterWindow:
def on_add_to_roster(self, widget, contact, account):
dialogs.AddNewContactWindow(account, contact.jid, contact.name)
def on_roster_treeview_scroll_event(self, widget, event):
self.tooltip.hide_tooltip()
def on_roster_treeview_key_press_event(self, widget, event):
"""
When a key is pressed in the treeviews
"""
self.tooltip.hide_tooltip()
if event.keyval == Gdk.KEY_Escape:
if self.rfilter_enabled:
self.disable_rfilter()
@ -3577,8 +3439,6 @@ class RosterWindow:
self.enable_rfilter('')
def on_roster_treeview_button_press_event(self, widget, event):
# hide tooltip, no matter the button is pressed
self.tooltip.hide_tooltip()
try:
pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
path, x = pos[0], pos[2]
@ -4079,7 +3939,6 @@ class RosterWindow:
'quit_on_roster_x_button') and ((gajim.interface.systray_enabled and\
gajim.config.get('trayicon') == 'always') or gajim.config.get(
'allow_hide_roster')):
self.tooltip.hide_tooltip()
self.window.hide()
elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == \
Gdk.KEY_i:
@ -6268,6 +6127,54 @@ class RosterWindow:
renderer.set_property(self.renderers_propertys[renderer][0],
self.renderers_propertys[renderer][1])
def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
try:
row = widget.get_path_at_pos(x_pos, y_pos)[0]
except TypeError:
return False
if not row:
return False
iter_ = None
try:
model = widget.get_model()
iter_ = model.get_iter(row)
except Exception:
return False
typ = model[iter_][C_TYPE]
account = model[iter_][C_ACCOUNT]
jid = model[iter_][C_JID]
connected_contacts = []
if typ in ('contact', 'self_contact'):
contacts = gajim.contacts.get_contacts(account, jid)
for c in contacts:
if c.show not in ('offline', 'error'):
connected_contacts.append(c)
if not connected_contacts:
# no connected contacts, show the offline one
connected_contacts = contacts
elif typ == 'groupchat':
connected_contacts = gajim.contacts.get_contacts(account, jid)
elif typ != 'account':
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
tooltip.populate(connected_contacts, account, typ)
return True
################################################################################
###
################################################################################
@ -6515,7 +6422,10 @@ class RosterWindow:
self.combobox_callback_active = True
self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
self.tooltip = tooltips.RosterTooltip()
self.tree.set_has_tooltip(True)
self.tree.set_tooltip_window(tooltips.RosterTooltip(self.window))
self.current_tooltip = None
self.tree.connect('query-tooltip', self.query_tooltip)
# Workaroung: For strange reasons signal is behaving like row-changed
self._toggeling_row = False
self.setup_and_draw_roster()

View File

@ -195,28 +195,28 @@ class StatusTable:
"""
def __init__(self):
self.current_row = 1
self.current_row = 0
self.table = None
self.text_label = None
self.spacer_label = ' '
def create_table(self):
self.table = Gtk.Grid()
self.table.insert_row(0)
self.table.insert_row(0)
self.table.insert_column(0)
self.table.set_property('column-spacing', 2)
def add_text_row(self, text, col_inc = 0):
self.current_row += 1
def add_text_row(self, text, col_inc=0):
self.table.insert_row(self.current_row)
self.text_label = Gtk.Label()
self.text_label.set_line_wrap(True)
self.text_label.set_max_width_chars(35)
self.text_label.set_halign(Gtk.Align.START)
self.text_label.set_valign(Gtk.Align.START)
self.text_label.set_selectable(False)
self.text_label.set_markup(text)
self.table.attach(self.text_label, 1 + col_inc, self.current_row,
3 - col_inc, 1)
self.current_row += 1
def get_status_info(self, resource, priority, show, status):
str_status = resource + ' (' + str(priority) + ')'
@ -235,9 +235,7 @@ class StatusTable:
"""
Append a new row with status icon to the table
"""
self.table.insert_row(0)
self.table.insert_row(0)
self.current_row += 1
self.table.insert_row(self.current_row)
state_file = show.replace(' ', '_')
files = []
files.append(os.path.join(file_path, state_file + '.png'))
@ -265,6 +263,7 @@ class StatusTable:
lock_image.set_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
Gtk.IconSize.MENU)
self.table.attach(lock_image, 4, self.current_row, 1, 1)
self.current_row += 1
class NotificationAreaTooltip(BaseTooltip, StatusTable):
"""
@ -414,83 +413,181 @@ class GCTooltip(Gtk.Window):
affiliation = formatted % (color, affiliation)
return affiliation
class RosterTooltip(NotificationAreaTooltip):
"""
Tooltip that is shown in the roster treeview
"""
def __init__(self):
self.account = None
self.avatar_image = Gtk.Image()
NotificationAreaTooltip.__init__(self)
def populate(self, contacts):
self.create_window()
class RosterTooltip(Gtk.Window, StatusTable):
# pylint: disable=E1101
def __init__(self, parent):
Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP, transient_for=parent)
StatusTable.__init__(self)
self.create_table()
if not contacts or len(contacts) == 0:
self.row = None
self.check_last_time = {}
self.contact_jid = None
self.last_widget = None
self.num_resources = 0
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)
self.xml = gtkgui_helpers.get_gtk_builder('tooltip_roster_contact.ui')
for name in ('name', 'status', 'jid', 'user_show', 'fillelement',
'resource', 'avatar', 'resource_label', 'pgp', 'pgp_label',
'jid_label', 'tooltip_grid', 'idle_since', 'idle_for',
'idle_since_label', 'idle_for_label', 'mood', 'tune',
'activity', 'location', 'tune_label', 'location_label',
'activity_label', 'mood_label', 'sub_label', 'sub',
'status_label'):
setattr(self, name, self.xml.get_object(name))
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()
status_table = self.tooltip_grid.get_child_at(0, 3)
if status_table:
status_table.destroy()
self.create_table()
def fill_table_with_accounts(self, accounts):
iconset = gajim.config.get('iconset')
if not iconset:
iconset = 'dcraven'
file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
for acct in accounts:
message = acct['message']
message = helpers.reduce_chars_newlines(message, 100, 1)
message = GLib.markup_escape_text(message)
if acct['name'] in gajim.con_types and \
gajim.con_types[acct['name']] in ('tls', 'ssl'):
show_lock = True
else:
show_lock = False
if message:
self.add_status_row(file_path, acct['show'],
GLib.markup_escape_text(acct['name']) + ' - ' + message,
show_lock=show_lock, indent=False)
else:
self.add_status_row(file_path, acct['show'],
GLib.markup_escape_text(acct['name']), show_lock=show_lock,
indent=False)
for line in acct['event_lines']:
self.add_text_row(' ' + line, 1)
def populate(self, contacts, account, typ):
"""
Populate the Tooltip Grid with data of from the contact
"""
self.current_row = 0
self.account = account
if self.last_widget:
self.last_widget.set_vexpand(False)
self.clear_tooltip()
if account == 'all':
# Tooltip for merged accounts row
accounts = helpers.get_notification_icon_tooltip_dict()
self.spacer_label = ''
self.fill_table_with_accounts(accounts)
self.win.add(self.table)
self.tooltip_grid.attach(self.table, 0, 3, 2, 1)
self.table.show_all()
return
# primary contact
prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
if typ == 'account':
jid = gajim.get_jid_from_account(account)
contacts = []
connection = gajim.connections[account]
# get our current contact info
nbr_on, nbr_total = gajim.\
contacts.get_nb_online_total_contacts(
accounts=[account])
account_name = account
if gajim.account_is_connected(account):
account_name += ' (%s/%s)' % (repr(nbr_on),
repr(nbr_total))
contact = gajim.contacts.create_self_contact(jid=jid,
account=account, name=account_name,
show=connection.get_status(), status=connection.status,
resource=connection.server_resource,
priority=connection.priority)
if gajim.connections[account].gpg:
contact.keyID = gajim.config.get_per('accounts',
connection.name, 'keyid')
contacts.append(contact)
# if we're online ...
if connection.connection:
roster = connection.connection.getRoster()
# in threadless connection when no roster stanza is sent
# 'roster' is None
if roster and roster.getItem(jid):
resources = roster.getResources(jid)
# ...get the contact info for our other online
# resources
for resource in resources:
# Check if we already have this resource
found = False
for contact_ in contacts:
if contact_.resource == resource:
found = True
break
if found:
continue
show = roster.getShow(jid + '/' + resource)
if not show:
show = 'online'
contact = gajim.contacts.create_self_contact(
jid=jid, account=account, show=show,
status=roster.getStatus(
jid + '/' + resource),
priority=roster.getPriority(
jid + '/' + resource), resource=resource)
contacts.append(contact)
# Username/Account/Groupchat
self.prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
contacts)
puny_jid = helpers.sanitize_filename(prim_contact.jid)
table_size = 3
file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH,
puny_jid))
if file_:
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)
table_size = 4
else:
self.avatar_image.set_from_pixbuf(None)
vcard_table = Gtk.Grid()
vcard_table.insert_row(0)
for i in range(0, table_size):
vcard_table.insert_column(0)
vcard_table.set_property('column-spacing', 2)
vcard_current_row = 1
properties = []
name_markup = '<span weight="bold">' + GLib.markup_escape_text(
prim_contact.get_shown_name()) + '</span>'
self.contact_jid = self.prim_contact.jid
name = GLib.markup_escape_text(self.prim_contact.get_shown_name())
name_markup = '<b>{}</b>'.format(name)
if gajim.config.get('mergeaccounts'):
name_markup += " <span foreground='%s'>(%s)</span>" % (
gajim.config.get('tooltip_account_name_color'),
GLib.markup_escape_text(prim_contact.account.name))
color = gajim.config.get('tooltip_account_name_color')
account_name = GLib.markup_escape_text(self.prim_contact.account.name)
name_markup += " <span foreground='{}'>({})</span>".format(
color, account_name)
if self.account and helpers.jid_is_blocked(self.account,
prim_contact.jid):
if account and helpers.jid_is_blocked(account, self.prim_contact.jid):
name_markup += _(' [blocked]')
if self.account and \
self.account in gajim.interface.minimized_controls and \
prim_contact.jid in gajim.interface.minimized_controls[self.account]:
name_markup += _(' [minimized]')
properties.append((name_markup, None))
num_resources = 0
try:
if self.prim_contact.jid in gajim.interface.minimized_controls[account]:
name_markup += _(' [minimized]')
except KeyError:
pass
self.name.set_markup(name_markup)
self.name.show()
self.num_resources = 0
# put contacts in dict, where key is priority
contacts_dict = {}
for contact in contacts:
if contact.resource:
num_resources += 1
if contact.priority in contacts_dict:
contacts_dict[int(contact.priority)].append(contact)
self.num_resources += 1
priority = int(contact.priority)
if priority in contacts_dict:
contacts_dict[priority].append(contact)
else:
contacts_dict[int(contact.priority)] = [contact]
if num_resources > 1:
properties.append((_('Status: '), ' '))
transport = gajim.get_transport_name_from_jid(prim_contact.jid)
contacts_dict[priority] = [contact]
if self.num_resources > 1:
self.status_label.show()
transport = gajim.get_transport_name_from_jid(self.prim_contact.jid)
if transport:
file_path = os.path.join(helpers.get_transport_path(transport),
'16x16')
@ -505,98 +602,140 @@ class RosterTooltip(NotificationAreaTooltip):
contact_keys.reverse()
for priority in contact_keys:
for acontact in contacts_dict[priority]:
status_line = self.get_status_info(acontact.resource,
acontact.priority, acontact.show, acontact.status)
icon_name = self._get_icon_name_for_tooltip(acontact)
if acontact.status and len(acontact.status) > 25:
status = ''
add_text = True
else:
status = acontact.status
add_text = False
status_line = self.get_status_info(acontact.resource,
acontact.priority, acontact.show, status)
self.add_status_row(file_path, icon_name, status_line,
acontact.last_status_time)
properties.append((self.table, None))
if add_text:
self.add_text_row(acontact.status, 2)
else: # only one resource
self.tooltip_grid.attach(self.table, 0, 3, 2, 1)
self.table.show_all()
else: # only one resource
if contact.show:
show = helpers.get_uf_show(contact.show)
if not self.check_last_time and self.account:
request_time = False
try:
last_time = self.check_last_time[contact]
if isinstance(last_time, float) and last_time < time.time() - 60:
request_time = True
except KeyError:
request_time = True
if request_time:
if contact.show == 'offline':
if not contact.last_status_time:
gajim.connections[self.account].\
request_last_status_time(contact.jid, '')
else:
self.check_last_time = contact.last_status_time
gajim.connections[account].\
request_last_status_time(contact.jid, '')
elif contact.resource:
gajim.connections[self.account].\
gajim.connections[account].\
request_last_status_time(
contact.jid, contact.resource)
if contact.last_activity_time:
self.check_last_time = contact.last_activity_time
else:
self.check_last_time = None
if contact.last_status_time:
vcard_current_row += 1
if contact.show == 'offline':
text = ' - ' + _('Last status: %s')
else:
text = _(' since %s')
if time.strftime('%j', time.localtime()) == \
time.strftime('%j', contact.last_status_time):
# it's today, show only the locale hour representation
local_time = time.strftime('%X',
contact.last_status_time)
else:
# time.strftime returns locale encoded string
local_time = time.strftime('%c',
contact.last_status_time)
text = text % local_time
show += text
if self.account and \
prim_contact.jid in gajim.gc_connected[self.account]:
if gajim.gc_connected[self.account][prim_contact.jid]:
show = _('Connected')
else:
show = _('Disconnected')
show = colorize_status(show)
self.check_last_time[contact] = time.time()
if contact.status:
status = contact.status.strip()
if status:
# reduce long status
# (no more than 300 chars on line and no more than
# 5 lines)
# status is wrapped
status = helpers.reduce_chars_newlines(status, 300, 5)
# escape markup entities.
status = GLib.markup_escape_text(status)
properties.append(('<i>%s</i>' % status, None))
properties.append((show, None))
self.status.set_text(status)
self.status.show()
self.status_label.show()
self._append_pep_info(contact, properties)
# PEP Info
self._append_pep_info(contact)
properties.append((_('Jabber ID: '), '\u200E' + "<b>%s</b>" % \
prim_contact.jid))
# JID
self.jid.set_text(self.prim_contact.jid)
self.jid.show()
self.jid_label.show()
# contact has only one ressource
if num_resources == 1 and contact.resource:
properties.append((_('Resource: '), GLib.markup_escape_text(
contact.resource) + ' (' + str(contact.priority) + ')'))
if self.num_resources == 1 and contact.resource:
res = GLib.markup_escape_text(contact.resource)
prio = str(contact.priority)
self.resource.set_text("{} ({})".format(res, prio))
self.resource.show()
self.resource_label.show()
if self.account and prim_contact.sub and prim_contact.sub != 'both' and\
prim_contact.jid not in gajim.gc_connected[self.account]:
# ('both' is the normal sub so we don't show it)
properties.append(( _('Subscription: '), GLib.markup_escape_text(
helpers.get_uf_sub(prim_contact.sub))))
if self.prim_contact.jid not in gajim.gc_connected[account]:
if (account and
self.prim_contact.sub and
self.prim_contact.sub != 'both'):
# ('both' is the normal sub so we don't show it)
self.sub.set_text(helpers.get_uf_sub(self.prim_contact.sub))
self.sub.show()
self.sub_label.show()
if prim_contact.keyID:
if self.prim_contact.keyID:
keyID = None
if len(prim_contact.keyID) == 8:
keyID = prim_contact.keyID
elif len(prim_contact.keyID) == 16:
keyID = prim_contact.keyID[8:]
if len(self.prim_contact.keyID) == 8:
keyID = self.prim_contact.keyID
elif len(self.prim_contact.keyID) == 16:
keyID = self.prim_contact.keyID[8:]
if keyID:
properties.append((_('OpenPGP: '), GLib.markup_escape_text(
keyID)))
self.pgp.set_text(keyID)
self.pgp.show()
self.pgp_label.show()
self._set_idle_time(contact)
# Avatar
puny_jid = helpers.sanitize_filename(self.prim_contact.jid)
file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH,
puny_jid))
if file_:
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.set_from_pixbuf(pix)
self.avatar.show()
# Sets the Widget that is at the bottom to expand.
# This is needed in case the Picture takes more Space then the Labels
i = 1
while i < 15:
if self.tooltip_grid.get_child_at(0, i):
if self.tooltip_grid.get_child_at(0, i).get_visible():
self.last_widget = self.tooltip_grid.get_child_at(0, i)
i += 1
self.last_widget.set_vexpand(True)
def _append_pep_info(self, contact):
"""
Append Tune, Mood, Activity, Location information of the specified contact
to the given property list.
"""
if 'mood' in contact.pep:
mood = contact.pep['mood'].asMarkupText()
self.mood.set_markup(mood)
self.mood.show()
self.mood_label.show()
if 'activity' in contact.pep:
activity = contact.pep['activity'].asMarkupText()
self.activity.set_markup(activity)
self.activity.show()
self.activity_label.show()
if 'tune' in contact.pep:
tune = contact.pep['tune'].asMarkupText()
self.tune.set_markup(tune)
self.tune.show()
self.tune_label.show()
if 'location' in contact.pep:
location = contact.pep['location'].asMarkupText()
self.location.set_markup(location)
self.location.show()
self.location_label.show()
def _set_idle_time(self, contact):
if contact.last_activity_time:
last_active = datetime(*contact.last_activity_time[:6])
current = datetime.now()
@ -613,78 +752,67 @@ class RosterTooltip(NotificationAreaTooltip):
# is no meaningful difference between last activity time and
# current time.
if diff.days > 0 or diff.seconds > 0:
cs = "<span foreground='%s'>" % gajim.config.get(
'tooltip_idle_color')
cs += '%s</span>'
properties.append((str(), None))
idle_since = cs % _("Idle since %s")
properties.append((idle_since % formatted, None))
idle_for = cs % _("Idle for %s")
properties.append((idle_for % str(diff), None))
idle_color = gajim.config.get('tooltip_idle_color')
idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, formatted)
self.idle_since.set_markup(idle_markup)
self.idle_since.show()
self.idle_since_label.show()
idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, str(diff))
self.idle_for.set_markup(idle_markup)
self.idle_for_label.show()
self.idle_for.show()
while properties:
property_ = properties.pop(0)
vcard_current_row += 1
label = Gtk.Label()
if not properties and table_size == 4:
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 and table_size == 4:
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:
if isinstance(property_[0], str):
label.set_markup(property_[0])
label.set_line_wrap(True)
if contact.show and self.num_resources < 2:
show = helpers.get_uf_show(contact.show)
if contact.last_status_time:
if contact.show == 'offline':
text = ' - ' + _('Last status: %s')
else:
label = property_[0]
vcard_table.attach(label, 1, vcard_current_row, 2, 1)
self.avatar_image.set_halign(Gtk.Align.START)
self.avatar_image.set_valign(Gtk.Align.START)
if table_size == 4:
vcard_table.attach(self.avatar_image, 3, 2, 1, vcard_current_row - 1)
text = _(' since %s')
gajim.plugin_manager.gui_extension_point('roster_tooltip_populate',
self, contacts, vcard_table)
self.win.add(vcard_table)
if time.strftime('%j', time.localtime()) == \
time.strftime('%j', contact.last_status_time):
# it's today, show only the locale hour representation
local_time = time.strftime('%X', contact.last_status_time)
else:
# time.strftime returns locale encoded string
local_time = time.strftime('%c', contact.last_status_time)
def update_last_time(self, last_time):
if not self.check_last_time or time.strftime('%x %I:%M %p', last_time) !=\
time.strftime('%x %I:%M %p', self.check_last_time):
self.win.destroy()
self.win = None
self.populate(self.cur_data)
self.win.show_all()
text = text % local_time
show += text
def _append_pep_info(self, contact, properties):
# Contact is Groupchat
if (self.account and
self.prim_contact.jid in gajim.gc_connected[self.account]):
if gajim.gc_connected[self.account][self.prim_contact.jid]:
show = _('Connected')
else:
show = _('Disconnected')
self.user_show.set_markup(colorize_status(show))
self.user_show.show()
def _get_icon_name_for_tooltip(self, contact):
"""
Append Tune, Mood, Activity, Location information of the specified contact
to the given property list.
Helper function used for tooltip contacts/acounts
Tooltip on account has fake contact with sub == '', in this case we show
real status of the account
"""
if 'mood' in contact.pep:
mood = contact.pep['mood'].asMarkupText()
properties.append((_('Mood: '), "%s" % mood, None))
if contact.ask == 'subscribe':
return 'requested'
elif contact.sub in ('both', 'to', ''):
return contact.show
return 'not in roster'
if 'activity' in contact.pep:
activity = contact.pep['activity'].asMarkupText()
properties.append((_('Activity: '), "%s" % activity, None))
if 'tune' in contact.pep:
tune = contact.pep['tune'].asMarkupText()
properties.append((_('Tune: '), "%s" % tune, None))
if 'location' in contact.pep:
location = contact.pep['location'].asMarkupText()
properties.append((_('Location: '), "%s" % location, None))
def update_last_time(self, contact, error=False):
if not contact:
return
if error:
self.check_last_time[contact] = 'error'
return
if contact.jid == self.contact_jid:
self._set_idle_time(contact)
class FileTransfersTooltip(BaseTooltip):