diff --git a/src/GrampsDb/_ProgressMonitor.py b/src/GrampsDb/_ProgressMonitor.py new file mode 100644 index 000000000..ce72ab4dd --- /dev/null +++ b/src/GrampsDb/_ProgressMonitor.py @@ -0,0 +1,159 @@ +""" +This module provides a progess dialog for displaying the status of +long running operations. +""" + +class _StatusObjectFacade(object): + """This provides a simple structure for recording the information + needs about a status object.""" + + def __init__(self, status_obj, heartbeat_cb_id=None, end_cb_id=None): + """ + @param status_obj: + @type status_obj: L{GrampsDb.LongOpStatus} + + @param heartbeat_cb_id: (default: None) + @type heartbeat_cb_id: int + + @param end_cb_id: (default: None) + @type end_cb_id: int + """ + self.status_obj = status_obj + self.heartbeat_cb_id = heartbeat_cb_id + self.end_cb_id = end_cb_id + self.pbar_idx = None + self.active = False + +class ProgressMonitor(object): + """A dialog for displaying the status of long running operations. + + It will work with L{GrampsDb.LongOpStatus} objects to track the + progress of long running operations. If the operations is going to + take longer than I{popup_time} it will pop up a dialog with a + progress bar so that the user gets some feedback about what is + happening. + """ + + __default_popup_time = 5 # seconds + + def __init__(self, dialog_class, popup_time = None): + """ + @param dialog_class: A class used to display the progress dialog. + @type dialog_class: L{_GtkProgressDialog} or the same interface. + + @param popup_time: number of seconds to wait before popup. + @type popup_time: int + """ + self._dialog_class = dialog_class + self._popup_time = popup_time + + if self._popup_time == None: + self._popup_time = self.__class__.__default_popup_time + + self._status_stack = [] # list of current status objects + self._dlg = None + + def _get_dlg(self): + if self._dlg == None: + self._dlg = self._dialog_class("Long running operation.") + self._dlg.show() + + return self._dlg + + def add_op(self, op_status): + """Add a new status object to the progress dialog. + + @param op_status: the status object. + @type op_status: L{GrampsDb.LongOpStatus} + """ + facade = _StatusObjectFacade(op_status) + self._status_stack.append(facade) + idx = len(self._status_stack)-1 + + # wrap up the op_status object idx into the callback calls + def heartbeat_cb(): + self._heartbeat(idx) + def end_cb(): + self._end(idx) + + facade.heartbeat_cb_id = op_status.connect('op-heartbeat', + heartbeat_cb) + facade.end_cb_id = op_status.connect('op-end', end_cb) + + def _heartbeat(self, idx): + # check the estimated time to complete to see if we need + # to pop up a progress dialog. + + facade = self._status_stack[idx] + + if facade.status_obj.estimated_secs_to_complete() > self._popup_time: + facade.active = True + + if facade.active: + dlg = self._get_dlg() + + if facade.pbar_idx == None: + facade.pbar_idx = dlg.add(facade.status_obj) + + dlg.step(facade.pbar_idx) + + def _end(self, idx): + # hide any progress dialog + # remove the status object from the stack + facade = self._status_stack[idx] + if facade.active: + dlg = self._get_dlg() + + if len(self._status_stack) == 1: + dlg.hide() + + dlg.remove(facade.pbar_idx) + + facade.status_obj.disconnect(facade.heartbeat_cb_id) + facade.status_obj.disconnect(facade.end_cb_id) + del self._status_stack[idx] + + +if __name__ == '__main__': + import time + from GrampsDb import LongOpStatus + + def test(a,b): + d = ProgressDialog(_GtkProgressDialog) + + s = LongOpStatus("Doing very long operation", 100, 10) + + d.add_op(s) + + for i in xrange(0, 99): + time.sleep(0.1) + if i == 30: + t = LongOpStatus("doing a shorter one", 100, 10, + can_cancel=True) + d.add_op(t) + for j in xrange(0, 99): + if t.should_cancel(): + break + time.sleep(0.1) + t.heartbeat() + t.end() + if i == 60: + t = LongOpStatus("doing another shorter one", 100, 10) + d.add_op(t) + for j in xrange(0, 99): + time.sleep(0.1) + t.heartbeat() + t.end() + s.heartbeat() + s.end() + + w = gtk.Window(gtk.WINDOW_TOPLEVEL) + w.connect('destroy', gtk.main_quit) + button = gtk.Button("Test") + button.connect("clicked", test, None) + w.add(button) + button.show() + w.show() + gtk.main() + print 'done' + \ No newline at end of file diff --git a/src/GrampsDb/__init__.py b/src/GrampsDb/__init__.py index f64c3ec30..8d7577fc2 100644 --- a/src/GrampsDb/__init__.py +++ b/src/GrampsDb/__init__.py @@ -52,4 +52,6 @@ from _DbUtils import * import _GrampsDbConst as GrampsDbConst from _LongOpStatus import LongOpStatus +from _ProgressMonitor import ProgressMonitor + diff --git a/src/ProgressDialog.py b/src/ProgressDialog.py index 0c450c0b9..9d797df60 100644 --- a/src/ProgressDialog.py +++ b/src/ProgressDialog.py @@ -4,36 +4,41 @@ long running operations. """ import gtk - class _GtkProgressBar(gtk.VBox): - """This is just a structure to hold the visual elements of a - progress indicator.""" + """This widget displays the progress bar and labels for a progress + indicator. It provides an interface to updating the progress bar. + """ def __init__(self, long_op_status): + """@param long_op_status: the status of the operation. + @type long_op_status: L{GrampsDb.LongOpStatus} + """ gtk.VBox.__init__(self) msg = long_op_status.get_msg() self._old_val = -1 self._lbl = gtk.Label(msg) self._lbl.set_use_markup(True) - self.set_border_width(24) + #self.set_border_width(24) self._pbar = gtk.ProgressBar() self._hbox = gtk.HBox() + # Only display the cancel button is the operation + # can be canceled. if long_op_status.can_cancel(): self._cancel = gtk.Button(stock=gtk.STOCK_CANCEL) self._cancel.connect("clicked", lambda x: long_op_status.cancel()) + self._cancel.show() self._hbox.pack_end(self._cancel) self._hbox.pack_start(self._pbar) self.pack_start(self._lbl, expand=False, fill=False) self.pack_start(self._hbox, expand=False, fill=False) - if msg == '': - self._lbl.hide() + self._pbar_max = (long_op_status.get_total_steps()/ long_op_status.get_interval()) @@ -41,11 +46,15 @@ class _GtkProgressBar(gtk.VBox): self._pbar.set_fraction((float(long_op_status.get_total_steps())/ (float(long_op_status.get_interval())))/ 100.0) - #self._lbl.show() - #self._pbar.show() - self.show_all() + + if msg != '': + self._lbl.show() + self._pbar.show() + self._hbox.show() def step(self): + """Move the progress bar on a step. + """ self._pbar_index = self._pbar_index + 1.0 if self._pbar_index > self._pbar_max: @@ -66,15 +75,18 @@ class _GtkProgressDialog(gtk.Dialog): process.""" def __init__(self, title): + """@param title: The title to display on the top of the window. + @type title: string + """ gtk.Dialog.__init__(self) - self.connect('delete_event', self.warn) + self.connect('delete_event', self._warn) self.set_has_separator(False) self.set_title(title) - self.set_border_width(12) - self.vbox.set_spacing(10) - lbl = gtk.Label('%s' % title) - lbl.set_use_markup(True) - self.vbox.pack_start(lbl) + #self.set_border_width(12) + #self.vbox.set_spacing(10) + #lbl = gtk.Label('%s' % title) + #lbl.set_use_markup(True) + #self.vbox.pack_start(lbl) #self.set_size_request(350,125) self.set_resize_mode(gtk.RESIZE_IMMEDIATE) self.show_all() @@ -82,140 +94,75 @@ class _GtkProgressDialog(gtk.Dialog): self._progress_bars = [] def add(self,long_op_status): - # Create a new progress bar + """Add a new status object to the progress dialog. + + @param long_op_status: the status object. + @type long_op_status: L{GrampsDb.LongOpStatus} + @return: a key that can be used as the L{pbar_idx} + to the other methods. + @rtype: int + """ pbar = _GtkProgressBar(long_op_status) self.vbox.pack_start(pbar, expand=False, fill=False) + pbar.show() + self.resize_children() - self.process_events() + self._process_events() self._progress_bars.append(pbar) return len(self._progress_bars)-1 def remove(self, pbar_idx): + """Remove the specified status object from the progress dialog. + + @param pbar_idx: the index as returned from L{add} + @type pbar_idx: int + """ pbar = self._progress_bars[pbar_idx] self.vbox.remove(pbar) del self._progress_bars[pbar_idx] def step(self, pbar_idx): """Click the progress bar over to the next value. Be paranoid - and insure that it doesn't go over 100%.""" + and insure that it doesn't go over 100%. + + @param pbar_idx: the index as returned from L{add} + @type pbar_idx: int + """ self._progress_bars[pbar_idx].step() - self.process_events() + self._process_events() - def process_events(self): + def _process_events(self): while gtk.events_pending(): gtk.main_iteration() def show(self): + """Show the dialog and process any events. + """ gtk.Dialog.show(self) - self.process_events() + self._process_events() def hide(self): + """Hide the dialog and process any events. + """ gtk.Dialog.hide(self) - self.process_events() + self._process_events() - def warn(self,x,y): + def _warn(self,x,y): return True def close(self): self.destroy() - -class _StatusObjectFacade(object): - """This provides a simple structure for recording the information - needs about a status object.""" - - def __init__(self, status_obj, heartbeat_cb_id=None, end_cb_id=None): - self.status_obj = status_obj - self.heartbeat_cb_id = heartbeat_cb_id - self.end_cb_id = end_cb_id - self.pbar_idx = None - self.active = False - -class ProgressDialog(object): - """A dialog for displaying the status of long running operations. - - It will work with L{GrampsDb.LongOpStatus} objects to track the - progress of long running operations. If the operations is going to - take longer than I{popup_time} it will pop up a dialog with a - progress bar so that the user gets some feedback about what is - happening. - """ - - __default_popup_time = 5 # seconds - - def __init__(self, popup_time = None): - self._popup_time = popup_time - if self._popup_time == None: - self._popup_time = self.__class__.__default_popup_time - - self._status_stack = [] # list of current status objects - self._dlg = None - - def _get_dlg(self): - if self._dlg == None: - self._dlg = _GtkProgressDialog("Long running operation.") - self._dlg.show() - - return self._dlg - - def add_op(self, op_status): - facade = _StatusObjectFacade(op_status) - self._status_stack.append(facade) - idx = len(self._status_stack)-1 - - # wrap up the op_status object idx into the callback calls - def heartbeat_cb(): - self._heartbeat(idx) - def end_cb(): - self._end(idx) - - facade.heartbeat_cb_id = op_status.connect('op-heartbeat', - heartbeat_cb) - facade.end_cb_id = op_status.connect('op-end', end_cb) - - def _heartbeat(self, idx): - # check the estimated time to complete to see if we need - # to pop up a progress dialog. - - facade = self._status_stack[idx] - - if facade.status_obj.estimated_secs_to_complete() > self._popup_time: - facade.active = True - - if facade.active: - dlg = self._get_dlg() - - if facade.pbar_idx == None: - facade.pbar_idx = dlg.add(facade.status_obj) - - dlg.step(facade.pbar_idx) - - def _end(self, idx): - # hide any progress dialog - # remove the status object from the stack - facade = self._status_stack[idx] - if facade.active: - dlg = self._get_dlg() - - if len(self._status_stack) == 1: - dlg.hide() - - dlg.remove(facade.pbar_idx) - - facade.status_obj.disconnect(facade.heartbeat_cb_id) - facade.status_obj.disconnect(facade.end_cb_id) - del self._status_stack[idx] - if __name__ == '__main__': import time - from GrampsDb import LongOpStatus + from GrampsDb import LongOpStatus, ProgressMonitor def test(a,b): - d = ProgressDialog() + d = ProgressMonitor(_GtkProgressDialog) s = LongOpStatus("Doing very long operation", 100, 10)