diff --git a/src/BasicUtils/NameDisplay.py b/src/BasicUtils/NameDisplay.py index ee65eb7e2..eed406706 100644 --- a/src/BasicUtils/NameDisplay.py +++ b/src/BasicUtils/NameDisplay.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2004-2006 Donald N. Allingham +# Copyright (C) 2004-2007 Donald N. Allingham # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -38,6 +38,7 @@ import re # #------------------------------------------------------------------------- from RelLib import Name +from Errors import NameDisplayError try: import Config @@ -243,19 +244,19 @@ class NameDisplay: def _gen_raw_func(self, format_str): - """The job of building the name from a format string is rather - expensive and it is called lots and lots of times. So it is worth - going to some length to optimise it as much as possible. + """The job of building the name from a format string is rather + expensive and it is called lots and lots of times. So it is worth + going to some length to optimise it as much as possible. - This method constructs a new function that is specifically written - to format a name given a particualar format string. This is worthwhile - because the format string itself rarely changes, so by caching the new - function and calling it directly when asked to format a name to the - same format string again we can be as quick as possible. + This method constructs a new function that is specifically written + to format a name given a particualar format string. This is worthwhile + because the format string itself rarely changes, so by caching the new + function and calling it directly when asked to format a name to the + same format string again we can be as quick as possible. - The new function is of the form: + The new function is of the form: - def fn(raw_data): + def fn(raw_data): return "%s %s %s %s %s" % (raw_data[_TITLE], raw_data[_FIRSTNAME], raw_data[_PREFIX], @@ -264,15 +265,15 @@ class NameDisplay: """ - # we need the names of each of the variables or methods that are - # called to fill in each format flag. + # we need the names of each of the variables or methods that are + # called to fill in each format flag. d = {"%t":"raw_data[_TITLE]", "%f":"raw_data[_FIRSTNAME]", "%p":"raw_data[_PREFIX]", "%l":"raw_data[_SURNAME]", "%s":"raw_data[_SUFFIX]", "%y":"raw_data[_PATRONYM]", - "%c":"raw_data[_CALL]", + "%c":"raw_data[_CALL]", "%T":"raw_data[_TITLE].upper()", "%F":"raw_data[_FIRSTNAME].upper()", "%P":"raw_data[_PREFIX].upper()", @@ -284,8 +285,8 @@ class NameDisplay: new_fmt = format_str - # replace the specific format string flags with a - # flag that works in standard python format strings. + # replace the specific format string flags with a + # flag that works in standard python format strings. new_fmt = new_fmt.replace("%t","%s") new_fmt = new_fmt.replace("%f","%s") new_fmt = new_fmt.replace("%p","%s") @@ -303,10 +304,10 @@ class NameDisplay: new_fmt = new_fmt.replace("%C","%s") new_fmt = new_fmt.replace("%%",'%') - # find each format flag in the original format string - # for each one we find the variable name that is needed to - # replace it and add this to a list. This list will be used - # generate the replacement tuple. + # find each format flag in the original format string + # for each one we find the variable name that is needed to + # replace it and add this to a list. This list will be used + # generate the replacement tuple. pat = re.compile("%.") param = () @@ -317,37 +318,37 @@ class NameDisplay: s = 'def fn(raw_data):\n'\ ' return "%s" %% (%s)' % (new_fmt,",".join(param)) - exec(s) + exec(s) return fn def _gen_cooked_func(self, format_str): - """The job of building the name from a format string is rather - expensive and it is called lots and lots of times. So it is worth - going to some length to optimise it as much as possible. + """The job of building the name from a format string is rather + expensive and it is called lots and lots of times. So it is worth + going to some length to optimise it as much as possible. - This method constructs a new function that is specifically written - to format a name given a particualar format string. This is worthwhile - because the format string itself rarely changes, so by caching the new - function and calling it directly when asked to format a name to the - same format string again we can be as quick as possible. + This method constructs a new function that is specifically written + to format a name given a particualar format string. This is worthwhile + because the format string itself rarely changes, so by caching the new + function and calling it directly when asked to format a name to the + same format string again we can be as quick as possible. - The new function is of the form: + The new function is of the form: - def fn(first,surname,prefix,suffix,patronymic,title,call,): + def fn(first,surname,prefix,suffix,patronymic,title,call,): return "%s %s %s %s %s" % (first,surname,prefix,suffix,patronymic) """ - # we need the names of each of the variables or methods that are - # called to fill in each format flag. + # we need the names of each of the variables or methods that are + # called to fill in each format flag. d = {"%t":"title", "%f":"first", "%p":"prefix", "%l":"surname", "%s":"suffix", "%y":"patronymic", - "%c":"call", + "%c":"call", "%T":"title.upper()", "%F":"first.upper()", "%P":"prefix.upper()", @@ -360,8 +361,8 @@ class NameDisplay: new_fmt = format_str - # replace the specific format string flags with a - # flag that works in standard python format strings. + # replace the specific format string flags with a + # flag that works in standard python format strings. new_fmt = new_fmt.replace("%t","%s") new_fmt = new_fmt.replace("%f","%s") new_fmt = new_fmt.replace("%p","%s") @@ -379,11 +380,11 @@ class NameDisplay: new_fmt = new_fmt.replace("%C","%s") new_fmt = new_fmt.replace("%%",'%') - # find each format flag in the original format string - # for each one we find the variable name that is needed to - # replace it and add this to a list. This list will be used - # generate the replacement tuple. - pat = re.compile("%.") + # find each format flag in the original format string + # for each one we find the variable name that is needed to + # replace it and add this to a list. This list will be used + # generate the replacement tuple. + pat = re.compile('|'.join(d.keys())) param = () mat = pat.search(format_str) @@ -393,7 +394,7 @@ class NameDisplay: s = 'def fn(first,surname,prefix,suffix,patronymic,title,call,):\n'\ ' return "%s" %% (%s)' % (new_fmt,",".join(param)) - exec(s) + exec(s) return fn @@ -403,19 +404,19 @@ class NameDisplay: name.call,format_str) def format_str_raw(self,raw_data,format_str): - """ - Format a name from the raw name list. To make this as fast as possible - this uses _gen_raw_func to generate a new method for each new format_string. - - Is does not call _format_str_base because it would introduce an extra - method call and we need all the speed we can squeeze out of this. - """ + """ + Format a name from the raw name list. To make this as fast as possible + this uses _gen_raw_func to generate a new method for each new format_string. + + Is does not call _format_str_base because it would introduce an extra + method call and we need all the speed we can squeeze out of this. + """ func = self.__class__.raw_format_funcs.get(format_str) if func == None: func = self._gen_raw_func(format_str) self.__class__.raw_format_funcs[format_str] = func - s = func(raw_data) + s = func(raw_data) return ' '.join(s.split()) @@ -443,10 +444,14 @@ class NameDisplay: func = self._gen_cooked_func(format_str) self.__class__.format_funcs[format_str] = func - s = func(first,surname,prefix,suffix,patronymic,title,call) + try: + s = func(first,surname,prefix,suffix,patronymic,title,call) + except (ValueError,TypeError,): + raise NameDisplayError, "Incomplete format string" + return ' '.join(s.split()) - #------------------------------------------------------------------------- + #------------------------------------------------------------------------- def sort_string(self,name): return u"%-25s%-30s%s" % (name.surname,name.first_name,name.suffix) diff --git a/src/Editors/_EditFamily.py b/src/Editors/_EditFamily.py index 083e76651..56184851a 100644 --- a/src/Editors/_EditFamily.py +++ b/src/Editors/_EditFamily.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2000-2007 Donald N. Allingham # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -383,8 +383,6 @@ class EditFamily(EditPrimary): EditPrimary.__init__(self, dbstate, uistate, track, family, dbstate.db.get_family_from_handle) - self.in_save = False - # look for the scenerio of a child and no parents on a new # family @@ -433,7 +431,7 @@ class EditFamily(EditPrimary): def check_for_family_change(self, handles): # check to see if the handle matches the current object - if not self.in_save and self.obj.get_handle() in handles: + if self.obj.get_handle() in handles: self.obj = self.dbstate.db.get_family_from_handle(self.obj.get_handle()) self.reload_people() @@ -802,7 +800,6 @@ class EditFamily(EditPrimary): def __do_save(self): self.ok_button.set_sensitive(False) - self.in_save = True if not self.added: original = self.db.get_family_from_handle(self.obj.handle) @@ -834,7 +831,21 @@ class EditFamily(EditPrimary): self.ok_button.set_sensitive(True) return + if not original and self.object_is_empty(): + QuestionDialog.ErrorDialog( + _("Cannot save family"), + _("No data exists for this family. " + "Please enter data or cancel the edit.")) + self.ok_button.set_sensitive(True) + return + # We disconnect the callbacks to all signals we connected earlier. + # This prevents the signals originating in any of the following + # commits from being caught by us again. + for key in self.signal_keys: + self.db.disconnect(key) + self.signal_keys = [] + if not original and not self.object_is_empty(): trans = self.db.transaction_begin() @@ -861,12 +872,6 @@ class EditFamily(EditPrimary): self.db.add_family(self.obj,trans) self.db.transaction_commit(trans,_("Add Family")) - elif not original and self.object_is_empty(): - QuestionDialog.ErrorDialog(_("Cannot save family"), - _("No data exists for this family. Please " - "enter data or cancel the edit.")) - self.ok_button.set_sensitive(True) - return elif original and self.object_is_empty(): trans = self.db.transaction_begin() self.db.remove_family(self.obj.handle,trans) @@ -901,7 +906,6 @@ class EditFamily(EditPrimary): self.db.commit_family(self.obj,trans) self.db.transaction_commit(trans,_("Edit Family")) - self.in_save = False self.close() def _cleanup_on_exit(self): diff --git a/src/Errors.py b/src/Errors.py index 5b9d5a4b6..139548138 100644 --- a/src/Errors.py +++ b/src/Errors.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2003-2006 Donald N. Allingham +# Copyright (C) 2003-2007 Donald N. Allingham # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -151,3 +151,14 @@ class DbError(Exception): def __str__(self): "Return string representation" return self.value + +class NameDisplayError(Exception): + """ + Error used to report that the name display format string is invalid. + """ + def __init__(self,value): + Exception.__init__(self) + self.value = value + + def __str__(self): + return self.value diff --git a/src/GrampsCfg.py b/src/GrampsCfg.py index f6664a560..3e8ceb2b4 100644 --- a/src/GrampsCfg.py +++ b/src/GrampsCfg.py @@ -26,6 +26,7 @@ # #------------------------------------------------------------------------- from gettext import gettext as _ +from xml.sax.saxutils import escape #------------------------------------------------------------------------- # @@ -46,6 +47,7 @@ from RelLib import Name import ManagedWindow from GrampsWidgets import * import QuestionDialog +from Errors import NameDisplayError #------------------------------------------------------------------------- # @@ -379,12 +381,12 @@ class GrampsPreferences(ManagedWindow.ManagedWindow): Name format editor Edit button callback """ num,name,fmt = self.selected_fmt[COL_NUM:COL_EXPL] - dlg = NameFormatEditDlg(name,fmt,self.examplename) + dlg = NameFormatEditDlg(name, fmt, self.examplename) dlg.dlg.set_transient_for(self.window) (res,name,fmt) = dlg.run() - if name != self.selected_fmt[COL_NAME] or \ - fmt != self.selected_fmt[COL_FMT]: + if res == gtk.RESPONSE_OK and (name != self.selected_fmt[COL_NAME] or + fmt != self.selected_fmt[COL_FMT]): exmpl = _nd.format_str(self.examplename,fmt) self.fmt_model.set(self.iter,COL_NAME,name, COL_FMT,fmt, @@ -586,12 +588,13 @@ class NameFormatEditDlg: """ """ - def __init__(self,fmt_name,fmt_str,name): + def __init__(self, fmt_name, fmt_str, name): self.fmt_name = fmt_name self.fmt_str = fmt_str self.name = name + self.valid = True - self.top = gtk.glade.XML(const.gladeFile,'namefmt_edit','gramps') + self.top = gtk.glade.XML(const.gladeFile, 'namefmt_edit','gramps') self.dlg = self.top.get_widget('namefmt_edit') ManagedWindow.set_titles(self.dlg, None, _('Name Format Editor')) @@ -601,7 +604,7 @@ class NameFormatEditDlg: self.nameentry.set_text(self.fmt_name) self.formatentry = self.top.get_widget('format_entry') - self.formatentry.connect('changed',self.cb_format_changed) + self.formatentry.connect('changed', self.cb_format_changed) self.formatentry.set_text(self.fmt_str) def run(self): @@ -614,7 +617,15 @@ class NameFormatEditDlg: self.fmt_str = self.formatentry.get_text() if self.response == gtk.RESPONSE_OK: - if self.fmt_name == '' and self.fmt_str == '': + if not self.valid: + q = QuestionDialog.QuestionDialog2( + _('The format definition is invalid'), + _('What would you like to do?'), + _('_Continue anyway'), _('_Modify format'), + parent=self.dlg) + running = not q.run() + self.response = gtk.RESPONSE_CANCEL + elif self.fmt_name == '' and self.fmt_str == '': self.response = gtk.RESPONSE_CANCEL elif (self.fmt_name == '') ^ (self.fmt_str == ''): QuestionDialog.ErrorDialog( @@ -625,11 +636,15 @@ class NameFormatEditDlg: self.dlg.destroy() return (self.response, self.fmt_name, self.fmt_str) - def cb_format_changed(self,obj): + def cb_format_changed(self, obj): try: - t = (_nd.format_str(self.name,obj.get_text())) - except ValueError, msg: - t = _("Invalid format string: %s") % msg - self.examplelabel.set_text( - '%s' % t) + t = (_nd.format_str(self.name, escape(obj.get_text()))) + sample = '%s' % t + self.valid = True + except NameDisplayError: + t = _("Invalid or incomplete format definition") + sample = '%s' % t + self.valid = False + + self.examplelabel.set_text(sample) self.examplelabel.set_use_markup(True)