From 151c90743efd374e72bf1b18f5ae0c392a3811ac Mon Sep 17 00:00:00 2001
From: Aaron Bohy <aab@odoo.com>
Date: Thu, 5 Feb 2015 16:34:25 +0100
Subject: [PATCH] [IMP] Web et al.: unique main ControlPanel

A unique main ControlPanel is instantiated by the ActionManager and it
is updated each time the state changes (i.e. when an action is performed
on the ActionManager or when switch_mode() is called on the ViewManager).

Introduction of State classes to facilitate the way of restoring
previously rendered states in ControlPanel. The notion of widgets
previously used in the ActionManager is now encapsulated in the notion
of states.

The search() function has been moved from ControlPanel to ViewManager as
it makes more sense like this.

The ViewManager doesn't have the reference to the ControlPanel but
rather communicate with it through a dedicated bus.

Some refactoring has been done on the ControlPanel as well so that it
doesn't need to know the ActionManager and the current Action anymore
(it communicates with the AM with events, and it updates itself using
information passed in arguments).

Improves the way the ViewManager requests the ControlPanel to
instantiate a SearchView, and how it waits for this SearchView to be
corretly loaded.

Move back the views logic to the ViewManager which directly asks
the ControlPanel to render the buttons and switch-buttons areas with
the views passed in arguments.

Removes option replace_breadcrumb in push_state() of ActionManager as it
is not used.

Side changes:
* Account: account_widget.js:
    Adapt code to retrieve the current state of the ActionManager.
    Required due to the introduction of State classes that encapsulate
    the notions of widgets and inner_widget in the ActionManager.
* Board: dashboard.js:
    Some refactoring to adapt the code according to the new Widgets
    structure. Several dependencies (e.g. searchview, action) were
    unnecessary. Do not append the "add to dashboard" option to the
    Favorite menu if there is no ViewManager available (e.g. in
    Messaging) instead of hidding it with a CSS rule, as a ViewManager
    required to retrieve the active_view.
* Google_spreadsheet: search.js:
    Change the way to retrieve the list_view_id s.t. it does not rely on
    parent of the SearchView anymore.
* Web: search.js:
    Change the way to retrieve the title in search.js for Favorites s.t.
    it does not rely on the parent of the SearchView anymore. Doing so,
    the ControlPanel does not need the title attribute anymore.
---
 addons/account/static/src/js/widgets.js       |  10 +-
 addons/board/static/src/js/dashboard.js       |  54 ++--
 .../static/src/js/search.js                   |  15 +-
 addons/mail/static/src/css/mail.css           |  24 +-
 addons/mail/static/src/xml/mail.xml           |  24 +-
 addons/web/static/src/css/base.css            |  78 +++--
 addons/web/static/src/css/base.sass           |  76 ++---
 addons/web/static/src/js/action_manager.js    | 289 +++++++++++++-----
 addons/web/static/src/js/control_panel.js     | 261 +++++++++-------
 addons/web/static/src/js/view_manager.js      | 144 ++++++---
 .../web/static/src/js/views/search_menus.js   |   2 +-
 addons/web/static/src/js/views/search_view.js |   4 +
 addons/web/static/src/xml/base.xml            |  60 ++--
 13 files changed, 655 insertions(+), 386 deletions(-)

diff --git a/addons/account/static/src/js/widgets.js b/addons/account/static/src/js/widgets.js
index 054735cb60d1..852acbc3795e 100644
--- a/addons/account/static/src/js/widgets.js
+++ b/addons/account/static/src/js/widgets.js
@@ -539,12 +539,12 @@ var bankStatementReconciliation = Widget.extend({
             .call("get_object_reference", ['account', 'action_bank_statement_tree'])
             .then(function (result) {
                 var action_id = result[1];
-                var breadcrumbs = self.action_manager.get_widgets();
-                var widget = _.find(breadcrumbs, function(widget){
-                    return widget.action && widget.action.id === action_id;
+                var breadcrumbs = self.action_manager.get_states();
+                var state = _.find(breadcrumbs, function(state){
+                    return state.widget.action && state.widget.action.id === action_id;
                 });
-                if (widget) {
-                    self.action_manager.select_widget(widget, 0);
+                if (state) {
+                    self.action_manager.select_state(state, 0);
                 } else {
                     self.action_manager.do_action(action_id, {
                         clear_breadcrumbs: true
diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js
index fc7e12322e3a..b2c91b23c5e3 100644
--- a/addons/board/static/src/js/dashboard.js
+++ b/addons/board/static/src/js/dashboard.js
@@ -334,17 +334,24 @@ FavoriteMenu.include({
     prepare_dropdown_menu: function (filters) {
         var self = this;
         this._super(filters);
-        this.$('.favorites-menu').append(QWeb.render('SearchView.addtodashboard'));
-        var $add_to_dashboard = this.$('.add-to-dashboard');
-        this.$add_dashboard_btn = $add_to_dashboard.eq(2).find('button');
-        this.$add_dashboard_input = $add_to_dashboard.eq(1).find('input');
-        this.$add_dashboard_link = $add_to_dashboard.first();
-        var title = this.searchview.getParent().title;
-        this.$add_dashboard_input.val(title);
-        this.$add_dashboard_link.click(function () {
-            self.toggle_dashboard_menu();
+        var am = this.findAncestor(function (a) {
+            return a instanceof ActionManager;
         });
-        this.$add_dashboard_btn.click(this.proxy('add_dashboard'));
+        if (am && am.get_inner_widget() instanceof ViewManager) {
+            this.view_manager = am.get_inner_widget();
+            this.add_to_dashboard_available = true;
+            this.$('.favorites-menu').append(QWeb.render('SearchView.addtodashboard'));
+            var $add_to_dashboard = this.$('.add-to-dashboard');
+            this.$add_dashboard_btn = $add_to_dashboard.eq(2).find('button');
+            this.$add_dashboard_input = $add_to_dashboard.eq(1).find('input');
+            this.$add_dashboard_link = $add_to_dashboard.first();
+            var title = this.searchview.get_title();
+            this.$add_dashboard_input.val(title);
+            this.$add_dashboard_link.click(function () {
+                self.toggle_dashboard_menu();
+            });
+            this.$add_dashboard_btn.click(this.proxy('add_dashboard'));
+        }
     },
     toggle_dashboard_menu: function (is_open) {
         this.$add_dashboard_link
@@ -357,29 +364,24 @@ FavoriteMenu.include({
         }
     },
     close_menus: function () {
-        this.toggle_dashboard_menu(false);
+        if (this.add_to_dashboard_available) {
+            this.toggle_dashboard_menu(false);
+        }
         this._super();
     },
     add_dashboard: function () {
-        var self = this,
-            view_manager = this.findAncestor(function (a) {
-                return a instanceof ViewManager;
-            });
-        if (!view_manager.action) {
-            this.do_warn(_t("Can't find dashboard action"));
-            return;
-        }
-        var searchview = view_manager.get_searchview();
-        var search_data = searchview.build_search_data();
-        var context = new data.CompoundContext(searchview.dataset.get_context() || []);
-        var domain = new data.CompoundDomain(searchview.dataset.get_domain() || []);
+        var self = this;
+
+        var search_data = this.searchview.build_search_data(),
+            context = new data.CompoundContext(this.searchview.dataset.get_context() || []),
+            domain = new data.CompoundDomain(this.searchview.dataset.get_domain() || []);
         _.each(search_data.contexts, context.add, context);
         _.each(search_data.domains, domain.add, domain);
 
         context.add({
             group_by: pyeval.eval('groupbys', search_data.groupbys || [])
         });
-        context.add(view_manager.active_view.controller.get_context());
+        context.add(this.view_manager.active_view.controller.get_context());
         var c = pyeval.eval('context', context);
         for(var k in c) {
             if (c.hasOwnProperty(k) && /^search_default_/.test(k)) {
@@ -396,10 +398,10 @@ FavoriteMenu.include({
             .then(function (board_list) {
                 return self.rpc('/board/add_to_dashboard', {
                     menu_id: board_list[0].id,                    
-                    action_id: view_manager.action.id,
+                    action_id: self.action_id,
                     context_to_save: c,
                     domain: d,
-                    view_mode: view_manager.active_view.type,
+                    view_mode: self.view_manager.active_view.type,
                     name: name,
                 });
             }).then(function (r) {
diff --git a/addons/google_spreadsheet/static/src/js/search.js b/addons/google_spreadsheet/static/src/js/search.js
index 2736088fd8cb..030c84b1a842 100644
--- a/addons/google_spreadsheet/static/src/js/search.js
+++ b/addons/google_spreadsheet/static/src/js/search.js
@@ -1,11 +1,13 @@
 odoo.define('google_spreadsheet.google.spreadsheet', function (require) {
 "use strict";
 
+var ActionManager = require('web.ActionManager');
 var core = require('web.core');
 var data = require('web.data');
 var FavoriteMenu = require('web.FavoriteMenu');
 var FormView = require('web.FormView');
 var pyeval = require('web.pyeval');
+var ViewManager = require('web.ViewManager');
 
 var QWeb = core.qweb;
 
@@ -27,14 +29,19 @@ FormView.include({
 FavoriteMenu.include({
     prepare_dropdown_menu: function (filters) {
         this._super(filters);
-        this.$('.favorites-menu').append(QWeb.render('SearchView.addtogooglespreadsheet'));
-        this.$('.add-to-spreadsheet').click(this.add_to_spreadsheet.bind(this));
+        var am = this.findAncestor(function(a) {
+            return a instanceof ActionManager;
+        });
+        if (am && am.get_inner_widget() instanceof ViewManager) {
+            this.view_manager = am.get_inner_widget();
+            this.$('.favorites-menu').append(QWeb.render('SearchView.addtogooglespreadsheet'));
+            this.$('.add-to-spreadsheet').click(this.add_to_spreadsheet.bind(this));
+        }
     },
     add_to_spreadsheet: function () {
         var sv_data = this.searchview.build_search_data(),
             model = this.searchview.dataset.model,
-            view_manager = this.searchview.getParent(),
-            list_view = view_manager.views.list,
+            list_view = this.view_manager.views.list,
             list_view_id = list_view ? list_view.view_id : false,
             context = this.searchview.dataset.get_context() || [],
             compound_context = new data.CompoundContext(context),
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index 687ae228b737..cd6da8a4dc31 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -25,6 +25,26 @@
     margin-top: -2px;
 }
 
+/* -------- Mail Wall + Control Panel --------- */
+.openerp .oe_mail_wall{
+    display: -webkit-box;
+    display: -moz-box;
+    display: -ms-flexbox;
+    display: -webkit-flex;
+    display: flex;
+    -ms-flex-direction: column;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+}
+.openerp .oe_mail_wall .oe-view-manager-content{
+    -webkit-box-flex: 1;
+    -webkit-flex: 1 1 auto;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
+    overflow: auto;
+    position: static;
+}
+
 /* ---------------- MESSAGES ------------------ */
 
 .openerp .oe_mail .oe_msg{
@@ -731,10 +751,6 @@
     margin-bottom: 10px;
 }
 
-.oe_mail_wall .oe-groupby-menu, .oe_mail_wall .add-to-dashboard {
-    display: none;
-}
-
 .oe-view-manager-content .oe_mail {
     display: inline-block !important;
     position: relative !important;
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index b88f254cf4e7..f97d036bb2a5 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -171,18 +171,20 @@
         Template used to display the communication history in the wall.
         -->
     <div t-name="mail.wall" class="oe-view-manager oe_mail_wall oe_view_manager_current">
-        <div class="oe-control-panel container-fluid">
-            <div class="row">
-                <div class="col-md-6 oe-cp-title">
-                    <ol class="oe-view-title breadcrumb">
-                        <li class="active"><t t-esc="widget.action.name"/></li>
-                    </ol>
+        <div class="oe-control-panel">
+            <div class="container-fluid">
+                <div class="row">
+                    <div class="col-md-6 oe-cp-title">
+                        <ol class="oe-view-title breadcrumb">
+                            <li class="active"><t t-esc="widget.action.name"/></li>
+                        </ol>
+                    </div>
+                    <div class="oe-cp-search-view col-md-6" />
                 </div>
-                <div class="oe-cp-search-view col-md-6" />
-            </div>
-            <div class="row">
-                <div class="col-sm-6 col-sm-offset-6">
-                    <div class="oe-search-options btn-group"/>
+                <div class="row">
+                    <div class="col-sm-6 col-sm-offset-6">
+                        <div class="oe-search-options btn-group"/>
+                    </div>
                 </div>
             </div>
         </div>
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index 5626ccec148b..5181e22a2628 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -923,60 +923,74 @@
 }
 .openerp .oe_application {
   height: 100%;
-  -webkit-flex-grow: 1;
-  flex-grow: 1;
-  -ms-flex-negative: 1;
-  display: inline-block\9;
-  overflow: auto\9;
-  width: -webkit-calc(100% - 220px);
-  width: calc(100% - 220px);
-}
-.openerp .oe_application .oe_application {
-  width: 100%;
-}
-.openerp .oe-view-manager {
   width: 100%;
-  height: 100%;
+  display: -webkit-box;
+  display: -moz-box;
+  display: -ms-flexbox;
   display: -webkit-flex;
   display: flex;
+  -ms-flex-direction: column;
   -webkit-flex-direction: column;
   flex-direction: column;
 }
-.openerp .oe-view-manager .oe-view-manager-content {
+.openerp .oe_application .oe_application {
+  width: 100%;
+}
+.openerp .oe-view-manager {
+  -webkit-box-flex: 1;
+  -webkit-flex: 1 1 auto;
+  -ms-flex: 1 1 auto;
+  flex: 1 1 auto;
   overflow: auto;
-  -webkit-flex-grow: 1;
-  flex-grow: 1;
   position: relative;
 }
-.openerp .oe-view-manager .oe-view-manager-content a {
-  color: #7C7BAD;
-}
-.openerp .oe-view-manager .oe-view-manager-content > div {
+.openerp .oe-view-manager .oe-view-manager-content {
   position: absolute;
-  position: static\9;
   top: 0;
   bottom: 0;
   right: 0;
   left: 0;
+}
+.openerp .oe-view-manager .oe-view-manager-content a {
+  color: #7C7BAD;
+}
+.openerp .oe-view-manager .oe-view-manager-content > div {
+  height: 100%;
+  position: static;
   display: none;
 }
+.openerp .oe-view-manager .oe-view-manager-content .oe-view-manager-content {
+  position: static;
+}
 .openerp .oe-view-manager .oe-view-manager-content .oe-view-manager-content > div {
-  position: relative;
   display: block;
 }
 .openerp .oe-view-manager .oe-view-manager-debug {
   margin-right: 5px;
 }
+.openerp .oe-o2m-control-panel {
+  width: 100%;
+}
+.openerp .oe-o2m-control-panel .oe-cp-buttons div {
+  display: inline-block;
+  padding: 5px 5px;
+}
+.openerp .oe-o2m-control-panel .oe-cp-pager div {
+  display: inline-block;
+  padding: 5px 5px;
+}
 .openerp .oe-control-panel {
   background-color: #f0eeee;
   border-bottom: 1px solid #afafb6;
-  -webkit-flex-shrink: 0;
-  flex-shrink: 0;
-  width: 100%;
-  -webkit-user-select: none;
-  -moz-user-select: none;
+  -webkit-box-flex: 0;
+  -webkit-flex: 0 0 auto;
+  -ms-flex: 0 0 auto;
+  flex: 0 0 auto;
   user-select: none;
 }
+.openerp .oe-control-panel > .container-fluid {
+  width: 100%;
+}
 .openerp .oe-control-panel .oe-button-column {
   height: 30px;
 }
@@ -1092,7 +1106,8 @@
 }
 .openerp .oe-control-panel .oe-cp-sidebar .oe_sidebar_delete_item {
   padding: 0;
-  display: inline-block;
+  position: absolute;
+  right: 10px;
 }
 .openerp .oe-control-panel .oe-cp-sidebar .dropdown-menu li a {
   width: 100%;
@@ -1110,9 +1125,6 @@
 .openerp .oe-control-panel .oe-pager-buttons {
   min-height: 30px;
 }
-.openerp .oe_view_manager_inline > .oe-control-panel-content, .openerp .oe_view_manager_inlineview > .oe-control-panel-content {
-  display: none;
-}
 .openerp .o-modal-header > div {
   margin-left: 45%;
 }
@@ -2010,7 +2022,7 @@
   margin: 4px 7px;
 }
 .openerp .oe_form .oe-view-manager-content {
-  overflow: visible;
+  overflow: hidden;
 }
 .openerp .oe_form_editable .oe_form .oe_form_field_integer input {
   width: 6em;
@@ -3206,7 +3218,7 @@ body.oe_single_form .oe_single_form_container {
 .modal .oe_act_window.modal-body {
   padding: 0;
 }
-.modal .oe-view-manager-content > div {
+.modal .oe-view-manager-content {
   position: static !important;
 }
 
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 63ef0cd5bdba..a4250f78d916 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -801,54 +801,64 @@ $sheet-padding: 16px
         text-decoration: underline
     .oe_application
         height: 100%
-        -webkit-flex-grow: 1
-        flex-grow: 1
-        -ms-flex-negative: 1
-        display: inline-block\9
-        overflow: auto\9
-        width: -webkit-calc(100% - 220px)
-        width: calc(100% - 220px)
+        width: 100%
+        display: -webkit-box
+        display: -moz-box
+        display: -ms-flexbox
+        display: -webkit-flex
+        display: flex
+        -ms-flex-direction: column
+        -webkit-flex-direction: column
+        flex-direction: column        
         .oe_application
             width: 100%
     // }}}
     // ViewManager common {{{
     .oe-view-manager
-        width: 100%
-        height: 100%
-        display: -webkit-flex
-        display: flex
-        -webkit-flex-direction: column
-        flex-direction: column
+        -webkit-box-flex: 1
+        -webkit-flex: 1 1 auto
+        -ms-flex: 1 1 auto
+        flex: 1 1 auto
+        overflow: auto
+        position: relative
         .oe-view-manager-content
-            overflow: auto
-            -webkit-flex-grow: 1
-            flex-grow: 1
-            position: relative
+            position: absolute
+            top: 0
+            bottom: 0
+            right: 0
+            left: 0
             a
                 color: $link-color
             > div
-                position: absolute
-                position: static\9
-                top: 0
-                bottom: 0
-                right: 0
-                left: 0
+                height: 100%
+                position: static
                 display: none
             .oe-view-manager-content
+                position: static
                 > div
-                    position: relative
                     display: block
         .oe-view-manager-debug
             margin-right: 5px
+    .oe-o2m-control-panel
+        width: 100%
+        .oe-cp-buttons
+            div
+                display: inline-block
+                padding: 5px 5px
+        .oe-cp-pager
+            div
+                display: inline-block
+                padding: 5px 5px
     .oe-control-panel
         background-color: rgb(240, 238, 238)
         border-bottom: 1px solid #afafb6
-        -webkit-flex-shrink: 0
-        flex-shrink: 0
-        width: 100%
-        -webkit-user-select: none
-        -moz-user-select: none
+        -webkit-box-flex: 0
+        -webkit-flex: 0 0 auto
+        -ms-flex: 0 0 auto
+        flex: 0 0 auto
         user-select: none
+        > .container-fluid
+            width: 100%
         .oe-button-column
             height: 30px
         .dropdown-menu
@@ -950,9 +960,6 @@ $sheet-padding: 16px
 
         .oe-pager-buttons
             min-height: 30px
-    .oe_view_manager_inline, .oe_view_manager_inlineview
-        > .oe-control-panel-content
-            display: none
 
         // }}}
     // FormPopup {{{
@@ -1673,8 +1680,7 @@ $sheet-padding: 16px
             margin: 4px 7px
         // Override ViewManager overflow auto for One2Many fields
         .oe-view-manager-content
-            overflow: visible
-
+            overflow: hidden
     .oe_form_editable
         .oe_form
             .oe_form_field_integer input
@@ -2641,7 +2647,7 @@ body.oe_single_form
         margin: 0 4px 0 0
     .oe_act_window.modal-body
         padding: 0
-    .oe-view-manager-content > div
+    .oe-view-manager-content
         position: static !important
 
 @media (min-width: 768px)
diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js
index 61686dc4e7e6..8c560830493a 100644
--- a/addons/web/static/src/js/action_manager.js
+++ b/addons/web/static/src/js/action_manager.js
@@ -1,6 +1,7 @@
 odoo.define('web.ActionManager', function (require) {
 "use strict";
 
+var ControlPanel = require('web.ControlPanel');
 var core = require('web.core');
 var crash_manager = require('web.crash_manager');
 var data = require('web.data');
@@ -11,7 +12,63 @@ var session = require('web.session');
 var ViewManager = require('web.ViewManager');
 var Widget = require('web.Widget');
 
-var CompoundContext = data.CompoundContext;
+var State = core.Class.extend({
+    init: function(title, flags) {
+        this.title = title;
+        this.flags = flags;
+    },
+    enable: function() { return $.when(); },
+    disable: function() {},
+    destroy: function() {},
+    get_widget: function() {},
+    get_title: function() { return this.title; },
+    get_flags: function() { return this.flags; },
+    get_action: function() {},
+    get_active_view: function() {},
+});
+
+var WidgetState = State.extend({
+    init: function(title, flags, widget) {
+        this._super(title, flags);
+
+        this.widget = widget;
+        if (this.widget.get('title')) {
+            this.title = title;
+        } else {
+            this.widget.set('title', title);
+        }
+        this.widget.on("do_search", this, function() {
+            // active_search = this.control_panel.activate_search(view.created);
+            // call activate search on search view
+            // domain context and groupby computed are important
+        });
+    },
+    set_cp_content: function(content) { this.cp_content = content; },
+    get_cp_content: function() { return this.cp_content; },
+    get_action: function() { return this.widget.action; },
+    get_active_view: function() { return this.widget.active_view; },
+    destroy: function() { 
+        if (this.cp_content && this.cp_content.searchview) {
+            this.cp_content.searchview.destroy();
+        }
+        this.widget.destroy();
+    },
+    get_widget: function() {
+        return this.widget;
+    },
+});
+
+var FunctionState = State.extend({
+    init: function(title, flags) {
+        this._super(title, flags);
+
+        this.widget = {
+            view_stack: [{
+                controller: { get: function () { return this.title; }}
+            }]
+        };
+    }
+});
 
 var ActionManager = Widget.extend({
     template: "ActionManager",
@@ -22,9 +79,28 @@ var ActionManager = Widget.extend({
         this.webclient = parent;
         this.dialog = null;
         this.dialog_widget = null;
-        this.widgets = [];
+        this.states = [];
         this.on('history_back', this, this.proxy('history_back'));
     },
+    start: function() {
+        this._super();
+
+        // Instantiate the unique main control panel used by every widget in this.states
+        this.main_control_panel = new ControlPanel(this);
+        // Listen to event "switch_view" trigerred on the control panel when clicking
+        // on switch buttons. Forward this event to the current inner_widget
+        this.main_control_panel.on("switch_view", this, function(view_type) {
+            this.inner_widget.trigger("switch_view", view_type);
+        });
+        // Listen to event "on_breadcrumb_click" trigerred on the control panel when
+        // clicking on a part of the breadcrumbs. Call select_state for this breadcrumb.
+        this.main_control_panel.on("on_breadcrumb_click", this, function(state, index) {
+            this.select_state(state, index);
+        });
+
+        // Append the main control panel to the DOM (inside the ActionManager jQuery element)
+        this.main_control_panel.appendTo(this.$el);
+    },
     dialog_stop: function (reason) {
         if (this.dialog) {
             this.dialog.destroy(reason);
@@ -32,125 +108,150 @@ var ActionManager = Widget.extend({
         this.dialog = null;
     },
     /**
-     * Add a new widget to the action manager
+     * Add a new state to the action manager
      *
-     * widget: typically, widgets added are instance.web.ViewManager.  The action manager
+     * widget: typically, widgets added are web.ViewManager.  The action manager
      *      uses this list of widget to handle the breadcrumbs.
      * action: new action
      * options.on_reverse_breadcrumb: will be called when breadcrumb is selected
      * options.clear_breadcrumbs: boolean, if true, current widgets are destroyed
-     * options.replace_breadcrumb: boolean, if true, replace current breadcrumb
      */
-    push_widget: function(widget, action, options) {
+    push_state: function(widget, action, options) {
         var self = this,
             to_destroy,
-            old_widget = this.inner_widget;
+            old_widget = this.inner_widget,
+            old_state = this.get_current_state();
         options = options || {};
 
         if (options.clear_breadcrumbs) {
-            to_destroy = this.widgets;
-            this.widgets = [];
-        } else if (options.replace_breadcrumb) {
-            to_destroy = _.last(this.widgets);
-            this.widgets = _.initial(this.widgets);
+            to_destroy = this.states;
+            this.states = [];
         }
+        var new_state,
+            title = action.display_name || action.name;
         if (widget instanceof Widget) {
-            var title = widget.get('title') || action.display_name || action.name;
-            widget.set('title', title);
-            this.widgets.push(widget);
+            new_state = new WidgetState(title, action.flags, widget);
         } else {
-            this.widgets.push({
-                view_stack: [{
-                    controller: {get: function () {return action.display_name || action.name; }},
-                }],
-                destroy: function () {},
-            });
+            new_state = new FunctionState(title, action.flags);
         }
-        _.last(this.widgets).__on_reverse_breadcrumb = options.on_reverse_breadcrumb;
+        this.states.push(new_state);
+
+        this.get_current_state().__on_reverse_breadcrumb = options.on_reverse_breadcrumb;
         this.inner_action = action;
         this.inner_widget = widget;
-        return $.when(this.inner_widget.appendTo(this.$el)).done(function () {
-            if ((action.target !== 'inline') && (!action.flags.headless) && widget.$header) {
-                widget.$header.show();
+
+        // Sets the main ControlPanel state
+        // AAB: temporary restrict the use of main control panel to act_window actions
+        if (action.type === 'ir.actions.act_window') {
+            // if (old_state) old_state.disable();
+            // new_state.enable();
+            this.main_control_panel.set_state(new_state, old_state);
+            // Expose the ControlPanel nodes to the inner_widget only if the ControlPanel is
+            // displayed so that the inner_widget don't insert stuff in hidden nodes
+            var new_state_flags = new_state.get_flags();
+            if (!new_state_flags.headless) {
+                this.inner_widget.set_external_nodes(this.main_control_panel.get_cp_nodes());
             }
+        }
+
+        return $.when(this.inner_widget.appendTo(this.$el)).done(function () {
             if (old_widget) {
                 old_widget.$el.hide();
             }
             if (options.clear_breadcrumbs) {
-                self.clear_widgets(to_destroy);
+                self.clear_states(to_destroy);
             }
         });
     },
     get_breadcrumbs: function () {
-        return _.flatten(_.map(this.widgets, function (widget) {
-            if (widget instanceof ViewManager) {
-                return widget.view_stack.map(function (view, index) { 
+        return _.flatten(_.map(this.states, function (state) {
+            if (state.widget instanceof ViewManager) {
+                return state.widget.view_stack.map(function (view, index) {
                     return {
-                        title: view.controller.get('title') || widget.title,
+                        title: view.controller.get('title') || state.get_title(),
                         index: index,
-                        widget: widget,
+                        widget: state,
                     }; 
                 });
             } else {
-                return {title: widget.get('title'), widget: widget };
+                return { title: state.get_title(), widget: state };
             }
         }), true);
     },
     get_title: function () {
-        if (this.widgets.length === 1) {
+        if (this.states.length === 1) {
             // horrible hack to display the action title instead of "New" for the actions
             // that use a form view to edit something that do not correspond to a real model
             // for example, point of sale "Your Session" or most settings form,
-            var widget = this.widgets[0];
-            if (widget instanceof ViewManager && widget.view_stack.length === 1) {
-                return widget.title;
+            var state = this.states[0];
+            if (state.widget instanceof ViewManager && state.widget.view_stack.length === 1) {
+                return state.get_title();
             }
         }
         return _.pluck(this.get_breadcrumbs(), 'title').join(' / ');
     },
-    get_widgets: function () {
-        return this.widgets.slice(0);
+    get_states: function () {
+        return this.states;
+    },
+    get_current_state: function() {
+        return _.last(this.states);
+    },
+    get_inner_action: function() {
+        return this.inner_action;
+    },
+    get_inner_widget: function() {
+        return this.inner_widget;
     },
     history_back: function() {
-        var widget = _.last(this.widgets);
-        if (widget instanceof ViewManager) {
-            var nbr_views = widget.view_stack.length;
+        var state = this.get_current_state();
+        if (state.widget instanceof ViewManager) {
+            var nbr_views = state.widget.view_stack.length;
             if (nbr_views > 1) {
-                return this.select_widget(widget, nbr_views - 2);
+                return this.select_state(state, nbr_views - 2);
             } 
         } 
-        if (this.widgets.length > 1) {
-            widget = this.widgets[this.widgets.length - 2];
-            var index = widget.view_stack && widget.view_stack.length - 1;
-            return this.select_widget(widget, index);
+        if (this.states.length > 1) {
+            state = this.states[this.states.length - 2];
+            var index = state.widget.view_stack && state.widget.view_stack.length - 1;
+            return this.select_state(state, index);
         }
         return $.Deferred().reject();
     },
-    select_widget: function(widget, index) {
+    select_state: function(state, index) {
         var self = this;
         if (this.webclient.has_uncommitted_changes()) {
             return $.Deferred().reject();
         }
-        var widget_index = this.widgets.indexOf(widget),
-            def = $.when(widget.select_view && widget.select_view(index));
 
+        // Client widget (-> put in ClientState?)
+        if (state.__on_reverse_breadcrumb) {
+            state.__on_reverse_breadcrumb();
+        }
+        // Set the control panel new state
+        // Put in VMState?
+        // Inform the ControlPanel that the current state changed
+        self.main_control_panel.set_state(state, this.get_current_state());
+
+        var state_index = this.states.indexOf(state),
+            def = $.when(state.widget.select_view && state.widget.select_view(index));
+
+        self.clear_states(self.states.splice(state_index + 1));
+        var last_state = _.last(self.states);
+        self.inner_widget = last_state.widget;
+        // AAB: preceeding two lines probably equal to
+        // self.inner_widget = state;
         return def.done(function () {
-            if (widget.__on_reverse_breadcrumb) {
-                widget.__on_reverse_breadcrumb();
-            }
-            _.each(self.widgets.splice(widget_index + 1), function (w) {
-                w.destroy();
-            });
-            self.inner_widget = _.last(self.widgets);
             if (self.inner_widget.do_show) {
                 self.inner_widget.do_show();
             }
         });
     },
-    clear_widgets: function(widgets) {
-        _.invoke(widgets || this.widgets, 'destroy');
-        if (!widgets) {
-            this.widgets = [];
+    clear_states: function(states) {
+        _.map(states || this.states, function(state) {
+            state.destroy();
+        });
+        if (!states) {
+            this.states = [];
             this.inner_widget = null;
         }
     },
@@ -202,7 +303,7 @@ var ActionManager = Widget.extend({
         var self = this,
             action_loaded;
         if (state.action) {
-            if (_.isString(state.action) && core.action_registry.contains(state.action)) {
+            if (_.isString(state.action) && core.action_regisry.contains(state.action)) {
                 var action_client = {
                     type: "ir.actions.client",
                     tag: state.action,
@@ -300,7 +401,7 @@ var ActionManager = Widget.extend({
 
         if (action === false) {
             action = { type: 'ir.actions.act_window_close' };
-        } else if (_.isString(action) && core.action_registry.contains(action)) {
+        } else if (_.isString(action) && core.action_regisry.contains(action)) {
             var action_client = { type: "ir.actions.client", tag: action, params: {} };
             return this.do_action(action_client, options);
         } else if (_.isNumber(action) || _.isString(action)) {
@@ -318,7 +419,7 @@ var ActionManager = Widget.extend({
         core.bus.trigger('action', action);
 
         // Ensure context & domain are evaluated and can be manipulated/used
-        var ncontext = new CompoundContext(options.additional_context, action.context || {});
+        var ncontext = new data.CompoundContext(options.additional_context, action.context || {});
         action.context = pyeval.eval('context', ncontext);
         if (action.context.active_id || action.context.active_ids) {
             // Here we assume that when an `active_id` or `active_ids` is used
@@ -335,39 +436,56 @@ var ActionManager = Widget.extend({
             console.error("No type for action", action);
             return $.Deferred().reject();
         }
+
         var type = action.type.replace(/\./g,'_');
-        var popup = action.target === 'new';
-        var inline = action.target === 'inline' || action.target === 'inlineview';
-        var form = _.str.startsWith(action.view_mode, 'form');
-        action.flags = _.defaults(action.flags || {}, {
-            views_switcher : !popup && !inline,
-            search_view : !popup && !inline,
-            action_buttons : !popup && !inline,
-            sidebar : !popup && !inline,
-            pager : (!popup || !form) && !inline,
-            display_title : !popup,
-            headless: (popup || inline) && form,
-            search_disable_custom_filters: action.context && action.context.search_disable_custom_filters
-        });
         action.menu_id = options.action_menu_id;
         action.context.params = _.extend({ 'action' : action.id }, action.context.params);
         if (!(type in this)) {
             console.error("Action manager can't handle action of type " + action.type, action);
             return $.Deferred().reject();
         }
+
+        // Special case for Dashboards, this should definitively be done upstream
+        if (action.res_model === 'board.board' && action.view_mode === 'form') {
+            action.target = 'inline';
+            _.extend(action.flags, {
+                headless: true,
+                views_switcher: false,
+                display_title: false,
+                search_view: false,
+                pager: false,
+                sidebar: false,
+                action_buttons: false
+            });
+        } else {
+            var popup = action.target === 'new';
+            var inline = action.target === 'inline' || action.target === 'inlineview';
+            var form = _.str.startsWith(action.view_mode, 'form');
+            action.flags = _.defaults(action.flags || {}, {
+                views_switcher : !popup && !inline,
+                search_view : !popup && !inline,
+                action_buttons : !popup && !inline,
+                sidebar : !popup && !inline,
+                pager : (!popup || !form) && !inline,
+                display_title : !popup,
+                headless: (popup || inline) && form,
+                search_disable_custom_filters: action.context && action.context.search_disable_custom_filters
+            });
+        }
+
         return this[type](action, options);
     },
     null_action: function() {
         this.dialog_stop();
-        this.clear_widgets();
+        this.clear_states();
     },
     /**
      *
      * @param {Object} executor
      * @param {Object} executor.action original action
-     * @param {Function<instance.web.Widget>} executor.widget function used to fetch the widget instance
+     * @param {Function<web.Widget>} executor.widget function used to fetch the widget instance
      * @param {String} executor.klass CSS class to add on the dialog root, if action.target=new
-     * @param {Function<instance.web.Widget, undefined>} executor.post_process cleanup called after a widget has been added as inner_widget
+     * @param {Function<web.Widget, undefined>} executor.post_process cleanup called after a widget has been added as inner_widget
      * @param {Object} options
      * @return {*}
      */
@@ -425,14 +543,13 @@ var ActionManager = Widget.extend({
         }
         widget = executor.widget();
         this.dialog_stop(executor.action);
-        return this.push_widget(widget, executor.action, options);
+        return this.push_state(widget, executor.action, options);
     },
     ir_actions_act_window: function (action, options) {
         var self = this;
-
         return this.ir_actions_common({
             widget: function () { 
-                return new ViewManager(self, null, null, null, action); 
+                return new ViewManager(self, null, null, null, action, self.main_control_panel.get_bus());
             },
             action: action,
             klass: 'oe_act_window',
@@ -440,7 +557,7 @@ var ActionManager = Widget.extend({
     },
     ir_actions_client: function (action, options) {
         var self = this;
-        var ClientWidget = core.action_registry.get(action.tag);
+        var ClientWidget = core.action_registry.get_object(action.tag);
         if (!ClientWidget) {
             return self.do_warn("Action Error", "Could not find client action '" + action.tag + "'.");
         }
@@ -454,7 +571,11 @@ var ActionManager = Widget.extend({
         }
 
         return this.ir_actions_common({
-            widget: function () { return new ClientWidget(self, action); },
+            widget: function () {
+                // AAB: temporary fix: Hide main control panel as client actions do not use it
+                self.main_control_panel.do_hide();
+                return new ClientWidget(self, action);
+            },
             action: action,
             klass: 'oe_act_client',
         }, options).then(function () {
diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js
index d5c016522f50..b71aefa60f10 100644
--- a/addons/web/static/src/js/control_panel.js
+++ b/addons/web/static/src/js/control_panel.js
@@ -5,12 +5,13 @@ var core = require('web.core');
 var Dialog = require('web.Dialog');
 var formats = require('web.formats');
 var framework = require('web.framework');
-var Model = require('web.Model');
-var pyeval = require('web.pyeval');
 var SearchView = require('web.SearchView');
 var utils = require('web.utils');
 var Widget = require('web.Widget');
 
+var QWeb = core.qweb;
+var _t = core._t;
+
 var ControlPanel = Widget.extend({
     template: 'ControlPanel',
     events: {
@@ -27,58 +28,118 @@ var ControlPanel = Widget.extend({
             this.template = template;
         }
 
-        this.view_manager = parent;
-        this.action_manager = this.view_manager.action_manager;
-        this.action = this.view_manager.action;
-        this.dataset = this.view_manager.dataset;
-        this.active_view = this.view_manager.active_view;
-        this.views = this.view_manager.views;
-        this.flags = this.view_manager.flags;
-        this.title = this.view_manager.title; // needed for Favorites of searchview
-        this.view_order = this.view_manager.view_order;
-        this.multiple_views = (this.view_order.length > 1);
+        this.bus = new core.Bus();
+        this.bus.on("setup_search_view", this, this.setup_search_view);
+        this.bus.on("update", this, this.update);
+        this.bus.on("update_breadcrumbs", this, this.update_breadcrumbs);
+        this.bus.on("render_buttons", this, this.render_buttons);
+        this.bus.on("render_switch_buttons", this, this.render_switch_buttons);
+
+        this.searchview = null;
+
+        this.flags = null;
+        this.dataset = null;
+        this.active_view = null;
+        this.title = null; // Needed for Favorites of searchview
     },
     start: function() {
-        var self = this;
-
-        // Retrieve control panel elements
+        // Retrieve ControlPanel jQuery nodes
         this.$control_panel = this.$('.oe-control-panel-content');
         this.$breadcrumbs = this.$('.oe-view-title');
-        this.$switch_buttons = this.$('.oe-cp-switch-buttons button');
+        this.$buttons = this.$('.oe-cp-buttons');
+        this.$switch_buttons = this.$('.oe-cp-switch-buttons');
         this.$title_col = this.$control_panel.find('.oe-cp-title');
         this.$search_col = this.$control_panel.find('.oe-cp-search-view');
-        // AAB: Use sidebar and pager of the ControlPanel only if it is displayed, otherwise set them
-        // to undefined to that the view uses its own elements to sidebar and pager, as follows:
-        // this.$sidebar = !this.flags.headless && this.flags.sidebar ? this.$('.oe-cp-sidebar') : undefined,
-        // this.$pager = !this.flags.headless ? this.$('.oe-cp-pager') : undefined;
-        // But rather use the following definition to keep behavior as it is for now (i.e. it does not
-        // display the pager in one2many list views)
-        this.$sidebar = this.flags.sidebar ? this.$('.oe-cp-sidebar') : undefined;
+        this.$searchview = this.$(".oe-cp-search-view");
+        this.$searchview_buttons = this.$('.oe-search-options');
         this.$pager = this.$('.oe-cp-pager');
+        this.$sidebar = this.$('.oe-cp-sidebar');
+
+        return this._super();
+    },
+    /**
+     * @return {Object} Dictionnary of ControlPanel nodes
+     */
+    get_cp_nodes: function() {
+        return {
+            $buttons: this.$buttons,
+            $switch_buttons: this.$switch_buttons,
+            $sidebar: this.$sidebar,
+            $pager: this.$pager
+        };
+    },
+    get_bus: function() {
+        return this.bus;
+    },
+    // Sets the state of the controlpanel (in the case of a viewmanager, set_state must be
+    // called before switch_mode for the controlpanel and the viewmanager to be synchronized)
+    set_state: function(state, old_state) {
+        // Detach control panel elements in which sub-elements are inserted by other widgets
+        var old_content = {
+            '$buttons': this.$buttons.contents().detach(),
+            '$switch_buttons': this.$switch_buttons.contents().detach(),
+            '$pager': this.$pager.contents().detach(),
+            '$sidebar': this.$sidebar.contents().detach(),
+            'searchview': this.searchview, // AAB: do better with searchview
+            '$searchview': this.$searchview.contents().detach(),
+            '$searchview_buttons': this.$searchview_buttons.contents().detach(),
+        };
+        if (old_state) {
+            // Store them to re-attach them if we come back to that state (e.g. using breadcrumbs)
+            old_state.set_cp_content(old_content);
+        }
+
+        this.state = state;
+        this.flags = state.get_flags();
+
+        this.flags = state.widget.flags;
+        this.active_view = state.widget.active_view;
+        this.dataset = state.widget.dataset;
+        this.title = state.widget.title; // needed for Favorites of searchview
 
         // Hide the ControlPanel in headless mode
-        if (this.flags.headless) {
-            this.$control_panel.hide();
+        this.$el.toggle(!this.flags.headless);
+
+        var content = state.get_cp_content();
+        if (content) {
+            // This state has already been rendered once
+            content.$buttons.appendTo(this.$buttons);
+            content.$switch_buttons.appendTo(this.$switch_buttons);
+            content.$pager.appendTo(this.$pager);
+            content.$sidebar.appendTo(this.$sidebar);
+            this.searchview = content.searchview;
+            content.$searchview.appendTo(this.$searchview);
+            content.$searchview_buttons.appendTo(this.$searchview_buttons);
         }
+    },
+    get_state: function() {
+        return this.state;
+    },
+    render_buttons: function(views) {
+        var self = this;
 
-        _.each(this.views, function (view) {
-            // Expose control panel elements to the views so that they can insert stuff in them
-            view.options = _.extend(view.options, {
-                $buttons : !self.flags.headless ? self.$('.oe-' + view.type + '-buttons') : undefined,
-                $sidebar : self.$sidebar,
-                $pager : self.$pager,
-            }, self.flags, self.flags[view.type], view.options);
-            // Show $buttons as views will put their own buttons inside it and show/hide them
-            if (view.options.$buttons) view.options.$buttons.show();
-            self.$('.oe-cp-switch-' + view.type).tooltip();
+        var buttons_divs = QWeb.render('ControlPanel.buttons', {views: views});
+        $(buttons_divs).appendTo(this.$buttons);
+
+        // Show each div as views will put their own buttons inside it and show/hide them
+        _.each(views, function(view) {
+            self.$('.oe-' + view.type + '-buttons').show();
         });
+    },
+    render_switch_buttons: function(views) {
+        if (views.length > 1) {
+            var self = this;
 
-        // Create the searchview
-        this.search_view_loaded = this.setup_search_view();
+            var switch_buttons = QWeb.render('ControlPanel.switch-buttons', {views: views});
+            $(switch_buttons).appendTo(this.$switch_buttons);
 
-        return this._super();
+            // Create tooltips
+            _.each(views, function(view) {
+                self.$('.oe-cp-switch-' + view.type).tooltip();
+            });
+        }
     },
-    /**
+     /**
      * Triggers an event when switch-buttons are clicked on
      */
     on_switch_buttons_click: function(event) {
@@ -87,22 +148,34 @@ var ControlPanel = Widget.extend({
     },
     /**
      * Updates its status according to the active_view
+     * @param {Object} [active_view] the current active view
+     * @param {Boolean} [search_view_hidden] true if the searchview is hidden, false otherwise
+     * @param {Array} [breadcrumbs] the breadcrumbs to display
      */
-    update: function(active_view) {
-        this.active_view = active_view;
+    update: function(active_view, search_view_hidden, breadcrumbs) {
+        this.active_view = active_view; // this.active_view only used for debug view
 
-        this.update_search_view();
-        this.update_breadcrumbs();
+        this.update_switch_buttons(active_view);
+        this.update_search_view(search_view_hidden);
+        this.update_breadcrumbs(breadcrumbs);
         this.render_debug_view();
-
-        // Update switch-buttons
-        this.$switch_buttons.removeClass('active');
-        this.$('.oe-cp-switch-' + this.active_view.type).addClass('active');
     },
-    update_breadcrumbs: function () {
+    /**
+     * Removes active class on all switch-buttons and adds it to the one of the active view
+     * @param {Object} [active_view] the active_view
+     */
+    update_switch_buttons: function(active_view) {
+        _.each(this.$switch_buttons.contents('button'), function(button) {
+            $(button).removeClass('active');
+        });
+        this.$('.oe-cp-switch-' + active_view.type).addClass('active');
+    },
+    /**
+     * Updates the breadcrumbs
+     **/
+    update_breadcrumbs: function (breadcrumbs) {
         var self = this;
-        if (!this.action_manager) return;
-        var breadcrumbs = this.action_manager.get_breadcrumbs();
+
         if (!breadcrumbs.length) return;
 
         var $breadcrumbs = breadcrumbs.map(function (bc, index) {
@@ -119,27 +192,27 @@ var ControlPanel = Widget.extend({
                     .toggleClass('active', is_last);
             if (!is_last) {
                 $bc.click(function () {
-                    self.action_manager.select_widget(bc.widget, bc.index);
+                    self.trigger("on_breadcrumb_click", bc.widget, bc.index);
                 });
             }
             return $bc;
         }
     },
     /**
-     * Sets up the search view.
+     * Sets up the search view and calls set_search_view on the widget requesting it
      *
-     * @returns {jQuery.Deferred} search view startup deferred
+     * @param {Object} [src] the widget requesting a search_view
+     * @param {Object} [action] the action (required to instantiated the SearchView)
+     * @param {Object} [dataset] the dataset (required to instantiated the SearchView)
+     * @param {Object} [flags] a dictionnary of Booleans
      */
-    setup_search_view: function() {
-        if (this.searchview) {
-            this.searchview.destroy();
-        }
-
-        var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false;
+    setup_search_view: function(src, action, dataset, flags) {
+        var self = this;
+        var view_id = (action && action.search_view_id && action.search_view_id[0]) || false;
 
         var search_defaults = {};
 
-        var context = this.action ? this.action.context : [];
+        var context = action ? action.context : [];
         _.each(context, function (value, key) {
             var match = /^search_default_(.*)$/.exec(key);
             if (match) {
@@ -148,67 +221,29 @@ var ControlPanel = Widget.extend({
         });
 
         var options = {
-            hidden: this.flags.search_view === false,
-            disable_custom_filters: this.flags.search_disable_custom_filters,
-            $buttons: this.$('.oe-search-options'),
-            action: this.action,
+            hidden: flags.search_view === false,
+            disable_custom_filters: flags.search_disable_custom_filters,
+            $buttons: this.$searchview_buttons,
+            action: action,
         };
-        this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options);
 
-        this.searchview.on('search_data', this, this.search.bind(this));
-        return this.searchview.appendTo(this.$(".oe-cp-search-view:first"));
+        // Instantiate the SearchView and append it to the DOM
+        this.searchview = new SearchView(this, dataset, view_id, search_defaults, options);
+        var search_view_loaded = this.searchview.appendTo(this.$searchview);
+        // Sets the SearchView in the widget which made the request
+        src.set_search_view(self.searchview, search_view_loaded);
     },
-    update_search_view: function() {
+    /**
+     * Updates the SearchView's visibility and extend the breadcrumbs area if the SearchView is not visible
+     * @param {Boolean} [is_hidden] visibility of the searchview
+     **/
+    update_search_view: function(is_hidden) {
         if (this.searchview) {
-            var is_hidden = this.active_view.controller.searchable === false;
             this.searchview.toggle_visibility(!is_hidden);
             this.$title_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden);
             this.$search_col.toggle(!is_hidden);
         }
     },
-    search: function(domains, contexts, groupbys) {
-        var self = this,
-            controller = this.active_view.controller,
-            action_context = this.action.context || {},
-            view_context = controller.get_context();
-        pyeval.eval_domains_and_contexts({
-            domains: [this.action.domain || []].concat(domains || []),
-            contexts: [action_context, view_context].concat(contexts || []),
-            group_by_seq: groupbys || []
-        }).done(function (results) {
-            if (results.error) {
-                self.active_search.resolve();
-                throw new Error(
-                        _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
-                                      JSON.stringify(results.error)));
-            }
-            self.dataset._model = new Model(
-                self.dataset.model, results.context, results.domain);
-            var groupby = results.group_by.length ?
-                          results.group_by :
-                          action_context.group_by;
-            if (_.isString(groupby)) {
-                groupby = [groupby];
-            }
-            if (!controller.grouped && !_.isEmpty(groupby)){
-                self.dataset.set_sort([]);
-            }
-            $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() {
-                self.active_search.resolve();
-            });
-        });
-    },
-    activate_search: function(view_created_def) {
-        this.active_search = $.Deferred();
-        if (this.searchview &&
-                this.flags.auto_search &&
-                this.active_view.controller.searchable !== false) {
-            $.when(this.search_view_loaded,view_created_def).done(this.searchview.do_search);
-        } else {
-            this.active_search.resolve();
-        }
-        return this.active_search;
-    },
     on_debug_changed: function (evt) {
         var self = this,
             params = $(evt.target).data(),
@@ -231,7 +266,7 @@ var ControlPanel = Widget.extend({
                 var ids = current_view.get_selected_ids();
                 if (ids.length === 1) {
                     this.dataset.call('get_metadata', [ids]).done(function(result) {
-                        var dialog = new Dialog(this, {
+                        new Dialog(this, {
                             title: _.str.sprintf(_t("Metadata (%s)"), self.dataset.model),
                             size: 'medium',
                             buttons: {
diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js
index 0474f0e127be..94af09129072 100644
--- a/addons/web/static/src/js/view_manager.js
+++ b/addons/web/static/src/js/view_manager.js
@@ -1,21 +1,13 @@
 odoo.define('web.ViewManager', function (require) {
 "use strict";
 
-var ControlPanel = require('web.ControlPanel');
 var core = require('web.core');
 var data = require('web.data');
-var Dialog = require('web.Dialog');
-var formats = require('web.formats');
-var framework = require('web.framework');
 var Model = require('web.Model');
 var pyeval = require('web.pyeval');
-var utils = require('web.utils');
-var SearchView = require('web.SearchView');
-var session = require('web.session');
 var Widget = require('web.Widget');
 
 var _t = core._t;
-var QWeb = core.qweb;
 
 var ViewManager = Widget.extend({
     template: "ViewManager",
@@ -23,25 +15,14 @@ var ViewManager = Widget.extend({
      * @param {Object} [dataset] null object (... historical reasons)
      * @param {Array} [views] List of [view_id, view_type]
      * @param {Object} [flags] various boolean describing UI state
+     * @param {Object} [cp_bus] Bus to allow communication with ControlPanel
      */
-    init: function(parent, dataset, views, flags, action) {
+    init: function(parent, dataset, views, flags, action, cp_bus) {
         if (action) {
             flags = action.flags || {};
             if (!('auto_search' in flags)) {
                 flags.auto_search = action.auto_search !== false;
             }
-            if (action.res_model === 'board.board' && action.view_mode === 'form') {
-                action.target = 'inline';
-                // Special case for Dashboards
-                _.extend(flags, {
-                    views_switcher : false,
-                    display_title : false,
-                    search_view : false,
-                    pager : false,
-                    sidebar : false,
-                    action_buttons : false
-                });
-            }
             this.action = action;
             this.action_manager = parent;
             dataset = new data.DataSetSearch(this, action.res_model, action.context, action.domain);
@@ -62,6 +43,7 @@ var ViewManager = Widget.extend({
         this.active_view = null;
         this.registry = core.view_registry;
         this.title = this.action && this.action.name;
+        this.cp_bus = cp_bus;
 
         _.each(views, function (view) {
             var view_type = view[1] || view.view_type,
@@ -81,10 +63,8 @@ var ViewManager = Widget.extend({
             self.views[view_type] = view_descr;
         });
 
-        // Instantiate ControlPanel
-        this.control_panel = new ControlPanel(self);
-        // Listen to event 'switch_view' trigerred when clicking on switch-buttons
-        this.control_panel.on('switch_view', this, function(view_type) {
+        // Listen to event 'switch_view' indicating that the VM must now display view wiew_type
+        this.on('switch_view', this, function(view_type) {
             if (view_type === 'form' && this.active_view && this.active_view.type === 'form') {
                 this._display_view(view_type);
             } else {
@@ -114,17 +94,85 @@ var ViewManager = Widget.extend({
 
         this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current'));
 
-        // Insert ControlPanel in the DOM
-        var cp_loaded = this.control_panel.prependTo(this.$el);
-        var main_view_loaded = this.switch_mode(default_view, null, default_options);
+        if (this.cp_bus) {
+            // Tell the ControlPanel to setup its search view
+            this.search_view_loaded = $.Deferred();
+            this.cp_bus.trigger('setup_search_view', this, this.action, this.dataset, this.flags);
+            $.when(this.search_view_loaded).then(function() {
+                self.searchview.on('search_data', self, self.search);
+            });
+
+            // Tell the ControlPanel to render and append the (switch-)buttons to the DOM
+            this.cp_bus.trigger('render_buttons', this.views);
+            this.cp_bus.trigger('render_switch_buttons', this.view_order);
+        }
+
+        _.each(this.views, function (view) {
+            // Expose buttons, sidebar and pager elements to the views so that they can insert stuff in them
+            view.options = _.extend(view.options, {
+                $buttons : self.$ext_buttons ? self.$ext_buttons.find('.oe-' + view.type + '-buttons') : undefined,
+                $sidebar : self.$ext_sidebar,
+                $pager : self.$ext_pager,
+            }, self.flags, self.flags[view.type], view.options);
+        });
+
+        // Switch to the default_view to load it
+        this.main_view_loaded = this.switch_mode(default_view, null, default_options);
 
-        return $.when(main_view_loaded, cp_loaded);
+        return $.when(self.main_view_loaded, this.search_view_loaded);
+    },
+    /**
+     * Sets the external nodes in which the ViewManager and its views should insert elements
+     * @param {Object} [nodes] a dictionnary of jQuery nodes
+     */
+    set_external_nodes: function(nodes) {
+        this.$ext_buttons = nodes.$buttons;
+        this.$ext_sidebar = nodes.$sidebar;
+        this.$ext_pager = nodes.$pager;
     },
     /**
-     * Needed for dashboard.js to add Favorites to Dashboard
+     * Executed by the ControlPanel when the searchview requested by this ViewManager is loaded
+     * @param {Widget} [searchview] the SearchView
+     * @param {Deferred} [search_view_loaded_def] will be resolved when the SearchView is loaded
      */
-    get_searchview: function() {
-        return this.control_panel.searchview;
+    set_search_view: function(searchview, search_view_loaded_def) {
+        this.searchview = searchview;
+        this.search_view_loaded = search_view_loaded_def;
+    },
+    /**
+     * Executed on event "search_data" thrown by the SearchView
+     */
+    search: function(domains, contexts, groupbys) {
+        var self = this,
+            controller = this.active_view.controller, // AAB: Correct view must be loaded here
+            action_context = this.action.context || {},
+            view_context = controller.get_context();
+        pyeval.eval_domains_and_contexts({
+            domains: [this.action.domain || []].concat(domains || []),
+            contexts: [action_context, view_context].concat(contexts || []),
+            group_by_seq: groupbys || []
+        }).done(function (results) {
+            if (results.error) {
+                self.active_search.resolve();
+                throw new Error(
+                        _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
+                                      JSON.stringify(results.error)));
+            }
+            self.dataset._model = new Model(
+                self.dataset.model, results.context, results.domain);
+            var groupby = results.group_by.length
+                        ? results.group_by
+                        : action_context.group_by;
+            if (_.isString(groupby)) {
+                groupby = [groupby];
+            }
+            if (!controller.grouped && !_.isEmpty(groupby)){
+                self.dataset.set_sort([]);
+            }
+            $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() {
+                self.active_search.resolve();
+            });
+        });
     },
     get_default_view: function() {
         return this.flags.default_view || this.view_order[0].type;
@@ -147,15 +195,22 @@ var ViewManager = Widget.extend({
             if (this.active_view.controller) this.active_view.controller.do_hide();
             if (this.active_view.$container) this.active_view.$container.hide();
         }
-        this.active_view = this.control_panel.active_view = view;
+        this.active_view = view;
 
         if (!view.created) {
             view.created = this.create_view.bind(this)(view, view_options);
         }
-        // Tell the ControlPanel to call do_search on its searchview
-        var active_search = this.control_panel.activate_search(view.created);
 
-        return $.when(view.created, active_search).done(function () {
+        // Call do_search on the searchview to compute domains, contexts and groupbys
+        if (this.searchview &&
+                this.flags.auto_search &&
+                view.controller.searchable !== false) {
+            this.active_search = $.Deferred();
+            $.when(this.search_view_loaded, view.created).done(function() {
+                self.searchview.do_search();
+            });
+        }
+        return $.when(view.created, this.active_search).done(function () {
             self._display_view(view_options);
             self.trigger('switch_mode', view_type, no_store, view_options);
         });
@@ -165,17 +220,21 @@ var ViewManager = Widget.extend({
         this.active_view.$container.show();
         $.when(this.active_view.controller.do_show(view_options)).done(function () {
             // Tell the ControlPanel to update its elemnts
-            self.control_panel.update(self.active_view);
+            if (self.cp_bus) {
+                var search_view_hidden = self.active_view.controller.searchable === false;
+                var breadcrumbs = self.action_manager.get_breadcrumbs();
+                self.cp_bus.trigger("update", self.active_view, search_view_hidden, breadcrumbs);
+            }
         });
     },
     create_view: function(view, view_options) {
         var self = this,
-            View = this.registry.get_object(view.type),
+            View = this.registry.get(view.type),
             options = _.clone(view.options),
             view_loaded = $.Deferred();
 
-        if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline')) ||
-                (view_options && view_options.mode === 'edit'))) {
+        if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline'))
+                || (view_options && view_options.mode === 'edit'))) {
             options.initial_mode = 'edit';
         }
         var controller = new View(this, this.dataset, view.view_id, options),
@@ -193,7 +252,10 @@ var ViewManager = Widget.extend({
             if (self.action_manager) self.action_manager.trigger('history_back');
         });
         controller.on("change:title", this, function() {
-            self.control_panel.update_breadcrumbs();
+            if (self.cp_bus) {
+                var breadcrumbs = self.action_manager.get_breadcrumbs();
+                self.cp_bus.trigger("update_breadcrumbs", breadcrumbs);
+            }
         });
         controller.on('view_loaded', this, function () {
             view_loaded.resolve();
diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js
index 54d83ba8f78a..0860c8aa553f 100644
--- a/addons/web/static/src/js/views/search_menus.js
+++ b/addons/web/static/src/js/views/search_menus.js
@@ -37,7 +37,7 @@ return Widget.extend({
         this.$save_name = this.$('.oe-save-name');
         this.$inputs = this.$save_name.find('input');
         this.$divider = this.$('.divider');
-        this.$inputs.eq(0).val(this.searchview.getParent().title);
+        this.$inputs.eq(0).val(this.searchview.get_title());
         var $shared_filter = this.$inputs.eq(1),
             $default_filter = this.$inputs.eq(2);
         $shared_filter.click(function () {$default_filter.prop('checked', false);});
diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js
index af18bdea9aca..6e78e69b4662 100644
--- a/addons/web/static/src/js/views/search_view.js
+++ b/addons/web/static/src/js/views/search_view.js
@@ -376,6 +376,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
         this.query = undefined;
         this.dataset = dataset;
         this.view_id = view_id;
+        this.title = options.action && options.action.name;
         this.search_fields = [];
         this.filters = [];
         this.groupbys = [];
@@ -411,6 +412,9 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
             .toggleClass('fa-caret-up', this.visible_filters);
         return this.alive($.when(this._super(), this.alive(load_view).then(this.view_loaded.bind(this))));
     },
+    get_title: function() {
+        return this.title;
+    },
     view_loaded: function (r) {
         var custom_filters_ready;
         this.fields_view_get = r;
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 3758aec3366a..d148ce2f17a9 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -225,7 +225,6 @@
                         </td>
                     </tr>
                     <tr>
-                        <td><label for="backup_pwd">Master Password:</label></td>
                         <td><input type="password" name="backup_pwd" class="required" /></td>
                     </tr>
                 </table>
@@ -470,42 +469,45 @@
 <div t-name="ActionManager" class="oe_application" />
 
 <t t-name="ControlPanel">
-    <div class="oe-control-panel container-fluid">
-        <div class="row">
-            <div class="col-md-6 oe-cp-title">
-                <div t-if="widget.debug" class="oe_debug_view btn-group btn-group-sm"/>
-                <ol class="oe-view-title breadcrumb" t-if="widget.flags.display_title !== false">
-                </ol>
-            </div>
-            <div class="oe-cp-search-view col-md-6" />
-        </div>
-        <div class="row">
-            <div class="col-md-6 oe-button-column">
-                <div class="oe-cp-buttons">
-                    <t t-foreach="widget.views" t-as="view">
-                        <div t-attf-class="oe-#{view}-buttons"/>
-                    </t>
+    <div class="oe-control-panel">
+        <div class="container-fluid">
+            <div class="row">
+                <div class="col-md-6 oe-cp-title">
+                    <div t-if="widget.debug" class="oe_debug_view btn-group btn-group-sm"/>
+                    <ol class="oe-view-title breadcrumb">
+                    </ol>
                 </div>
-                <div class="oe-cp-sidebar"></div>
+                <div class="oe-cp-search-view col-md-6" />
             </div>
-            <div class="col-md-6">
-                <div class="oe-search-options btn-group"/>
-                <div class="oe-right-toolbar">
-                    <div class="oe-cp-pager"></div>
-                    <t t-if="widget.multiple_views">
-                        <div class="oe-cp-switch-buttons btn-group btn-group-sm">
-                            <t t-foreach="widget.view_order" t-as="view">
-                                <button type="button" t-attf-class="btn btn-default fa oe-cp-switch-#{view.type}" t-att-data-view-type="view.type" t-att-title="view.label">
-                                </button>
-                            </t>
-                        </div>
-                    </t>
+            <div class="row">
+                <div class="col-md-6 oe-button-column">
+                    <div class="oe-cp-buttons"></div>
+                    <div class="oe-cp-sidebar"></div>
+                </div>
+                <div class="col-md-6">
+                    <div class="oe-search-options btn-group"/>
+                    <div class="oe-right-toolbar">
+                        <div class="oe-cp-pager"></div>
+                        <div class="oe-cp-switch-buttons btn-group btn-group-sm"></div>
+                    </div>
                 </div>
             </div>
         </div>
     </div>
 </t>
 
+<t t-name="ControlPanel.buttons">
+    <t t-foreach="views" t-as="view">
+        <div t-attf-class="oe-#{view}-buttons"/>
+    </t>
+</t>
+
+<t t-name="ControlPanel.switch-buttons">
+    <t t-foreach="views" t-as="view">
+        <button type="button" t-attf-class="btn btn-default fa oe-cp-switch-#{view.type}" t-att-data-view-type="view.type" t-att-title="view.label"/>
+    </t>
+</t>
+
 <div t-name="ViewManager" class="oe-view-manager">
     <div class="oe-view-manager-content">
         <t t-foreach="widget.views" t-as="view">
-- 
GitLab