diff --git a/src/ReportBase/_GraphvizReportDialog.py b/src/ReportBase/_GraphvizReportDialog.py
index 74c05e9b1..8f0fd8c75 100644
--- a/src/ReportBase/_GraphvizReportDialog.py
+++ b/src/ReportBase/_GraphvizReportDialog.py
@@ -28,8 +28,8 @@
import os
from cStringIO import StringIO
import tempfile
-import thread
import threading
+import time
from types import ClassType, InstanceType
from gettext import gettext as _
@@ -52,7 +52,6 @@ import BaseDoc
import Config
from ReportBase import CATEGORY_GRAPHVIZ
from _ReportDialog import ReportDialog
-from _FileEntry import FileEntry
from _PaperMenu import PaperFrame
from gen.plug.menu import NumberOption, TextOption, EnumeratedListOption
@@ -102,6 +101,43 @@ else:
_GS_CMD = "gs"
else:
_GS_CMD = ""
+
+#-------------------------------------------------------------------------------
+#
+# Private Functions
+#
+#-------------------------------------------------------------------------------
+def _run_long_process_in_thread(func, header):
+ """
+ This function will spawn a new thread to execute the provided function.
+ While the function is running, a progress bar will be created.
+ The progress bar will show activity while the function executes.
+
+ @param func: A function that will take an unknown amount of time to
+ complete.
+ @type category: callable
+ @param header: A header for the progress bar.
+ Example: "Updating Data"
+ @type name: string
+ @return: nothing
+
+ """
+ pbar = Utils.ProgressMeter(_('Processing File'))
+ pbar.set_pass(total=40,
+ mode=Utils.ProgressMeter.MODE_ACTIVITY,
+ header=header)
+
+ sys_thread = threading.Thread(target=func)
+ sys_thread.start()
+
+ while sys_thread.isAlive():
+ # The loop runs 20 times per second until the thread completes.
+ # With the progress pass total set at 40, it should move across the bar
+ # every two seconds.
+ time.sleep(0.05)
+ pbar.step()
+
+ pbar.close()
#-------------------------------------------------------------------------------
#
@@ -221,53 +257,6 @@ class GVDocBase(BaseDoc.BaseDoc, BaseDoc.GVDoc):
self.write( '}\n\n' )
- def animate_progress_bar(self):
- """
- The progress bar wont animate unless it is pulsed,
- so a timer is created to regularly call this method.
- """
- self.progress.pbar.pulse()
-
- # Schedule the next pulse
- self.progress_timer = threading.Timer(1.0, self.animate_progress_bar)
- self.progress_timer.start()
-
- def generate_output_file_from_dot_file(self, dotcommand, mimetype):
- """
- We now have the entire content of the .dot file. Last thing
- we need to do is to call dot (part of the Graphviz package)
- to generate the .png, .gif, ...etc... output file. Note that
- this can take a relatively long time for large or complicated
- graphs, so this method is called on a 2nd thread to prevent
- GRAMPS from appearing to have "hung".
- """
-
- self.progress = Utils.ProgressMeter(_('Processing File'), '')
- self.progress.set_pass(self._filename, 10)
- self.progress.pbar.set_pulse_step(0.1)
-
- # Start a timer to ensure the progress bar is animated
- self.animate_progress_bar()
-
- # Create a temporary dot file
- (handle, tmp_dot) = tempfile.mkstemp(".dot")
- dotfile = os.fdopen(handle, "w")
- dotfile.write(self._dot.getvalue())
- dotfile.close()
-
- # Use the temporary dot file to generate the final output file
- os.system(dotcommand % (self._filename, tmp_dot))
-
- # Delete the temporary dot file
- os.remove(tmp_dot)
-
- if mimetype and self.print_req:
- app = Mime.get_application(mimetype)
- Utils.launch(app[0], self._filename)
-
- self.progress_timer.cancel()
- self.progress.close()
-
def add_node(self, node_id, label, shape="", color="",
style="", fillcolor="", url="", htmloutput=False):
"""
@@ -372,7 +361,15 @@ class GVDotDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-4:] != ".dot":
self._filename += ".dot"
-
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
dotfile = open(self._filename, "w")
dotfile.write(self._dot.getvalue())
dotfile.close()
@@ -402,10 +399,30 @@ class GVPsDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-3:] != ".ps":
self._filename += ".ps"
-
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tps2 -o"%s" "%s"', "application/postscript"))
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the PS file.
+ os.system( 'dot -Tps2 -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("application/postscript")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -429,10 +446,30 @@ class GVSvgDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-4:] != ".svg":
self._filename += ".svg"
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tsvg -o"%s" "%s"', "image/svg"))
+ # Generate the SVG file.
+ os.system( 'dot -Tsvg -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("image/svg")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -456,11 +493,31 @@ class GVSvgzDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-5:] != ".svgz":
self._filename += ".svgz"
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the SVGZ file.
+ os.system( 'dot -Tsvgz -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("image/svgz")
+ Utils.launch(app[0], self._filename)
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tsvgz -o"%s" "%s"', "image/svgz"))
-
#-------------------------------------------------------------------------------
#
# GVPngDoc
@@ -479,14 +536,34 @@ class GVPngDoc(GVDocBase):
def close(self):
""" Implements GVDocBase.close() """
GVDocBase.close(self)
-
+
# Make sure the extension is correct
if self._filename[-4:] != ".png":
self._filename += ".png"
-
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tpng -o"%s" "%s"', "image/png"))
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the PNG file.
+ os.system( 'dot -Tpng -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("image/png")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -510,10 +587,30 @@ class GVJpegDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-4:] != ".jpg":
self._filename += ".jpg"
-
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tjpg -o"%s" "%s"', "image/jpeg"))
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the JPEG file.
+ os.system( 'dot -Tjpg -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("image/jpeg")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -537,10 +634,30 @@ class GVGifDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-4:] != ".gif":
self._filename += ".gif"
-
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tgif -o"%s" "%s"', "image/gif"))
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the GIF file.
+ os.system( 'dot -Tgif -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("image/gif")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -567,10 +684,30 @@ class GVPdfGvDoc(GVDocBase):
# Make sure the extension is correct
if self._filename[-4:] != ".pdf":
self._filename += ".pdf"
-
- thread.start_new_thread(
- self.generate_output_file_from_dot_file,
- ('dot -Tpdf -o"%s" "%s"', "application/pdf"))
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Generate the PDF file.
+ os.system( 'dot -Tpdf -o"%s" "%s"' % (self._filename, tmp_dot) )
+
+ # Delete the temporary dot file
+ os.remove(tmp_dot)
+
+ if self.print_req:
+ app = Mime.get_application("application/pdf")
+ Utils.launch(app[0], self._filename)
#-------------------------------------------------------------------------------
#
@@ -584,43 +721,55 @@ class GVPdfGsDoc(GVDocBase):
# GV documentation says dpi is only for image formats.
options.menu.get_option_by_name('dpi').set_value(72)
GVDocBase.__init__(self, options, paper_style)
-
+
def close(self):
""" Implements GVDocBase.close() """
GVDocBase.close(self)
- # First step is to create a temporary .ps file
- original_name = self._filename
- if self._filename[-3:] != ".ps":
- self._filename += ".ps"
-
- self.generate_output_file_from_dot_file(
- 'dot -Tps -o"%s" "%s"', None)
-
- tmp_ps = self._filename
-
# Make sure the extension is correct
- self._filename = original_name
if self._filename[-4:] != ".pdf":
self._filename += ".pdf"
-
+
+ _run_long_process_in_thread(self.__generate, self._filename)
+
+ def __generate(self):
+ """
+ This function will generate the actual file.
+ It is nice to run this function in the background so that the
+ application does not appear to hang.
+ """
+ # Create a temporary dot file
+ (handle, tmp_dot) = tempfile.mkstemp(".dot" )
+ dotfile = os.fdopen(handle,"w")
+ dotfile.write(self._dot.getvalue())
+ dotfile.close()
+
+ # Create a temporary PostScript file
+ (handle, tmp_ps) = tempfile.mkstemp(".ps" )
+ os.close( handle )
+
+ # Generate PostScript using dot
+ command = 'dot -Tps -o"%s" "%s"' % ( tmp_ps, tmp_dot )
+ os.system(command)
+
# Add .5 to remove rounding errors.
paper_size = self._paper.get_size()
width_pt = int( (paper_size.get_width_inches() * 72) + 0.5 )
height_pt = int( (paper_size.get_height_inches() * 72) + 0.5 )
-
+
# Convert to PDF using ghostscript
command = '%s -q -sDEVICE=pdfwrite -dNOPAUSE -dDEVICEWIDTHPOINTS=%d' \
' -dDEVICEHEIGHTPOINTS=%d -sOutputFile="%s" "%s" -c quit' \
% ( _GS_CMD, width_pt, height_pt, self._filename, tmp_ps )
os.system(command)
-
+
os.remove(tmp_ps)
-
+ os.remove(tmp_dot)
+
if self.print_req:
app = Mime.get_application("application/pdf")
Utils.launch(app[0], self._filename)
-
+
#-------------------------------------------------------------------------------
#
# Various Graphviz formats.
diff --git a/src/Utils.py b/src/Utils.py
index aa36aa653..0cf721a1a 100644
--- a/src/Utils.py
+++ b/src/Utils.py
@@ -1010,81 +1010,118 @@ def media_path_full(db, filename):
class ProgressMeter:
"""
Progress meter class for GRAMPS.
+
+ The progress meter has two modes:
+
+ MODE_FRACTION is used when you know the number of steps that will be taken.
+ Set the total number of steps, and then call step() that many times.
+ The progress bar will progress from left to right.
+
+ MODE_ACTIVITY is used when you don't know the number of steps that will be
+ taken. Set up the total number of steps for the bar to get from one end of
+ the bar to the other. Then, call step() as many times as you want. The bar
+ will move from left to right until you stop calling step.
"""
+
+ MODE_FRACTION = 0
+ MODE_ACTIVITY = 1
+
def __init__(self, title, header=''):
"""
Specify the title and the current pass header.
"""
- self.old_val = -1
- self.ptop = gtk.Dialog()
- self.ptop.connect('delete_event', self.warn)
- self.ptop.set_has_separator(False)
- self.ptop.set_title(title)
- self.ptop.set_border_width(12)
- self.ptop.vbox.set_spacing(10)
- lbl = gtk.Label('%s' % title)
- lbl.set_use_markup(True)
- self.lbl = gtk.Label(header)
- self.lbl.set_use_markup(True)
- self.ptop.vbox.add(lbl)
- self.ptop.vbox.add(self.lbl)
- self.ptop.vbox.set_border_width(24)
- self.pbar = gtk.ProgressBar()
-
- self.ptop.set_size_request(350, 125)
- self.ptop.vbox.add(self.pbar)
- self.ptop.show_all()
+ self.__mode = ProgressMeter.MODE_FRACTION
+ self.__pbar_max = 100.0
+ self.__pbar_index = 0.0
+ self.__old_val = -1
+
+ self.__dialog = gtk.Dialog()
+ self.__dialog.connect('delete_event', self.__warn)
+ self.__dialog.set_has_separator(False)
+ self.__dialog.set_title(title)
+ self.__dialog.set_border_width(12)
+ self.__dialog.vbox.set_spacing(10)
+ self.__dialog.vbox.set_border_width(24)
+ self.__dialog.set_size_request(350, 125)
+
+ tlbl = gtk.Label('%s' % title)
+ tlbl.set_use_markup(True)
+ self.__dialog.vbox.add(tlbl)
+
+ self.__lbl = gtk.Label(header)
+ self.__lbl.set_use_markup(True)
+ self.__dialog.vbox.add(self.__lbl)
+
+ self.__pbar = gtk.ProgressBar()
+ self.__dialog.vbox.add(self.__pbar)
+
+ self.__dialog.show_all()
if header == '':
- self.lbl.hide()
+ self.__lbl.hide()
- def set_pass(self, header, total):
+ def set_pass(self, header="", total=100, mode=MODE_FRACTION):
"""
Reset for another pass. Provide a new header and define number
of steps to be used.
"""
+ self.__mode = mode
+ self.__pbar_max = total
+ self.__pbar_index = 0.0
+
+ self.__lbl.set_text(header)
if header == '':
- self.lbl.hide()
+ self.__lbl.hide()
else:
- self.lbl.show()
- self.pbar_max = total
- self.pbar_index = 0.0
- self.lbl.set_text(header)
- self.pbar.set_fraction(0.0)
+ self.__lbl.show()
+
+ if self.__mode is ProgressMeter.MODE_FRACTION:
+ self.__pbar.set_fraction(0.0)
+ else: # ProgressMeter.MODE_ACTIVITY
+ self.__pbar.set_pulse_step(1.0/self.__pbar_max)
+
while gtk.events_pending():
gtk.main_iteration()
def step(self):
"""Click the progress bar over to the next value. Be paranoid
and insure that it doesn't go over 100%."""
- self.pbar_index = self.pbar_index + 1.0
- if self.pbar_index > self.pbar_max:
- self.pbar_index = self.pbar_max
-
- try:
- val = int(100*self.pbar_index/self.pbar_max)
- except ZeroDivisionError:
- val = 0
-
- if val != self.old_val:
- self.pbar.set_text("%d%%" % val)
- self.pbar.set_fraction(val/100.0)
- self.old_val = val
+ if self.__mode is ProgressMeter.MODE_FRACTION:
+ self.__pbar_index = self.__pbar_index + 1.0
+
+ if self.__pbar_index > self.__pbar_max:
+ self.__pbar_index = self.__pbar_max
+
+ try:
+ val = int(100*self.__pbar_index/self.__pbar_max)
+ except ZeroDivisionError:
+ val = 0
+
+ if val != self.__old_val:
+ self.__pbar.set_text("%d%%" % val)
+ self.__pbar.set_fraction(val/100.0)
+ self.__old_val = val
+ else: # ProgressMeter.MODE_ACTIVITY
+ self.__pbar.pulse()
+
while gtk.events_pending():
gtk.main_iteration()
- def warn(self, *obj):
+ def __warn(self, *obj):
+ """
+ Don't let the user close the progress dialog.
+ """
WarningDialog(
_("Attempt to force closing the dialog"),
_("Please do not force closing this important dialog."),
- self.ptop)
+ self.__dialog)
return True
def close(self):
"""
Close the progress meter
"""
- self.ptop.destroy()
+ self.__dialog.destroy()
def launch(prog_str, path):