Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / examples / desktop / js / TaskBar.js
index 86513c2..0837014 100644 (file)
-/*\r
- * Ext JS Library 2.2.1\r
- * Copyright(c) 2006-2009, Ext JS, LLC.\r
- * licensing@extjs.com\r
- * \r
- * http://extjs.com/license\r
- */\r
-\r
-/**\r
- * @class Ext.ux.TaskBar\r
- * @extends Ext.util.Observable\r
- */\r
-Ext.ux.TaskBar = function(app){\r
-    this.app = app;\r
-    this.init();\r
-}\r
-\r
-Ext.extend(Ext.ux.TaskBar, Ext.util.Observable, {\r
-    init : function(){\r
-               this.startMenu = new Ext.ux.StartMenu(Ext.apply({\r
-                       iconCls: 'user',\r
-                       height: 300,\r
-                       shadow: true,\r
-                       title: 'Jack Slocum',\r
-                       width: 300\r
-               }, this.app.startConfig));\r
-               \r
-               this.startBtn = new Ext.Button({\r
-            text: 'Start',\r
-            id: 'ux-startbutton',\r
-            iconCls:'start',\r
-            menu: this.startMenu,\r
-            menuAlign: 'bl-tl',\r
-            renderTo: 'ux-taskbar-start',\r
-            clickEvent:'mousedown',\r
-            template: new Ext.Template(\r
-                               '<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap"><tbody><tr>',\r
-                               '<td class="ux-startbutton-left"><i>&#160;</i></td><td class="ux-startbutton-center"><em unselectable="on"><button class="x-btn-text" type="{1}" style="height:30px;">{0}</button></em></td><td class="ux-startbutton-right"><i>&#160;</i></td>',\r
-                               "</tr></tbody></table>")\r
-        });\r
-        \r
-        var width = Ext.get('ux-startbutton').getWidth()+10;\r
-        \r
-        var sbBox = new Ext.BoxComponent({\r
-                       el: 'ux-taskbar-start',\r
-               id: 'TaskBarStart',\r
-               minWidth: width,\r
-                       region:'west',\r
-                       split: true,\r
-                       width: width\r
-               });\r
-               \r
-               this.tbPanel = new Ext.ux.TaskButtonsPanel({\r
-                       el: 'ux-taskbuttons-panel',\r
-                       id: 'TaskBarButtons',\r
-                       region:'center'\r
-               });\r
-                               \r
-        var container = new Ext.ux.TaskBarContainer({\r
-                       el: 'ux-taskbar',\r
-                       layout: 'border',\r
-                       items: [sbBox,this.tbPanel]\r
-               });\r
-               \r
-               return this;\r
-    },\r
-    \r
-    addTaskButton : function(win){\r
-               return this.tbPanel.addButton(win, 'ux-taskbuttons-panel');\r
-       },\r
-       \r
-       removeTaskButton : function(btn){\r
-               this.tbPanel.removeButton(btn);\r
-       },\r
-       \r
-       setActiveButton : function(btn){\r
-               this.tbPanel.setActiveButton(btn);\r
-       }\r
-});\r
-\r
-\r
-\r
-/**\r
- * @class Ext.ux.TaskBarContainer\r
- * @extends Ext.Container\r
- */\r
-Ext.ux.TaskBarContainer = Ext.extend(Ext.Container, {\r
-    initComponent : function() {\r
-        Ext.ux.TaskBarContainer.superclass.initComponent.call(this);\r
-        \r
-        this.el = Ext.get(this.el) || Ext.getBody();\r
-        this.el.setHeight = Ext.emptyFn;\r
-        this.el.setWidth = Ext.emptyFn;\r
-        this.el.setSize = Ext.emptyFn;\r
-        this.el.setStyle({\r
-            overflow:'hidden',\r
-            margin:'0',\r
-            border:'0 none'\r
-        });\r
-        this.el.dom.scroll = 'no';\r
-        this.allowDomMove = false;\r
-        this.autoWidth = true;\r
-        this.autoHeight = true;\r
-        Ext.EventManager.onWindowResize(this.fireResize, this);\r
-        this.renderTo = this.el;\r
-    },\r
-\r
-    fireResize : function(w, h){\r
-        this.fireEvent('resize', this, w, h, w, h);\r
-    }\r
-});\r
-\r
-\r
-\r
-/**\r
- * @class Ext.ux.TaskButtonsPanel\r
- * @extends Ext.BoxComponent\r
- */\r
-Ext.ux.TaskButtonsPanel = Ext.extend(Ext.BoxComponent, {\r
-       activeButton: null,\r
-       enableScroll: true,\r
-       scrollIncrement: 0,\r
-    scrollRepeatInterval: 400,\r
-    scrollDuration: .35,\r
-    animScroll: true,\r
-    resizeButtons: true,\r
-    buttonWidth: 168,\r
-    minButtonWidth: 118,\r
-    buttonMargin: 2,\r
-    buttonWidthSet: false,\r
-       \r
-       initComponent : function() {\r
-        Ext.ux.TaskButtonsPanel.superclass.initComponent.call(this);\r
-        this.on('resize', this.delegateUpdates);\r
-        this.items = [];\r
-        \r
-        this.stripWrap = Ext.get(this.el).createChild({\r
-               cls: 'ux-taskbuttons-strip-wrap',\r
-               cn: {\r
-               tag:'ul', cls:'ux-taskbuttons-strip'\r
-            }\r
-               });\r
-        this.stripSpacer = Ext.get(this.el).createChild({\r
-               cls:'ux-taskbuttons-strip-spacer'\r
-        });\r
-        this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
-        \r
-        this.edge = this.strip.createChild({\r
-               tag:'li',\r
-               cls:'ux-taskbuttons-edge'\r
-        });\r
-        this.strip.createChild({\r
-               cls:'x-clear'\r
-        });\r
-       },\r
-       \r
-       addButton : function(win){\r
-               var li = this.strip.createChild({tag:'li'}, this.edge); // insert before the edge\r
-        var btn = new Ext.ux.TaskBar.TaskButton(win, li);\r
-               \r
-               this.items.push(btn);\r
-               \r
-               if(!this.buttonWidthSet){\r
-                       this.lastButtonWidth = btn.container.getWidth();\r
-               }\r
-               \r
-               this.setActiveButton(btn);\r
-               return btn;\r
-       },\r
-       \r
-       removeButton : function(btn){\r
-               var li = document.getElementById(btn.container.id);\r
-               btn.destroy();\r
-               li.parentNode.removeChild(li);\r
-               \r
-               var s = [];\r
-               for(var i = 0, len = this.items.length; i < len; i++) {\r
-                       if(this.items[i] != btn){\r
-                               s.push(this.items[i]);\r
-                       }\r
-               }\r
-               this.items = s;\r
-               \r
-               this.delegateUpdates();\r
-       },\r
-       \r
-       setActiveButton : function(btn){\r
-               this.activeButton = btn;\r
-               this.delegateUpdates();\r
-       },\r
-       \r
-       delegateUpdates : function(){\r
-               /*if(this.suspendUpdates){\r
-            return;\r
-        }*/\r
-        if(this.resizeButtons && this.rendered){\r
-            this.autoSize();\r
-        }\r
-        if(this.enableScroll && this.rendered){\r
-            this.autoScroll();\r
-        }\r
-    },\r
-    \r
-    autoSize : function(){\r
-        var count = this.items.length;\r
-        var ow = this.el.dom.offsetWidth;\r
-        var aw = this.el.dom.clientWidth;\r
-\r
-        if(!this.resizeButtons || count < 1 || !aw){ // !aw for display:none\r
-            return;\r
-        }\r
-        \r
-        var each = Math.max(Math.min(Math.floor((aw-4) / count) - this.buttonMargin, this.buttonWidth), this.minButtonWidth); // -4 for float errors in IE\r
-        var btns = this.stripWrap.dom.getElementsByTagName('button');\r
-        \r
-        this.lastButtonWidth = Ext.get(btns[0].id).findParent('li').offsetWidth;\r
-        \r
-        for(var i = 0, len = btns.length; i < len; i++) {            \r
-            var btn = btns[i];\r
-            \r
-            var tw = Ext.get(btns[i].id).findParent('li').offsetWidth;\r
-            var iw = btn.offsetWidth;\r
-            \r
-            btn.style.width = (each - (tw-iw)) + 'px';\r
-        }\r
-    },\r
-    \r
-    autoScroll : function(){\r
-       var count = this.items.length;\r
-        var ow = this.el.dom.offsetWidth;\r
-        var tw = this.el.dom.clientWidth;\r
-        \r
-        var wrap = this.stripWrap;\r
-        var cw = wrap.dom.offsetWidth;\r
-        var pos = this.getScrollPos();\r
-        var l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos;\r
-        \r
-        if(!this.enableScroll || count < 1 || cw < 20){ // 20 to prevent display:none issues\r
-            return;\r
-        }\r
-        \r
-        wrap.setWidth(tw); // moved to here because of problem in Safari\r
-        \r
-        if(l <= tw){\r
-            wrap.dom.scrollLeft = 0;\r
-            //wrap.setWidth(tw); moved from here because of problem in Safari\r
-            if(this.scrolling){\r
-                this.scrolling = false;\r
-                this.el.removeClass('x-taskbuttons-scrolling');\r
-                this.scrollLeft.hide();\r
-                this.scrollRight.hide();\r
-            }\r
-        }else{\r
-            if(!this.scrolling){\r
-                this.el.addClass('x-taskbuttons-scrolling');\r
-            }\r
-            tw -= wrap.getMargins('lr');\r
-            wrap.setWidth(tw > 20 ? tw : 20);\r
-            if(!this.scrolling){\r
-                if(!this.scrollLeft){\r
-                    this.createScrollers();\r
-                }else{\r
-                    this.scrollLeft.show();\r
-                    this.scrollRight.show();\r
-                }\r
-            }\r
-            this.scrolling = true;\r
-            if(pos > (l-tw)){ // ensure it stays within bounds\r
-                wrap.dom.scrollLeft = l-tw;\r
-            }else{ // otherwise, make sure the active button is still visible\r
-                               this.scrollToButton(this.activeButton, true); // true to animate\r
-            }\r
-            this.updateScrollButtons();\r
-        }\r
-    },\r
-\r
-    createScrollers : function(){\r
-        var h = this.el.dom.offsetHeight; //var h = this.stripWrap.dom.offsetHeight;\r
-               \r
-        // left\r
-        var sl = this.el.insertFirst({\r
-            cls:'ux-taskbuttons-scroller-left'\r
-        });\r
-        sl.setHeight(h);\r
-        sl.addClassOnOver('ux-taskbuttons-scroller-left-over');\r
-        this.leftRepeater = new Ext.util.ClickRepeater(sl, {\r
-            interval : this.scrollRepeatInterval,\r
-            handler: this.onScrollLeft,\r
-            scope: this\r
-        });\r
-        this.scrollLeft = sl;\r
-\r
-        // right\r
-        var sr = this.el.insertFirst({\r
-            cls:'ux-taskbuttons-scroller-right'\r
-        });\r
-        sr.setHeight(h);\r
-        sr.addClassOnOver('ux-taskbuttons-scroller-right-over');\r
-        this.rightRepeater = new Ext.util.ClickRepeater(sr, {\r
-            interval : this.scrollRepeatInterval,\r
-            handler: this.onScrollRight,\r
-            scope: this\r
-        });\r
-        this.scrollRight = sr;\r
-    },\r
-    \r
-    getScrollWidth : function(){\r
-        return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos();\r
-    },\r
-\r
-    getScrollPos : function(){\r
-        return parseInt(this.stripWrap.dom.scrollLeft, 10) || 0;\r
-    },\r
-\r
-    getScrollArea : function(){\r
-        return parseInt(this.stripWrap.dom.clientWidth, 10) || 0;\r
-    },\r
-\r
-    getScrollAnim : function(){\r
-        return {\r
-               duration: this.scrollDuration,\r
-               callback: this.updateScrollButtons,\r
-               scope: this\r
-        };\r
-    },\r
-\r
-    getScrollIncrement : function(){\r
-       return (this.scrollIncrement || this.lastButtonWidth+2);\r
-    },\r
-    \r
-    /* getBtnEl : function(item){\r
-        return document.getElementById(item.id);\r
-    }, */\r
-    \r
-    scrollToButton : function(item, animate){\r
-       item = item.el.dom.parentNode; // li\r
-        if(!item){ return; }\r
-        var el = item; //this.getBtnEl(item);\r
-        var pos = this.getScrollPos(), area = this.getScrollArea();\r
-        var left = Ext.fly(el).getOffsetsTo(this.stripWrap)[0] + pos;\r
-        var right = left + el.offsetWidth;\r
-        if(left < pos){\r
-            this.scrollTo(left, animate);\r
-        }else if(right > (pos + area)){\r
-            this.scrollTo(right - area, animate);\r
-        }\r
-    },\r
-    \r
-    scrollTo : function(pos, animate){\r
-        this.stripWrap.scrollTo('left', pos, animate ? this.getScrollAnim() : false);\r
-        if(!animate){\r
-            this.updateScrollButtons();\r
-        }\r
-    },\r
-    \r
-    onScrollRight : function(){\r
-        var sw = this.getScrollWidth()-this.getScrollArea();\r
-        var pos = this.getScrollPos();\r
-        var s = Math.min(sw, pos + this.getScrollIncrement());\r
-        if(s != pos){\r
-               this.scrollTo(s, this.animScroll);\r
-        }        \r
-    },\r
-\r
-    onScrollLeft : function(){\r
-        var pos = this.getScrollPos();\r
-        var s = Math.max(0, pos - this.getScrollIncrement());\r
-        if(s != pos){\r
-            this.scrollTo(s, this.animScroll);\r
-        }\r
-    },\r
-    \r
-    updateScrollButtons : function(){\r
-        var pos = this.getScrollPos();\r
-        this.scrollLeft[pos == 0 ? 'addClass' : 'removeClass']('ux-taskbuttons-scroller-left-disabled');\r
-        this.scrollRight[pos >= (this.getScrollWidth()-this.getScrollArea()) ? 'addClass' : 'removeClass']('ux-taskbuttons-scroller-right-disabled');\r
-    }\r
-});\r
-\r
-\r
-\r
-/**\r
- * @class Ext.ux.TaskBar.TaskButton\r
- * @extends Ext.Button\r
- */\r
-Ext.ux.TaskBar.TaskButton = function(win, el){\r
-       this.win = win;\r
-    Ext.ux.TaskBar.TaskButton.superclass.constructor.call(this, {\r
-        iconCls: win.iconCls,\r
-        text: Ext.util.Format.ellipsis(win.title, 12),\r
-        renderTo: el,\r
-        handler : function(){\r
-            if(win.minimized || win.hidden){\r
-                win.show();\r
-            }else if(win == win.manager.getActive()){\r
-                win.minimize();\r
-            }else{\r
-                win.toFront();\r
-            }\r
-        },\r
-        clickEvent:'mousedown',\r
-        template: new Ext.Template(\r
-                       '<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap"><tbody><tr>',\r
-                       '<td class="ux-taskbutton-left"><i>&#160;</i></td><td class="ux-taskbutton-center"><em unselectable="on"><button class="x-btn-text" type="{1}" style="height:28px;">{0}</button></em></td><td class="ux-taskbutton-right"><i>&#160;</i></td>',\r
-                       "</tr></tbody></table>")\r
-    });\r
-};\r
-\r
-Ext.extend(Ext.ux.TaskBar.TaskButton, Ext.Button, {\r
-    onRender : function(){\r
-        Ext.ux.TaskBar.TaskButton.superclass.onRender.apply(this, arguments);\r
-\r
-        this.cmenu = new Ext.menu.Menu({\r
-            items: [{\r
-                text: 'Restore',\r
-                handler: function(){\r
-                    if(!this.win.isVisible()){\r
-                        this.win.show();\r
-                    }else{\r
-                        this.win.restore();\r
-                    }\r
-                },\r
-                scope: this\r
-            },{\r
-                text: 'Minimize',\r
-                handler: this.win.minimize,\r
-                scope: this.win\r
-            },{\r
-                text: 'Maximize',\r
-                handler: this.win.maximize,\r
-                scope: this.win\r
-            }, '-', {\r
-                text: 'Close',\r
-                handler: this.closeWin.createDelegate(this, this.win, true),\r
-                scope: this.win\r
-            }]\r
-        });\r
-\r
-        this.cmenu.on('beforeshow', function(){\r
-            var items = this.cmenu.items.items;\r
-            var w = this.win;\r
-            items[0].setDisabled(w.maximized !== true && w.hidden !== true);\r
-            items[1].setDisabled(w.minimized === true);\r
-            items[2].setDisabled(w.maximized === true || w.hidden === true);\r
-        }, this);\r
-\r
-        this.el.on('contextmenu', function(e){\r
-            e.stopEvent();\r
-            if(!this.cmenu.el){\r
-                this.cmenu.render();\r
-            }\r
-            var xy = e.getXY();\r
-            xy[1] -= this.cmenu.el.getHeight();\r
-            this.cmenu.showAt(xy);\r
-        }, this);\r
-    },\r
-    \r
-    closeWin : function(cMenu, e, win){\r
-               if(!win.isVisible()){\r
-                       win.show();\r
-               }else{\r
-                       win.restore();\r
-               }\r
-               win.close();\r
-       }\r
-});
\ No newline at end of file
+/*
+
+This file is part of Ext JS 4
+
+Copyright (c) 2011 Sencha Inc
+
+Contact:  http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
+
+*/
+/*!
+ * Ext JS Library 4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+
+/**
+ * @class Ext.ux.desktop.TaskBar
+ * @extends Ext.toolbar.Toolbar
+ */
+Ext.define('Ext.ux.desktop.TaskBar', {
+    extend: 'Ext.toolbar.Toolbar', // TODO - make this a basic hbox panel...
+
+    requires: [
+        'Ext.button.Button',
+        'Ext.resizer.Splitter',
+        'Ext.menu.Menu',
+
+        'Ext.ux.desktop.StartMenu'
+    ],
+
+    alias: 'widget.taskbar',
+
+    cls: 'ux-taskbar',
+
+    /**
+     * @cfg {String} startBtnText
+     * The text for the Start Button.
+     */
+    startBtnText: 'Start',
+
+    initComponent: function () {
+        var me = this;
+
+        me.startMenu = new Ext.ux.desktop.StartMenu(me.startConfig);
+
+        me.quickStart = new Ext.toolbar.Toolbar(me.getQuickStart());
+
+        me.windowBar = new Ext.toolbar.Toolbar(me.getWindowBarConfig());
+
+        me.tray = new Ext.toolbar.Toolbar(me.getTrayConfig());
+
+        me.items = [
+            {
+                xtype: 'button',
+                cls: 'ux-start-button',
+                iconCls: 'ux-start-button-icon',
+                menu: me.startMenu,
+                menuAlign: 'bl-tl',
+                text: me.startBtnText
+            },
+            me.quickStart,
+            {
+                xtype: 'splitter', html: '&#160;',
+                height: 14, width: 2, // TODO - there should be a CSS way here
+                cls: 'x-toolbar-separator x-toolbar-separator-horizontal'
+            },
+            //'-',
+            me.windowBar,
+            '-',
+            me.tray
+        ];
+
+        me.callParent();
+    },
+
+    afterLayout: function () {
+        var me = this;
+        me.callParent();
+        me.windowBar.el.on('contextmenu', me.onButtonContextMenu, me);
+    },
+
+    /**
+     * This method returns the configuration object for the Quick Start toolbar. A derived
+     * class can override this method, call the base version to build the config and
+     * then modify the returned object before returning it.
+     */
+    getQuickStart: function () {
+        var me = this, ret = {
+            minWidth: 20,
+            width: 60,
+            items: [],
+            enableOverflow: true
+        };
+
+        Ext.each(this.quickStart, function (item) {
+            ret.items.push({
+                tooltip: { text: item.name, align: 'bl-tl' },
+                //tooltip: item.name,
+                overflowText: item.name,
+                iconCls: item.iconCls,
+                module: item.module,
+                handler: me.onQuickStartClick,
+                scope: me
+            });
+        });
+
+        return ret;
+    },
+
+    /**
+     * This method returns the configuration object for the Tray toolbar. A derived
+     * class can override this method, call the base version to build the config and
+     * then modify the returned object before returning it.
+     */
+    getTrayConfig: function () {
+        var ret = {
+            width: 80,
+            items: this.trayItems
+        };
+        delete this.trayItems;
+        return ret;
+    },
+
+    getWindowBarConfig: function () {
+        return {
+            flex: 1,
+            cls: 'ux-desktop-windowbar',
+            items: [ '&#160;' ],
+            layout: { overflowHandler: 'Scroller' }
+        };
+    },
+
+    getWindowBtnFromEl: function (el) {
+        var c = this.windowBar.getChildByElement(el);
+        return c || null;
+    },
+
+    onQuickStartClick: function (btn) {
+        var module = this.app.getModule(btn.module);
+        if (module) {
+            module.createWindow();
+        }
+    },
+    
+    onButtonContextMenu: function (e) {
+        var me = this, t = e.getTarget(), btn = me.getWindowBtnFromEl(t);
+        if (btn) {
+            e.stopEvent();
+            me.windowMenu.theWin = btn.win;
+            me.windowMenu.showBy(t);
+        }
+    },
+
+    onWindowBtnClick: function (btn) {
+        var win = btn.win;
+
+        if (win.minimized || win.hidden) {
+            win.show();
+        } else if (win.active) {
+            win.minimize();
+        } else {
+            win.toFront();
+        }
+    },
+
+    addTaskButton: function(win) {
+        var config = {
+            iconCls: win.iconCls,
+            enableToggle: true,
+            toggleGroup: 'all',
+            width: 140,
+            text: Ext.util.Format.ellipsis(win.title, 20),
+            listeners: {
+                click: this.onWindowBtnClick,
+                scope: this
+            },
+            win: win
+        };
+
+        var cmp = this.windowBar.add(config);
+        cmp.toggle(true);
+        return cmp;
+    },
+
+    removeTaskButton: function (btn) {
+        var found, me = this;
+        me.windowBar.items.each(function (item) {
+            if (item === btn) {
+                found = item;
+            }
+            return !found;
+        });
+        if (found) {
+            me.windowBar.remove(found);
+        }
+        return found;
+    },
+
+    setActiveButton: function(btn) {
+        if (btn) {
+            btn.toggle(true);
+        } else {
+            this.windowBar.items.each(function (item) {
+                if (item.isButton) {
+                    item.toggle(false);
+                }
+            });
+        }
+    }
+});
+
+/**
+ * @class Ext.ux.desktop.TrayClock
+ * @extends Ext.toolbar.TextItem
+ * This class displays a clock on the toolbar.
+ */
+Ext.define('Ext.ux.desktop.TrayClock', {
+    extend: 'Ext.toolbar.TextItem',
+
+    alias: 'widget.trayclock',
+
+    cls: 'ux-desktop-trayclock',
+
+    html: '&#160;',
+
+    timeFormat: 'g:i A',
+
+    tpl: '{time}',
+
+    initComponent: function () {
+        var me = this;
+
+        me.callParent();
+
+        if (typeof(me.tpl) == 'string') {
+            me.tpl = new Ext.XTemplate(me.tpl);
+        }
+    },
+
+    afterRender: function () {
+        var me = this;
+        Ext.Function.defer(me.updateTime, 100, me);
+        me.callParent();
+    },
+
+    onDestroy: function () {
+        var me = this;
+
+        if (me.timer) {
+            window.clearTimeout(me.timer);
+            me.timer = null;
+        }
+
+        me.callParent();
+    },
+
+    updateTime: function () {
+        var me = this, time = Ext.Date.format(new Date(), me.timeFormat),
+            text = me.tpl.apply({ time: time });
+        if (me.lastText != text) {
+            me.setText(text);
+            me.lastText = text;
+        }
+        me.timer = Ext.Function.defer(me.updateTime, 10000, me);
+    }
+});
+