0002418: Gramps hangs when producing graphs on Windows
svn: r11097
This commit is contained in:
parent
b0f5322346
commit
5c3bffeb21
@ -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
|
||||
|
||||
@ -103,6 +102,43 @@ else:
|
||||
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()
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# GVDocBase
|
||||
@ -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):
|
||||
"""
|
||||
@ -373,6 +362,14 @@ class GVDotDoc(GVDocBase):
|
||||
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()
|
||||
@ -403,9 +400,29 @@ class GVPsDoc(GVDocBase):
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -430,9 +447,29 @@ class GVSvgDoc(GVDocBase):
|
||||
if self._filename[-4:] != ".svg":
|
||||
self._filename += ".svg"
|
||||
|
||||
thread.start_new_thread(
|
||||
self.generate_output_file_from_dot_file,
|
||||
('dot -Tsvg -o"%s" "%s"', "image/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()
|
||||
|
||||
# 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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -457,9 +494,29 @@ class GVSvgzDoc(GVDocBase):
|
||||
if self._filename[-5:] != ".svgz":
|
||||
self._filename += ".svgz"
|
||||
|
||||
thread.start_new_thread(
|
||||
self.generate_output_file_from_dot_file,
|
||||
('dot -Tsvgz -o"%s" "%s"', "image/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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -484,9 +541,29 @@ class GVPngDoc(GVDocBase):
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -511,9 +588,29 @@ class GVJpegDoc(GVDocBase):
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -538,9 +635,29 @@ class GVGifDoc(GVDocBase):
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -568,9 +685,29 @@ class GVPdfGvDoc(GVDocBase):
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
@ -589,21 +726,32 @@ class GVPdfGsDoc(GVDocBase):
|
||||
""" 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 )
|
||||
@ -616,6 +764,7 @@ class GVPdfGsDoc(GVDocBase):
|
||||
os.system(command)
|
||||
|
||||
os.remove(tmp_ps)
|
||||
os.remove(tmp_dot)
|
||||
|
||||
if self.print_req:
|
||||
app = Mime.get_application("application/pdf")
|
||||
|
111
src/Utils.py
111
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('<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.__mode = ProgressMeter.MODE_FRACTION
|
||||
self.__pbar_max = 100.0
|
||||
self.__pbar_index = 0.0
|
||||
self.__old_val = -1
|
||||
|
||||
self.ptop.set_size_request(350, 125)
|
||||
self.ptop.vbox.add(self.pbar)
|
||||
self.ptop.show_all()
|
||||
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
|
||||
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)
|
||||
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 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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user