[MERGE] forward port of branch 8.0 up to 591e329
[odoo/odoo.git] / addons / web / static / src / js / chrome.js
index 3ad8c60..c39e5bd 100644 (file)
@@ -46,6 +46,8 @@ instance.web.Notification =  instance.web.Widget.extend({
     }
 });
 
+var opened_modal = [];
+
 instance.web.action_notify = function(element, action) {
     element.do_notify(action.params.title, action.params.text, action.params.sticky);
 };
@@ -57,16 +59,6 @@ instance.web.action_warn = function(element, action) {
 instance.web.client_actions.add("action_warn", "instance.web.action_warn");
 
 /**
- * The very minimal function everything should call to create a dialog
- * in OpenERP Web Client.
- */
-instance.web.dialog = function(element) {
-    var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
-    result.dialog("widget").openerpClass();
-    return result;
-};
-
-/**
     A useful class to handle dialogs.
 
     Attributes:
@@ -81,6 +73,8 @@ instance.web.Dialog = instance.web.Widget.extend({
         @param {Widget} parent
         @param {dictionary} options A dictionary that will be forwarded to jQueryUI Dialog. Additionaly, that
             dictionary can contain the following keys:
+            - size: one of the following: 'large', 'medium', 'small'
+            - dialogClass: class to add to the body of dialog
             - buttons: Deprecated. The buttons key is not propagated to jQueryUI Dialog. It must be a dictionary (key = button
                 label, value = click handler) or a list of dictionaries (each element in the dictionary is send to the
                 corresponding method of a jQuery element targeting the <button> tag). It is deprecated because all dialogs
@@ -94,57 +88,15 @@ instance.web.Dialog = instance.web.Widget.extend({
         this._super(parent);
         this.content_to_set = content;
         this.dialog_options = {
-            modal: true,
             destroy_on_close: true,
-            width: 900,
-            min_width: 0,
-            max_width: '95%',
-            height: 'auto',
-            min_height: 0,
-            max_height: $(window.top).height() - 200,
-            autoOpen: false,
-            position: [false, 40],
+            size: 'large', //'medium', 'small'
             buttons: null,
-            resizeStop: function() {
-                self.trigger("resized");
-            },
         };
         if (options) {
             _.extend(this.dialog_options, options);
         }
         this.on("closing", this, this._closing);
-        this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><span class="oe_dialog_custom_buttons"/></div>');
-    },
-    _get_options: function() {
-        var self = this;
-        var o = _.extend({}, this.dialog_options);
-        var sizes = {
-            width: $(window.top).width(),
-            height: $(window.top).height(),
-        };
-        _.each(sizes, function(available_size, unit) {
-            o[unit] = self._get_size(o[unit], available_size);
-            o['min_' + unit] = self._get_size(o['min_' + unit] || 0, available_size);
-            o['max_' + unit] = self._get_size(o['max_' + unit] || 0, available_size);
-            if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) {
-                o[unit] = o['min_' + unit];
-            }
-            if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) {
-                o[unit] = o['max_' + unit];
-            }
-        });
-        o.title = o.title || this.dialog_title;
-        return o;
-    },
-    _get_size: function(val, available_size) {
-        val = val.toString();
-        if (val === 'auto') {
-            return val;
-        } else if (val.slice(-1) === "%") {
-            return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
-        } else {
-            return parseInt(val, 10);
-        }
+        this.$buttons = $('<div class="modal-footer"><span class="oe_dialog_custom_buttons"/></div>');
     },
     renderElement: function() {
         if (this.content_to_set) {
@@ -162,8 +114,10 @@ instance.web.Dialog = instance.web.Widget.extend({
         if (!this.dialog_inited) {
             this.init_dialog();
         }
-        this.$el.dialog('open');
-        this.$el.dialog("widget").append(this.$buttons);
+        this.$buttons.insertAfter(this.$dialog_box.find(".modal-body"));
+        $('.tooltip').remove(); //remove open tooltip if any to prevent them staying when modal is opened
+        //add to list of currently opened modal
+        opened_modal.push(this.$dialog_box);
         return this;
     },
     _add_buttons: function(buttons) {
@@ -171,13 +125,16 @@ instance.web.Dialog = instance.web.Widget.extend({
         var $customButons = this.$buttons.find('.oe_dialog_custom_buttons').empty();
         _.each(buttons, function(fn, text) {
             // buttons can be object or array
+            var pre_text  = fn.pre_text || "";
+            var post_text = fn.post_text || "";
+            var oe_link_class = fn.oe_link_class;
             if (!_.isFunction(fn)) {
                 text = fn.text;
                 fn = fn.click;
             }
-            var $but = $(QWeb.render('WidgetButton', { widget : { string: text, node: { attrs: {} }}}));
+            var $but = $(QWeb.render('WidgetButton', { widget : { pre_text: pre_text, post_text: post_text, string: text, node: { attrs: {'class': oe_link_class} }}}));
             $customButons.append($but);
-            $but.on('click', function(ev) {
+            $but.filter('button').on('click', function(ev) {
                 fn.call(self.$el, ev);
             });
         });
@@ -188,31 +145,54 @@ instance.web.Dialog = instance.web.Widget.extend({
         @return The result returned by start().
     */
     init_dialog: function() {
-        var options = this._get_options();
+        var self = this;
+        var options = _.extend({}, this.dialog_options);
+        options.title = options.title || this.dialog_title;
         if (options.buttons) {
             this._add_buttons(options.buttons);
             delete(options.buttons);
         }
         this.renderElement();
-        instance.web.dialog(this.$el, options);
-        if (options.height === 'auto' && options.max_height) {
-            this.$el.css({ 'max-height': options.max_height, 'overflow-y': 'auto' });
+        this.$dialog_box = $(QWeb.render('Dialog', options)).appendTo("body");
+        this.$el.modal({
+            'backdrop': false,
+            'keyboard': true,
+        });
+        if (options.size !== 'large'){
+            var dialog_class_size = this.$dialog_box.find('.modal-lg').removeClass('modal-lg');
+            if (options.size === 'small'){
+                dialog_class_size.addClass('modal-sm');
+            }
         }
-        var self = this;
-        this.$el.on('dialogclose', function() { self.close(); });
+
+        this.$el.appendTo(this.$dialog_box.find(".modal-body"));
+        var $dialog_content = this.$dialog_box.find('.modal-content');
+        if (options.dialogClass){
+            $dialog_content.find(".modal-body").addClass(options.dialogClass);
+        }
+        $dialog_content.openerpClass();
+
+        this.$dialog_box.on('hidden.bs.modal', this, function() {
+            self.close();
+        });
+        this.$dialog_box.modal('show');
+
         this.dialog_inited = true;
         var res = this.start();
         return res;
     },
     /**
-        Closes the popup, if destroy_on_close was passed to the constructor, it is also destroyed.
+        Closes (hide) the popup, if destroy_on_close was passed to the constructor, it will be destroyed instead.
     */
     close: function(reason) {
-        if (this.dialog_inited) {
-            this.trigger("closing", reason);
-            if (this.$el.is(":data(dialog)")) {     // may have been destroyed by closing signal
-                this.$el.dialog('close');
+        if (this.dialog_inited && !this.__tmp_dialog_hiding) {
+            $('.tooltip').remove(); //remove open tooltip if any to prevent them staying when modal has disappeared
+            if (this.$el.is(":data(bs.modal)")) {     // may have been destroyed by closing signal
+                this.__tmp_dialog_hiding = true;
+                this.$dialog_box.modal('hide');
+                this.__tmp_dialog_hiding = undefined;
             }
+            this.trigger("closing", reason);
         }
     },
     _closing: function() {
@@ -229,6 +209,7 @@ instance.web.Dialog = instance.web.Widget.extend({
     */
     destroy: function (reason) {
         this.$buttons.remove();
+        var self = this;
         _.each(this.getChildren(), function(el) {
             el.destroy();
         });
@@ -237,8 +218,25 @@ instance.web.Dialog = instance.web.Widget.extend({
             this.close(reason);
             this.__tmp_dialog_destroying = undefined;
         }
-        if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(dialog)")) {
-            this.$el.dialog('destroy');
+        if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(bs.modal)")) {
+            //we need this to put the instruction to remove modal from DOM at the end
+            //of the queue, otherwise it might already have been removed before the modal-backdrop
+            //is removed when pressing escape key
+            var $element = this.$dialog_box;
+            setTimeout(function () {
+                //remove modal from list of opened modal since we just destroy it
+                var modal_list_index = $.inArray($element, opened_modal);
+                if (modal_list_index > -1){
+                    opened_modal.splice(modal_list_index,1)[0].remove();
+                }
+                if (opened_modal.length > 0){
+                    //we still have other opened modal so we should focus it
+                    opened_modal[opened_modal.length-1].focus();
+                    //keep class modal-open (deleted by bootstrap hide fnct) on body 
+                    //to allow scrolling inside the modal
+                    $('body').addClass('modal-open');
+                }
+            },0);
         }
         this._super();
     }
@@ -259,11 +257,10 @@ instance.web.CrashManager = instance.web.Class.extend({
             return;
         }
         if (error.data.name === "openerp.http.SessionExpiredException" || error.data.name === "werkzeug.exceptions.Forbidden") {
-            this.show_warning({type: "Session Expired", data: { message: _t("Your OpenERP session expired. Please refresh the current web page.") }});
+            this.show_warning({type: "Session Expired", data: { message: _t("Your Odoo session expired. Please refresh the current web page.") }});
             return;
         }
-        if (error.data.exception_type === "except_osv" || error.data.exception_type === "warning"
-                || error.data.exception_type === "access_error") {
+        if (error.data.exception_type === "except_osv" || error.data.exception_type === "warning" || error.data.exception_type === "access_error") {
             this.show_warning(error);
         } else {
             this.show_error(error);
@@ -276,12 +273,13 @@ instance.web.CrashManager = instance.web.Class.extend({
         if (error.data.exception_type === "except_osv") {
             error = _.extend({}, error, {data: _.extend({}, error.data, {message: error.data.arguments[0] + "\n\n" + error.data.arguments[1]})});
         }
-        instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
-            title: "OpenERP " + (_.str.capitalize(error.type) || "Warning"),
+        new instance.web.Dialog(this, {
+            size: 'medium',
+            title: "Odoo " + (_.str.capitalize(error.type) || "Warning"),
             buttons: [
-                {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
-            ]
-        });
+                {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
+            ],
+        }, $('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>')).open();
     },
     show_error: function(error) {
         if (!this.active) {
@@ -289,17 +287,12 @@ instance.web.CrashManager = instance.web.Class.extend({
         }
         var buttons = {};
         buttons[_t("Ok")] = function() {
-            $(this).dialog("close");
+            this.parents('.modal').modal('hide');
         };
-        var dialog = new instance.web.Dialog(this, {
-            title: "OpenERP " + _.str.capitalize(error.type),
-            width: '80%',
-            height: '50%',
-            min_width: '800px',
-            min_height: '600px',
+        new instance.web.Dialog(this, {
+            title: "Odoo " + _.str.capitalize(error.type),
             buttons: buttons
-        }).open();
-        dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error}));
+        }, QWeb.render('CrashManager.error', {session: instance.session, error: error})).open();
     },
     show_message: function(exception) {
         this.show_error({
@@ -343,20 +336,24 @@ instance.web.RedirectWarningHandler = instance.web.Dialog.extend(instance.web.Ex
         this.error = error;
     },
     display: function() {
+        var self = this;
         error = this.error;
         error.data.message = error.data.arguments[0];
 
-        instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
-            title: "OpenERP " + (_.str.capitalize(error.type) || "Warning"),
+        new instance.web.Dialog(this, {
+            size: 'medium',
+            title: "Odoo " + (_.str.capitalize(error.type) || "Warning"),
             buttons: [
-                {text: _t("Ok"), click: function() { $(this).dialog("close"); }},
-                {text: error.data.arguments[2], click: function() {
-                    window.location.href='#action='+error.data.arguments[1];
-                    $(this).dialog("close");
-                }}
-            ]
-        });
-        this.destroy();
+                {text: error.data.arguments[2], 
+                    oe_link_class : 'oe_highlight',
+                    post_text : _t("or"),
+                    click: function() {
+                        window.location.href='#action='+error.data.arguments[1]
+                        self.destroy();
+                    }},
+                {text: _t("Cancel"), oe_link_class: 'oe_link', click: function() { self.$el.parents('.modal').modal('hide');  self.destroy();}}
+            ],
+        }, QWeb.render('CrashManager.warning', {error: error})).open();
     }
 });
 instance.web.crash_manager_registry.add('openerp.exceptions.RedirectWarning', 'instance.web.RedirectWarningHandler');
@@ -502,20 +499,20 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
      * @param {String} error.error message of the error dialog
      */
     display_error: function (error) {
-        return instance.web.dialog($('<div>'), {
-            modal: true,
+        return new instance.web.Dialog(this, {
+            size: 'medium',
             title: error.title,
             buttons: [
-                {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+                {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
             ]
-        }).html(error.error);
+        }, $('<div>').html(error.error)).open();
     },
     do_create: function(form) {
         var self = this;
         var fields = $(form).serializeArray();
         self.rpc("/web/database/create", {'fields': fields}).done(function(result) {
             if (result) {
-                instance.web.redirect('/web')
+                instance.web.redirect('/web');
             } else {
                 alert("Failed to create database");
             }
@@ -692,6 +689,19 @@ instance.web.Reload = function(parent, action) {
 instance.web.client_actions.add("reload", "instance.web.Reload");
 
 /**
+ * Client action to refresh the session context (making sure
+ * HTTP requests will have the right one) then reload the
+ * whole interface.
+ */
+instance.web.ReloadContext = function(parent, action) {
+    // side-effect of get_session_info is to refresh the session context
+    instance.session.rpc("/web/session/get_session_info", {}).then(function() {
+        instance.web.Reload(parent, action);
+    });
+}
+instance.web.client_actions.add("reload_context", "instance.web.ReloadContext");
+
+/**
  * Client action to go back in breadcrumb history.
  * If can't go back in history stack, will go back to home.
  */
@@ -719,7 +729,7 @@ instance.web.ChangePassword =  instance.web.Widget.extend({
         var $button = self.$el.find('.oe_form_button');
         $button.appendTo(this.getParent().$buttons);
         $button.eq(2).click(function(){
-           self.getParent().close();
+           self.$el.parents('.modal').modal('hide');
         });
         $button.eq(0).click(function(){
           self.rpc("/web/session/change_password",{
@@ -735,63 +745,64 @@ instance.web.ChangePassword =  instance.web.Widget.extend({
        });
     },
     display_error: function (error) {
-        return instance.web.dialog($('<div>'), {
-            modal: true,
+        return new instance.web.Dialog(this, {
+            size: 'medium',
             title: error.title,
             buttons: [
-                {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+                {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
             ]
-        }).html(error.error);
+        }, $('<div>').html(error.error)).open();
     },
 });
 instance.web.client_actions.add("change_password", "instance.web.ChangePassword");
 
 instance.web.Menu =  instance.web.Widget.extend({
-    template: 'Menu',
     init: function() {
         var self = this;
         this._super.apply(this, arguments);
-        this.has_been_loaded = $.Deferred();
+        this.is_bound = $.Deferred();
         this.maximum_visible_links = 'auto'; // # of menu to show. 0 = do not crop, 'auto' = algo
         this.data = {data:{children:[]}};
-        this.on("menu_loaded", this, function (menu_data) {
-            self.reflow();
+        this.on("menu_bound", this, function() {
             // launch the fetch of needaction counters, asynchronous
-            if (!_.isEmpty(menu_data.all_menu_ids)) {
-                this.do_load_needaction(menu_data.all_menu_ids);
+            var $all_menus = self.$el.parents('body').find('.oe_webclient').find('[data-menu]');
+            var all_menu_ids = _.map($all_menus, function (menu) {return parseInt($(menu).attr('data-menu'), 10);});
+            if (!_.isEmpty(all_menu_ids)) {
+                this.do_load_needaction(all_menu_ids);
             }
         });
-        var lazyreflow = _.debounce(this.reflow.bind(this), 200);
-        instance.web.bus.on('resize', this, function() {
-            self.$el.height(0);
-            lazyreflow();
-        });
     },
     start: function() {
         this._super.apply(this, arguments);
-        this.$secondary_menus = this.getParent().$el.find('.oe_secondary_menus_container');
-        this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
-        return this.do_reload();
+        return this.bind_menu();
     },
     do_reload: function() {
         var self = this;
-        return this.rpc("/web/menu/load", {}).done(function(r) {
-            self.menu_loaded(r);
-        });
+        self.bind_menu();
     },
-    menu_loaded: function(data) {
+    bind_menu: function() {
         var self = this;
-        this.data = {data: data};
-        this.renderElement();
-        this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this }));
+        this.$secondary_menus = this.$el.parents().find('.oe_secondary_menus_container')
+        this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
         this.$el.on('click', 'a[data-menu]', this.on_top_menu_click);
         // Hide second level submenus
         this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide();
         if (self.current_menu) {
             self.open_menu(self.current_menu);
         }
-        this.trigger('menu_loaded', data);
-        this.has_been_loaded.resolve();
+        this.trigger('menu_bound');
+
+        var lazyreflow = _.debounce(this.reflow.bind(this), 200);
+        instance.web.bus.on('resize', this, function() {
+            if (parseInt(self.$el.parent().css('width')) <= 768 ) {
+                lazyreflow('all_outside');
+            } else {
+                lazyreflow();
+            }
+        });
+        instance.web.bus.trigger('resize');
+
+        this.is_bound.resolve();
     },
     do_load_needaction: function (menu_ids) {
         var self = this;
@@ -816,30 +827,48 @@ instance.web.Menu =  instance.web.Widget.extend({
     },
     /**
      * Reflow the menu items and dock overflowing items into a "More" menu item.
-     * Automatically called when 'menu_loaded' event is triggered and on window resizing.
+     * Automatically called when 'menu_bound' event is triggered and on window resizing.
+     *
+     * @param {string} behavior If set to 'all_outside', all the items are displayed. If set to
+     * 'all_inside', all the items are hidden under the more item. If not set, only the 
+     * overflowing items are hidden.
      */
-    reflow: function() {
+    reflow: function(behavior) {
         var self = this;
-        this.$el.height('auto').show();
         var $more_container = this.$('#menu_more_container').hide();
         var $more = this.$('#menu_more');
-        $more.children('li').insertBefore($more_container);
-        var $toplevel_items = this.$el.children('li').not($more_container).hide();
+        var $systray = this.$el.parents().find('.oe_systray');
+
+        $more.children('li').insertBefore($more_container);  // Pull all the items out of the more menu
+        
+        // 'all_outside' beahavior should display all the items, so hide the more menu and exit
+        if (behavior === 'all_outside') {
+            this.$el.find('li').show();
+            $more_container.hide();
+            return;
+        }
+
+        var $toplevel_items = this.$el.find('li').not($more_container).not($systray.find('li')).hide();
         $toplevel_items.each(function() {
+            // In all inside mode, we do not compute to know if we must hide the items, we hide them all
+            if (behavior === 'all_inside') {
+                return false;
+            }
             var remaining_space = self.$el.parent().width() - $more_container.outerWidth();
             self.$el.parent().children(':visible').each(function() {
                 remaining_space -= $(this).outerWidth();
             });
+
             if ($(this).width() > remaining_space) {
                 return false;
             }
             $(this).show();
         });
         $more.append($toplevel_items.filter(':hidden').show());
-        $more_container.toggle(!!$more.children().length);
+        $more_container.toggle(!!$more.children().length || behavior === 'all_inside');
         // Hide toplevel item if there is only one
         var $toplevel = this.$el.children("li:visible");
-        if ($toplevel.length === 1) {
+        if ($toplevel.length === 1 && behavior != 'all_inside') {
             $toplevel.hide();
         }
     },
@@ -866,7 +895,7 @@ instance.web.Menu =  instance.web.Widget.extend({
 
         // Activate current main menu
         this.$el.find('.active').removeClass('active');
-        $main_menu.addClass('active');
+        $main_menu.parent().addClass('active');
 
         // Show current sub menu
         this.$secondary_menus.find('.oe_secondary_menu').hide();
@@ -885,6 +914,10 @@ instance.web.Menu =  instance.web.Widget.extend({
                 $clicked_menu.parent().addClass('active');
             }
         }
+        // add a tooltip to cropped menu items
+        this.$secondary_menus.find('.oe_secondary_submenu li a span').each(function() {
+            $(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} :'destroy');
+       });
     },
     /**
      * Call open_menu with the first menu_item matching an action_id
@@ -931,6 +964,8 @@ instance.web.Menu =  instance.web.Widget.extend({
                 id: id,
                 previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu)
             }, $item);
+        } else {
+            console.log('Menu no action found web test 04 will fail');
         }
         this.open_menu(id);
     },
@@ -948,22 +983,20 @@ instance.web.Menu =  instance.web.Widget.extend({
      * @param {Event} ev the jquery event
      */
     on_top_menu_click: function(ev) {
+        ev.preventDefault();
         var self = this;
         var id = $(ev.currentTarget).data('menu');
-        var menu_ids = [id];
-        var menu = _.filter(this.data.data.children, function (menu) {return menu.id == id;})[0];
-        function add_menu_ids (menu) {
-            if (menu.children) {
-                _.each(menu.children, function (menu) {
-                    menu_ids.push(menu.id);
-                    add_menu_ids(menu);
-                });
-            }
-        }
-        add_menu_ids(menu);
+
+        // Fetch the menu leaves ids in order to check if they need a 'needaction'
+        var $secondary_menu = this.$el.parents().find('.oe_secondary_menu[data-menu-parent=' + id + ']');
+        var $menu_leaves = $secondary_menu.children().find('.oe_menu_leaf');
+        var menu_ids = _.map($menu_leaves, function (leave) {return parseInt($(leave).attr('data-menu'), 10);});
+
         self.do_load_needaction(menu_ids).then(function () {
             self.trigger("need_action_reloaded");
         });
+        this.$el.parents().find(".oe_secondary_menus_container").scrollTop(0,0);
+
         this.on_menu_click(ev);
     },
     on_menu_click: function(ev) {
@@ -989,6 +1022,7 @@ instance.web.UserMenu =  instance.web.Widget.extend({
                 f($(this));
             }
         });
+        this.$el.parent().show()
     },
     do_update: function () {
         var self = this;
@@ -1010,6 +1044,8 @@ instance.web.UserMenu =  instance.web.Widget.extend({
                 }
                 var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
                 $avatar.attr('src', avatar_src);
+
+                openerp.web.bus.trigger('resize');  // Re-trigger the reflow logic
             });
         };
         this.update_promise = this.update_promise.then(fct, fct);
@@ -1059,8 +1095,11 @@ instance.web.UserMenu =  instance.web.Widget.extend({
                 e.preventDefault();
                 window.location = $.param.querystring( window.location.href, 'debug');
             });
-            instance.web.dialog($help, {autoOpen: true,
-                modal: true, width: 507, height: 290, resizable: false, title: _t("About")});
+            new instance.web.Dialog(this, {
+                size: 'medium',
+                dialogClass: 'oe_act_window',
+                title: _t("About"),
+            }, $help).open();
         });
     },
 });
@@ -1095,17 +1134,15 @@ instance.web.Client = instance.web.Widget.extend({
     start: function() {
         var self = this;
         return instance.session.session_bind(this.origin).then(function() {
-            var $e = $(QWeb.render(self._template, {widget: self}));
-            self.replaceElement($e);
-            $e.openerpClass();
             self.bind_events();
             return self.show_common();
         });
     },
     bind_events: function() {
         var self = this;
-        this.$el.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() {
-            $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter');
+        $('.oe_systray').show();
+        this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function() {
+            $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter');
         });
         this.$el.on('click', '.oe_dropdown_toggle', function(ev) {
             ev.preventDefault();
@@ -1129,12 +1166,19 @@ instance.web.Client = instance.web.Widget.extend({
             }, 0);
         });
         instance.web.bus.on('click', this, function(ev) {
-            $.fn.tipsy.clear();
+            $('.tooltip').remove();
             if (!$(ev.target).is('input[type=file]')) {
                 self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened');
             }
         });
     },
+    on_logo_click: function(ev){
+        if (!this.has_uncommitted_changes()) {
+            return;
+        } else {
+            ev.preventDefault();
+        }
+    },
     show_common: function() {
         var self = this;
         this.crashmanager =  new instance.web.CrashManager();
@@ -1142,9 +1186,9 @@ instance.web.Client = instance.web.Widget.extend({
         self.notification = new instance.web.Notification(this);
         self.notification.appendTo(self.$el);
         self.loading = new instance.web.Loading(self);
-        self.loading.appendTo(self.$el);
+        self.loading.appendTo(self.$('.openerp_webclient_container'));
         self.action_manager = new instance.web.ActionManager(self);
-        self.action_manager.appendTo(self.$('.oe_application'));
+        self.action_manager.replace(self.$('.oe_application'));
     },
     toggle_bars: function(value) {
         this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
@@ -1155,10 +1199,6 @@ instance.web.Client = instance.web.Widget.extend({
 });
 
 instance.web.WebClient = instance.web.Client.extend({
-    _template: 'WebClient',
-    events: {
-        'click .oe_logo_edit_admin': 'logo_edit'
-    },
     init: function(parent, client_options) {
         this._super(parent);
         if (client_options) {
@@ -1167,12 +1207,13 @@ instance.web.WebClient = instance.web.Client.extend({
         this._current_state = null;
         this.menu_dm = new instance.web.DropMisordered();
         this.action_mutex = new $.Mutex();
-        this.set('title_part', {"zopenerp": "OpenERP"});
+        this.set('title_part', {"zopenerp": "Odoo"});
     },
     start: function() {
         var self = this;
         this.on("change:title_part", this, this._title_changed);
         this._title_changed();
+
         return $.when(this._super()).then(function() {
             if (jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
                 self.to_kitten();
@@ -1236,12 +1277,22 @@ instance.web.WebClient = instance.web.Client.extend({
     show_application: function() {
         var self = this;
         self.toggle_bars(true);
+
         self.update_logo();
+        this.$('.oe_logo_edit_admin').click(function(ev) {
+            self.logo_edit(ev);
+        });
+
+        this.$('.oe_logo img').click(function(ev) {
+               self.on_logo_click(ev);
+           });
+        // Menu is rendered server-side thus we don't want the widget to create any dom
         self.menu = new instance.web.Menu(self);
-        self.menu.replace(this.$el.find('.oe_menu_placeholder'));
+        self.menu.setElement(this.$el.parents().find('.oe_application_menu_placeholder'));
+        self.menu.start();
         self.menu.on('menu_click', this, this.on_menu_action);
         self.user_menu = new instance.web.UserMenu(self);
-        self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder'));
+        self.user_menu.appendTo(this.$el.parents().find('.oe_user_menu_placeholder'));
         self.user_menu.on('user_logout', self, self.on_logout);
         self.user_menu.do_update();
         self.bind_hashchange();
@@ -1253,12 +1304,14 @@ instance.web.WebClient = instance.web.Client.extend({
         }
     },
     update_logo: function() {
-        var img = this.session.url('/web/binary/company_logo');
+        var company = this.session.company_id;
+        var img = this.session.url('/web/binary/company_logo' + (company ? '?company=' + company : ''));
         this.$('.oe_logo img').attr('src', '').attr('src', img);
         this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', this.session.uid === 1);
     },
     logo_edit: function(ev) {
         var self = this;
+        ev.preventDefault();
         self.alive(new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"])).then(function(res) {
             self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) {
                 result.res_id = res['company_id'][0];
@@ -1303,7 +1356,7 @@ instance.web.WebClient = instance.web.Client.extend({
                         return false;
                     });
                 });
-                $icon.appendTo(self.$('.oe_systray'));
+                $icon.prependTo(window.$('.oe_systray'));
             }
         });
     },
@@ -1318,7 +1371,6 @@ instance.web.WebClient = instance.web.Client.extend({
         return this.session.session_reload().then(function () {
             instance.session.load_modules(true).then(
                 self.menu.proxy('do_reload')); });
-
     },
     do_notify: function() {
         var n = this.notification;
@@ -1340,16 +1392,16 @@ instance.web.WebClient = instance.web.Client.extend({
 
         var state = $.bbq.getState(true);
         if (_.isEmpty(state) || state.action == "login") {
-            self.menu.has_been_loaded.done(function() {
+            self.menu.is_bound.done(function() {
                 new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) {
                     if(data.action_id) {
                         self.action_manager.do_action(data.action_id[0]);
                         self.menu.open_action(data.action_id[0]);
                     } else {
                         var first_menu_id = self.menu.$el.find("a:first").data("menu");
-                        if(first_menu_id)
+                        if(first_menu_id) {
                             self.menu.menu_click(first_menu_id);
-                    }
+                        }                    }
                 });
             });
         } else {
@@ -1362,10 +1414,8 @@ instance.web.WebClient = instance.web.Client.extend({
         if (!_.isEqual(this._current_state, stringstate)) {
             var state = event.getState(true);
             if(!state.action && state.menu_id) {
-                self.menu.has_been_loaded.done(function() {
-                    self.menu.do_reload().done(function() {
-                        self.menu.menu_click(state.menu_id);
-                    });
+                self.menu.is_bound.done(function() {
+                    self.menu.menu_click(state.menu_id);
                 });
             } else {
                 state._push_me = false;  // no need to push state back...
@@ -1483,7 +1533,7 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
     $('head').append($('<link>', {
         'rel': 'stylesheet',
         'type': 'text/css',
-        'href': origin +'/web/webclient/css'
+        'href': origin +'/web/css/web.assets_webclient'
     }));
     var currentScript = document.currentScript;
     if (!currentScript) {
@@ -1494,24 +1544,6 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
     client.insertAfter(currentScript);
 };
 
-openerp.web.LoginForm = openerp.web.Widget.extend({
-    init: function ($form) {
-        this._super(/* no parent */);
-        this.setElement($form);
-        this.$el.on('submit', this.on_submit);
-        this.start();
-    },
-    start: function () {
-        if (location.hash) {
-            this.$el.attr('action', this.$el.attr('action') + location.hash);
-        }
-        return this._super();
-    },
-    on_submit: function () {
-        return true;
-    },
-});
-
 })();
 
 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: