Subject: [sugar 1/4] Backup & Restore front end for control panel

From: "Jorge (jasg) Saldivar" <[email-masked]>
Date: Fri, 21 May 2010 17:18:12 -0400

From: Martin Abente <[email-masked]>

Signed-off-by: Jorge Saldivar Galli <[email-masked]>
---
 configure.ac                                       |    1 +
 data/icons/Makefile.am                             |    1 +
 data/icons/module-journalmanagement.svg            |  127 +++++++++++
 extensions/cpsection/Makefile.am                   |    2 +-
 extensions/cpsection/journalmanagement/Makefile.am |    6 +
 extensions/cpsection/journalmanagement/__init__.py |   22 ++
 extensions/cpsection/journalmanagement/model.py    |   78 +++++++
 extensions/cpsection/journalmanagement/view.py     |  236 ++++++++++++++++++++
 8 files changed, 472 insertions(+), 1 deletions(-)
 create mode 100644 data/icons/module-journalmanagement.svg
 create mode 100644 extensions/cpsection/journalmanagement/Makefile.am
 create mode 100644 extensions/cpsection/journalmanagement/__init__.py
 create mode 100644 extensions/cpsection/journalmanagement/model.py
 create mode 100644 extensions/cpsection/journalmanagement/view.py

diff --git a/configure.ac b/configure.ac
index 4f60892..d6bc609 100644
--- a/configure.ac
+++ b/configure.ac
@@ -53,6 +53,7 @@ extensions/cpsection/aboutcomputer/Makefile
 extensions/cpsection/aboutme/Makefile
 extensions/cpsection/datetime/Makefile
 extensions/cpsection/frame/Makefile
+extensions/cpsection/journalmanagement/Makefile
 extensions/cpsection/keyboard/Makefile
 extensions/cpsection/language/Makefile
 extensions/cpsection/modemconfiguration/Makefile
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am
index a35643a..4957276 100644
--- a/data/icons/Makefile.am
+++ b/data/icons/Makefile.am
@@ -5,6 +5,7 @@ sugar_DATA =                        \
 	module-about_my_computer.svg    \
 	module-date_and_time.svg        \
 	module-frame.svg                \
+	module-journalmanagement.svg    \
 	module-keyboard.svg             \
 	module-language.svg             \
 	module-modemconfiguration.svg	\
diff --git a/data/icons/module-journalmanagement.svg b/data/icons/module-journalmanagement.svg
new file mode 100644
index 0000000..9250c6f
--- /dev/null
+++ b/data/icons/module-journalmanagement.svg
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   enable-background="new 0 0 55 55"
+   height="55px"
+   id="Layer_1"
+   version="1.1"
+   viewBox="0 0 55 55"
+   width="55px"
+   x="0px"
+   xml:space="preserve"
+   y="0px"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="journalmanagement.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"><sodipodi:namedview
+   inkscape:window-height="827"
+   inkscape:window-width="1440"
+   inkscape:pageshadow="2"
+   inkscape:pageopacity="0.0"
+   guidetolerance="10.0"
+   gridtolerance="10.0"
+   objecttolerance="10.0"
+   borderopacity="1.0"
+   bordercolor="#666666"
+   pagecolor="#ffffff"
+   id="base"
+   showgrid="false"
+   inkscape:zoom="8.9818182"
+   inkscape:cx="7.7378543"
+   inkscape:cy="27.5"
+   inkscape:window-x="0"
+   inkscape:window-y="25"
+   inkscape:current-layer="Layer_1" /><metadata
+   id="metadata35"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs33"><inkscape:perspective
+     sodipodi:type="inkscape:persp3d"
+     inkscape:vp_x="0 : 27.5 : 1"
+     inkscape:vp_y="0 : 1000 : 0"
+     inkscape:vp_z="55 : 27.5 : 1"
+     inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+     id="perspective37" /><inkscape:perspective
+     id="perspective12"
+     inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+     inkscape:vp_z="55 : 27.5 : 1"
+     inkscape:vp_y="0 : 1000 : 0"
+     inkscape:vp_x="0 : 27.5 : 1"
+     sodipodi:type="inkscape:persp3d" /></defs>
+
+<g
+   display="block"
+   id="document-save"
+   transform="matrix(1.2198714,0,0,1.0017443,-2.2062535,-2.4418416)"
+   style="display:block">
+	<g
+   id="g4">
+		<g
+   id="g6">
+			<g
+   id="g8">
+				<path
+   d="M 6.736,49.002 L 31.256,49.002 C 33.481,49.002 34.695,47.555 34.695,45.561 L 34.695,18.281 C 34.695,16.551 32.963,14.84 31.256,14.84 L 26.867,14.84"
+   id="path10"
+   style="fill:#ffffff;stroke:#010101;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+		</g>
+		<g
+   id="g12">
+			<g
+   id="g14">
+				<path
+   d="M 26.867,38.592 C 26.867,40.428 25.522,41.793 23.426,42.639 L 6.736,49.002 L 6.736,14.84 L 23.426,6.241 C 25.654,5.847 26.867,7.081 26.867,9.075 L 26.867,38.592 z"
+   id="path16"
+   style="fill:#ffffff;stroke:#010101;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+		</g>
+		<path
+   d="M 9.424,42.607 C 9.424,42.607 8.073,42.064 6.722,42.064 C 5.371,42.064 4.019,42.607 4.019,42.607"
+   id="path18"
+   style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+		<path
+   d="M 9.424,32.006 C 9.424,32.006 8.185,31.463 6.609,31.463 C 5.032,31.463 4.019,32.006 4.019,32.006"
+   id="path20"
+   style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+		<path
+   d="M 9.424,21.678 C 9.424,21.678 8.299,21.134 6.497,21.134 C 4.695,21.134 4.019,21.678 4.019,21.678"
+   id="path22"
+   style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+		
+		<line
+   x1="13.209"
+   x2="13.209"
+   y1="46.533001"
+   y2="11.505"
+   id="line24"
+   style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+		
+		<g
+   id="g26">
+			
+			
+		</g>
+	</g>
+</g><rect
+   y="28.864494"
+   x="28.753164"
+   height="22.87829"
+   width="23.546305"
+   id="rect2668"
+   style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.99260712;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><g
+   id="view-expand"
+   display="block"
+   transform="matrix(0.4680612,0,0,0.4482681,27.768809,28.143274)"
+   style="fill:#000000;display:block">
+	<path
+   id="path5"
+   d="M 4.875,5 L 4.875,50 L 49.875,50 L 49.875,5 L 4.875,5 z M 23.638,44.051 L 12.574,44.051 C 12.503,44.051 12.433,44.038 12.364,44.03 C 12.322,44.025 12.28,44.026 12.238,44.017 C 12.205,44.01 12.174,43.997 12.141,43.989 C 12.062,43.968 11.983,43.949 11.907,43.918 C 11.894,43.912 11.882,43.903 11.869,43.897 C 11.779,43.857 11.691,43.813 11.607,43.757 C 11.511,43.694 11.42,43.62 11.338,43.538 C 11.255,43.455 11.181,43.363 11.116,43.266 C 11.086,43.22 11.065,43.17 11.04,43.122 C 11.012,43.07 10.98,43.02 10.957,42.965 C 10.925,42.889 10.905,42.809 10.885,42.73 C 10.877,42.7 10.864,42.67 10.858,42.639 C 10.839,42.541 10.831,42.443 10.829,42.343 C 10.829,42.328 10.825,42.315 10.825,42.3 L 10.825,31.236 C 10.825,30.267 11.608,29.485 12.574,29.485 C 13.542,29.485 14.325,30.267 14.325,31.236 L 14.325,38.075 L 22.337,30.06 C 23.021,29.378 24.129,29.378 24.813,30.06 C 25.495,30.744 25.495,31.852 24.813,32.537 L 16.8,40.549 L 23.638,40.549 L 23.638,40.552 C 24.604,40.552 25.388,41.334 25.388,42.3 C 25.389,43.267 24.604,44.051 23.638,44.051 z M 42.573,25.114 C 41.605,25.114 40.822,24.331 40.822,23.363 L 40.822,16.523 L 31.809,25.536 C 31.467,25.878 31.019,26.049 30.571,26.049 C 30.124,26.049 29.675,25.878 29.334,25.536 C 28.651,24.852 28.651,23.745 29.334,23.061 L 38.345,14.049 L 31.511,14.049 C 30.545,14.049 29.76,13.264 29.76,12.298 C 29.76,11.332 30.545,10.547 31.511,10.547 L 42.573,10.547 C 42.677,10.547 42.782,10.559 42.885,10.578 C 42.892,10.579 42.9,10.579 42.906,10.58 C 42.961,10.591 43.012,10.611 43.066,10.627 C 43.124,10.644 43.182,10.657 43.238,10.68 C 43.27,10.693 43.3,10.713 43.331,10.729 C 43.402,10.763 43.475,10.796 43.541,10.841 C 43.598,10.878 43.646,10.924 43.697,10.968 C 43.734,10.999 43.774,11.025 43.809,11.059 C 43.856,11.106 43.892,11.16 43.933,11.211 C 43.963,11.25 43.999,11.285 44.027,11.326 C 44.088,11.417 44.137,11.513 44.178,11.611 C 44.181,11.617 44.185,11.622 44.188,11.628 C 44.222,11.711 44.245,11.796 44.265,11.883 C 44.272,11.908 44.283,11.933 44.287,11.959 C 44.299,12.02 44.301,12.083 44.306,12.145 C 44.311,12.196 44.322,12.247 44.322,12.298 L 44.324,23.363 C 44.324,24.332 43.54,25.114 42.573,25.114 z"
+   style="fill:#000000" />
+</g></svg>
\ No newline at end of file
diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am
index a92b5dd..8edc4b7 100644
--- a/extensions/cpsection/Makefile.am
+++ b/extensions/cpsection/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = aboutme aboutcomputer datetime frame keyboard language \
+SUBDIRS = aboutme aboutcomputer datetime frame journalmanagement keyboard language \
     modemconfiguration network power updater
 
 sugardir = $(pkgdatadir)/extensions/cpsection
diff --git a/extensions/cpsection/journalmanagement/Makefile.am b/extensions/cpsection/journalmanagement/Makefile.am
new file mode 100644
index 0000000..ed68aa7
--- /dev/null
+++ b/extensions/cpsection/journalmanagement/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/journalmanagement
+
+sugar_PYTHON = 		\
+	__init__.py	\
+	model.py	\
+	view.py
diff --git a/extensions/cpsection/journalmanagement/__init__.py b/extensions/cpsection/journalmanagement/__init__.py
new file mode 100644
index 0000000..6eace00
--- /dev/null
+++ b/extensions/cpsection/journalmanagement/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2010 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
+
+from gettext import gettext as _
+
+CLASS = 'JournalManagement'
+ICON = 'module-journalmanagement'
+TITLE = _('Journal Management')
+
diff --git a/extensions/cpsection/journalmanagement/model.py b/extensions/cpsection/journalmanagement/model.py
new file mode 100644
index 0000000..811bdfb
--- /dev/null
+++ b/extensions/cpsection/journalmanagement/model.py
@@ -0,0 +1,78 @@
+# Copyright (C) 2010 Paraguay Educa, Martin Abente, Bernie Innocenti
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
+
+import os
+import gobject
+import glib
+import gio
+import logging
+
+BYTES_TO_READ = 100
+
+class JournalManagementModel(gobject.GObject):
+
+    __gtype_name__ = 'JournalManagementModel'
+
+    __gsignals__ = {
+        'journal-management-running'    : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])),
+        'journal-management-started'    : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+        'journal-management-finished'    : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+        'journal-management-failed'    : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
+    }
+
+    def __init__(self):
+        gobject.GObject.__init__(self)
+
+        self._running = False
+        self._backup_script_path = ['/usr/bin/journal-backup']
+        self._restore_script_path = ['/usr/bin/journal-restore']
+
+    def do_backup(self):
+        self.run_cmd_async(self._backup_script_path)
+
+    def do_restore(self):
+        self.run_cmd_async(self._restore_script_path)
+
+    def report_process_status(self, stream, result):
+        data = stream.read_finish(result)
+
+        if len(data):
+            self.emit('journal-management-running', data)
+            stream.read_async(BYTES_TO_READ, self.report_process_status)
+
+    def run_cmd_async(self, cmd):
+        if self._running == False:
+            try:
+                pid, stdin, stdout, stderr = glib.spawn_async(cmd, flags=glib.SPAWN_DO_NOT_REAP_CHILD, standard_output=True)
+                gobject.child_watch_add(pid, _handle_process_end, (self))
+            except Exception:
+                self.emit('journal-management-failed')
+            else:
+                stdin_stream = gio.unix.InputStream(stdout, True)
+                stdin_stream.read_async(BYTES_TO_READ, self.report_process_status)
+
+                self._running  = True
+                self.emit('journal-management-started')
+
+def _handle_process_end(pid, condition, myself):
+    myself._running = False
+
+    if os.WIFEXITED(condition) and\
+        os.WEXITSTATUS(condition) == 0:
+            myself.emit('journal-management-finished')
+    else:
+        myself.emit('journal-management-failed')
+
diff --git a/extensions/cpsection/journalmanagement/view.py b/extensions/cpsection/journalmanagement/view.py
new file mode 100644
index 0000000..ae3194c
--- /dev/null
+++ b/extensions/cpsection/journalmanagement/view.py
@@ -0,0 +1,236 @@
+# Copyright (C) 2010 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US.
+
+import gtk
+import gobject
+import logging
+from gettext import gettext as _
+
+from jarabe.model.session import get_session_manager
+
+from sugar.graphics import style
+from sugar.graphics.alert import Alert
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+from model import JournalManagementModel
+
+class JournalManagement(SectionView):
+    def __init__(self, model, alerts):
+        SectionView.__init__(self)
+
+        self._model = JournalManagementModel()
+        self._model.connect('journal-management-running', self._set_update_log)
+        self._model.connect('journal-management-started', self._set_status_started)
+        self._model.connect('journal-management-finished', self._set_status_finished)
+        self._model.connect('journal-management-failed', self._set_status_failed)
+
+        self.restart_alerts = alerts
+        self._showing_alert = False
+        self._process_name = None
+        self._needs_restart = True
+
+        self.set_border_width(style.DEFAULT_SPACING)
+        self.set_spacing(style.DEFAULT_SPACING)
+        self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+        self._vbox = gtk.VBox()
+
+        self._setup_options()
+        self._setup_status_info()
+        self._setup_more_info()
+
+        self._vbox.show()
+        self.pack_start(self._vbox, expand=True)
+
+    def _setup_options(self):
+        self._setup_separator(_('Options'))
+
+        self._setup_option(_('Backup'), \
+            _('Save your Journal to the School Server'), \
+                self._pre_start_backup_cb)
+
+        self._setup_option(_('Restore'), \
+            _('Restore your Journal to the last backup'), \
+                self._pre_start_restore_cb)
+
+    def _setup_option(self, button_text, description_text, button_callback):
+        option_hbox = gtk.HBox()
+
+        button = gtk.Button(label=button_text)
+        button.connect('pressed', button_callback)
+        button.show()
+        option_hbox.pack_start(button, expand=True, fill=True)
+
+        label = gtk.Label(description_text)
+        label.show()
+        option_hbox.pack_start(label, expand=True, fill=True)
+
+        option_hbox.show()
+        self._vbox.pack_start(option_hbox, expand=False, padding=5)
+
+    def _setup_expander(self):
+        expander = gtk.Expander(_('Show'))
+        expander.connect("notify::expanded", self._log_menu_control)
+        expander.show()
+
+        self._vbox.pack_start(expander, expand=False)
+
+    def _setup_more_info(self):
+        self._setup_separator(_('Log'))
+        self._setup_expander()
+        self._setup_log()
+
+    def _setup_status_info(self):
+        self._setup_separator(_('Status'))
+
+        self._status_label = gtk.Label(_('No process is running'))
+        self._status_label.show()
+
+        self._vbox.pack_start(self._status_label, expand=False, fill=False)
+
+    def _setup_log(self):
+        self._log_vbox = gtk.VBox()
+
+        scrolled_window = gtk.ScrolledWindow()
+        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self._log_vbox.pack_start(scrolled_window, expand=True, padding= 10)
+
+        self._log_view = gtk.TextView()
+        self._log_view.set_editable(False)
+        self._log_buffer = self._log_view.get_buffer()
+        scrolled_window.add(self._log_view)
+
+        self._vbox.pack_start(self._log_vbox, expand=True)
+
+    def _setup_separator(self, label_text):
+        separator_vbox = gtk.VBox()
+
+        separator = gtk.HSeparator()
+        separator.show()
+        separator_vbox.pack_start(separator, expand=False)
+
+        label = gtk.Label(label_text)
+        label.set_alignment(0, 0)
+        label.show()
+        separator_vbox.pack_start(label, expand=False)
+
+        separator_vbox.show()
+        self._vbox.pack_start(separator_vbox, expand=False, padding=10)
+
+    def _log_menu_control(self, expander, data= None):
+        if expander.get_expanded():
+            self._log_vbox.show_all()
+        else:
+            self._log_vbox.hide()
+
+    def _pre_start_restore_cb(self, button, data= None):
+        self._show_alert(_('Restore Warning!'), \
+            _('You are about to delete all your journal, are you sure?'), \
+                self._response_restore_cb)
+
+    def _response_restore_cb(self, alert, response_id):
+        if response_id == gtk.RESPONSE_APPLY:
+            self._process_name = _('Restore')
+            self._needs_restart = True
+            self._model.do_restore()
+
+        self._destroy_alert(alert)
+
+    def _pre_start_backup_cb(self, button, data= None):
+        self._show_alert(_('Backup Warning!'), \
+            _('This process could take several minutes, are you sure?'), \
+                self._response_backup_cb)
+
+    def _response_backup_cb(self, alert, response_id):
+        if response_id == gtk.RESPONSE_APPLY:
+            self._process_name = _('Backup')
+            self._needs_restart = False
+            self._model.do_backup()
+
+        self._destroy_alert(alert)
+
+    def _set_update_log(self, model, data):
+        self._log_buffer.insert(self._log_buffer.get_end_iter(), data)
+        self._log_view.scroll_to_iter(self._log_buffer.get_end_iter(), 0)
+
+    def _set_status_started(self, model, data=None):
+        self._status_label.set_text(self._process_name + ' ' +\
+            _('is running, please wait...'))
+
+        self._hack_hide_toolbar_buttons()
+
+    def _set_status_finished(self, model, data=None):
+        self._status_label.set_text(self._process_name + ' ' + \
+            _('has finished, please check the logs.'))
+
+        if self._needs_restart == True:
+            self._show_alert(self._process_name + ' ' + \
+                _('Warning!'), \
+                    _('The process has finished, Sugar will restart now.'), \
+                        self._post_process_restart_cb, \
+                            True)
+        else:
+            self._hack_show_toolbar_buttons()
+
+    def _post_process_restart_cb(self, alert, responde_id):
+        session_manager = get_session_manager()
+        session_manager.logout()
+
+    def _set_status_failed(self, model, data=None):
+        self._status_label.set_text(self._process_name + ' ' +\
+            _('has failed.'))
+
+        self._hack_show_toolbar_buttons()
+
+    def _show_alert(self, title, msg, response_listener_cb, only_yes= False):
+        if self._showing_alert == False and \
+            self._model._running == False:
+                alert = Alert()
+                alert.props.title = title
+                alert.props.msg = msg
+
+                if only_yes == False:
+                    icon = Icon(icon_name='dialog-cancel')
+                    alert.add_button(gtk.RESPONSE_CANCEL, _('No'), icon)
+                    icon.show()
+
+                icon = Icon(icon_name='dialog-ok')
+                alert.add_button(gtk.RESPONSE_APPLY, _('Yes'), icon)
+                icon.show()
+
+                alert.connect('response', response_listener_cb)
+                alert.show()
+
+                self._vbox.pack_start(alert, False)
+                self._vbox.reorder_child(alert, 0)
+                self._showing_alert = True
+
+    def _destroy_alert(self, alert):
+        self._vbox.remove(alert)
+        self._showing_alert = False
+
+    def _hack_show_toolbar_buttons(self):
+        panel_control = self.get_toplevel()
+        panel_control._section_toolbar.cancel_button.show()
+        panel_control._section_toolbar.accept_button.show()
+
+    def _hack_hide_toolbar_buttons(self):
+        panel_control = self.get_toplevel()
+        panel_control._section_toolbar.cancel_button.hide()
+        panel_control._section_toolbar.accept_button.hide()
+
-- 
1.7.0.4