0002418: Gramps hangs when producing graphs on Windows

svn: r11097
This commit is contained in:
Brian Matherly 2008-09-30 01:40:16 +00:00
parent b0f5322346
commit 5c3bffeb21
2 changed files with 324 additions and 138 deletions

View File

@ -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.

View File

@ -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('<span size="larger" weight="bold">%s</span>' % 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('<span size="larger" weight="bold">%s</span>' % 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):