346 lines
14 KiB
Python
346 lines
14 KiB
Python
import os
|
|
import time
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
import gi
|
|
from gi.repository import Gtk
|
|
|
|
from gajim.plugins.gui import GajimPluginConfigDialog
|
|
from gajim.plugins import GajimPlugin
|
|
from gajim.plugins.plugins_i18n import _
|
|
from gajim.plugins.helpers import log_calls
|
|
from gajim.common import app
|
|
from gajim.common import ged
|
|
from gajim.common import helpers
|
|
from gajim.common import configpaths
|
|
|
|
from gajim import gtkgui_helpers
|
|
from gajim.dialogs import InputDialog, WarningDialog
|
|
|
|
|
|
log = logging.getLogger('gajim.plugin_system.set_location')
|
|
|
|
CHAMPLAIN_AVAILABLE = True
|
|
|
|
try:
|
|
gi.require_version('Clutter', '1.0')
|
|
gi.require_version('GtkClutter', '1.0')
|
|
gi.require_version('Champlain', '0.12')
|
|
gi.require_version('GtkChamplain', '0.12')
|
|
from gi.repository import Clutter, GtkClutter
|
|
GtkClutter.init([]) # Must be initialized before importing those:
|
|
from gi.repository import Champlain, GtkChamplain
|
|
except:
|
|
log.exception('To view the map, you have to install all dependencies')
|
|
CHAMPLAIN_AVAILABLE = False
|
|
|
|
|
|
class SetLocationPlugin(GajimPlugin):
|
|
@log_calls('SetLocationPlugin')
|
|
def init(self):
|
|
self.description = _('Set information about your current geographical '
|
|
'or physical location. \nTo be able to set your location on the '
|
|
'built-in map, you need to have gir1.2-gtkchamplain and '
|
|
'gir1.2-gtkclutter-1.0 installed')
|
|
self.config_dialog = SetLocationPluginConfigDialog(self)
|
|
self.config_default_values = {
|
|
'alt': (1609, ''),
|
|
'area': ('Central Park', ''),
|
|
'building': ('The Empire State Building', ''),
|
|
'country': ('United States', ''),
|
|
'countrycode': ('US', ''),
|
|
'description': ('Bill\'s house', ''),
|
|
'floor': ('102', ''),
|
|
'lat': (39.75, ''),
|
|
'locality': ('New York City', ''),
|
|
'lon': (-104.99, ''),
|
|
'postalcode': ('10027', ''),
|
|
'region': ('New York', ''),
|
|
'room': ('Observatory', ''),
|
|
'street': ('34th and Broadway', ''),
|
|
'text': ('Northwest corner of the lobby', ''),
|
|
'uri': ('http://beta.plazes.com/plazes/1940:jabber_inc', ''),
|
|
'presets': ({'default': {}}, ''), }
|
|
|
|
@log_calls('SetLocationPlugin')
|
|
def activate(self):
|
|
app.ged.register_event_handler('signed-in', ged.POSTGUI,
|
|
self.on_signed_in)
|
|
self.send_locations()
|
|
|
|
@log_calls('SetLocationPlugin')
|
|
def deactivate(self):
|
|
self._data = {}
|
|
for acct in app.connections:
|
|
app.connections[acct].get_module('UserLocation').send(self._data)
|
|
app.ged.remove_event_handler('signed-in', ged.POSTGUI,
|
|
self.on_signed_in)
|
|
|
|
def on_signed_in(self, network_event):
|
|
self.send_locations(network_event.conn.name)
|
|
|
|
def send_locations(self, acct=False):
|
|
self._data = {}
|
|
timestamp = time.time()
|
|
timestamp = datetime.utcfromtimestamp(timestamp)
|
|
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
self._data['timestamp'] = timestamp
|
|
for name in self.config_default_values:
|
|
self._data[name] = self.config[name]
|
|
|
|
if not acct:
|
|
# Set geo for all accounts
|
|
for acct in app.connections:
|
|
if app.config.get_per('accounts', acct, 'publish_location'):
|
|
app.connections[acct].get_module('UserLocation').send(
|
|
self._data)
|
|
elif app.config.get_per('accounts', acct, 'publish_location'):
|
|
app.connections[acct].get_module('UserLocation').send(self._data)
|
|
|
|
|
|
class SetLocationPluginConfigDialog(GajimPluginConfigDialog):
|
|
def init(self):
|
|
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
|
|
'config_dialog.ui')
|
|
self.xml = Gtk.Builder()
|
|
self.xml.set_translation_domain('gajim_plugins')
|
|
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
|
|
['config_box'])
|
|
config_box = self.xml.get_object('config_box')
|
|
self.get_child().pack_start(config_box, True, True, 0)
|
|
self.xml.connect_signals(self)
|
|
self.connect('hide', self.on_hide)
|
|
self.connect('show', self.on_show)
|
|
self.is_active = None
|
|
|
|
self.preset_combo = self.xml.get_object('preset_combobox')
|
|
self.preset_liststore = Gtk.ListStore(str)
|
|
self.preset_combo.set_model(self.preset_liststore)
|
|
cellrenderer = Gtk.CellRendererText()
|
|
self.preset_combo.pack_start(cellrenderer, True)
|
|
self.preset_combo.add_attribute(cellrenderer, 'text', 0)
|
|
#self.plugin.config['presets'] = {'default': {}}
|
|
|
|
|
|
@log_calls('SetLocationPlugin.SetLocationPluginConfigDialog')
|
|
def on_run(self):
|
|
if not self.is_active:
|
|
pres_keys = sorted(self.plugin.config['presets'].keys())
|
|
for key in pres_keys:
|
|
self.preset_liststore.append((key,))
|
|
|
|
for name in self.plugin.config_default_values:
|
|
if name == 'presets':
|
|
continue
|
|
widget = self.xml.get_object(name)
|
|
widget.set_text(str(self.plugin.config[name]))
|
|
|
|
map_placeholder = self.xml.get_object('map_placeholder')
|
|
dependency_bar = self.xml.get_object('dependency_warning')
|
|
|
|
if CHAMPLAIN_AVAILABLE and not self.is_active:
|
|
map_placeholder.set_no_show_all(True)
|
|
map_placeholder.hide()
|
|
dependency_bar.hide()
|
|
map_box = self.xml.get_object('map_box')
|
|
map_box.set_size_request(400, -1)
|
|
|
|
embed = GtkChamplain.Embed()
|
|
|
|
self.view = embed.get_view()
|
|
self.view.set_reactive(True)
|
|
self.view.set_property('kinetic-mode', True)
|
|
self.view.set_property('zoom-level', 12)
|
|
self.view.connect('button-release-event', self.map_clicked,
|
|
self.view)
|
|
|
|
scale = Champlain.Scale()
|
|
scale.connect_view(self.view)
|
|
self.view.add_child(scale)
|
|
|
|
lat = self.plugin.config['lat']
|
|
lon = self.plugin.config['lon']
|
|
if not self.is_valid_coord(lat, lon):
|
|
self.lat = self.lon = 0.0
|
|
self.xml.get_object('lat').set_text('0.0')
|
|
self.xml.get_object('lon').set_text('0.0')
|
|
self.view.center_on(self.lat, self.lon)
|
|
|
|
self.path_to_image = os.path.abspath(gtkgui_helpers.get_icon_path(
|
|
'org.gajim.Gajim', 16))
|
|
map_box.pack_start(embed, expand=True, fill=True, padding=0)
|
|
|
|
self.is_active = True
|
|
self.layer = Champlain.MarkerLayer()
|
|
texture = Clutter.Texture()
|
|
texture.set_from_file(self.path_to_image)
|
|
texture.set_size(32,32)
|
|
self.marker = Champlain.Label.new_with_image(texture)
|
|
self.marker.set_location(self.lat, self.lon)
|
|
self.marker.set_text(_('Your location'))
|
|
self.view.add_layer(self.layer)
|
|
self.layer.add_marker(self.marker)
|
|
self.markers_is_visible = False
|
|
self.xml.get_object('lat').connect('changed', self.on_latlon_changed)
|
|
self.xml.get_object('lon').connect('changed', self.on_latlon_changed)
|
|
self.layer.animate_in_all_markers()
|
|
self.contacts_layer = Champlain.MarkerLayer()
|
|
|
|
def on_show(self, widget):
|
|
if CHAMPLAIN_AVAILABLE:
|
|
self.contacts_layer.remove_all()
|
|
self.view.center_on(self.lat, self.lon)
|
|
self.show_contacts()
|
|
|
|
def on_hide(self, widget):
|
|
for name in self.plugin.config_default_values:
|
|
if name in ['presets', 'lat', 'lon']:
|
|
continue
|
|
widget = self.xml.get_object(name)
|
|
self.plugin.config[name] = widget.get_text()
|
|
|
|
lat = self.xml.get_object('lat').get_text()
|
|
lon = self.xml.get_object('lon').get_text()
|
|
if self.is_valid_coord(lat, lon):
|
|
self.plugin.config['lat'] = lat
|
|
self.plugin.config['lon'] = lon
|
|
if self.plugin.active:
|
|
self.plugin.activate()
|
|
else:
|
|
self.plugin.config['lat'] = '0.0'
|
|
self.plugin.config['lon'] = '0.0'
|
|
error_text = _('Latitude or Longitude field contains an invalid value')
|
|
WarningDialog(_('Wrong coordinates'), error_text, self)
|
|
|
|
def map_clicked(self, actor, event, view):
|
|
x, y = event.x, event.y
|
|
lat, lon = view.x_to_longitude(x), view.y_to_latitude(y)
|
|
if event.button == 3:
|
|
self.marker.set_location(lat, lon)
|
|
self.xml.get_object('lon').set_text(str(lat))
|
|
self.xml.get_object('lat').set_text(str(lon))
|
|
if event.button == 2:
|
|
if self.markers_is_visible:
|
|
self.contacts_layer.animate_out_all_markers()
|
|
else:
|
|
self.contacts_layer.animate_in_all_markers()
|
|
self.markers_is_visible = not self.markers_is_visible
|
|
|
|
def is_valid_coord(self, lat, lon):
|
|
try:
|
|
self.lat = float(lat)
|
|
self.lon = float(lon)
|
|
except ValueError as e:
|
|
return
|
|
if not -85 < self.lat < 85 or not -180 < self.lon < 180:
|
|
return
|
|
return True
|
|
|
|
def on_latlon_changed(self, widget):
|
|
lat = self.xml.get_object('lat').get_text()
|
|
lon = self.xml.get_object('lon').get_text()
|
|
if self.is_valid_coord(lat, lon):
|
|
self.marker.set_location(self.lat, self.lon)
|
|
self.view.go_to(self.lat, self.lon)
|
|
|
|
def show_contacts(self):
|
|
data = {}
|
|
accounts = app.contacts._accounts
|
|
for account in accounts:
|
|
if not app.account_is_connected(account):
|
|
continue
|
|
for contact in accounts[account].contacts._contacts:
|
|
pep = accounts[account].contacts._contacts[contact][0].pep
|
|
if 'location' not in pep:
|
|
continue
|
|
lat = pep['location']._pep_specific_data.get('lat', None)
|
|
lon = pep['location']._pep_specific_data.get('lon', None)
|
|
if not lat or not lon:
|
|
continue
|
|
name = accounts[account].contacts.get_first_contact_from_jid(
|
|
contact).get_shown_name()
|
|
data[contact] = (lat, lon, name)
|
|
|
|
self.contacts_layer = Champlain.MarkerLayer()
|
|
for jid in data:
|
|
path = self.get_path_to_generic_or_avatar(self.path_to_image,
|
|
jid=jid, suffix='')
|
|
texture = Clutter.Texture()
|
|
texture.set_from_file(path)
|
|
texture.set_size(32,32)
|
|
marker = Champlain.Label.new_with_image(texture)
|
|
marker.set_text(data[jid][2])
|
|
marker.set_location(float(data[jid][0]), float(data[jid][1]))
|
|
self.contacts_layer.add_marker(marker)
|
|
|
|
self.view.add_layer(self.contacts_layer)
|
|
self.contacts_layer.animate_in_all_markers()
|
|
self.markers_is_visible = True
|
|
|
|
def get_path_to_generic_or_avatar(self, generic, jid=None, suffix=None):
|
|
"""
|
|
Choose between avatar image and default image
|
|
|
|
Returns full path to the avatar image if it exists, otherwise returns full
|
|
path to the image. generic must be with extension and suffix without
|
|
"""
|
|
if jid:
|
|
# we want an avatar
|
|
puny_jid = helpers.sanitize_filename(jid)
|
|
path_to_file = os.path.join(
|
|
configpaths.get('AVATAR'), puny_jid) + suffix
|
|
path_to_local_file = path_to_file + '_local'
|
|
for extension in ('.png', '.jpeg'):
|
|
path_to_local_file_full = path_to_local_file + extension
|
|
if os.path.exists(path_to_local_file_full):
|
|
return path_to_local_file_full
|
|
for extension in ('.png', '.jpeg'):
|
|
path_to_file_full = path_to_file + extension
|
|
if os.path.exists(path_to_file_full):
|
|
return path_to_file_full
|
|
return os.path.abspath(generic)
|
|
|
|
def on_preset_button_clicked(self, widget):
|
|
def on_ok(preset_name):
|
|
if preset_name == '':
|
|
return
|
|
preset = {}
|
|
for name in self.plugin.config_default_values:
|
|
if name == 'presets':
|
|
continue
|
|
widget = self.xml.get_object(name)
|
|
preset[name] = widget.get_text()
|
|
preset = {preset_name: preset}
|
|
presets = dict(list(self.plugin.config['presets'].items()) + \
|
|
list(preset.items()))
|
|
if preset_name not in list(self.plugin.config['presets'].keys()):
|
|
iter_ = self.preset_liststore.append((preset_name,))
|
|
self.plugin.config['presets'] = presets
|
|
self.set_modal(False)
|
|
InputDialog(_('Save as Preset'),
|
|
_('Please type a name for this preset'),
|
|
'default', is_modal=True, ok_handler=on_ok)
|
|
|
|
def on_preset_combobox_changed(self, widget):
|
|
model = widget.get_model()
|
|
active = widget.get_active()
|
|
if active < 0:
|
|
self.xml.get_object('del_preset').set_sensitive(False)
|
|
return
|
|
pres_name = model[active][0]
|
|
for name in list(self.plugin.config['presets'][pres_name].keys()):
|
|
widget = self.xml.get_object(name)
|
|
widget.set_text(str(self.plugin.config['presets'][pres_name][name]))
|
|
|
|
self.xml.get_object('del_preset').set_sensitive(True)
|
|
|
|
def on_del_preset_clicked(self, widget):
|
|
active = self.preset_combo.get_active()
|
|
active_iter = self.preset_combo.get_active_iter()
|
|
name = self.preset_liststore[active][0]
|
|
presets = self.plugin.config['presets']
|
|
del presets[name]
|
|
self.plugin.config['presets'] = presets
|
|
self.preset_liststore.remove(active_iter)
|