set_location.py 13.1 KB
Newer Older
Dicson's avatar
Dicson committed
1
2
3
4
# -*- coding: utf-8 -*-
##

from datetime import datetime
5
6
from gi.repository import Gtk
from gi.repository import GdkPixbuf
Dicson's avatar
Dicson committed
7
import os
8
import time
Dicson's avatar
Dicson committed
9
10
11

from plugins.gui import GajimPluginConfigDialog
from plugins import GajimPlugin
12
from plugins.helpers import log_calls
Dicson's avatar
Dicson committed
13
from common import gajim
14
from common import ged
15
import gtkgui_helpers
16
from dialogs import InputDialog, WarningDialog
Dicson's avatar
Dicson committed
17

Dicson's avatar
Dicson committed
18

Dicson's avatar
Dicson committed
19
20
21
class SetLocationPlugin(GajimPlugin):
    @log_calls('SetLocationPlugin')
    def init(self):
Dicson's avatar
Dicson committed
22
23
24
        self.description = _('Set information about the current geographical '
            'or physical location.\n'
            'To be able to specify a location on the built-in card, '
25
            'you must install gir1.2-gtkchamplain')
Dicson's avatar
Dicson committed
26
27
        self.config_dialog = SetLocationPluginConfigDialog(self)
        self.config_default_values = {
Dicson's avatar
Dicson committed
28
            'alt': (1609, ''),
Dicson's avatar
Dicson committed
29
            'area': ('Central Park', ''),
Dicson's avatar
Dicson committed
30
            'building': ('The Empire State Building', ''),
Dicson's avatar
Dicson committed
31
            'country': ('United States', ''),
Dicson's avatar
Dicson committed
32
33
34
35
36
37
38
39
40
41
42
43
44
            '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': {}}, ''), }
Dicson's avatar
Dicson committed
45
46
47

    @log_calls('SetLocationPlugin')
    def activate(self):
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        gajim.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 gajim.connections:
            gajim.connections[acct].send_location(self._data)
        gajim.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):
Dicson's avatar
Dicson committed
64
65
66
        self._data = {}
        timestamp = time.time()
        timestamp = datetime.utcfromtimestamp(timestamp)
67
        timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
Dicson's avatar
Dicson committed
68
69
70
71
        self._data['timestamp'] = timestamp
        for name in self.config_default_values:
            self._data[name] = self.config[name]

72
73
74
75
76
77
        if not acct:
            #set geo for all accounts
            for acct in gajim.connections:
                if gajim.config.get_per('accounts', acct, 'publish_location'):
                    gajim.connections[acct].send_location(self._data)
        elif gajim.config.get_per('accounts', acct, 'publish_location'):
Dicson's avatar
Dicson committed
78
79
80
81
82
83
84
            gajim.connections[acct].send_location(self._data)


class SetLocationPluginConfigDialog(GajimPluginConfigDialog):
    def init(self):
        self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
                'config_dialog.ui')
85
        self.xml = Gtk.Builder()
Dicson's avatar
Dicson committed
86
        self.xml.set_translation_domain('gajim_plugins')
Dicson's avatar
Dicson committed
87
        self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
88
89
                ['hbox1'])
        hbox = self.xml.get_object('hbox1')
90
        self.get_child().pack_start(hbox, True, True, 0)
Dicson's avatar
Dicson committed
91
        self.xml.connect_signals(self)
92
        self.connect('hide', self.on_hide)
Dicson's avatar
Dicson committed
93
        self.is_active = None
Dicson's avatar
Dicson committed
94

Dicson's avatar
Dicson committed
95
        self.preset_combo = self.xml.get_object('preset_combobox')
96
        self.preset_liststore = Gtk.ListStore(str)
Dicson's avatar
Dicson committed
97
        self.preset_combo.set_model(self.preset_liststore)
98
        cellrenderer = Gtk.CellRendererText()
Dicson's avatar
Dicson committed
99
100
101
102
        self.preset_combo.pack_start(cellrenderer, True)
        self.preset_combo.add_attribute(cellrenderer, 'text', 0)
        #self.plugin.config['presets'] = {'default': {}}

103
    @log_calls('SetLocationPlugin.SetLocationPluginConfigDialog')
Dicson's avatar
Dicson committed
104
    def on_run(self):
105
        no_map = None
Dicson's avatar
Dicson committed
106
107
108
109
        if not self.is_active:
            pres_keys = sorted(self.plugin.config['presets'].keys())
            for key in pres_keys:
                self.preset_liststore.append((key,))
Dicson's avatar
Dicson committed
110
111

        for name in self.plugin.config_default_values:
Dicson's avatar
Dicson committed
112
113
            if name == 'presets':
                continue
Dicson's avatar
Dicson committed
114
115
116
            widget = self.xml.get_object(name)
            widget.set_text(str(self.plugin.config[name]))

117
        try:
118
119
120
            from gi.repository import GtkClutter, Clutter
            GtkClutter.init([]) # Must be initialized before importing those:
            from gi.repository import Champlain, GtkChamplain
121
122
        except:
            no_map = True
123

Dicson's avatar
Dicson committed
124
        if not no_map and not self.is_active:
125
            #from layers import DummyLayer
126
127
128
129

            vbox = self.xml.get_object('vbox1')
            vbox.set_size_request(400, -1)

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
            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.bin_layout_add(scale, Clutter.BinAlignment.START,
                Clutter.BinAlignment.END)

            #license = self.view.get_license_actor()
            #license.set_extra_text("Don't eat cereals with orange juice\nIt tastes bad")

147
148
149
150
151
152
            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')
153
154
            self.view.center_on(self.lat, self.lon)

155
156
            self.path_to_image = os.path.abspath(gtkgui_helpers.get_icon_path(
                'gajim', 16))
157
158
            vbox.pack_start(embed, expand=True, fill=True, padding=6)
            label = Gtk.Label(_(
159
                'Click the right mouse button to specify the location, \n'\
Dicson's avatar
Dicson committed
160
                'middle mouse button to show / hide the contacts on the map'))
Dicson's avatar
Dicson committed
161
162
            vbox.pack_start(label, expand=False, fill=False, padding=6)
            self.is_active = True
163
164
165
166
167
168
169
            self.layer = Champlain.MarkerLayer()
            self.marker = Champlain.Label.new_from_file(self.path_to_image)
            self.marker.set_text("I am")
            self.marker.set_location(self.lat, self.lon)
            self.view.add_layer(self.layer)
            self.layer.add_marker(self.marker)
            self.markers_is_visible = False
Dicson's avatar
Dicson committed
170
171
            self.xml.get_object('lat').connect('changed', self.on_lon_changed)
            self.xml.get_object('lon').connect('changed', self.on_lon_changed)
172
173
            self.layer.animate_in_all_markers()
            self.show_contacts()
Dicson's avatar
Dicson committed
174

175
176
    def on_hide(self, widget):
        for name in self.plugin.config_default_values:
177
            if name in ['presets', 'lat', 'lon']:
Dicson's avatar
Dicson committed
178
                continue
179
180
            widget = self.xml.get_object(name)
            self.plugin.config[name] = widget.get_text()
181
182
183
184
185
        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
186
187
            if self.plugin.active:
                self.plugin.activate()
188
189
190
191
192
        else:
            self.plugin.config['lat'] = '0.0'
            self.plugin.config['lon'] = '0.0'
            error_text = 'lat or lon field contains wrong value.'
            WarningDialog(_('Wrong coordinates'), error_text, self)
193

194
195
196
    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)
197
        if event.button == 3:
198
199
200
            self.marker.set_location(lat, lon)
            self.xml.get_object('lon').set_text(str(lat))
            self.xml.get_object('lat').set_text(str(lon))
201
        if event.button == 2:
202
203
204
205
206
            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
Dicson's avatar
Dicson committed
207

208
    def is_valid_coord(self, lat, lon):
Dicson's avatar
Dicson committed
209
        try:
210
211
            self.lat = float(lat)
            self.lon = float(lon)
212
        except ValueError as e:
Dicson's avatar
Dicson committed
213
            return
214
        if not -85 < self.lat < 85 or not -180 < self.lon < 180:
Dicson's avatar
Dicson committed
215
            return
216
217
218
219
220
221
        return True

    def on_lon_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):
222
223
            #self.view.center_on(self.lat, self.lon)
            self.marker.set_location(self.lat, self.lon)
224
225

    def show_contacts(self):
226
227
        from gi.repository import Champlain
        if not self.markers_is_visible:
228
229
230
231
232
233
234
235
236
237
238
239
240
            data = {}
            accounts = gajim.contacts._accounts
            for account in accounts:
                if not gajim.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
241
242
243
                    name = accounts[account].contacts.get_first_contact_from_jid(
                        contact).name
                    data[contact] = (lat, lon, name)
244
            for jid in data:
Dicson's avatar
Dicson committed
245
246
                path = self.get_path_to_generic_or_avatar(self.path_to_image,
                    jid=jid, suffix='')
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
                marker = Champlain.Label.new_from_file(path)
                marker.set_text(data[jid][2])
                self.contacts_layer = Champlain.MarkerLayer()

                marker.set_location(float(data[jid][0]), float(data[jid][1]))
                self.view.add_layer(self.contacts_layer)
                self.contacts_layer.add_marker(marker)
                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:
            from common import helpers
            # we want an avatar
            puny_jid = helpers.sanitize_filename(jid)
            path_to_file = os.path.join(gajim.AVATAR_PATH, 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)
Dicson's avatar
Dicson committed
279
280
281
282
283
284
285
286
287
288
289
290

    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}
291
292
293
            presets = dict(list(self.plugin.config['presets'].items()) + \
                list(preset.items()))
            if preset_name not in list(self.plugin.config['presets'].keys()):
294
                iter_ = self.preset_liststore.append((preset_name,))
Dicson's avatar
Dicson committed
295
296
297
298
299
300
301
302
303
            self.plugin.config['presets'] = presets
        self.set_modal(False)
        InputDialog(_('Save as Preset'), _('Please type a name for this preset'),
            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:
304
            self.xml.get_object('del_preset').set_sensitive(False)
Dicson's avatar
Dicson committed
305
            return
306
307
        pres_name = model[active][0]
        for name in list(self.plugin.config['presets'][pres_name].keys()):
Dicson's avatar
Dicson committed
308
309
            widget = self.xml.get_object(name)
            widget.set_text(str(self.plugin.config['presets'][pres_name][name]))
310
311
312
313
314
315

        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()
316
        name = self.preset_liststore[active][0]
317
318
319
320
        presets = self.plugin.config['presets']
        del presets[name]
        self.plugin.config['presets'] = presets
        self.preset_liststore.remove(active_iter)