diff --git a/data/glade/message_window.glade b/data/glade/message_window.glade index 0e4a92643..7e86aad15 100644 --- a/data/glade/message_window.glade +++ b/data/glade/message_window.glade @@ -103,13 +103,31 @@ True - ../emoticons/static/music.png + None 1 2 + + + True + False + + + True + None + 1 + + + + + False + False + 3 + + True @@ -117,7 +135,7 @@ 1 - 3 + 4 @@ -127,7 +145,7 @@ 1 - 4 + 5 @@ -139,7 +157,7 @@ - 5 + 6 diff --git a/data/glade/preferences_window.glade b/data/glade/preferences_window.glade index c9d6a34b1..db6d98b45 100644 --- a/data/glade/preferences_window.glade +++ b/data/glade/preferences_window.glade @@ -127,6 +127,24 @@ 4 + + + Display _location of contacts in roster + True + True + False + True + If checked, Gajim will display the location of contacts in the roster window + True + True + + + + False + False + 5 + + True @@ -172,7 +190,7 @@ - 5 + 6 diff --git a/icons/hicolor/16x16/actions/gajim-earth.png b/icons/hicolor/16x16/actions/gajim-earth.png new file mode 100644 index 000000000..6c67fb76f Binary files /dev/null and b/icons/hicolor/16x16/actions/gajim-earth.png differ diff --git a/src/chat_control.py b/src/chat_control.py index 781d655fd..91b0b2b99 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -1338,6 +1338,7 @@ class ChatControl(ChatControlBase): self._pep_images['mood'] = self.xml.get_widget('mood_image') self._pep_images['activity'] = self.xml.get_widget('activity_image') self._pep_images['tune'] = self.xml.get_widget('tune_image') + self._pep_images['location'] = self.xml.get_widget('location_image') self.update_all_pep_types() # keep timeout id and window obj for possible big avatar @@ -1375,6 +1376,11 @@ class ChatControl(ChatControlBase): self.on_avatar_eventbox_button_press_event) self.handlers[id_] = widget + widget = self.xml.get_widget('location_eventbox') + id_ = widget.connect('button-release-event', + self.on_location_eventbox_button_release_event) + self.handlers[id_] = widget + for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'): widget = self.xml.get_widget(key + '_button') id_ = widget.connect('pressed', self.on_num_button_pressed, key) @@ -1678,6 +1684,14 @@ class ChatControl(ChatControlBase): menu.show_all() menu.popup(None, None, None, event.button, event.time) return True + + def on_location_eventbox_button_release_event(self, widget, event): + if 'location' in self.contact.pep: + location = self.contact.pep['location']._pep_specific_data + if ('lat' in location) and ('lon' in location): + uri = 'http://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_window_motion_notify(self, widget, event): """ diff --git a/src/common/config.py b/src/common/config.py index fb5f0ebfd..ae2f74373 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -225,6 +225,7 @@ class Config: 'show_mood_in_roster': [opt_bool, True, '', True], 'show_activity_in_roster': [opt_bool, True, '', True], 'show_tunes_in_roster': [opt_bool, True, '', True], + 'show_location_in_roster': [opt_bool, True, '', True], 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], @@ -353,6 +354,7 @@ class Config: 'subscribe_activity': [opt_bool, True], 'subscribe_tune': [opt_bool, True], 'subscribe_nick': [opt_bool, True], + 'subscribe_location': [opt_bool, True], 'ignore_unknown_contacts': [ opt_bool, False ], 'send_os_info': [ opt_bool, True ], 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], diff --git a/src/common/helpers.py b/src/common/helpers.py index ca590bbe7..cb1dc9f24 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1294,6 +1294,8 @@ def update_optional_features(account = None): gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify') if gajim.config.get_per('accounts', a, 'subscribe_nick'): gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify') + if gajim.config.get_per('accounts', a, 'subscribe_location'): + gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify') if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES) if not gajim.config.get('ignore_incoming_xhtml'): diff --git a/src/common/pep.py b/src/common/pep.py index 8e994b82a..b235e674c 100644 --- a/src/common/pep.py +++ b/src/common/pep.py @@ -191,6 +191,11 @@ ACTIVITIES = { TUNE_DATA = ['artist', 'title', 'source', 'track', 'length'] +LOCATION_DATA = ['accuracy', 'alt', 'area', 'bearing', 'building', 'country', + 'countrycode', 'datum', 'description', 'error', 'floor', 'lat', + 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street', + 'text', 'timestamp', 'uri'] + import gobject import gtk @@ -442,8 +447,47 @@ class UserNicknamePEP(AbstractPEP): gajim.nicks[account] = self._pep_specific_data +class UserLocationPEP(AbstractPEP): + '''XEP-0080: User Location''' + + type = 'location' + namespace = xmpp.NS_LOCATION + + def _extract_info(self, items): + location_dict = {} + + for item in items.getTags('item'): + location_tag = item.getTag('geoloc') + if location_tag: + for child in location_tag.getChildren(): + name = child.getName().strip() + data = child.getData().strip() + if child.getName() in LOCATION_DATA: + location_dict[name] = data + + retracted = items.getTag('retract') or not location_dict + return (location_dict, retracted) + + def asPixbufIcon(self): + gtkgui_helpers.get_icon_path('gajim-earth') + return gtk.gdk.pixbuf_new_from_file(path) + + def asMarkupText(self): + assert not self._retracted + location = self._pep_specific_data + location_string = '' + + for entry in location.keys(): + text = location[entry] + text = gobject.markup_escape_text(text) + location_string += '\n%(tag)s: %(text)s' % \ + {'tag': entry.capitalize(), 'text': text} + + return location_string.strip() + + SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP, - UserNicknamePEP] + UserNicknamePEP, UserLocationPEP] class ConnectionPEP(object): diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 17e2096a3..a59ea81cb 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -72,6 +72,7 @@ NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-01 NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 NS_LAST ='jabber:iq:last' +NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080 NS_MESSAGE ='message' # Jabberd2 NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 NS_MUC ='http://jabber.org/protocol/muc' diff --git a/src/config.py b/src/config.py index 5b7a0c8ce..e3a4334e4 100644 --- a/src/config.py +++ b/src/config.py @@ -137,6 +137,11 @@ class PreferencesWindow: self.xml.get_widget('show_tunes_in_roster_checkbutton'). \ set_active(st) + # Display location in roster + st = gajim.config.get('show_location_in_roster') + self.xml.get_widget('show_location_in_roster_checkbutton'). \ + set_active(st) + # Sort contacts by show st = gajim.config.get('sort_by_show_in_roster') self.xml.get_widget('sort_by_show_in_roster_checkbutton').set_active(st) @@ -615,6 +620,10 @@ class PreferencesWindow: self.on_checkbutton_toggled(widget, 'show_tunes_in_roster') gajim.interface.roster.setup_and_draw_roster() + def on_show_location_in_roster_checkbutton_toggled(self, widget): + self.on_checkbutton_toggled(widget, 'show_location_in_roster') + gajim.interface.roster.setup_and_draw_roster() + def on_emoticons_combobox_changed(self, widget): active = widget.get_active() model = widget.get_model() diff --git a/src/roster_window.py b/src/roster_window.py index 9e56d3c97..0a8349689 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -79,9 +79,10 @@ from common.pep import MOODS, ACTIVITIES C_MOOD_PIXBUF, C_ACTIVITY_PIXBUF, C_TUNE_PIXBUF, + C_LOCATION_PIXBUF, C_AVATAR_PIXBUF, # avatar_pixbuf C_PADLOCK_PIXBUF, # use for account row only -) = range(10) +) = range(11) class RosterWindow: """ @@ -293,7 +294,7 @@ class RosterWindow: self.model.append(None, [ gajim.interface.jabber_state_images['16'][show], gobject.markup_escape_text(account), 'account', - our_jid, account, None, None, None, None, + our_jid, account, None, None, None, None, None, tls_pixbuf]) self.draw_account(account) @@ -370,7 +371,7 @@ class RosterWindow: child_iterG = self.model.append(child_iterA, [gajim.interface.jabber_state_images['16']['closed'], gobject.markup_escape_text(group), - 'group', group, account, None, None, None, None, None]) + 'group', group, account, None, None, None, None, None, None]) self.draw_group(group, account) if contact.is_transport(): @@ -385,7 +386,7 @@ class RosterWindow: i_ = self.model.append(child_iterG, (None, contact.get_shown_name(), typestr, contact.jid, account, None, None, None, - None, None)) + None, None, None)) added_iters.append(i_) # Restore the group expand state @@ -627,7 +628,7 @@ class RosterWindow: child_iterA = self._get_account_iter(account, self.model) self.model.append(child_iterA, (None, gajim.nicks[account], 'self_contact', jid, account, None, None, None, None, - None)) + None, None)) self.draw_completely(jid, account) self.draw_account(account) @@ -1050,6 +1051,11 @@ class RosterWindow: self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon() else: self.model[child_iter][C_TUNE_PIXBUF] = None + + if gajim.config.get('show_location_in_roster') and 'location' in pep: + self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon() + else: + self.model[child_iter][C_LOCATION_PIXBUF] = None return False def draw_group(self, group, account): @@ -1264,6 +1270,8 @@ class RosterWindow: return gajim.config.get('show_activity_in_roster') elif pep_type == 'tune': return gajim.config.get('show_tunes_in_roster') + elif pep_type == 'location': + return gajim.config.get('show_location_in_roster') else: return False @@ -1362,10 +1370,11 @@ class RosterWindow: """ self.modelfilter = None # (icon, name, type, jid, account, editable, mood_pixbuf, - # activity_pixbuf, tune_pixbuf avatar_pixbuf, padlock_pixbuf) + # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf, + # padlock_pixbuf) self.model = gtk.TreeStore(gtk.Image, str, str, str, str, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, - gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) + gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) self.model.set_sort_func(1, self._compareIters) self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) @@ -5892,9 +5901,16 @@ class RosterWindow: col.set_cell_data_func(render_pixbuf, self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF) + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF) + col.set_cell_data_func(render_pixbuf, + self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF) + self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF, 'activity': C_ACTIVITY_PIXBUF, - 'tune': C_TUNE_PIXBUF} + 'tune': C_TUNE_PIXBUF, + 'location': C_LOCATION_PIXBUF} if gajim.config.get('avatar_position_in_roster') == 'right': add_avatar_renderer() diff --git a/src/tooltips.py b/src/tooltips.py index 22f16874e..7faed5c45 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -600,7 +600,7 @@ class RosterTooltip(NotificationAreaTooltip): def _append_pep_info(self, contact, properties): """ - Append Tune, Mood, Activity information of the specified contact + Append Tune, Mood, Activity, Location information of the specified contact to the given property list. """ if 'mood' in contact.pep: @@ -618,6 +618,11 @@ class RosterTooltip(NotificationAreaTooltip): tune_string = _('Tune:') + ' %s' % tune properties.append((tune_string, None)) + if 'location' in contact.pep: + location = contact.pep['location'].asMarkupText() + location_string = _('Location:') + ' %s' % location + properties.append((location_string, None)) + class FileTransfersTooltip(BaseTooltip): """