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

« back to all changes in this revision

Viewing changes to js/ui/appDisplay.js

  • Committer: Bazaar Package Importer
  • Author(s): Gustavo Noronha Silva
  • Date: 2009-11-25 19:06:40 UTC
  • mfrom: (1.1.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20091125190640-cl36tq1pgy3gkws5
Tags: 2.28.1~git20091125-1
New upstream snapshot

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
const Shell = imports.gi.Shell;
10
10
const Lang = imports.lang;
11
11
const Signals = imports.signals;
 
12
const St = imports.gi.St;
12
13
const Mainloop = imports.mainloop;
13
14
const Gettext = imports.gettext.domain('gnome-shell');
14
15
const _ = Gettext.gettext;
15
16
 
16
17
const AppFavorites = imports.ui.appFavorites;
17
 
const AppIcon = imports.ui.appIcon;
18
18
const DND = imports.ui.dnd;
19
19
const GenericDisplay = imports.ui.genericDisplay;
20
20
const Main = imports.ui.main;
21
21
const Workspaces = imports.ui.workspaces;
22
22
 
23
 
const ENTERED_MENU_COLOR = new Clutter.Color();
24
 
ENTERED_MENU_COLOR.from_pixel(0x00ff0022);
25
 
 
26
 
const WELL_DEFAULT_COLUMNS = 4;
27
 
const WELL_ITEM_MIN_HSPACING = 4;
28
 
const WELL_ITEM_VSPACING = 4;
29
 
 
30
 
const MENU_ARROW_SIZE = 12;
31
 
const MENU_SPACING = 7;
32
 
 
33
 
const MAX_ITEMS = 30;
 
23
const APPICON_SIZE = 48;
 
24
const WELL_MAX_COLUMNS = 8;
34
25
 
35
26
/* This class represents a single display item containing information about an application.
36
27
 *
86
77
    }
87
78
};
88
79
 
89
 
const MENU_UNSELECTED = 0;
90
 
const MENU_SELECTED = 1;
91
 
const MENU_ENTERED = 2;
92
 
 
93
 
function MenuItem(name, id) {
94
 
    this._init(name, id);
95
 
}
96
 
 
97
 
/**
98
 
 * MenuItem:
99
 
 * Shows the list of menus in the sidebar.
100
 
 */
101
 
MenuItem.prototype = {
102
 
    _init: function(name, id) {
103
 
        this.id = id;
104
 
 
105
 
        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
106
 
                                   spacing: 4,
107
 
                                   corner_radius: 4,
108
 
                                   padding_right: 4,
109
 
                                   padding_left: 4,
110
 
                                   reactive: true });
111
 
        this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
112
 
            this.setState(MENU_SELECTED);
113
 
        }));
114
 
 
115
 
        this._text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
116
 
                                        font_name: "Sans 14px",
117
 
                                        text: name });
118
 
 
119
 
        // We use individual boxes for the label and the arrow to ensure that they
120
 
        // are aligned vertically. Just setting y_align: Big.BoxAlignment.CENTER
121
 
        // on this.actor does not seem to achieve that.  
122
 
        let labelBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER,
123
 
                                     padding: 4 });
124
 
 
125
 
        labelBox.append(this._text, Big.BoxPackFlags.NONE);
126
 
       
127
 
        this.actor.append(labelBox, Big.BoxPackFlags.EXPAND);
128
 
 
129
 
        let arrowBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
130
 
 
131
 
        this._arrow = new Shell.Arrow({ surface_width: MENU_ARROW_SIZE,
132
 
                                        surface_height: MENU_ARROW_SIZE,
133
 
                                        direction: Gtk.ArrowType.RIGHT,
134
 
                                        opacity: 0 });
135
 
        arrowBox.append(this._arrow, Big.BoxPackFlags.NONE);
136
 
        this.actor.append(arrowBox, Big.BoxPackFlags.NONE);
137
 
    },
138
 
 
139
 
    getState: function() {
140
 
        return this._state;
141
 
    },
142
 
 
143
 
    setState: function (state) {
144
 
        if (state == this._state)
145
 
            return;
146
 
        this._state = state;
147
 
        if (this._state == MENU_UNSELECTED) {
148
 
            this.actor.background_color = null;
149
 
            this._arrow.set_opacity(0);
150
 
        } else if (this._state == MENU_ENTERED) {
151
 
            this.actor.background_color = ENTERED_MENU_COLOR;
152
 
            this._arrow.set_opacity(0xFF/2);
153
 
        } else {
154
 
            this.actor.background_color = GenericDisplay.ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR;
155
 
            this._arrow.set_opacity(0xFF);
156
 
        }
157
 
        this.emit('state-changed')
158
 
    }
159
 
}
160
 
Signals.addSignalMethods(MenuItem.prototype);
161
 
 
162
80
/* This class represents a display containing a collection of application items.
163
81
 * The applications are sorted based on their popularity by default, and based on
164
82
 * their name if some search filter is applied.
302
220
 
303
221
Signals.addSignalMethods(AppDisplay.prototype);
304
222
 
305
 
function BaseWellItem(app, isFavorite, hasMenu) {
306
 
    this._init(app, isFavorite, hasMenu);
 
223
 
 
224
function BaseWellItem(app, isFavorite) {
 
225
    this._init(app, isFavorite);
307
226
}
308
227
 
309
228
BaseWellItem.prototype = {
310
 
    __proto__: AppIcon.AppIcon.prototype,
311
 
 
312
 
    _init: function(app, isFavorite) {
313
 
        AppIcon.AppIcon.prototype._init.call(this, { app: app,
314
 
                                                     menuType: AppIcon.MenuType.ON_RIGHT,
315
 
                                                     glow: true });
316
 
 
317
 
        this.isFavorite = isFavorite;
 
229
    _init : function(app, isFavorite) {
 
230
        this.app = app;
 
231
 
 
232
        this._glowExtendVertical = 0;
 
233
        this._glowShrinkHorizontal = 0;
 
234
 
 
235
        this.actor = new St.Clickable({ style_class: 'app-well-app',
 
236
                                         reactive: true });
 
237
        this.actor._delegate = this;
 
238
        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
 
239
        this.actor.connect('notify::mapped', Lang.bind(this, this._onMapped));
 
240
 
 
241
        let box = new St.BoxLayout({ vertical: true });
 
242
        this.actor.set_child(box);
 
243
 
 
244
        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
 
245
 
 
246
        this._menu = null;
 
247
 
 
248
        this.icon = this.app.create_icon_texture(APPICON_SIZE);
 
249
 
 
250
        box.add(this.icon, { expand: true, x_fill: false, y_fill: false });
 
251
 
 
252
        let nameBox = new Shell.GenericContainer();
 
253
        nameBox.connect('get-preferred-width', Lang.bind(this, this._nameBoxGetPreferredWidth));
 
254
        nameBox.connect('get-preferred-height', Lang.bind(this, this._nameBoxGetPreferredHeight));
 
255
        nameBox.connect('allocate', Lang.bind(this, this._nameBoxAllocate));
 
256
        this._nameBox = nameBox;
 
257
 
 
258
        this._name = new St.Label({ text: this.app.get_name() });
 
259
        this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
 
260
        nameBox.add_actor(this._name);
 
261
        this._glowBox = new St.BoxLayout({ style_class: 'app-well-app-glow' });
 
262
        this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
 
263
        this._nameBox.add_actor(this._glowBox);
 
264
        this._glowBox.lower(this._name);
 
265
        this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
 
266
        this._rerenderGlow();
 
267
 
 
268
        box.add(nameBox);
318
269
 
319
270
        this._draggable = DND.makeDraggable(this.actor, true);
320
 
 
321
 
        // Do these as anonymous functions to avoid conflict with handlers in subclasses
322
 
        this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
323
 
            let [stageX, stageY] = event.get_coords();
324
 
            this._dragStartX = stageX;
325
 
            this._dragStartY = stageY;
326
 
            return false;
327
 
        }));
328
 
        this.actor.connect('notify::hover', Lang.bind(this, function () {
329
 
            let hover = this.actor.hover;
330
 
            if (!hover) {
331
 
                if (this.actor.pressed && this._dragStartX != null) {
332
 
                    this.actor.fake_release();
333
 
                    this._draggable.startDrag(this._dragStartX, this._dragStartY,
334
 
                                              Main.currentTime());
 
271
        this._dragStartX = null;
 
272
        this._dragStartY = null;
 
273
 
 
274
        this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
 
275
        this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChange));
 
276
    },
 
277
 
 
278
    _nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
 
279
        let [min, natural] = this._name.get_preferred_width(forHeight);
 
280
        alloc.min_size = min;
 
281
        alloc.natural_size = natural;
 
282
    },
 
283
 
 
284
    _nameBoxGetPreferredHeight: function (nameBox, forWidth, alloc) {
 
285
        let [min, natural] = this._name.get_preferred_height(forWidth);
 
286
        alloc.min_size = min + this._glowExtendVertical * 2;
 
287
        alloc.natural_size = natural + this._glowExtendVertical * 2;
 
288
    },
 
289
 
 
290
    _nameBoxAllocate: function (nameBox, box, flags) {
 
291
        let childBox = new Clutter.ActorBox();
 
292
        let [minWidth, naturalWidth] = this._name.get_preferred_width(-1);
 
293
        let [minHeight, naturalHeight] = this._name.get_preferred_height(-1);
 
294
        let availWidth = box.x2 - box.x1;
 
295
        let availHeight = box.y2 - box.y1;
 
296
        let targetWidth = availWidth;
 
297
        let xPadding = 0;
 
298
        if (naturalWidth < availWidth) {
 
299
            xPadding = Math.floor((availWidth - naturalWidth) / 2);
 
300
        }
 
301
        childBox.x1 = xPadding;
 
302
        childBox.x2 = availWidth - xPadding;
 
303
        childBox.y1 = this._glowExtendVertical;
 
304
        childBox.y2 = availHeight - this._glowExtendVertical;
 
305
        this._name.allocate(childBox, flags);
 
306
 
 
307
        // Now the glow
 
308
        let glowPaddingHoriz = Math.max(0, xPadding - this._glowShrinkHorizontal);
 
309
        glowPaddingHoriz = Math.max(this._glowShrinkHorizontal, glowPaddingHoriz);
 
310
        childBox.x1 = glowPaddingHoriz;
 
311
        childBox.x2 = availWidth - glowPaddingHoriz;
 
312
        childBox.y1 = 0;
 
313
        childBox.y2 = availHeight;
 
314
        this._glowBox.allocate(childBox, flags);
 
315
    },
 
316
 
 
317
    _onDestroy: function() {
 
318
        if (this._appWindowChangedId > 0)
 
319
            this.app.disconnect(this._appWindowChangedId);
 
320
    },
 
321
 
 
322
    _onMapped: function() {
 
323
        if (!this._queuedGlowRerender)
 
324
            return;
 
325
        this._queuedGlowRerender = false;
 
326
        this._rerenderGlow();
 
327
    },
 
328
 
 
329
    _rerenderGlow: function() {
 
330
        if (!this.actor.mapped) {
 
331
            this._queuedGlowRerender = true;
 
332
            return;
 
333
        }
 
334
        this._glowBox.destroy_children();
 
335
        let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
 
336
        let windows = this.app.get_windows();
 
337
        for (let i = 0; i < windows.length && i < 3; i++) {
 
338
            let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
 
339
                                                                      glowPath, -1, -1);
 
340
            glow.keep_aspect_ratio = false;
 
341
            this._glowBox.add(glow);
 
342
        }
 
343
    },
 
344
 
 
345
    _onButtonPress: function(actor, event) {
 
346
        let [stageX, stageY] = event.get_coords();
 
347
        this._dragStartX = stageX;
 
348
        this._dragStartY = stageY;
 
349
    },
 
350
 
 
351
    _onHoverChange: function(actor) {
 
352
        let hover = this.actor.hover;
 
353
        if (!hover) {
 
354
            if (this.actor.pressed && this._dragStartX != null) {
 
355
                this.actor.fake_release();
 
356
                this._draggable.startDrag(this._dragStartX, this._dragStartY,
 
357
                                          Main.currentTime());
 
358
            } else {
 
359
                this._dragStartX = null;
 
360
                this._dragStartY = null;
 
361
            }
 
362
        }
 
363
    },
 
364
 
 
365
    _onClicked: function(actor, event) {
 
366
        let button = event.get_button();
 
367
        if (button == 1) {
 
368
            this._onActivate(event);
 
369
        } else if (button == 3) {
 
370
            // Don't bind to the right click here; we want left click outside the
 
371
            // area to deactivate as well.
 
372
            this.popupMenu(0);
 
373
        }
 
374
        return false;
 
375
    },
 
376
 
 
377
    _onStyleChanged: function() {
 
378
        let themeNode = this._glowBox.get_theme_node();
 
379
 
 
380
        let success, len;
 
381
        [success, len] = themeNode.get_length('-shell-glow-extend-vertical', false);
 
382
        if (success)
 
383
            this._glowExtendVertical = len;
 
384
        [success, len] = themeNode.get_length('-shell-glow-shrink-horizontal', false);
 
385
        if (success)
 
386
            this._glowShrinkHorizontal = len;
 
387
        this.actor.queue_relayout();
 
388
    },
 
389
 
 
390
    popupMenu: function(activatingButton) {
 
391
        if (!this._menu) {
 
392
            this._menu = new AppIconMenu(this);
 
393
            this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
 
394
                this.highlightWindow(window);
 
395
            }));
 
396
            this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
 
397
                this.activateWindow(window);
 
398
            }));
 
399
            this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
 
400
                if (isPoppedUp) {
 
401
                    this._onMenuPoppedUp();
335
402
                } else {
336
 
                    this._dragStartX = null;
337
 
                    this._dragStartY = null;
 
403
                    this._onMenuPoppedDown();
338
404
                }
339
 
            }
340
 
        }));
 
405
            }));
 
406
        }
 
407
 
 
408
        this._menu.popup(activatingButton);
 
409
 
 
410
        return false;
 
411
    },
 
412
 
 
413
    // Default implementations; AppDisplay.RunningWellItem overrides these
 
414
    highlightWindow: function(window) {
 
415
        this.emit('highlight-window', window);
 
416
    },
 
417
 
 
418
    activateWindow: function(window) {
 
419
        this.emit('activate-window', window);
341
420
    },
342
421
 
343
422
    shellWorkspaceLaunch : function() {
353
432
    },
354
433
 
355
434
    getDragActor: function() {
356
 
        return this.createDragActor();
 
435
        return this.app.create_icon_texture(APPICON_SIZE);
357
436
    },
358
437
 
359
438
    // Returns the original icon that is being used as a source for the cloned texture
362
441
        return this.actor;
363
442
    }
364
443
}
 
444
Signals.addSignalMethods(BaseWellItem.prototype);
 
445
 
 
446
function AppIconMenu(source) {
 
447
    this._init(source);
 
448
}
 
449
 
 
450
AppIconMenu.prototype = {
 
451
    _init: function(source) {
 
452
        this._source = source;
 
453
 
 
454
        this._arrowSize = 4; // CSS default
 
455
        this._spacing = 0; // CSS default
 
456
 
 
457
        this._dragStartX = 0;
 
458
        this._dragStartY = 0;
 
459
 
 
460
        this.actor = new Shell.GenericContainer({ reactive: true });
 
461
        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
 
462
        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
 
463
        this.actor.connect('allocate', Lang.bind(this, this._allocate));
 
464
 
 
465
        this._windowContainerBox = new St.Bin({ style_class: 'app-well-menu' });
 
466
        this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
 
467
                                                  width: Main.overview._dash.actor.width });
 
468
        this._windowContainerBox.set_child(this._windowContainer);
 
469
        this._windowContainer.connect('unselected', Lang.bind(this, this._onItemUnselected));
 
470
        this._windowContainer.connect('selected', Lang.bind(this, this._onItemSelected));
 
471
        this._windowContainer.connect('cancelled', Lang.bind(this, this._onWindowSelectionCancelled));
 
472
        this._windowContainer.connect('activate', Lang.bind(this, this._onItemActivate));
 
473
        this.actor.add_actor(this._windowContainerBox);
 
474
 
 
475
        // Stay popped up on release over application icon
 
476
        this._windowContainer.set_persistent_source(this._source.actor);
 
477
 
 
478
        // Intercept events while the menu has the pointer grab to do window-related effects
 
479
        this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
 
480
        this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
 
481
        this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
 
482
 
 
483
        this._borderColor = new Clutter.Color();
 
484
        this._backgroundColor = new Clutter.Color();
 
485
        this._windowContainerBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
 
486
 
 
487
        this._arrow = new St.DrawingArea();
 
488
        this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
 
489
            Shell.draw_box_pointer(texture,
 
490
                                   Shell.PointerDirection.LEFT,
 
491
                                   this._borderColor,
 
492
                                   this._backgroundColor);
 
493
        }));
 
494
        this.actor.add_actor(this._arrow);
 
495
 
 
496
        // Chain our visibility and lifecycle to that of the source
 
497
        source.actor.connect('notify::mapped', Lang.bind(this, function () {
 
498
            if (!source.actor.mapped)
 
499
                this._windowContainer.popdown();
 
500
        }));
 
501
        source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
 
502
 
 
503
        global.stage.add_actor(this.actor);
 
504
    },
 
505
 
 
506
    _getPreferredWidth: function(actor, forHeight, alloc) {
 
507
        let [min, natural] = this._windowContainerBox.get_preferred_width(forHeight);
 
508
        min += this._arrowSize;
 
509
        natural += this._arrowSize;
 
510
        alloc.min_size = min;
 
511
        alloc.natural_size = natural;
 
512
    },
 
513
 
 
514
    _getPreferredHeight: function(actor, forWidth, alloc) {
 
515
        let [min, natural] = this._windowContainerBox.get_preferred_height(forWidth);
 
516
        alloc.min_size = min;
 
517
        alloc.natural_size = natural;
 
518
    },
 
519
 
 
520
    _allocate: function(actor, box, flags) {
 
521
        let childBox = new Clutter.ActorBox();
 
522
        let themeNode = this._windowContainerBox.get_theme_node();
 
523
 
 
524
        let width = box.x2 - box.x1;
 
525
        let height = box.y2 - box.y1;
 
526
 
 
527
        childBox.x1 = 0;
 
528
        childBox.x2 = this._arrowSize;
 
529
        childBox.y1 = Math.floor((height / 2) - (this._arrowSize / 2));
 
530
        childBox.y2 = childBox.y1 + this._arrowSize;
 
531
        this._arrow.allocate(childBox, flags);
 
532
 
 
533
        // Ensure the arrow is above the border area
 
534
        let border = themeNode.get_border_width(St.Side.LEFT);
 
535
        childBox.x1 = this._arrowSize - border;
 
536
        childBox.x2 = width;
 
537
        childBox.y1 = 0;
 
538
        childBox.y2 = height;
 
539
        this._windowContainerBox.allocate(childBox, flags);
 
540
    },
 
541
 
 
542
    _redisplay: function() {
 
543
        this._windowContainer.remove_all();
 
544
 
 
545
        let windows = this._source.app.get_windows();
 
546
 
 
547
        this._windowContainer.show();
 
548
 
 
549
        let iconsDiffer = false;
 
550
        let texCache = Shell.TextureCache.get_default();
 
551
        if (windows.length > 0) {
 
552
            let firstIcon = windows[0].mini_icon;
 
553
            for (let i = 1; i < windows.length; i++) {
 
554
                if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
 
555
                    iconsDiffer = true;
 
556
                    break;
 
557
                }
 
558
            }
 
559
        }
 
560
 
 
561
        // Display the app windows menu items and the separator between windows
 
562
        // of the current desktop and other windows.
 
563
        let activeWorkspace = global.screen.get_active_workspace();
 
564
        let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
 
565
 
 
566
        for (let i = 0; i < windows.length; i++) {
 
567
            if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
 
568
                this._appendSeparator();
 
569
                separatorShown = true;
 
570
            }
 
571
            let box = this._appendMenuItem(windows[i].title);
 
572
            box._window = windows[i];
 
573
        }
 
574
 
 
575
        if (windows.length > 0)
 
576
            this._appendSeparator();
 
577
 
 
578
        let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
 
579
 
 
580
        this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(_("New Window")) : null;
 
581
 
 
582
        if (windows.length > 0)
 
583
            this._appendSeparator();
 
584
        this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ? _("Remove from Favorites")
 
585
                                                                    : _("Add to Favorites"));
 
586
 
 
587
        this._highlightedItem = null;
 
588
    },
 
589
 
 
590
    _appendSeparator: function () {
 
591
        let bin = new St.Bin({ style_class: "app-well-menu-separator" });
 
592
        this._windowContainer.append_separator(bin, Big.BoxPackFlags.NONE);
 
593
    },
 
594
 
 
595
    _appendMenuItem: function(labelText) {
 
596
        let box = new St.BoxLayout({ style_class: 'app-well-menu-item',
 
597
                                      reactive: true });
 
598
        let label = new St.Label({ text: labelText });
 
599
        box.add(label);
 
600
        this._windowContainer.append(box, Big.BoxPackFlags.NONE);
 
601
        return box;
 
602
    },
 
603
 
 
604
    popup: function(activatingButton) {
 
605
        let [stageX, stageY] = this._source.actor.get_transformed_position();
 
606
        let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
 
607
 
 
608
        this._redisplay();
 
609
 
 
610
        this._windowContainer.popup(activatingButton, Main.currentTime());
 
611
 
 
612
        this.emit('popup', true);
 
613
 
 
614
        let x, y;
 
615
        x = Math.floor(stageX + stageWidth);
 
616
        y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
 
617
 
 
618
        this.actor.set_position(x, y);
 
619
        this.actor.show();
 
620
    },
 
621
 
 
622
    popdown: function() {
 
623
        this._windowContainer.popdown();
 
624
        this.emit('popup', false);
 
625
        this.actor.hide();
 
626
    },
 
627
 
 
628
    selectWindow: function(metaWindow) {
 
629
        this._selectMenuItemForWindow(metaWindow);
 
630
    },
 
631
 
 
632
    _findMetaWindowForActor: function (actor) {
 
633
        if (actor._delegate instanceof Workspaces.WindowClone)
 
634
            return actor._delegate.metaWindow;
 
635
        else if (actor.get_meta_window)
 
636
            return actor.get_meta_window();
 
637
        return null;
 
638
    },
 
639
 
 
640
    // This function is called while the menu has a pointer grab; what we want
 
641
    // to do is see if the mouse was released over a window representation
 
642
    _onMenuButtonRelease: function (actor, event) {
 
643
        let metaWindow = this._findMetaWindowForActor(event.get_source());
 
644
        if (metaWindow) {
 
645
            this.emit('activate-window', metaWindow);
 
646
        }
 
647
    },
 
648
 
 
649
    _updateHighlight: function (item) {
 
650
        if (this._highlightedItem) {
 
651
            this._highlightedItem.set_style_pseudo_class(null);
 
652
            this.emit('highlight-window', null);
 
653
        }
 
654
        this._highlightedItem = item;
 
655
        if (this._highlightedItem) {
 
656
            item.set_style_pseudo_class('hover');
 
657
            let window = this._highlightedItem._window;
 
658
            if (window)
 
659
                this.emit('highlight-window', window);
 
660
        }
 
661
    },
 
662
 
 
663
    _selectMenuItemForWindow: function (metaWindow) {
 
664
        let children = this._windowContainer.get_children();
 
665
        for (let i = 0; i < children.length; i++) {
 
666
            let child = children[i];
 
667
            let menuMetaWindow = child._window;
 
668
            if (menuMetaWindow == metaWindow)
 
669
                this._updateHighlight(child);
 
670
        }
 
671
    },
 
672
 
 
673
    // Called while menu has a pointer grab
 
674
    _onMenuEnter: function (actor, event) {
 
675
        let metaWindow = this._findMetaWindowForActor(event.get_source());
 
676
        if (metaWindow) {
 
677
            this._selectMenuItemForWindow(metaWindow);
 
678
        }
 
679
    },
 
680
 
 
681
    // Called while menu has a pointer grab
 
682
    _onMenuLeave: function (actor, event) {
 
683
        let metaWindow = this._findMetaWindowForActor(event.get_source());
 
684
        if (metaWindow) {
 
685
            this._updateHighlight(null);
 
686
        }
 
687
    },
 
688
 
 
689
    _onItemUnselected: function (actor, child) {
 
690
        this._updateHighlight(null);
 
691
    },
 
692
 
 
693
    _onItemSelected: function (actor, child) {
 
694
        this._updateHighlight(child);
 
695
    },
 
696
 
 
697
    _onItemActivate: function (actor, child) {
 
698
        if (child._window) {
 
699
            let metaWindow = child._window;
 
700
            this.emit('activate-window', metaWindow);
 
701
        } else if (child == this._newWindowMenuItem) {
 
702
            this._source.app.launch();
 
703
            this.emit('activate-window', null);
 
704
        } else if (child == this._toggleFavoriteMenuItem) {
 
705
            let favs = AppFavorites.getAppFavorites();
 
706
            let isFavorite = favs.isFavorite(this._source.app.get_id());
 
707
            if (isFavorite)
 
708
                favs.removeFavorite(this._source.app.get_id());
 
709
            else
 
710
                favs.addFavorite(this._source.app.get_id());
 
711
        }
 
712
        this.popdown();
 
713
    },
 
714
 
 
715
    _onWindowSelectionCancelled: function () {
 
716
        this.emit('highlight-window', null);
 
717
        this.popdown();
 
718
    },
 
719
 
 
720
    _onStyleChanged: function() {
 
721
        let themeNode = this._windowContainerBox.get_theme_node();
 
722
        let [success, len] = themeNode.get_length('-shell-arrow-size', false);
 
723
        if (success) {
 
724
            this._arrowSize = len;
 
725
            this.actor.queue_relayout();
 
726
        }
 
727
        [success, len] = themeNode.get_length('-shell-menu-spacing', false)
 
728
        if (success) {
 
729
            this._windowContainer.spacing = len;
 
730
        }
 
731
        let color = new Clutter.Color();
 
732
        if (themeNode.get_background_color(color)) {
 
733
            this._backgroundColor = color;
 
734
            color = new Clutter.Color();
 
735
        }
 
736
        if (themeNode.get_border_color(St.Side.LEFT, color)) {
 
737
            this._borderColor = color;
 
738
        }
 
739
        this._arrow.emit_redraw();
 
740
    }
 
741
};
 
742
Signals.addSignalMethods(AppIconMenu.prototype);
365
743
 
366
744
function RunningWellItem(app, isFavorite) {
367
745
    this._init(app, isFavorite);
372
750
 
373
751
    _init: function(app, isFavorite) {
374
752
        BaseWellItem.prototype._init.call(this, app, isFavorite);
375
 
 
376
 
        this._dragStartX = 0;
377
 
        this._dragStartY = 0;
378
 
 
379
 
        this.actor.connect('activate', Lang.bind(this, this._onActivate));
380
753
    },
381
754
 
382
 
    _onActivate: function (actor, event) {
 
755
    _onActivate: function (event) {
383
756
        let modifiers = Shell.get_event_state(event);
384
757
 
385
758
        if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
406
779
            Main.overview.hide();
407
780
    },
408
781
 
409
 
    menuPoppedUp: function() {
 
782
    _onMenuPoppedUp: function() {
410
783
        Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
411
784
    },
412
785
 
413
 
    menuPoppedDown: function() {
 
786
    _onMenuPoppedDown: function() {
414
787
        if (this._didActivateWindow)
415
788
            return;
416
789
 
427
800
 
428
801
    _init : function(app, isFavorite) {
429
802
        BaseWellItem.prototype._init.call(this, app, isFavorite);
430
 
 
431
 
        this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
432
 
        this.actor.connect('activate', Lang.bind(this, this._onActivate));
433
 
    },
434
 
 
435
 
    _onPressedChanged: function() {
436
 
        this.setHighlight(this.actor.pressed);
437
 
    },
438
 
 
439
 
    _onActivate: function() {
 
803
    },
 
804
 
 
805
    _onActivate: function(event) {
440
806
        this.app.launch();
441
807
        Main.overview.hide();
442
808
        return true;
443
809
    },
444
810
 
445
 
    menuPoppedUp: function() {
 
811
    _onMenuPoppedUp: function() {
446
812
    },
447
813
 
448
 
    menuPoppedDown: function() {
 
814
    _onMenuPoppedDown: function() {
449
815
    }
450
816
};
451
817
 
455
821
 
456
822
WellGrid.prototype = {
457
823
    _init: function() {
458
 
        this.actor = new Shell.GenericContainer();
459
 
 
460
 
        this._separator = new Big.Box({ height: 1 });
461
 
        this.actor.add_actor(this._separator);
462
 
        this._separatorIndex = 0;
463
 
        this._cachedSeparatorY = 0;
464
 
 
465
 
        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
466
 
        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
467
 
        this.actor.connect('allocate', Lang.bind(this, this._allocate));
 
824
        this.actor = new St.Bin({ name: "dashAppWell" });
 
825
        // Pulled from CSS, but hardcode some defaults here
 
826
        this._spacing = 0;
 
827
        this._item_size = 48;
 
828
        this._grid = new Shell.GenericContainer();
 
829
        this.actor.set_child(this._grid);
 
830
        this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
 
831
 
 
832
        this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
 
833
        this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
 
834
        this._grid.connect('allocate', Lang.bind(this, this._allocate));
468
835
    },
469
836
 
470
837
    _getPreferredWidth: function (grid, forHeight, alloc) {
471
 
        let [itemMin, itemNatural] = this._getItemPreferredWidth();
472
 
        let children = this._getItemChildren();
473
 
        let nColumns;
474
 
        if (children.length < WELL_DEFAULT_COLUMNS)
475
 
            nColumns = children.length;
 
838
        let children = this._grid.get_children();
 
839
        let nColumns = children.length;
 
840
        let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
 
841
        // Kind of a lie, but not really an issue right now.  If
 
842
        // we wanted to support some sort of hidden/overflow that would
 
843
        // need higher level design
 
844
        alloc.min_size = this._item_size;
 
845
        alloc.natural_size = nColumns * this._item_size + totalSpacing;
 
846
    },
 
847
 
 
848
    _getPreferredHeight: function (grid, forWidth, alloc) {
 
849
        let children = this._grid.get_children();
 
850
        let [nColumns, usedWidth] = this._computeLayout(forWidth);
 
851
        let nRows;
 
852
        if (nColumns > 0)
 
853
            nRows = Math.ceil(children.length / nColumns);
476
854
        else
477
 
            nColumns = WELL_DEFAULT_COLUMNS;
478
 
        alloc.min_size = itemMin;
479
 
        alloc.natural_size = itemNatural * nColumns;
480
 
    },
481
 
 
482
 
    _getPreferredHeight: function (grid, forWidth, alloc) {
483
 
        let [rows, columns, itemWidth, itemHeight] = this._computeLayout(forWidth);
484
 
        let totalVerticalSpacing = Math.max(rows - 1, 0) * WELL_ITEM_VSPACING;
485
 
 
486
 
        let [separatorMin, separatorNatural] = this._separator.get_preferred_height(forWidth);
487
 
        alloc.min_size = alloc.natural_size = rows * itemHeight + totalVerticalSpacing + separatorNatural;
 
855
            nRows = 0;
 
856
        let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
 
857
        let height = nRows * this._item_size + totalSpacing;
 
858
        alloc.min_size = height;
 
859
        alloc.natural_size = height;
488
860
    },
489
861
 
490
862
    _allocate: function (grid, box, flags) {
491
 
        let children = this._getItemChildren();
 
863
        let children = this._grid.get_children();
492
864
        let availWidth = box.x2 - box.x1;
493
865
        let availHeight = box.y2 - box.y1;
494
866
 
495
 
        let [rows, columns, itemWidth, itemHeight] = this._computeLayout(availWidth);
496
 
 
497
 
        let [separatorMin, separatorNatural] = this._separator.get_preferred_height(-1);
498
 
 
499
 
        let x = box.x1;
 
867
        let [nColumns, usedWidth] = this._computeLayout(availWidth);
 
868
 
 
869
        let overallPaddingX = Math.floor((availWidth - usedWidth) / 2);
 
870
 
 
871
        let x = box.x1 + overallPaddingX;
500
872
        let y = box.y1;
501
873
        let columnIndex = 0;
502
874
        for (let i = 0; i < children.length; i++) {
503
 
            let [childMinWidth, childNaturalWidth] = children[i].get_preferred_width(-1);
 
875
            let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
 
876
                = children[i].get_preferred_size();
504
877
 
505
878
            /* Center the item in its allocation horizontally */
506
 
            let width = Math.min(itemWidth, childNaturalWidth);
507
 
            let horizSpacing = (itemWidth - width) / 2;
 
879
            let width = Math.min(this._item_size, childNaturalWidth);
 
880
            let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
 
881
            let height = Math.min(this._item_size, childNaturalHeight);
 
882
            let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
508
883
 
509
884
            let childBox = new Clutter.ActorBox();
510
 
            childBox.x1 = Math.floor(x + horizSpacing);
511
 
            childBox.y1 = y;
 
885
            childBox.x1 = Math.floor(x + childXSpacing);
 
886
            childBox.y1 = Math.floor(y + childYSpacing);
512
887
            childBox.x2 = childBox.x1 + width;
513
 
            childBox.y2 = childBox.y1 + itemHeight;
 
888
            childBox.y2 = childBox.y1 + height;
514
889
            children[i].allocate(childBox, flags);
515
890
 
516
891
            columnIndex++;
517
 
            if (columnIndex == columns) {
 
892
            if (columnIndex == nColumns) {
518
893
                columnIndex = 0;
519
894
            }
520
895
 
521
896
            if (columnIndex == 0) {
522
 
                y += itemHeight + WELL_ITEM_VSPACING;
523
 
                x = box.x1;
 
897
                y += this._item_size + this._spacing;
 
898
                x = box.x1 + overallPaddingX;
524
899
            } else {
525
 
                x += itemWidth;
 
900
                x += this._item_size + this._spacing;
526
901
            }
527
902
        }
528
903
    },
529
904
 
530
 
    removeAll: function () {
531
 
        let itemChildren = this._getItemChildren();
532
 
        for (let i = 0; i < itemChildren.length; i++) {
533
 
            itemChildren[i].destroy();
534
 
        }
535
 
    },
536
 
 
537
 
    _getItemChildren: function () {
538
 
        let children = this.actor.get_children();
539
 
        children.shift();
540
 
        return children;
541
 
    },
542
 
 
543
905
    _computeLayout: function (forWidth) {
544
 
        let [itemMinWidth, itemNaturalWidth] = this._getItemPreferredWidth();
545
 
        let columnsNatural;
546
 
        let i;
547
 
        let children = this._getItemChildren();
548
 
        if (children.length == 0)
549
 
            return [0, WELL_DEFAULT_COLUMNS, 0, 0];
 
906
        let children = this._grid.get_children();
550
907
        let nColumns = 0;
551
908
        let usedWidth = 0;
552
 
        // Big.Box will allocate us at 0x0 if we are not visible; this is probably a
553
 
        // Big.Box bug but it can't be fixed because if children are skipped in allocate()
554
 
        // Clutter gets confused (see http://bugzilla.openedhand.com/show_bug.cgi?id=1831)
555
 
        if (forWidth <= 0) {
556
 
            nColumns = WELL_DEFAULT_COLUMNS;
557
 
        } else {
558
 
            while (nColumns < WELL_DEFAULT_COLUMNS &&
559
 
                   nColumns < children.length &&
560
 
                   usedWidth + itemMinWidth <= forWidth) {
561
 
               // By including WELL_ITEM_MIN_HSPACING in usedWidth, we are ensuring
562
 
               // that the number of columns we end up with will allow the spacing
563
 
               // between the columns to be at least that value.
564
 
               usedWidth += itemMinWidth + WELL_ITEM_MIN_HSPACING;
565
 
               nColumns++;
566
 
            }
567
 
        }
568
 
 
569
 
        if (nColumns == 0) {
570
 
           log("WellGrid: couldn't fit a column in width " + forWidth);
571
 
           /* FIXME - fall back to smaller icon size */
572
 
        }
573
 
 
574
 
        let minWidth = itemMinWidth * nColumns;
575
 
 
576
 
        let lastColumnIndex = nColumns - 1;
577
 
        let rows = Math.ceil(children.length / nColumns);
578
 
 
579
 
        let itemWidth;
580
 
        if (forWidth <= 0) {
581
 
            itemWidth = itemNaturalWidth;
582
 
        } else {
583
 
            itemWidth = Math.floor(forWidth / nColumns);
584
 
        }
585
 
 
586
 
        let itemNaturalHeight = 0;
587
 
        for (let i = 0; i < children.length; i++) {
588
 
            let [childMin, childNatural] = children[i].get_preferred_height(itemWidth);
589
 
            if (childNatural > itemNaturalHeight)
590
 
                itemNaturalHeight = childNatural;
591
 
        }
592
 
 
593
 
        return [rows, nColumns, itemWidth, itemNaturalHeight];
594
 
    },
595
 
 
596
 
    _getItemPreferredWidth: function () {
597
 
        let children = this._getItemChildren();
598
 
        let minWidth = 0;
599
 
        let naturalWidth = 0;
600
 
        for (let i = 0; i < children.length; i++) {
601
 
            let [childMin, childNatural] = children[i].get_preferred_width(-1);
602
 
            if (childMin > minWidth)
603
 
                minWidth = childMin;
604
 
            if (childNatural > naturalWidth)
605
 
                naturalWidth = childNatural;
606
 
        }
607
 
        return [minWidth, naturalWidth];
 
909
        while (nColumns < WELL_MAX_COLUMNS &&
 
910
                nColumns < children.length &&
 
911
                (usedWidth + this._item_size <= forWidth)) {
 
912
            usedWidth += this._item_size + this._spacing;
 
913
            nColumns += 1;
 
914
        }
 
915
 
 
916
        if (nColumns > 0)
 
917
            usedWidth -= this._spacing;
 
918
 
 
919
        return [nColumns, usedWidth];
 
920
    },
 
921
 
 
922
    _onStyleChanged: function() {
 
923
        let themeNode = this.actor.get_theme_node();
 
924
        let [success, len] = themeNode.get_length('spacing', false);
 
925
        if (success)
 
926
            this._spacing = len;
 
927
        [success, len] = themeNode.get_length('-shell-grid-item-size', false);
 
928
        if (success)
 
929
            this._item_size = len;
 
930
        this._grid.queue_relayout();
 
931
    },
 
932
 
 
933
    removeAll: function () {
 
934
        this._grid.get_children().forEach(Lang.bind(this, function (child) {
 
935
            child.destroy();
 
936
        }));
 
937
    },
 
938
 
 
939
    addItem: function(actor) {
 
940
        this._grid.add_actor(actor);
608
941
    }
609
942
}
610
943
 
671
1004
        let running = this._tracker.get_running_apps(contextId);
672
1005
        let runningIds = this._appIdListToHash(running);
673
1006
 
 
1007
        let nFavorites = 0;
674
1008
        for (let id in favorites) {
675
1009
            let app = favorites[id];
676
1010
            let display;
679
1013
            } else {
680
1014
                display = new InactiveWellItem(app, true);
681
1015
            }
682
 
            this._grid.actor.add_actor(display.actor);
 
1016
            this._grid.addItem(display.actor);
 
1017
            nFavorites++;
683
1018
        }
684
1019
 
685
1020
        for (let i = 0; i < running.length; i++) {
687
1022
            if (app.get_id() in favorites)
688
1023
                continue;
689
1024
            let display = new RunningWellItem(app, false);
690
 
            this._grid.actor.add_actor(display.actor);
 
1025
            this._grid.addItem(display.actor);
691
1026
        }
692
1027
 
693
 
        if (this._grid.actor.get_n_children() == 1) {
694
 
            let text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
695
 
                                          font_name: "Sans 14px",
696
 
                                          text: _("Drag here to add favorites")});
697
 
            this._grid.actor.add_actor(text);
 
1028
        if (running.length == 0 && nFavorites == 0) {
 
1029
            let text = new St.Label({ text: _("Drag here to add favorites")});
 
1030
            this._grid.actor.set_child(text);
698
1031
        }
699
1032
    },
700
1033