From ed25637875ccb25cddd26998c58740deb5eeede1 2012-02-01 05:42:01
From: Brian Granger <ellisonbg@gmail.com>
Date: 2012-02-01 05:42:01
Subject: [PATCH] Major refactoring of saving, notification.

* Refactored the save widget so that the notebook doesn't depend
  on it.  Now the notebook emits events and the save widget
  observes those events.
* Created a new event system for all IPython events (events.js).
  We should start to use this to allow our classes to be loosely
  coupled.
* Created a new notification widget that should be used for all
  notifications. Uses new event system.
* Removed the kernel status widget.
* All kernel status message use new event/notification system.
* The print notebook view works again.

---

diff --git a/IPython/frontend/html/notebook/static/css/notebook.css b/IPython/frontend/html/notebook/static/css/notebook.css
index 73bc5be..6fa3ab9 100644
--- a/IPython/frontend/html/notebook/static/css/notebook.css
+++ b/IPython/frontend/html/notebook/static/css/notebook.css
@@ -55,6 +55,19 @@ span#notebook_name {
     margin: 0.3em 0;
 }
 
+#menubar_container {
+    position: relative;
+}
+
+#notification {
+    position: absolute;
+    right: 3px;
+    top: 3px;
+    height: 25px;
+    padding: 3px 6px;
+    z-index: 10;
+}
+
 #toolbar {
     /* Initially hidden to prevent FLOUC */
     display: none;
@@ -71,31 +84,6 @@ span#quick_help_area {
     margin: 0px 0px 0px 0px;
 }
 
-span#kernel_status {
-    position: absolute;
-    padding: 8px 5px 5px 5px;
-    right: 10px;
-    font-weight: bold;
-}
-
-
-.status_idle {
-    color: gray;
-    visibility: hidden;
-}
-
-.status_busy {
-    color: red;
-}
-
-.status_restarting {
-    color: black;
-}
-
-#kernel_persist {
-    float: right;
-}
-
 .help_string {
     float: right;
     width: 170px;
diff --git a/IPython/frontend/html/notebook/static/js/events.js b/IPython/frontend/html/notebook/static/js/events.js
new file mode 100644
index 0000000..0ff950d
--- /dev/null
+++ b/IPython/frontend/html/notebook/static/js/events.js
@@ -0,0 +1,31 @@
+//----------------------------------------------------------------------------
+//  Copyright (C) 2008-2011  The IPython Development Team
+//
+//  Distributed under the terms of the BSD License.  The full license is in
+//  the file COPYING, distributed as part of this software.
+//----------------------------------------------------------------------------
+
+//============================================================================
+// Events
+//============================================================================
+
+// Give us an object to bind all events to. This object should be created
+// before all other objects so it exists when others register event handlers.
+// To trigger an event handler:
+// $([IPython.events]).trigger('event.Namespace);
+// To handle it:
+// $([IPython.events]).on('event.Namespace',function () {});
+
+var IPython = (function (IPython) {
+
+    var utils = IPython.utils;
+
+    var Events = function () {};
+
+    IPython.Events = Events;
+    IPython.events = new Events();
+
+    return IPython;
+
+}(IPython));
+
diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js
index 6f00922..68e486a 100644
--- a/IPython/frontend/html/notebook/static/js/kernel.js
+++ b/IPython/frontend/html/notebook/static/js/kernel.js
@@ -62,7 +62,7 @@ var IPython = (function (IPython) {
 
 
     Kernel.prototype.restart = function (callback) {
-        IPython.kernel_status_widget.status_restarting();
+        $([IPython.events]).trigger('status_restarting.Kernel');
         var url = this.kernel_url + "/restart";
         var that = this;
         if (this.running) {
@@ -84,20 +84,19 @@ var IPython = (function (IPython) {
         this.kernel_url = this.base_url + "/" + this.kernel_id;
         this.start_channels();
         callback();
-        IPython.kernel_status_widget.status_idle();
     };
 
     Kernel.prototype._websocket_closed = function(ws_url, early){
         var msg;
         var parent_item = $('body');
         if (early) {
-            msg = "Websocket connection to " + ws_url + " could not be established.<br/>" +
-            " You will NOT be able to run code.<br/>" +
+            msg = "Websocket connection to " + ws_url + " could not be established." +
+            " You will NOT be able to run code." +
             " Your browser may not be compatible with the websocket version in the server," +
             " or if the url does not look right, there could be an error in the" +
             " server's configuration.";
         } else {
-            msg = "Websocket connection closed unexpectedly.<br/>" +
+            msg = "Websocket connection closed unexpectedly." +
             " The kernel will no longer be responsive.";
         }
         var dialog = $('<div/>');
@@ -211,6 +210,7 @@ var IPython = (function (IPython) {
 
     Kernel.prototype.interrupt = function () {
         if (this.running) {
+            $([IPython.events]).trigger('status_interrupting.Kernel');
             $.post(this.kernel_url + "/interrupt");
         };
     };
diff --git a/IPython/frontend/html/notebook/static/js/kernelstatus.js b/IPython/frontend/html/notebook/static/js/kernelstatus.js
deleted file mode 100644
index 5b61157..0000000
--- a/IPython/frontend/html/notebook/static/js/kernelstatus.js
+++ /dev/null
@@ -1,65 +0,0 @@
-//----------------------------------------------------------------------------
-//  Copyright (C) 2008-2011  The IPython Development Team
-//
-//  Distributed under the terms of the BSD License.  The full license is in
-//  the file COPYING, distributed as part of this software.
-//----------------------------------------------------------------------------
-
-//============================================================================
-// Kernel Status widget
-//============================================================================
-
-var IPython = (function (IPython) {
-
-    var utils = IPython.utils;
-
-    var KernelStatusWidget = function (selector) {
-        this.selector = selector;
-        if (this.selector !== undefined) {
-            this.element = $(selector);
-            this.style();
-        }
-    };
-
-
-    KernelStatusWidget.prototype.style = function () {
-        this.element.addClass('ui-widget');
-        this.element.attr('title', "The kernel execution status." +
-        " If 'Busy', the kernel is currently running code." +
-        " If 'Idle', it is available for execution.");
-    };
-
-
-    KernelStatusWidget.prototype.status_busy = function () {
-        this.element.removeClass("status_idle");
-        this.element.removeClass("status_restarting");
-        this.element.addClass("status_busy");
-        window.document.title='(Busy) '+window.document.title;
-        this.element.text("Busy");
-    };
-
-
-    KernelStatusWidget.prototype.status_idle = function () {
-        this.element.removeClass("status_busy");
-        this.element.removeClass("status_restarting");
-        this.element.addClass("status_idle");
-        IPython.save_widget.set_document_title();
-        this.element.text("Idle");
-    };
-
-    KernelStatusWidget.prototype.status_restarting = function () {
-        this.element.removeClass("status_busy");
-        this.element.removeClass("status_idle");
-        this.element.addClass("status_restarting");
-        this.element.text("Restarting");
-    };
-
-
-
-
-    IPython.KernelStatusWidget = KernelStatusWidget;
-
-    return IPython;
-
-}(IPython));
-
diff --git a/IPython/frontend/html/notebook/static/js/menubar.js b/IPython/frontend/html/notebook/static/js/menubar.js
index b513ecd..eef2e44 100644
--- a/IPython/frontend/html/notebook/static/js/menubar.js
+++ b/IPython/frontend/html/notebook/static/js/menubar.js
@@ -45,21 +45,21 @@ var IPython = (function (IPython) {
             IPython.save_widget.rename_notebook();
         });
         this.element.find('#copy_notebook').click(function () {
-            var notebook_id = IPython.save_widget.get_notebook_id();
+            var notebook_id = IPython.notebook.get_notebook_id();
             var url = $('body').data('baseProjectUrl') + notebook_id + '/copy';
             window.open(url,'_newtab');
         });
         this.element.find('#save_notebook').click(function () {
-            IPython.save_widget.save_notebook();
+            IPython.notebook.save_notebook();
         });
         this.element.find('#download_ipynb').click(function () {
-            var notebook_id = IPython.save_widget.get_notebook_id();
+            var notebook_id = IPython.notebook.get_notebook_id();
             var url = $('body').data('baseProjectUrl') + 'notebooks/' +
                       notebook_id + '?format=json';
             window.open(url,'_newtab');
         });
         this.element.find('#download_py').click(function () {
-            var notebook_id = IPython.save_widget.get_notebook_id();
+            var notebook_id = IPython.notebook.get_notebook_id();
             var url = $('body').data('baseProjectUrl') + 'notebooks/' +
                       notebook_id + '?format=py';
             window.open(url,'_newtab');
diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js
index df20219..02221a0 100644
--- a/IPython/frontend/html/notebook/static/js/notebook.js
+++ b/IPython/frontend/html/notebook/static/js/notebook.js
@@ -26,6 +26,9 @@ var IPython = (function (IPython) {
         this.msg_cell_map = {};
         this.metadata = {};
         this.control_key_active = false;
+        this.notebook_id = null;
+        this.notebook_name = null;
+        this.notebook_name_blacklist_re = /[\/\\]/;
         this.style();
         this.create_elements();
         this.bind_events();
@@ -66,7 +69,7 @@ var IPython = (function (IPython) {
             // Save (CTRL+S) or (AppleKey+S) 
             //metaKey = applekey on mac
             if ((event.ctrlKey || event.metaKey) && event.keyCode==83) { 
-                IPython.save_widget.save_notebook();
+                that.save_notebook();
                 event.preventDefault();
                 return false;
             } else if (event.which === 27) {
@@ -177,7 +180,7 @@ var IPython = (function (IPython) {
                 return false;
             } else if (event.which === 83 && that.control_key_active) {
                 // Save notebook = s
-                IPython.save_widget.save_notebook();
+                that.save_notebook();
                 that.control_key_active = false;
                 return false;
             } else if (event.which === 74 && that.control_key_active) {
@@ -207,12 +210,12 @@ var IPython = (function (IPython) {
                 return false;
             } else if (event.which === 73 && that.control_key_active) {
                 // Interrupt kernel = i
-                IPython.notebook.kernel.interrupt();
+                that.kernel.interrupt();
                 that.control_key_active = false;
                 return false;
             } else if (event.which === 190 && that.control_key_active) {
                 // Restart kernel = .  # matches qt console
-                IPython.notebook.restart_kernel();
+                that.restart_kernel();
                 that.control_key_active = false;
                 return false;
             } else if (event.which === 72 && that.control_key_active) {
@@ -402,10 +405,14 @@ var IPython = (function (IPython) {
             var cell = this.get_cell(index)
             cell.select();
             if (cell.cell_type === 'heading') {
-                IPython.toolbar.set_cell_type(cell.cell_type+cell.level);
+                $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
+                    {'cell_type':cell.cell_type,level:cell.level}
+                );
             } else {
-                IPython.toolbar.set_cell_type(cell.cell_type)
-            }
+                $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
+                    {'cell_type':cell.cell_type}
+                );
+            };
         };
         return this;
     };
@@ -676,7 +683,9 @@ var IPython = (function (IPython) {
                 source_element.remove();
                 this.dirty = true;
             };
-            IPython.toolbar.set_cell_type("heading"+level);
+            $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
+                {'cell_type':'heading',level:level}
+            );
         };
     };
 
@@ -880,15 +889,12 @@ var IPython = (function (IPython) {
 
     Notebook.prototype.start_kernel = function () {
         this.kernel = new IPython.Kernel();
-        var notebook_id = IPython.save_widget.get_notebook_id();
-        this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
+        this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
     };
 
 
     Notebook.prototype.restart_kernel = function () {
         var that = this;
-        var notebook_id = IPython.save_widget.get_notebook_id();
-
         var dialog = $('<div/>');
         dialog.html('Do you want to restart the current kernel?  You will lose all variables defined in it.');
         $(document).append(dialog);
@@ -976,8 +982,8 @@ var IPython = (function (IPython) {
         var cell = this.cell_for_msg(reply.parent_header.msg_id);
         if (msg_type !== 'status' && !cell){
             // message not from this notebook, but should be attached to a cell
-            console.log("Received IOPub message not caused by one of my cells");
-            console.log(reply);
+            // console.log("Received IOPub message not caused by one of my cells");
+            // console.log(reply);
             return;
         }
         var output_types = ['stream','display_data','pyout','pyerr'];
@@ -985,9 +991,10 @@ var IPython = (function (IPython) {
             this.handle_output(cell, msg_type, content);
         } else if (msg_type === 'status') {
             if (content.execution_state === 'busy') {
+                $([IPython.events]).trigger('status_busy.Kernel');
                 IPython.kernel_status_widget.status_busy();
             } else if (content.execution_state === 'idle') {
-                IPython.kernel_status_widget.status_idle();
+                $([IPython.events]).trigger('status_idle.Kernel');
             } else if (content.execution_state === 'dead') {
                 this.handle_status_dead();
             };
@@ -1142,8 +1149,33 @@ var IPython = (function (IPython) {
         this.msg_cell_map[msg_id] = cell.cell_id;
     };
 
+
     // Persistance and loading
 
+    Notebook.prototype.get_notebook_id = function () {
+        return this.notebook_id;
+    };
+
+
+    Notebook.prototype.get_notebook_name = function () {
+        return this.notebook_name;
+    };
+
+
+    Notebook.prototype.set_notebook_name = function (name) {
+        this.notebook_name = name;
+    };
+
+
+    Notebook.prototype.test_notebook_name = function (nbname) {
+        nbname = nbname || '';
+        if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
+            return true;
+        } else {
+            return false;
+        };
+    };
+
 
     Notebook.prototype.fromJSON = function (data) {
         var ncells = this.ncells();
@@ -1152,8 +1184,9 @@ var IPython = (function (IPython) {
             // Always delete cell 0 as they get renumbered as they are deleted.
             this.delete_cell(0);
         };
-        // Save the metadata
+        // Save the metadata and name.
         this.metadata = data.metadata;
+        this.notebook_name = data.metadata.name;
         // Only handle 1 worksheet for now.
         var worksheet = data.worksheets[0];
         if (worksheet !== undefined) {
@@ -1186,11 +1219,9 @@ var IPython = (function (IPython) {
     };
 
     Notebook.prototype.save_notebook = function () {
-        var notebook_id = IPython.save_widget.get_notebook_id();
-        var nbname = IPython.save_widget.get_notebook_name();
         // We may want to move the name/id/nbformat logic inside toJSON?
         var data = this.toJSON();
-        data.metadata.name = nbname;
+        data.metadata.name = this.notebook_name;
         data.nbformat = 3;
         // We do the call with settings so we can set cache to false.
         var settings = {
@@ -1199,30 +1230,29 @@ var IPython = (function (IPython) {
             type : "PUT",
             data : JSON.stringify(data),
             headers : {'Content-Type': 'application/json'},
-            success : $.proxy(this.notebook_saved,this),
+            success : $.proxy(this.notebook_save_success,this),
             error : $.proxy(this.notebook_save_failed,this)
         };
-        IPython.save_widget.status_saving();
-        var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
+        $([IPython.events]).trigger('notebook_saving.Notebook');
+        var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
         $.ajax(url, settings);
     };
 
 
-    Notebook.prototype.notebook_saved = function (data, status, xhr) {
+    Notebook.prototype.notebook_save_success = function (data, status, xhr) {
         this.dirty = false;
-        IPython.save_widget.notebook_saved();
-        IPython.save_widget.status_last_saved();
+        $([IPython.events]).trigger('notebook_saved.Notebook');
     };
 
 
     Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
-        IPython.save_widget.status_save_failed();
+        $([IPython.events]).trigger('notebook_save_failed.Notebook');
     };
 
 
-    Notebook.prototype.load_notebook = function () {
+    Notebook.prototype.load_notebook = function (notebook_id) {
         var that = this;
-        var notebook_id = IPython.save_widget.get_notebook_id();
+        this.notebook_id = notebook_id;
         // We do the call with settings so we can set cache to false.
         var settings = {
             processData : false,
@@ -1230,30 +1260,27 @@ var IPython = (function (IPython) {
             type : "GET",
             dataType : "json",
             success : function (data, status, xhr) {
-                that.notebook_loaded(data, status, xhr);
+                that.load_notebook_success(data, status, xhr);
             }
         };
-        IPython.save_widget.status_loading();
-        var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
+        $([IPython.events]).trigger('notebook_loading.Notebook');
+        var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
         $.ajax(url, settings);
     };
 
 
-    Notebook.prototype.notebook_loaded = function (data, status, xhr) {
+    Notebook.prototype.load_notebook_success = function (data, status, xhr) {
         this.fromJSON(data);
         if (this.ncells() === 0) {
             this.insert_cell_below('code');
         };
-        IPython.save_widget.status_last_saved();
-        IPython.save_widget.set_notebook_name(data.metadata.name);
         this.dirty = false;
         if (! this.read_only) {
             this.start_kernel();
         }
         this.select(0);
         this.scroll_to_top();
-        IPython.save_widget.update_url();
-        IPython.layout_manager.do_resize();
+        $([IPython.events]).trigger('notebook_loaded.Notebook');
     };
 
     IPython.Notebook = Notebook;
diff --git a/IPython/frontend/html/notebook/static/js/notebookmain.js b/IPython/frontend/html/notebook/static/js/notebookmain.js
index bb5fd46..34cd61c 100644
--- a/IPython/frontend/html/notebook/static/js/notebookmain.js
+++ b/IPython/frontend/html/notebook/static/js/notebookmain.js
@@ -82,14 +82,13 @@ $(document).ready(function () {
 
     IPython.layout_manager = new IPython.LayoutManager();
     IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
-    IPython.save_widget = new IPython.SaveWidget('span#save_widget');
     IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
     IPython.login_widget = new IPython.LoginWidget('span#login_widget');
     IPython.notebook = new IPython.Notebook('div#notebook');
-    IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
+    IPython.save_widget = new IPython.SaveWidget('span#save_widget');
     IPython.menubar = new IPython.MenuBar('#menubar')
     IPython.toolbar = new IPython.ToolBar('#toolbar')
-    IPython.kernel_status_widget.status_idle();
+    IPython.notification_widget = new IPython.NotificationWidget('#notification')
 
     IPython.layout_manager.do_resize();
 
@@ -111,7 +110,11 @@ $(document).ready(function () {
     $('div#main_app').css('display','block');
 
     IPython.layout_manager.do_resize();
-    IPython.notebook.load_notebook();
+    $([IPython.events]).on('notebook_loaded.Notebook', function () {
+        IPython.layout_manager.do_resize();
+        IPython.save_widget.update_url();
+    })
+    IPython.notebook.load_notebook($('body').data('notebookId'));
 
 });
 
diff --git a/IPython/frontend/html/notebook/static/js/notificationwidget.js b/IPython/frontend/html/notebook/static/js/notificationwidget.js
new file mode 100644
index 0000000..9147afc
--- /dev/null
+++ b/IPython/frontend/html/notebook/static/js/notificationwidget.js
@@ -0,0 +1,102 @@
+//----------------------------------------------------------------------------
+//  Copyright (C) 2008-2011  The IPython Development Team
+//
+//  Distributed under the terms of the BSD License.  The full license is in
+//  the file COPYING, distributed as part of this software.
+//----------------------------------------------------------------------------
+
+//============================================================================
+// Notification widget
+//============================================================================
+
+var IPython = (function (IPython) {
+
+    var utils = IPython.utils;
+
+
+    var NotificationWidget = function (selector) {
+        this.selector = selector;
+        this.timeout = null;
+        this.busy = false;
+        if (this.selector !== undefined) {
+            this.element = $(selector);
+            this.style();
+            this.bind_events();
+        }
+    };
+
+
+    NotificationWidget.prototype.style = function () {
+        this.element.addClass('ui-widget ui-widget-content ui-corner-all');
+        this.element.addClass('border-box-sizing');
+    };
+
+
+    NotificationWidget.prototype.bind_events = function () {
+        var that = this;
+        // Kernel events
+        $([IPython.events]).on('status_idle.Kernel',function () {
+            IPython.save_widget.update_document_title();
+            if (that.get_message() === 'Kernel busy') {
+                that.element.fadeOut(100, function () {
+                    that.element.html('');
+                });
+            };
+        });
+        $([IPython.events]).on('status_busy.Kernel',function () {
+            window.document.title='(Busy) '+window.document.title;
+            that.set_message("Kernel busy");
+        });
+        $([IPython.events]).on('status_restarting.Kernel',function () {
+            that.set_message("Restarting kernel",500);
+        });
+        $([IPython.events]).on('status_interrupting.Kernel',function () {
+            that.set_message("Interrupting kernel",500);
+        });
+        // Notebook events
+        $([IPython.events]).on('notebook_loading.Notebook', function () {
+            that.set_message("Loading notebook",500);
+        });
+        $([IPython.events]).on('notebook_loaded.Notebook', function () {
+            that.set_message("Notebook loaded",500);
+        });
+        $([IPython.events]).on('notebook_saving.Notebook', function () {
+            that.set_message("Saving notebook",500);
+        });
+        $([IPython.events]).on('notebook_saved.Notebook', function () {
+            that.set_message("Notebook saved",500);
+        });
+        $([IPython.events]).on('notebook_save_failed.Notebook', function () {
+            that.set_message("Notebook save failed",500);
+        });
+    };
+
+
+    NotificationWidget.prototype.set_message = function (msg, timeout) {
+        var that = this;
+        this.element.html(msg);
+        this.element.fadeIn(100);
+        if (this.timeout !== null) {
+            clearTimeout(this.timeout);
+            this.timeout = null;
+        };
+        if (timeout !== undefined) {
+            this.timeout = setTimeout(function () {
+                that.element.fadeOut(100, function () {that.element.html('');});
+                that.timeout = null;
+            }, timeout)
+        };
+    };
+
+
+    NotificationWidget.prototype.get_message = function () {
+        return this.element.html();
+    };
+
+
+    IPython.NotificationWidget = NotificationWidget;
+
+    return IPython;
+
+}(IPython));
+
diff --git a/IPython/frontend/html/notebook/static/js/printnotebookmain.js b/IPython/frontend/html/notebook/static/js/printnotebookmain.js
index ce5ec98..55f168c 100644
--- a/IPython/frontend/html/notebook/static/js/printnotebookmain.js
+++ b/IPython/frontend/html/notebook/static/js/printnotebookmain.js
@@ -79,16 +79,15 @@ $(document).ready(function () {
     $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
     $('div#notebook_panel').addClass('border-box-sizing ui-widget');
 
-    IPython.save_widget = new IPython.SaveWidget('span#save_widget');
     IPython.login_widget = new IPython.LoginWidget('span#login_widget');
     IPython.notebook = new IPython.Notebook('div#notebook');
+    IPython.save_widget = new IPython.SaveWidget('span#save_widget');
 
     // These have display: none in the css file and are made visible here to prevent FLOUC.
     $('div#header').css('display','block');
     $('div#main_app').css('display','block');
 
-    // Perform these actions after the notebook has been loaded.
-    IPython.notebook.load_notebook(function () {});
+    IPython.notebook.load_notebook($('body').data('notebookId'));
 
 });
 
diff --git a/IPython/frontend/html/notebook/static/js/savewidget.js b/IPython/frontend/html/notebook/static/js/savewidget.js
index ad1f5f3..2ffaf31 100644
--- a/IPython/frontend/html/notebook/static/js/savewidget.js
+++ b/IPython/frontend/html/notebook/static/js/savewidget.js
@@ -15,8 +15,6 @@ var IPython = (function (IPython) {
 
     var SaveWidget = function (selector) {
         this.selector = selector;
-        this.notebook_name_blacklist_re = /[\/\\]/;
-        this.last_saved_name = '';
         if (this.selector !== undefined) {
             this.element = $(selector);
             this.style();
@@ -43,11 +41,19 @@ var IPython = (function (IPython) {
         }, function () {
             $(this).removeClass("ui-state-hover");
         });
-    };
-
-
-    SaveWidget.prototype.save_notebook = function () {
-        IPython.notebook.save_notebook();
+        $([IPython.events]).on('notebook_loaded.Notebook', function () {
+            that.set_last_saved();
+            that.update_notebook_name();
+            that.update_document_title();
+        });
+        $([IPython.events]).on('notebook_saved.Notebook', function () {
+            that.set_last_saved();
+            that.update_notebook_name();
+            that.update_document_title();
+        });
+        $([IPython.events]).on('notebook_save_failed.Notebook', function () {
+            that.set_save_status('');
+        });
     };
 
 
@@ -61,7 +67,7 @@ var IPython = (function (IPython) {
         dialog.append(
             $('<input/>').attr('type','text').attr('size','25')
             .addClass('ui-widget ui-widget-content')
-            .attr('value',that.get_notebook_name())
+            .attr('value',IPython.notebook.get_notebook_name())
         );
         // $(document).append(dialog);
         dialog.dialog({
@@ -73,15 +79,15 @@ var IPython = (function (IPython) {
             buttons : {
                 "OK": function () {
                     var new_name = $(this).find('input').attr('value');
-                    if (!that.test_notebook_name(new_name)) {
+                    if (!IPython.notebook.test_notebook_name(new_name)) {
                         $(this).find('h3').html(
                             "Invalid notebook name. Notebook names must "+
                             "have 1 or more characters and can contain any characters " +
                             "except / and \\. Please enter a new notebook name:"
                         );
                     } else {
-                        that.set_notebook_name(new_name);
-                        that.save_notebook();
+                        IPython.notebook.set_notebook_name(new_name);
+                        IPython.notebook.save_notebook();
                         $(this).dialog('close');
                     }
                 },
@@ -92,82 +98,36 @@ var IPython = (function (IPython) {
         });
     }
 
-    SaveWidget.prototype.notebook_saved = function () {
-        this.set_document_title();
-        this.last_saved_name = this.get_notebook_name();
-    };
-
 
-    SaveWidget.prototype.get_notebook_name = function () {
-        return this.element.find('span#notebook_name').html();
-    };
-
-
-    SaveWidget.prototype.set_notebook_name = function (nbname) {
+    SaveWidget.prototype.update_notebook_name = function () {
+        var nbname = IPython.notebook.get_notebook_name();
         this.element.find('span#notebook_name').html(nbname);
-        this.set_document_title();
-        this.last_saved_name = nbname;
     };
 
 
-    SaveWidget.prototype.set_document_title = function () {
-        nbname = this.get_notebook_name();
+    SaveWidget.prototype.update_document_title = function () {
+        var nbname = IPython.notebook.get_notebook_name();
         document.title = nbname;
     };
-        
-
-    SaveWidget.prototype.get_notebook_id = function () {
-        return $('body').data('notebookId');
-    };
 
 
     SaveWidget.prototype.update_url = function () {
-        var notebook_id = this.get_notebook_id();
-        if (notebook_id !== '') {
+        var notebook_id = IPython.notebook.get_notebook_id();
+        if (notebook_id !== null) {
             var new_url = $('body').data('baseProjectUrl') + notebook_id;
             window.history.replaceState({}, '', new_url);
         };
     };
 
 
-    SaveWidget.prototype.test_notebook_name = function (nbname) {
-        nbname = nbname || '';
-        if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
-            return true;
-        } else {
-            return false;
-        };
-    };
+    SaveWidget.prototype.set_save_status = function (msg) {
+        this.element.find('span#save_status').html(msg);
+    }
 
 
     SaveWidget.prototype.set_last_saved = function () {
         var d = new Date();
-        $('#save_status').html('Last saved: '+d.format('mmm dd h:MM TT'));
-        
-    };
-
-    SaveWidget.prototype.reset_status = function () {
-        this.element.find('span#save_status').html('');
-    };
-
-
-    SaveWidget.prototype.status_last_saved = function () {
-        this.set_last_saved();
-    };
-
-
-    SaveWidget.prototype.status_saving = function () {
-        this.element.find('span#save_status').html('Saving...');
-    };
-
-
-    SaveWidget.prototype.status_save_failed = function () {
-        this.element.find('span#save_status').html('Save failed');
-    };
-
-
-    SaveWidget.prototype.status_loading = function () {
-        this.element.find('span#save_status').html('Loading...');
+        this.set_save_status('Last saved: '+d.format('mmm dd h:MM TT'));
     };
 
 
diff --git a/IPython/frontend/html/notebook/static/js/toolbar.js b/IPython/frontend/html/notebook/static/js/toolbar.js
index df0de25..2ecdfa0 100644
--- a/IPython/frontend/html/notebook/static/js/toolbar.js
+++ b/IPython/frontend/html/notebook/static/js/toolbar.js
@@ -72,8 +72,9 @@ var IPython = (function (IPython) {
 
 
     ToolBar.prototype.bind_events = function () {
+        var that = this;
         this.element.find('#save_b').click(function () {
-            IPython.save_widget.save_notebook();
+            IPython.notebook.save_notebook();
         });
         this.element.find('#cut_b').click(function () {
             IPython.notebook.cut_cell();
@@ -124,12 +125,13 @@ var IPython = (function (IPython) {
                 IPython.notebook.to_heading(undefined, 6);
             };
         });
-
-    };
-
-
-    ToolBar.prototype.set_cell_type = function (cell_type) {
-        this.element.find('#cell_type').val(cell_type);
+        $([IPython.events]).on('selected_cell_type_changed', function (event, data) {
+            if (data.cell_type === 'heading') {
+                that.element.find('#cell_type').val(data.cell_type+data.level);
+            } else {
+                that.element.find('#cell_type').val(data.cell_type);
+            }
+        });
     };
 
 
diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html
index 242a5d0..7b0b087 100644
--- a/IPython/frontend/html/notebook/templates/notebook.html
+++ b/IPython/frontend/html/notebook/templates/notebook.html
@@ -54,10 +54,9 @@
         <button id="login">Login</button>
       {% end %}
     </span>
-
-    <span id="kernel_status">Idle</span>
 </div>
 
+<div id="menubar_container">
 <div id="menubar">
     <ul id="menus">
         <li><a href="#">File</a>
@@ -153,6 +152,9 @@
     </ul>
 
 </div>
+<div id="notification"></div>
+</div>
+
 
 <div id="toolbar">
 
@@ -220,12 +222,12 @@
 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
 
 <script src="{{ static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script>
+<script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
-<script src="{{ static_url("js/kernelstatus.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/layout.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -234,6 +236,7 @@
 <script src="{{ static_url("js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
+<script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script>
 
 </body>
diff --git a/IPython/frontend/html/notebook/templates/printnotebook.html b/IPython/frontend/html/notebook/templates/printnotebook.html
index 003fe33..2849e61 100644
--- a/IPython/frontend/html/notebook/templates/printnotebook.html
+++ b/IPython/frontend/html/notebook/templates/printnotebook.html
@@ -17,10 +17,7 @@
 
     <link rel="stylesheet" href="{{ static_url("jquery/css/themes/base/jquery-ui.min.css") }}" type="text/css" />
     <link rel="stylesheet" href="{{ static_url("codemirror/lib/codemirror.css") }}">
-    <link rel="stylesheet" href="{{ static_url("codemirror/mode/markdown/markdown.css") }}">
-    <link rel="stylesheet" href="{{ static_url("codemirror/mode/rst/rst.css") }}">
     <link rel="stylesheet" href="{{ static_url("codemirror/theme/ipython.css") }}">
-    <link rel="stylesheet" href="{{ static_url("codemirror/theme/default.css") }}">
 
     <link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/>
 
@@ -88,6 +85,7 @@
 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
 
 <script src="{{ static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script>
+<script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
 <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>