1226c0d55a
* src/AddrEdit.py: glade path * src/AttrEdit.py: glade path * src/DateEdit.py: glade path * src/EventEdit.py: glade path * src/GrampsCfg.py: glade path * src/ImageSelect.py: glade path * src/Sources.py: glade path * src/StyleEditor.py: glade path * src/const.py.in: glade path * src/Marriage.py: glade path * src/unlocked.png: added * src/GrampsDb/_ReadGedcom.py: optimize UI updates, add test code svn: r5772
378 lines
14 KiB
Python
378 lines
14 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2002-2005 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
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
# $Id$
|
|
|
|
"""
|
|
Date editing module for GRAMPS.
|
|
|
|
The DateEdit.DateEdit provides visual feedback to the user via a pixamp
|
|
to indicate if the assocated GtkEntry box contains a valid date. Green
|
|
means complete and regular date. Yellow means a valid, but not a regular date.
|
|
Red means that the date is not valid, and will be viewed as a text string
|
|
instead of a date.
|
|
|
|
The DateEdit.DateEditor provides a dialog in which the date can be
|
|
unambiguously built using UI controls such as menus and spin buttons.
|
|
"""
|
|
|
|
__author__ = "Donald N. Allingham"
|
|
__version__ = "$Revision$"
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gettext import gettext as _
|
|
import gc
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GNOME modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import gtk
|
|
import gtk.gdk
|
|
import gtk.glade
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import Date
|
|
import DateHandler
|
|
import const
|
|
import Utils
|
|
import GrampsDisplay
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Constants
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
MOD_TEXT = (
|
|
(Date.MOD_NONE , _('Regular')),
|
|
(Date.MOD_BEFORE , _('Before')),
|
|
(Date.MOD_AFTER , _('After')),
|
|
(Date.MOD_ABOUT , _('About')),
|
|
(Date.MOD_RANGE , _('Range')),
|
|
(Date.MOD_SPAN , _('Span')),
|
|
(Date.MOD_TEXTONLY , _('Text only')) )
|
|
|
|
QUAL_TEXT = (
|
|
(Date.QUAL_NONE, _('Regular')),
|
|
(Date.QUAL_ESTIMATED, _('Estimated')),
|
|
(Date.QUAL_CALCULATED, _('Calculated')) )
|
|
|
|
CAL_TO_MONTHS_NAMES = {
|
|
Date.CAL_GREGORIAN : DateHandler.displayer._MONS,
|
|
Date.CAL_JULIAN : DateHandler.displayer._MONS,
|
|
Date.CAL_HEBREW : DateHandler.displayer._hebrew,
|
|
Date.CAL_FRENCH : DateHandler.displayer._french,
|
|
Date.CAL_PERSIAN : DateHandler.displayer._persian,
|
|
Date.CAL_ISLAMIC : DateHandler.displayer._islamic }
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# DateEdit
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class DateEdit:
|
|
"""Class that associates a pixmap with a text widget, providing visual
|
|
feedback that indicates if the text widget contains a valid date"""
|
|
|
|
good = gtk.gdk.pixbuf_new_from_file(const.good_xpm)
|
|
bad = gtk.gdk.pixbuf_new_from_file(const.bad_xpm)
|
|
caution = gtk.gdk.pixbuf_new_from_file(const.caution_xpm)
|
|
|
|
def __init__(self,date_obj,text_obj,button_obj,parent_window=None):
|
|
"""
|
|
Creates a connection between the date_obj, text_obj and the pixmap_obj.
|
|
Assigns callbacks to parse and change date when the text
|
|
in text_obj is changed, and to invoke Date Editor when the LED
|
|
button_obj is pressed.
|
|
"""
|
|
|
|
self.date_obj = date_obj
|
|
self.text_obj = text_obj
|
|
self.button_obj = button_obj
|
|
self.parent_window = parent_window
|
|
|
|
self.pixmap_obj = button_obj.get_child()
|
|
|
|
self.text_obj.connect('focus-out-event',self.parse_and_check)
|
|
self.button_obj.connect('clicked',self.invoke_date_editor)
|
|
|
|
self.text = unicode(self.text_obj.get_text())
|
|
self.check()
|
|
|
|
def check(self):
|
|
"""
|
|
Check current date object and display LED indicating the validity.
|
|
"""
|
|
if self.date_obj.get_modifier() == Date.MOD_TEXTONLY:
|
|
self.pixmap_obj.set_from_pixbuf(self.pixmap_obj.render_icon(gtk.STOCK_DIALOG_ERROR,gtk.ICON_SIZE_MENU))
|
|
elif self.date_obj.is_regular():
|
|
self.pixmap_obj.set_from_pixbuf(self.pixmap_obj.render_icon(gtk.STOCK_YES,gtk.ICON_SIZE_MENU))
|
|
else:
|
|
self.pixmap_obj.set_from_pixbuf(self.pixmap_obj.render_icon(gtk.STOCK_DIALOG_WARNING,gtk.ICON_SIZE_MENU))
|
|
|
|
def parse_and_check(self,obj,val):
|
|
"""
|
|
Called with the text box loses focus. Parses the text and calls
|
|
the check() method ONLY if the text has changed.
|
|
"""
|
|
|
|
text = unicode(self.text_obj.get_text())
|
|
if text != self.text:
|
|
self.text = text
|
|
self.date_obj.copy(DateHandler.parser.parse(text))
|
|
self.text_obj.set_text(DateHandler.displayer.display(self.date_obj))
|
|
self.check()
|
|
|
|
def invoke_date_editor(self,obj):
|
|
"""
|
|
Invokes Date Editor dialog when the user clicks the LED button.
|
|
If date was in fact built, sets the date_obj to the newly built
|
|
date.
|
|
"""
|
|
date_dialog = DateEditorDialog(self.date_obj,self.parent_window)
|
|
the_date = date_dialog.return_date
|
|
self.update_after_editor(the_date)
|
|
|
|
def update_after_editor(self,date_obj):
|
|
"""
|
|
Update text field and LED button to reflect the given date instance.
|
|
"""
|
|
if date_obj:
|
|
self.date_obj.copy(date_obj)
|
|
self.text_obj.set_text(DateHandler.displayer.display(self.date_obj))
|
|
self.check()
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# DateEditorDialog
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class DateEditorDialog:
|
|
"""
|
|
Dialog allowing to build the date precisely, to correct possible
|
|
limitations of parsing and/or underlying structure of Date.
|
|
"""
|
|
|
|
def __init__(self,date,parent_window=None):
|
|
"""
|
|
Initiate and display the dialog.
|
|
"""
|
|
|
|
# Create self.date as a copy of the given Date object.
|
|
self.date = Date.Date(date)
|
|
|
|
self.top = gtk.glade.XML(const.gladeFile, "date_edit","gramps" )
|
|
self.top_window = self.top.get_widget('date_edit')
|
|
self.top_window.hide()
|
|
title = self.top.get_widget('title')
|
|
Utils.set_titles(self.top_window,title,_('Date selection'))
|
|
|
|
self.calendar_box = self.top.get_widget('calendar_box')
|
|
for name in Date.Date.ui_calendar_names:
|
|
self.calendar_box.append_text(name)
|
|
self.calendar_box.set_active(self.date.get_calendar())
|
|
self.calendar_box.connect('changed',self.switch_calendar)
|
|
|
|
self.quality_box = self.top.get_widget('quality_box')
|
|
for item_number in range(len(QUAL_TEXT)):
|
|
self.quality_box.append_text(QUAL_TEXT[item_number][1])
|
|
if self.date.get_quality() == QUAL_TEXT[item_number][0]:
|
|
self.quality_box.set_active(item_number)
|
|
|
|
self.type_box = self.top.get_widget('type_box')
|
|
for item_number in range(len(MOD_TEXT)):
|
|
self.type_box.append_text(MOD_TEXT[item_number][1])
|
|
if self.date.get_modifier() == MOD_TEXT[item_number][0]:
|
|
self.type_box.set_active(item_number)
|
|
self.type_box.connect('changed',self.switch_type)
|
|
|
|
self.start_month_box = self.top.get_widget('start_month_box')
|
|
self.stop_month_box = self.top.get_widget('stop_month_box')
|
|
month_names = CAL_TO_MONTHS_NAMES[self.date.get_calendar()]
|
|
for name in month_names:
|
|
self.start_month_box.append_text(name)
|
|
self.stop_month_box.append_text(name)
|
|
self.start_month_box.set_active(self.date.get_month())
|
|
self.stop_month_box.set_active(self.date.get_stop_month())
|
|
|
|
self.start_day = self.top.get_widget('start_day')
|
|
self.start_day.set_value(self.date.get_day())
|
|
self.start_year = self.top.get_widget('start_year')
|
|
self.start_year.set_value(self.date.get_year())
|
|
|
|
self.stop_day = self.top.get_widget('stop_day')
|
|
self.stop_day.set_value(self.date.get_stop_day())
|
|
self.stop_year = self.top.get_widget('stop_year')
|
|
self.stop_year.set_value(self.date.get_stop_year())
|
|
|
|
# Disable second date controls if not compound date
|
|
if not self.date.is_compound():
|
|
self.stop_day.set_sensitive(0)
|
|
self.stop_month_box.set_sensitive(0)
|
|
self.stop_year.set_sensitive(0)
|
|
|
|
# Disable the rest of controls if a text-only date
|
|
if self.date.get_modifier() == Date.MOD_TEXTONLY:
|
|
self.start_day.set_sensitive(0)
|
|
self.start_month_box.set_sensitive(0)
|
|
self.start_year.set_sensitive(0)
|
|
self.calendar_box.set_sensitive(0)
|
|
self.quality_box.set_sensitive(0)
|
|
|
|
self.text_entry = self.top.get_widget('date_text_entry')
|
|
self.text_entry.set_text(self.date.get_text())
|
|
|
|
# The dialog is modal -- since dates don't have names, we don't
|
|
# want to have several open dialogs, since then the user will
|
|
# loose track of which is which. Much like opening files.
|
|
if parent_window:
|
|
self.top_window.set_transient_for(parent_window)
|
|
|
|
self.return_date = None
|
|
while 1:
|
|
response = self.top_window.run()
|
|
if response == gtk.RESPONSE_HELP:
|
|
GrampsDisplay.help('adv-dates')
|
|
|
|
elif response == gtk.RESPONSE_OK:
|
|
(the_quality,the_modifier,the_calendar,the_value,the_text) = \
|
|
self.build_date_from_ui()
|
|
self.return_date = Date.Date(self.date)
|
|
self.return_date.set(
|
|
quality=the_quality,
|
|
modifier=the_modifier,
|
|
calendar=the_calendar,
|
|
value=the_value,
|
|
text=the_text)
|
|
break
|
|
else:
|
|
break
|
|
self.top_window.destroy()
|
|
gc.collect()
|
|
|
|
def build_date_from_ui(self):
|
|
"""
|
|
Collect information from the UI controls and return
|
|
5-tuple of (quality,modifier,calendar,value,text)
|
|
"""
|
|
# It is important to not set date based on these controls.
|
|
# For example, changing the caledar makes the date inconsistent
|
|
# until the callback of the calendar menu is finished.
|
|
# We need to be able to use this function from that callback,
|
|
# so here we just report on the state of all widgets, without
|
|
# actually modifying the date yet.
|
|
|
|
modifier = MOD_TEXT[self.type_box.get_active()][0]
|
|
text = self.text_entry.get_text()
|
|
|
|
if modifier == Date.MOD_TEXTONLY:
|
|
return (Date.QUAL_NONE,Date.MOD_TEXTONLY,Date.CAL_GREGORIAN,
|
|
Date.EMPTY,text)
|
|
|
|
quality = QUAL_TEXT[self.quality_box.get_active()][0]
|
|
|
|
if modifier in (Date.MOD_RANGE,Date.MOD_SPAN):
|
|
value = (
|
|
self.start_day.get_value_as_int(),
|
|
self.start_month_box.get_active(),
|
|
self.start_year.get_value_as_int(),
|
|
False,
|
|
self.stop_day.get_value_as_int(),
|
|
self.stop_month_box.get_active(),
|
|
self.stop_year.get_value_as_int(),
|
|
False)
|
|
else:
|
|
value = (
|
|
self.start_day.get_value_as_int(),
|
|
self.start_month_box.get_active(),
|
|
self.start_year.get_value_as_int(),
|
|
False)
|
|
calendar = self.calendar_box.get_active()
|
|
return (quality,modifier,calendar,value,text)
|
|
|
|
def switch_type(self,obj):
|
|
"""
|
|
Disable/enable various date controls depending on the date
|
|
type selected via the menu.
|
|
"""
|
|
|
|
the_modifier = MOD_TEXT[self.type_box.get_active()][0]
|
|
|
|
# Disable/enable second date controls based on whether
|
|
# the type allows compound dates
|
|
if the_modifier in (Date.MOD_RANGE,Date.MOD_SPAN):
|
|
stop_date_sensitivity = 1
|
|
else:
|
|
stop_date_sensitivity = 0
|
|
self.stop_day.set_sensitive(stop_date_sensitivity)
|
|
self.stop_month_box.set_sensitive(stop_date_sensitivity)
|
|
self.stop_year.set_sensitive(stop_date_sensitivity)
|
|
|
|
# Disable/enable the rest of the controls if the type is text-only.
|
|
date_sensitivity = not the_modifier == Date.MOD_TEXTONLY
|
|
self.start_day.set_sensitive(date_sensitivity)
|
|
self.start_month_box.set_sensitive(date_sensitivity)
|
|
self.start_year.set_sensitive(date_sensitivity)
|
|
self.calendar_box.set_sensitive(date_sensitivity)
|
|
self.quality_box.set_sensitive(date_sensitivity)
|
|
|
|
def switch_calendar(self,obj):
|
|
"""
|
|
Change month names and convert the date based on the calendar
|
|
selected via the menu.
|
|
"""
|
|
|
|
old_cal = self.date.get_calendar()
|
|
new_cal = self.calendar_box.get_active()
|
|
|
|
(the_quality,the_modifier,the_calendar,the_value,the_text) = \
|
|
self.build_date_from_ui()
|
|
self.date.set(
|
|
quality=the_quality,
|
|
modifier=the_modifier,
|
|
calendar=old_cal,
|
|
value=the_value,
|
|
text=the_text)
|
|
|
|
self.date.convert_calendar(new_cal)
|
|
|
|
self.start_month_box.get_model().clear()
|
|
self.stop_month_box.get_model().clear()
|
|
month_names = CAL_TO_MONTHS_NAMES[new_cal]
|
|
for name in month_names:
|
|
self.start_month_box.append_text(name)
|
|
self.stop_month_box.append_text(name)
|
|
|
|
self.start_day.set_value(self.date.get_day())
|
|
self.start_month_box.set_active(self.date.get_month())
|
|
self.start_year.set_value(self.date.get_year())
|
|
self.stop_day.set_value(self.date.get_stop_day())
|
|
self.stop_month_box.set_active(self.date.get_stop_month())
|
|
self.stop_year.set_value(self.date.get_stop_year())
|