# -*- coding: utf-8 -*- """ $Id$ Copyright 2011 Lars Kruse <devel@sumpfralle.de> This file is part of PyCAM. PyCAM 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 3 of the License, or (at your option) any later version. PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>. """ import os import time import datetime import pycam.Plugins class ProgressBar(pycam.Plugins.PluginBase): UI_FILE = "progress_bar.ui" CATEGORIES = ["System"] def setup(self): if self.gui: box = self.gui.get_object("ProgressBox") box.unparent() self.core.register_ui("main_window", "Progress", box, 50) self.core.add_item("progress", lambda: ProgressGTK(self.core, self.gui, self.log)) show_progress_button = self.gui.get_object( "ShowToolpathProgressButton") # TODO: move this setting somewhere else or rename it self.core.add_item("show_drill_progress", show_progress_button.get_active, show_progress_button.set_active) return True def teardown(self): if self.gui: self.core.unregister_ui("main_window", self.gui.get_object("ProgressBox")) self.core.set("progress", None) class ProgressGTK(object): _PROGRESS_STACK = [] def __init__(self, core, gui, log): ProgressGTK._PROGRESS_STACK.append(self) import gtk self._finished = False self._gtk = gtk self._gui = gui self.log = log self.core = core self._cancel_requested = False self._start_time = 0 self._multi_maximum = 0 self._multi_counter = 0 self._multi_base_text = "" self._last_gtk_events_time = None self._main_widget = self._gui.get_object("ProgressWidget") self._multi_widget = self._gui.get_object("MultipleProgressBar") self._cancel_button = self._gui.get_object("ProgressCancelButton") self._cancel_button.connect("clicked", self.cancel) self._progress_bar = self._gui.get_object("ProgressBar") self._progress_button = self._gui.get_object( "ShowToolpathProgressButton") self._start_time = time.time() self._progress_button.show() self._last_text = None self._last_percent = None self.update(text="", percent=0) self._cancel_button.set_sensitive(True) self._progress_button.hide() # enable "pulse" mode for a start (in case of unknown ETA) self._progress_bar.pulse() self._main_widget.show() self._multi_widget.hide() self._multi_widget.set_text("") self._multi_widget.set_fraction(0) self.core.emit_event("gui-disable") def set_multiple(self, count, base_text=None): if base_text: self._multi_base_text = base_text else: self._multi_base_text = "" self._multi_counter = 0 if count > 1: self._multi_maximum = count self.update_multiple(increment=False) else: self._multi_maximum = 0 def update_multiple(self, increment=True): if self._multi_maximum <= 1: self._multi_widget.hide() return self._multi_widget.show() if increment: self._multi_counter += 1 self._progress_bar.set_fraction(0) if self._multi_base_text: text = "%s %d/%d" % (self._multi_base_text, self._multi_counter + 1, self._multi_maximum) else: text = "%d/%d" % (self._multi_counter + 1, self._multi_maximum) self._multi_widget.set_text(text) self._multi_widget.set_fraction(min(1.0, float(self._multi_counter) / self._multi_maximum)) def disable_cancel(self): self._cancel_button.set_sensitive(False) def cancel(self, widget=None): self._cancel_requested = True def finish(self): if self._finished: self.log.debug("Called progressbar 'finish' twice: %s" % self) return ProgressGTK._PROGRESS_STACK.remove(self) if ProgressGTK._PROGRESS_STACK: # restore the latest state of the previous progress current = ProgressGTK._PROGRESS_STACK[-1] current.update(text=current._last_text, percent=current._last_percent) current.update_multiple(increment=False) else: # hide the widget self._main_widget.hide() self._multi_widget.hide() widget = self._main_widget while widget: if hasattr(widget, "resize_children"): widget.resize_children() if hasattr(widget, "check_resize"): widget.check_resize() widget = widget.get_parent() self.core.emit_event("gui-enable") self._finished = True def __del__(self): if not self._finished: self.finish() def update(self, text=None, percent=None): if text: self._last_text = text if percent: self._last_percent = percent if not percent is None: percent = min(max(percent, 0.0), 100.0) self._progress_bar.set_fraction(percent/100.0) if (not percent) and (self._progress_bar.get_fraction() == 0): # use "pulse" mode until we reach 1% of the work to be done self._progress_bar.pulse() # update the GUI current_time = time.time() # Don't update the GUI more often than once per second. # Exception: text-only updates # This restriction improves performance and reduces the # "snappiness" of the GUI. if (self._last_gtk_events_time is None) \ or text \ or (self._last_gtk_events_time + 0.5 <= current_time): # "estimated time of arrival" text time_estimation_suffix = " remaining ..." if self._progress_bar.get_fraction() > 0: total_fraction = (self._progress_bar.get_fraction() + self._multi_counter) / max(1, self._multi_maximum) total_fraction = max(0.0, min(total_fraction, 1.0)) eta_full = (time.time() - self._start_time) / total_fraction if eta_full > 0: eta_delta = eta_full - (time.time() - self._start_time) eta_delta = int(round(eta_delta)) if hasattr(self, "_last_eta_delta"): previous_eta_delta = self._last_eta_delta if eta_delta == previous_eta_delta + 1: # We are currently toggling between two numbers. # We want to avoid screen flicker, thus we just live # with the slight inaccuracy. eta_delta = self._last_eta_delta self._last_eta_delta = eta_delta eta_delta_obj = datetime.timedelta(seconds=eta_delta) eta_text = "%s%s" % (eta_delta_obj, time_estimation_suffix) else: eta_text = None else: eta_text = None if not text is None: lines = [text] else: old_lines = self._progress_bar.get_text().split(os.linesep) # skip the time estimation line lines = [line for line in old_lines if not line.endswith(time_estimation_suffix)] if eta_text: lines.append(eta_text) self._progress_bar.set_text(os.linesep.join(lines)) # show the "show_tool_button" ("hide" is called in the progress decorator) # TODO: move "in_progress" somewhere else if self.core.get("toolpath_in_progress"): self._progress_button.show() while self._gtk.events_pending(): self._gtk.main_iteration() if not text or (self._start_time + 5 < current_time): # We don't store the timining if the text was changed. # This is especially nice for the snappines during font # initialization. This exception is only valid for the first # five seconds of the operation. self._last_gtk_events_time = current_time # return if the user requested a break return self._cancel_requested