Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / examples / desktop / js / Desktop.js
1 /*!
2  * Ext JS Library 4.0
3  * Copyright(c) 2006-2011 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7
8 /**
9  * @class Ext.ux.desktop.Desktop
10  * @extends Ext.panel.Panel
11  * <p>This class manages the wallpaper, shortcuts and taskbar.</p>
12  */
13 Ext.define('Ext.ux.desktop.Desktop', {
14     extend: 'Ext.panel.Panel',
15
16     alias: 'widget.desktop',
17
18     uses: [
19         'Ext.util.MixedCollection',
20         'Ext.menu.Menu',
21         'Ext.view.View', // dataview
22         'Ext.window.Window',
23
24         'Ext.ux.desktop.TaskBar',
25         'Ext.ux.desktop.Wallpaper',
26         'Ext.ux.desktop.FitAllLayout'
27     ],
28
29     activeWindowCls: 'ux-desktop-active-win',
30     inactiveWindowCls: 'ux-desktop-inactive-win',
31     lastActiveWindow: null,
32
33     border: false,
34     html: '&#160;',
35     layout: 'fitall',
36
37     xTickSize: 1,
38     yTickSize: 1,
39
40     app: null,
41
42     /**
43      * @cfg {Array|Store} shortcuts
44      * The items to add to the DataView. This can be a {@link Ext.data.Store Store} or a
45      * simple array. Items should minimally provide the fields in the
46      * {@link Ext.ux.desktop.ShorcutModel ShortcutModel}.
47      */
48     shortcuts: null,
49
50     /**
51      * @cfg {String} shortcutItemSelector
52      * This property is passed to the DataView for the desktop to select shortcut items.
53      * If the {@link #shortcutTpl} is modified, this will probably need to be modified as
54      * well.
55      */
56     shortcutItemSelector: 'div.ux-desktop-shortcut',
57
58     /**
59      * @cfg {String} shortcutTpl
60      * This XTemplate is used to render items in the DataView. If this is changed, the
61      * {@link shortcutItemSelect} will probably also need to changed.
62      */
63     shortcutTpl: [
64         '<tpl for=".">',
65             '<div class="ux-desktop-shortcut" id="{name}-shortcut">',
66                 '<div class="ux-desktop-shortcut-icon {iconCls}">',
67                     '<img src="',Ext.BLANK_IMAGE_URL,'" title="{name}">',
68                 '</div>',
69                 '<span class="ux-desktop-shortcut-text">{name}</span>',
70             '</div>',
71         '</tpl>',
72         '<div class="x-clear"></div>'
73     ],
74
75     /**
76      * @cfg {Object} taskbarConfig
77      * The config object for the TaskBar.
78      */
79     taskbarConfig: null,
80
81     windowMenu: null,
82
83     initComponent: function () {
84         var me = this;
85
86         me.windowMenu = new Ext.menu.Menu(me.createWindowMenu());
87
88         me.bbar = me.taskbar = new Ext.ux.desktop.TaskBar(me.taskbarConfig);
89         me.taskbar.windowMenu = me.windowMenu;
90
91         me.windows = new Ext.util.MixedCollection();
92
93         me.contextMenu = new Ext.menu.Menu(me.createDesktopMenu());
94
95         me.items = [
96             { xtype: 'wallpaper', id: me.id+'_wallpaper' },
97             me.createDataView()
98         ];
99
100         me.callParent();
101
102         me.shortcutsView = me.items.getAt(1);
103         me.shortcutsView.on('itemclick', me.onShortcutItemClick, me);
104
105         var wallpaper = me.wallpaper;
106         me.wallpaper = me.items.getAt(0);
107         if (wallpaper) {
108             me.setWallpaper(wallpaper, me.wallpaperStretch);
109         }
110     },
111
112     afterRender: function () {
113         var me = this;
114         me.callParent();
115         me.el.on('contextmenu', me.onDesktopMenu, me);
116     },
117
118     //------------------------------------------------------
119     // Overrideable configuration creation methods
120
121     createDataView: function () {
122         var me = this;
123         return {
124             xtype: 'dataview',
125             overItemCls: 'x-view-over',
126             trackOver: true,
127             itemSelector: me.shortcutItemSelector,
128             store: me.shortcuts,
129             tpl: new Ext.XTemplate(me.shortcutTpl)
130         };
131     },
132
133     createDesktopMenu: function () {
134         var me = this, ret = {
135             items: me.contextMenuItems || []
136         };
137
138         if (ret.items.length) {
139             ret.items.push('-');
140         }
141
142         ret.items.push(
143                 { text: 'Tile', handler: me.tileWindows, scope: me, minWindows: 1 },
144                 { text: 'Cascade', handler: me.cascadeWindows, scope: me, minWindows: 1 })
145
146         return ret;
147     },
148
149     createWindowMenu: function () {
150         var me = this;
151         return {
152             defaultAlign: 'br-tr',
153             items: [
154                 { text: 'Restore', handler: me.onWindowMenuRestore, scope: me },
155                 { text: 'Minimize', handler: me.onWindowMenuMinimize, scope: me },
156                 { text: 'Maximize', handler: me.onWindowMenuMaximize, scope: me },
157                 '-',
158                 { text: 'Close', handler: me.onWindowMenuClose, scope: me }
159             ],
160             listeners: {
161                 beforeshow: me.onWindowMenuBeforeShow,
162                 hide: me.onWindowMenuHide,
163                 scope: me
164             }
165         };
166     },
167
168     //------------------------------------------------------
169     // Event handler methods
170
171     onDesktopMenu: function (e) {
172         var me = this, menu = me.contextMenu;
173         e.stopEvent();
174         if (!menu.rendered) {
175             menu.on('beforeshow', me.onDesktopMenuBeforeShow, me);
176         }
177         menu.showAt(e.getXY());
178         menu.doConstrain();
179     },
180
181     onDesktopMenuBeforeShow: function (menu) {
182         var me = this, count = me.windows.getCount();
183
184         menu.items.each(function (item) {
185             var min = item.minWindows || 0;
186             item.setDisabled(count < min);
187         });
188     },
189
190     onShortcutItemClick: function (dataView, record) {
191         var me = this, module = me.app.getModule(record.data.module),
192             win = module && module.createWindow();
193
194         if (win) {
195             me.restoreWindow(win);
196         }
197     },
198
199     onWindowClose: function(win) {
200         var me = this;
201         me.windows.remove(win);
202         me.taskbar.removeTaskButton(win.taskButton);
203         me.updateActiveWindow();
204     },
205
206     //------------------------------------------------------
207     // Window context menu handlers
208
209     onWindowMenuBeforeShow: function (menu) {
210         var items = menu.items.items, win = menu.theWin;
211         items[0].setDisabled(win.maximized !== true && win.hidden !== true); // Restore
212         items[1].setDisabled(win.minimized === true); // Minimize
213         items[2].setDisabled(win.maximized === true || win.hidden === true); // Maximize
214     },
215
216     onWindowMenuClose: function () {
217         var me = this, win = me.windowMenu.theWin;
218
219         win.close();
220     },
221
222     onWindowMenuHide: function (menu) {
223         menu.theWin = null;
224     },
225
226     onWindowMenuMaximize: function () {
227         var me = this, win = me.windowMenu.theWin;
228
229         win.maximize();
230     },
231
232     onWindowMenuMinimize: function () {
233         var me = this, win = me.windowMenu.theWin;
234
235         win.minimize();
236     },
237
238     onWindowMenuRestore: function () {
239         var me = this, win = me.windowMenu.theWin;
240
241         me.restoreWindow(win);
242     },
243
244     //------------------------------------------------------
245     // Dynamic (re)configuration methods
246
247     getWallpaper: function () {
248         return this.wallpaper.wallpaper;
249     },
250
251     setTickSize: function(xTickSize, yTickSize) {
252         var me = this,
253             xt = me.xTickSize = xTickSize,
254             yt = me.yTickSize = (arguments.length > 1) ? yTickSize : xt;
255
256         me.windows.each(function(win) {
257             var dd = win.dd, resizer = win.resizer;
258             dd.xTickSize = xt;
259             dd.yTickSize = yt;
260             resizer.widthIncrement = xt;
261             resizer.heightIncrement = yt;
262         });
263     },
264
265     setWallpaper: function (wallpaper, stretch) {
266         this.wallpaper.setWallpaper(wallpaper, stretch);
267         return this;
268     },
269
270     //------------------------------------------------------
271     // Window management methods
272
273     cascadeWindows: function() {
274         var x = 0, y = 0,
275             zmgr = this.getDesktopZIndexManager();
276
277         zmgr.eachBottomUp(function(win) {
278             if (win.isWindow && win.isVisible() && !win.maximized) {
279                 win.setPosition(x, y);
280                 x += 20;
281                 y += 20;
282             }
283         });
284     },
285
286     createWindow: function(config, cls) {
287         var me = this, win, cfg = Ext.applyIf(config || {}, {
288                 stateful: false,
289                 isWindow: true,
290                 constrainHeader: true,
291                 minimizable: true,
292                 maximizable: true
293             });
294
295         cls = cls || Ext.window.Window;
296         win = me.add(new cls(cfg));
297
298         me.windows.add(win);
299
300         win.taskButton = me.taskbar.addTaskButton(win);
301         win.animateTarget = win.taskButton.el;
302
303         win.on({
304             activate: me.updateActiveWindow,
305             beforeshow: me.updateActiveWindow,
306             deactivate: me.updateActiveWindow,
307             minimize: me.minimizeWindow,
308             destroy: me.onWindowClose,
309             scope: me
310         });
311
312         win.on({
313             afterrender: function () {
314                 win.dd.xTickSize = me.xTickSize;
315                 win.dd.yTickSize = me.yTickSize;
316
317                 if (win.resizer) {
318                     win.resizer.widthIncrement = me.xTickSize;
319                     win.resizer.heightIncrement = me.yTickSize;
320                 }
321             },
322             single: true
323         });
324
325         // replace normal window close w/fadeOut animation:
326         win.doClose = function ()  {
327             win.el.disableShadow();
328             win.el.fadeOut({
329                 listeners: {
330                     afteranimate: function () {
331                         win.destroy();
332                     }
333                 }
334             });
335         };
336
337         return win;
338     },
339
340     getActiveWindow: function () {
341         var win = null,
342             zmgr = this.getDesktopZIndexManager();
343
344         if (zmgr) {
345             // We cannot rely on activate/deactive because that fires against non-Window
346             // components in the stack.
347
348             zmgr.eachTopDown(function (comp) {
349                 if (comp.isWindow && !comp.hidden) {
350                     win = comp;
351                     return false;
352                 }
353                 return true;
354             });
355         }
356
357         return win;
358     },
359
360     getDesktopZIndexManager: function () {
361         var windows = this.windows;
362         // TODO - there has to be a better way to get this...
363         return (windows.getCount() && windows.getAt(0).zIndexManager) || null;
364     },
365
366     getWindow: function(id) {
367         return this.windows.get(id);
368     },
369
370     minimizeWindow: function(win) {
371         win.minimized = true;
372         win.hide();
373     },
374
375     restoreWindow: function (win) {
376         if (win.isVisible()) {
377             win.restore();
378             win.toFront();
379         } else {
380             win.show();
381         }
382         return win;
383     },
384
385     tileWindows: function() {
386         var me = this, availWidth = me.body.getWidth(true);
387         var x = me.xTickSize, y = me.yTickSize, nextY = y;
388
389         me.windows.each(function(win) {
390             if (win.isVisible() && !win.maximized) {
391                 var w = win.el.getWidth();
392
393                 // Wrap to next row if we are not at the line start and this Window will
394                 // go off the end
395                 if (x > me.xTickSize && x + w > availWidth) {
396                     x = me.xTickSize;
397                     y = nextY;
398                 }
399
400                 win.setPosition(x, y);
401                 x += w + me.xTickSize;
402                 nextY = Math.max(nextY, y + win.el.getHeight() + me.yTickSize);
403             }
404         });
405     },
406
407     updateActiveWindow: function () {
408         var me = this, activeWindow = me.getActiveWindow(), last = me.lastActiveWindow;
409         if (activeWindow === last) {
410             return;
411         }
412
413         if (last) {
414             if (last.el.dom) {
415                 last.addCls(me.inactiveWindowCls);
416                 last.removeCls(me.activeWindowCls);
417             }
418             last.active = false;
419         }
420
421         me.lastActiveWindow = activeWindow;
422
423         if (activeWindow) {
424             activeWindow.addCls(me.activeWindowCls);
425             activeWindow.removeCls(me.inactiveWindowCls);
426             activeWindow.minimized = false;
427             activeWindow.active = true;
428         }
429
430         me.taskbar.setActiveButton(activeWindow && activeWindow.taskButton);
431     }
432 });