~ubuntu-branches/debian/sid/gnome-shell/sid

« back to all changes in this revision

Viewing changes to js/ui/placeDisplay.js

  • Committer: Package Import Robot
  • Author(s): Emilio Pozuelo Monfort, Petr Salinger, Emilio Pozuelo Monfort
  • Date: 2013-10-13 17:47:35 UTC
  • mfrom: (1.2.17) (18.1.41 experimental)
  • Revision ID: package-import@ubuntu.com-20131013174735-2npsu0w5wk0e6vgb
Tags: 3.8.4-4
[ Petr Salinger ]
* Restrict dependency on gir1.2-nmgtk-1.0 to linux-any (Closes: #726099)

[ Emilio Pozuelo Monfort ]
* Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
2
 
 
3
 
const GLib = imports.gi.GLib;
4
 
const Gio = imports.gi.Gio;
5
 
const Shell = imports.gi.Shell;
6
 
const Lang = imports.lang;
7
 
const Mainloop = imports.mainloop;
8
 
const Signals = imports.signals;
9
 
const St = imports.gi.St;
10
 
 
11
 
const DND = imports.ui.dnd;
12
 
const Main = imports.ui.main;
13
 
const Params = imports.misc.params;
14
 
const Search = imports.ui.search;
15
 
const Util = imports.misc.util;
16
 
 
17
 
/**
18
 
 * Represents a place object, which is most normally a bookmark entry,
19
 
 * a mount/volume, or a special place like the Home Folder, Computer, and Network.
20
 
 *
21
 
 * @name: String title
22
 
 * @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
23
 
 * @launch: A JavaScript callback to launch the entry
24
 
 */
25
 
const PlaceInfo = new Lang.Class({
26
 
    Name: 'PlaceInfo',
27
 
 
28
 
    _init: function(id, name, iconFactory, launch) {
29
 
        this.id = id;
30
 
        this.name = name;
31
 
        this._lowerName = name.toLowerCase();
32
 
        this.iconFactory = iconFactory;
33
 
        this.launch = launch;
34
 
    },
35
 
 
36
 
    matchTerms: function(terms) {
37
 
        let mtype = Search.MatchType.NONE;
38
 
        for (let i = 0; i < terms.length; i++) {
39
 
            let term = terms[i];
40
 
            let idx = this._lowerName.indexOf(term);
41
 
            if (idx == 0) {
42
 
                mtype = Search.MatchType.PREFIX;
43
 
            } else if (idx > 0) {
44
 
                if (mtype == Search.MatchType.NONE)
45
 
                    mtype = Search.MatchType.SUBSTRING;
46
 
            } else {
47
 
                return Search.MatchType.NONE;
48
 
            }
49
 
        }
50
 
        return mtype;
51
 
    },
52
 
 
53
 
    isRemovable: function() {
54
 
        return false;
55
 
    }
56
 
});
57
 
 
58
 
// Helper function to translate launch parameters into a GAppLaunchContext
59
 
function _makeLaunchContext(params)
60
 
{
61
 
    params = Params.parse(params, { workspace: -1,
62
 
                                    timestamp: 0 });
63
 
 
64
 
    let launchContext = global.create_app_launch_context();
65
 
    if (params.workspace != -1)
66
 
        launchContext.set_desktop(params.workspace);
67
 
    if (params.timestamp != 0)
68
 
        launchContext.set_timestamp(params.timestamp);
69
 
 
70
 
    return launchContext;
71
 
}
72
 
 
73
 
const PlaceDeviceInfo = new Lang.Class({
74
 
    Name: 'PlaceDeviceInfo',
75
 
    Extends: PlaceInfo,
76
 
 
77
 
    _init: function(mount) {
78
 
        this._mount = mount;
79
 
        this.name = mount.get_name();
80
 
        this._lowerName = this.name.toLowerCase();
81
 
        this.id = 'mount:' + mount.get_root().get_uri();
82
 
    },
83
 
 
84
 
    iconFactory: function(size) {
85
 
        let icon = this._mount.get_icon();
86
 
        return St.TextureCache.get_default().load_gicon(null, icon, size);
87
 
    },
88
 
 
89
 
    launch: function(params) {
90
 
        Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
91
 
                                            _makeLaunchContext(params));
92
 
    },
93
 
 
94
 
    isRemovable: function() {
95
 
        return this._mount.can_unmount();
96
 
    },
97
 
 
98
 
    remove: function() {
99
 
        if (!this.isRemovable())
100
 
            return;
101
 
 
102
 
        if (this._mount.can_eject())
103
 
            this._mount.eject(0, null, Lang.bind(this, this._removeFinish));
104
 
        else
105
 
            this._mount.unmount(0, null, Lang.bind(this, this._removeFinish));
106
 
    },
107
 
 
108
 
    _removeFinish: function(o, res, data) {
109
 
        try {
110
 
            if (this._mount.can_eject())
111
 
                this._mount.eject_finish(res);
112
 
            else
113
 
                this._mount.unmount_finish(res);
114
 
        } catch (e) {
115
 
            let message = _("Failed to unmount '%s'").format(o.get_name());
116
 
            Main.overview.setMessage(message,
117
 
                                     Lang.bind(this, this.remove),
118
 
                                     _("Retry"));
119
 
        }
120
 
    }
121
 
});
122
 
 
123
 
const PlacesManager = new Lang.Class({
124
 
    Name: 'PlacesManager',
125
 
 
126
 
    _init: function() {
127
 
        this._defaultPlaces = [];
128
 
        this._mounts = [];
129
 
        this._bookmarks = [];
130
 
 
131
 
        let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
132
 
        let homeUri = homeFile.get_uri();
133
 
        let homeLabel = Shell.util_get_label_for_uri (homeUri);
134
 
        let homeIcon = Shell.util_get_icon_for_uri (homeUri);
135
 
        this._home = new PlaceInfo('special:home', homeLabel,
136
 
            function(size) {
137
 
                return St.TextureCache.get_default().load_gicon(null, homeIcon, size);
138
 
            },
139
 
            function(params) {
140
 
                Gio.app_info_launch_default_for_uri(homeUri, _makeLaunchContext(params));
141
 
            });
142
 
 
143
 
        let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
144
 
        let desktopFile = Gio.file_new_for_path (desktopPath);
145
 
        let desktopUri = desktopFile.get_uri();
146
 
        let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
147
 
        let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
148
 
        this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
149
 
            function(size) {
150
 
                return St.TextureCache.get_default().load_gicon(null, desktopIcon, size);
151
 
            },
152
 
            function(params) {
153
 
                Gio.app_info_launch_default_for_uri(desktopUri, _makeLaunchContext(params));
154
 
            });
155
 
 
156
 
        this._connect = new PlaceInfo('special:connect', _("Connect to..."),
157
 
            function (size) {
158
 
                // do NOT use St.Icon here, it crashes the shell
159
 
                // see wanda.js for details
160
 
                return St.TextureCache.get_default().load_icon_name(null,
161
 
                                                                    'applications-internet',
162
 
                                                                    St.IconType.FULLCOLOR,
163
 
                                                                    size);
164
 
            },
165
 
            function (params) {
166
 
                // BUG: nautilus-connect-server doesn't have a desktop file, so we can't
167
 
                // launch it with the workspace from params. It's probably pretty rare
168
 
                // and odd to drag this place onto a workspace in any case
169
 
 
170
 
                Util.spawn(['nautilus-connect-server']);
171
 
            });
172
 
 
173
 
        this._defaultPlaces.push(this._home);
174
 
        this._defaultPlaces.push(this._desktopMenu);
175
 
        this._defaultPlaces.push(this._connect);
176
 
 
177
 
        /*
178
 
        * Show devices, code more or less ported from nautilus-places-sidebar.c
179
 
        */
180
 
        this._volumeMonitor = Gio.VolumeMonitor.get();
181
 
        this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
182
 
        this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
183
 
        this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
184
 
        this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
185
 
        this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
186
 
        this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
187
 
        this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
188
 
        this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
189
 
        this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
190
 
        this._updateDevices();
191
 
 
192
 
        this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']);
193
 
        this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
194
 
        this._monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
195
 
        this._bookmarkTimeoutId = 0;
196
 
        this._monitor.connect('changed', Lang.bind(this, function () {
197
 
            if (this._bookmarkTimeoutId > 0)
198
 
                return;
199
 
            /* Defensive event compression */
200
 
            this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
201
 
                this._bookmarkTimeoutId = 0;
202
 
                this._reloadBookmarks();
203
 
                return false;
204
 
            }));
205
 
        }));
206
 
 
207
 
        this._reloadBookmarks();
208
 
    },
209
 
 
210
 
    _updateDevices: function() {
211
 
        this._mounts = [];
212
 
 
213
 
        /* first go through all connected drives */
214
 
        let drives = this._volumeMonitor.get_connected_drives();
215
 
        for (let i = 0; i < drives.length; i++) {
216
 
            let volumes = drives[i].get_volumes();
217
 
            for(let j = 0; j < volumes.length; j++) {
218
 
                let mount = volumes[j].get_mount();
219
 
                if(mount != null) {
220
 
                    this._addMount(mount);
221
 
                }
222
 
            }
223
 
        }
224
 
 
225
 
        /* add all volumes that is not associated with a drive */
226
 
        let volumes = this._volumeMonitor.get_volumes();
227
 
        for(let i = 0; i < volumes.length; i++) {
228
 
            if(volumes[i].get_drive() != null)
229
 
                continue;
230
 
 
231
 
            let mount = volumes[i].get_mount();
232
 
            if(mount != null) {
233
 
                this._addMount(mount);
234
 
            }
235
 
        }
236
 
 
237
 
        /* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
238
 
        let mounts = this._volumeMonitor.get_mounts();
239
 
        for(let i = 0; i < mounts.length; i++) {
240
 
            if(mounts[i].is_shadowed())
241
 
                continue;
242
 
 
243
 
            if(mounts[i].get_volume())
244
 
                continue;
245
 
 
246
 
            this._addMount(mounts[i]);
247
 
        }
248
 
 
249
 
        /* We emit two signals, one for a generic 'all places' update
250
 
         * and the other for one specific to mounts. We do this because
251
 
         * clients like PlaceDisplay may only care about places in general
252
 
         * being updated while clients like DashPlaceDisplay care which
253
 
         * specific type of place got updated.
254
 
         */
255
 
        this.emit('mounts-updated');
256
 
        this.emit('places-updated');
257
 
 
258
 
    },
259
 
 
260
 
    _reloadBookmarks: function() {
261
 
 
262
 
        this._bookmarks = [];
263
 
 
264
 
        if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
265
 
            return;
266
 
 
267
 
        let bookmarksContent = Shell.get_file_contents_utf8_sync(this._bookmarksPath);
268
 
 
269
 
        let bookmarks = bookmarksContent.split('\n');
270
 
 
271
 
        let bookmarksToLabel = {};
272
 
        let bookmarksOrder = [];
273
 
        for (let i = 0; i < bookmarks.length; i++) {
274
 
            let bookmarkLine = bookmarks[i];
275
 
            let components = bookmarkLine.split(' ');
276
 
            let bookmark = components[0];
277
 
            if (bookmark in bookmarksToLabel)
278
 
                continue;
279
 
            let label = null;
280
 
            if (components.length > 1)
281
 
                label = components.slice(1).join(' ');
282
 
            bookmarksToLabel[bookmark] = label;
283
 
            bookmarksOrder.push(bookmark);
284
 
        }
285
 
 
286
 
        for (let i = 0; i < bookmarksOrder.length; i++) {
287
 
            let bookmark = bookmarksOrder[i];
288
 
            let label = bookmarksToLabel[bookmark];
289
 
            let file = Gio.file_new_for_uri(bookmark);
290
 
            if (!file.query_exists(null))
291
 
                continue;
292
 
            if (label == null)
293
 
                label = Shell.util_get_label_for_uri(bookmark);
294
 
            if (label == null)
295
 
                continue;
296
 
            let icon = Shell.util_get_icon_for_uri(bookmark);
297
 
 
298
 
            let item = new PlaceInfo('bookmark:' + bookmark, label,
299
 
                function(size) {
300
 
                    return St.TextureCache.get_default().load_gicon(null, icon, size);
301
 
                },
302
 
                function(params) {
303
 
                    Gio.app_info_launch_default_for_uri(bookmark, _makeLaunchContext(params));
304
 
                });
305
 
            this._bookmarks.push(item);
306
 
        }
307
 
 
308
 
        /* See comment in _updateDevices for explanation why there are two signals. */
309
 
        this.emit('bookmarks-updated');
310
 
        this.emit('places-updated');
311
 
    },
312
 
 
313
 
    _addMount: function(mount) {
314
 
        let devItem = new PlaceDeviceInfo(mount);
315
 
        this._mounts.push(devItem);
316
 
    },
317
 
 
318
 
    getAllPlaces: function () {
319
 
        return this.getDefaultPlaces().concat(this.getBookmarks(), this.getMounts());
320
 
    },
321
 
 
322
 
    getDefaultPlaces: function () {
323
 
        return this._defaultPlaces;
324
 
    },
325
 
 
326
 
    getBookmarks: function () {
327
 
        return this._bookmarks;
328
 
    },
329
 
 
330
 
    getMounts: function () {
331
 
        return this._mounts;
332
 
    },
333
 
 
334
 
    _lookupIndexById: function(sourceArray, id) {
335
 
        for (let i = 0; i < sourceArray.length; i++) {
336
 
            let place = sourceArray[i];
337
 
            if (place.id == id)
338
 
                return i;
339
 
        }
340
 
        return -1;
341
 
    },
342
 
 
343
 
    lookupPlaceById: function(id) {
344
 
        let colonIdx = id.indexOf(':');
345
 
        let type = id.substring(0, colonIdx);
346
 
        let sourceArray = null;
347
 
        if (type == 'special')
348
 
            sourceArray = this._defaultPlaces;
349
 
        else if (type == 'mount')
350
 
            sourceArray = this._mounts;
351
 
        else if (type == 'bookmark')
352
 
            sourceArray = this._bookmarks;
353
 
        return sourceArray[this._lookupIndexById(sourceArray, id)];
354
 
    },
355
 
 
356
 
    _removeById: function(sourceArray, id) {
357
 
        sourceArray.splice(this._lookupIndexById(sourceArray, id), 1);
358
 
    }
359
 
});
360
 
Signals.addSignalMethods(PlacesManager.prototype);
361
 
 
362
 
const PlaceSearchProvider = new Lang.Class({
363
 
    Name: 'PlaceSearchProvider',
364
 
    Extends: Search.SearchProvider,
365
 
 
366
 
    _init: function() {
367
 
        this.parent(_("PLACES & DEVICES"));
368
 
    },
369
 
 
370
 
    getResultMetas: function(resultIds) {
371
 
        let metas = [];
372
 
        for (let i = 0; i < resultIds.length; i++) {
373
 
            let placeInfo = Main.placesManager.lookupPlaceById(resultIds[i]);
374
 
            if (!placeInfo)
375
 
                metas.push(null);
376
 
            else
377
 
                metas.push({ 'id': resultIds[i],
378
 
                             'name': placeInfo.name,
379
 
                             'createIcon': function(size) {
380
 
                                 return placeInfo.iconFactory(size);
381
 
                             }
382
 
                           });
383
 
        }
384
 
        return metas;
385
 
    },
386
 
 
387
 
    activateResult: function(id, params) {
388
 
        let placeInfo = Main.placesManager.lookupPlaceById(id);
389
 
        placeInfo.launch(params);
390
 
    },
391
 
 
392
 
    _compareResultMeta: function (idA, idB) {
393
 
        let infoA = Main.placesManager.lookupPlaceById(idA);
394
 
        let infoB = Main.placesManager.lookupPlaceById(idB);
395
 
        return infoA.name.localeCompare(infoB.name);
396
 
    },
397
 
 
398
 
    _searchPlaces: function(places, terms) {
399
 
        let multiplePrefixResults = [];
400
 
        let prefixResults = [];
401
 
        let multipleSubstringResults = [];
402
 
        let substringResults = [];
403
 
 
404
 
        terms = terms.map(String.toLowerCase);
405
 
 
406
 
        for (let i = 0; i < places.length; i++) {
407
 
            let place = places[i];
408
 
            let mtype = place.matchTerms(terms);
409
 
            if (mtype == Search.MatchType.MULTIPLE_PREFIX)
410
 
                multiplePrefixResults.push(place.id);
411
 
            else if (mtype == Search.MatchType.PREFIX)
412
 
                prefixResults.push(place.id);
413
 
            else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
414
 
                multipleSubstringResults.push(place.id);
415
 
            else if (mtype == Search.MatchType.SUBSTRING)
416
 
                substringResults.push(place.id);
417
 
        }
418
 
        multiplePrefixResults.sort(this._compareResultMeta);
419
 
        prefixResults.sort(this._compareResultMeta);
420
 
        multipleSubstringResults.sort(this._compareResultMeta);
421
 
        substringResults.sort(this._compareResultMeta);
422
 
        return multiplePrefixResults.concat(prefixResults.concat(multipleSubstringResults.concat(substringResults)));
423
 
    },
424
 
 
425
 
    getInitialResultSet: function(terms) {
426
 
        let places = Main.placesManager.getAllPlaces();
427
 
        return this._searchPlaces(places, terms);
428
 
    },
429
 
 
430
 
    getSubsearchResultSet: function(previousResults, terms) {
431
 
        let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
432
 
        return this._searchPlaces(places, terms);
433
 
    }
434
 
});