gramps/src/plugins/DefaultGramplets.py
Doug Blank 2fbee469a7 * src/plugins/DefaultGramplets.py: added gettext (thanks, Jim!)
2008-01-06  Douglas S. Blank  <dblank@cs.brynmawr.edu>


svn: r9736
2008-01-07 00:15:49 +00:00

679 lines
25 KiB
Python

# Gramps - a GTK+/GNOME based genealogy program
#
# 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
# 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
__author__ = "Douglas Blank <dblank@cs.brynmawr.edu>"
__version__ = "$Revision: $"
import sys
import os
import re
import time
import urllib
import gen.lib
from DataViews import register, Gramplet
from BasicUtils import name_displayer
from QuickReports import run_quick_report_by_name
import DateHandler
from gettext import gettext as _
#
# Hello World, in Gramps Gramplets
#
# First, you need a function or class that takes a single argument
# a GuiGramplet:
#from DataViews import register
#def init(gui):
# gui.set_text("Hello world!")
# In this function, you can do some things to update the gramplet,
# like set text of the main scroll window.
# Then, you need to register the gramplet:
#register(type="gramplet", # case in-senstitive keyword "gramplet"
# name="Hello World Gramplet", # gramplet name, unique among gramplets
# height = 20,
# content = init, # function/class; takes guigramplet
# title="Sample Gramplet", # default title, user changeable
# )
# There are a number of arguments that you can provide, including:
# name, height, content, title, expand, state, data
# Here is a Gramplet object. It has a number of method possibilities:
# init- run once, on construction
# active_changed- run when active-changed is triggered
# db_changed- run when db-changed is triggered
# main- run once per db change, main process (a generator)
# You should call update() to run main; don't call main directly
class CalendarGramplet(Gramplet):
def init(self):
import gtk
self.tooltip = _("Double-click a day for details")
self.gui.calendar = gtk.Calendar()
self.gui.calendar.connect('day-selected-double-click', self.double_click)
self.gui.calendar.connect('month-changed', self.refresh)
db_signals = ['event-add',
'event-update',
'event-delete',
'event-rebuild',
]
for signal in db_signals:
self.dbstate.db.connect(signal, lambda *args: self.run_update(signal, *args))
self.gui.scrolledwindow.remove(self.gui.textview)
self.gui.scrolledwindow.add_with_viewport(self.gui.calendar)
self.gui.calendar.show()
self.birthdays = True
self.dates = {}
def db_changed(self):
self.update()
def run_update(self, signal, *args):
self.update()
def refresh(self, *obj):
self.gui.calendar.freeze()
self.gui.calendar.clear_marks()
year, month, day = self.gui.calendar.get_date()
for date in self.dates:
if ((date[0] == year) and
(date[1] == month + 1) and
(date[2] > 0 and date[2] <= day)):
self.gui.calendar.mark_day(date[2])
self.gui.calendar.thaw()
def main(self):
self.dates = {}
# for each day in events
people = self.gui.dbstate.db.get_person_handles(sort_handles=False)
cnt = 0
for person_handle in people:
if cnt % 350 == 0:
yield True
person = self.gui.dbstate.db.get_person_from_handle(person_handle)
birth_ref = person.get_birth_ref()
birth_date = None
if birth_ref:
birth_event = self.gui.dbstate.db.get_event_from_handle(birth_ref.ref)
birth_date = birth_event.get_date_object()
if self.birthdays and birth_date != None:
year = birth_date.get_year()
month = birth_date.get_month()
day = birth_date.get_day()
#age = self.year - year
self.dates[(year, month, day)] = birth_event.handle
cnt += 1
self.refresh()
def double_click(self, obj):
# bring up events on this day
year, month, day = self.gui.calendar.get_date()
month += 1
date = gen.lib.Date()
date.set_yr_mon_day(year, month, day)
run_quick_report_by_name(self.gui.dbstate,
self.gui.uistate,
'onthisday',
date)
class LogGramplet(Gramplet):
def init(self):
self.tooltip = _("Click name to change active\nDouble-click name to edit")
self.set_text(_("Log for this Session"))
self.append_text("\n--------------------\n")
self.history = {}
def db_changed(self):
self.dbstate.db.connect('person-add', self.log_person_add)
self.dbstate.db.connect('person-delete', self.log_person_delete)
self.dbstate.db.connect('person-update', self.log_person_update)
self.dbstate.db.connect('family-add', self.log_family_add)
self.dbstate.db.connect('family-delete', self.log_family_delete)
self.dbstate.db.connect('family-update', self.log_family_update)
def on_load(self):
if len(self.gui.data) > 0:
self.show_duplicates = self.gui.data[0]
def on_save(self):
self.gui.data = [self.show_duplicates]
def active_changed(self, handle):
self.log_active_changed(handle)
# FIXME: added support for family display and clicks
def log_person_add(self, handles):
self.get_person(handles, _("Added"))
def log_person_delete(self, handles):
self.get_person(handles, _("Deleted"))
def log_person_update(self, handles):
self.get_person(handles, _("Updated"))
def log_family_add(self, handles):
self.append_text(_("Added") + ": family" )
def log_family_delete(self, handles):
self.append_text(_("Deleted") + ": family" )
def log_family_update(self, handles):
self.append_text(_("Updated") + ": family" )
def log_active_changed(self, handles):
self.get_person([handles], "Selected")
def get_person(self, handles, ltype):
for person_handle in handles:
if ((self.show_duplicates == "no" and
ltype + ": " + person_handle not in self.history) or
self.show_duplicates == "yes"):
self.append_text("%s: " % ltype)
self.history[ltype + ": " + person_handle] = 1
person = self.dbstate.db.get_person_from_handle(person_handle)
if person:
self.link(name_displayer.display(person), 'Person',
person_handle)
else:
self.link(_("Unknown"), 'Person', person_handle)
self.append_text("\n")
class TopSurnamesGramplet(Gramplet):
def init(self):
self.tooltip = _("Double-click surname for details")
self.top_size = 10 # will be overwritten in load
self.set_text(_("No Family Tree loaded."))
def db_changed(self):
self.dbstate.db.connect('person-add', self.update)
self.dbstate.db.connect('person-delete', self.update)
self.dbstate.db.connect('person-update', self.update)
def on_load(self):
if len(self.gui.data) > 0:
self.top_size = int(self.gui.data[0])
def on_save(self):
self.gui.data = [self.top_size]
def main(self):
self.set_text(_("Processing...") + "\n")
people = self.dbstate.db.get_person_handles(sort_handles=False)
surnames = {}
representative_handle = {}
cnt = 0
for person_handle in people:
person = self.dbstate.db.get_person_from_handle(person_handle)
if person:
surname = person.get_primary_name().get_surname().strip()
surnames[surname] = surnames.get(surname, 0) + 1
representative_handle[surname] = person_handle
if cnt % 350 == 0:
yield True
cnt += 1
total_people = cnt
surname_sort = []
total = 0
cnt = 0
for surname in surnames:
surname_sort.append( (surnames[surname], surname) )
total += surnames[surname]
if cnt % 350 == 0:
yield True
cnt += 1
total_surnames = cnt
surname_sort.sort(lambda a,b: -cmp(a,b))
line = 0
### All done!
self.set_text("")
for (count, surname) in surname_sort:
self.append_text(" %d. " % (line + 1))
self.link(surname, 'Surname', representative_handle[surname])
self.append_text(", %d%% (%d)\n" %
(int((float(count)/total) * 100), count))
line += 1
if line >= self.top_size:
break
self.append_text(("\n" + _("Total unique surnames") + ": %d\n") %
total_surnames)
self.append_text((_("Total people") + ": %d") % total_people)
class StatsGramplet(Gramplet):
def init(self):
self.set_text(_("No Family Tree loaded."))
def db_changed(self):
self.dbstate.db.connect('person-add', self.update)
self.dbstate.db.connect('person-delete', self.update)
self.dbstate.db.connect('family-add', self.update)
self.dbstate.db.connect('family-delete', self.update)
def main(self):
self.set_text(_("Processing..."))
database = self.dbstate.db
personList = database.get_person_handles(sort_handles=False)
familyList = database.get_family_handles()
with_photos = 0
total_photos = 0
incomp_names = 0
disconnected = 0
missing_bday = 0
males = 0
females = 0
unknowns = 0
bytes = 0
namelist = []
notfound = []
pobjects = len(database.get_media_object_handles())
for photo_id in database.get_media_object_handles():
photo = database.get_object_from_handle(photo_id)
try:
bytes = bytes + posixpath.getsize(photo.get_path())
except:
notfound.append(photo.get_path())
cnt = 0
for person_handle in personList:
person = database.get_person_from_handle(person_handle)
if not person:
continue
length = len(person.get_media_list())
if length > 0:
with_photos = with_photos + 1
total_photos = total_photos + length
person = database.get_person_from_handle(person_handle)
name = person.get_primary_name()
if name.get_first_name() == "" or name.get_surname() == "":
incomp_names = incomp_names + 1
if ((not person.get_main_parents_family_handle()) and
(not len(person.get_family_handle_list()))):
disconnected = disconnected + 1
birth_ref = person.get_birth_ref()
if birth_ref:
birth = database.get_event_from_handle(birth_ref.ref)
if not DateHandler.get_date(birth):
missing_bday = missing_bday + 1
else:
missing_bday = missing_bday + 1
if person.get_gender() == gen.lib.Person.FEMALE:
females = females + 1
elif person.get_gender() == gen.lib.Person.MALE:
males = males + 1
else:
unknowns += 1
if name.get_surname() not in namelist:
namelist.append(name.get_surname())
if cnt % 200 == 0:
yield True
cnt += 1
text = _("Individuals") + "\n"
text = text + "----------------------------\n"
text = text + "%s: %d\n" % (_("Number of individuals"),len(personList))
text = text + "%s: %d\n" % (_("Males"),males)
text = text + "%s: %d\n" % (_("Females"),females)
text = text + "%s: %d\n" % (_("Individuals with unknown gender"),unknowns)
text = text + "%s: %d\n" % (_("Individuals with incomplete names"),incomp_names)
text = text + "%s: %d\n" % (_("Individuals missing birth dates"),missing_bday)
text = text + "%s: %d\n" % (_("Disconnected individuals"),disconnected)
text = text + "\n%s\n" % _("Family Information")
text = text + "----------------------------\n"
text = text + "%s: %d\n" % (_("Number of families"),len(familyList))
text = text + "%s: %d\n" % (_("Unique surnames"),len(namelist))
text = text + "\n%s\n" % _("Media Objects")
text = text + "----------------------------\n"
text = text + "%s: %d\n" % (_("Individuals with media objects"),with_photos)
text = text + "%s: %d\n" % (_("Total number of media object references"),total_photos)
text = text + "%s: %d\n" % (_("Number of unique media objects"),pobjects)
text = text + "%s: %d %s\n" % (_("Total size of media objects"),bytes,\
_("bytes"))
if len(notfound) > 0:
text = text + "\n%s\n" % _("Missing Media Objects")
text = text + "----------------------------\n"
for p in notfound:
text = text + "%s\n" % p
self.set_text(text)
class PythonGramplet(Gramplet):
def init(self):
self.tooltip = _("Enter Python expressions")
self.env = {"dbstate": self.gui.dbstate,
"uistate": self.gui.uistate,
"self": self,
"Date": gen.lib.Date,
}
# GUI setup:
self.gui.textview.set_editable(True)
self.set_text("Python %s\n> " % sys.version)
self.gui.textview.connect('key-press-event', self.on_key_press)
def format_exception(self, max_tb_level=10):
retval = ''
cla, exc, trbk = sys.exc_info()
retval += _("Error") + (" : %s %s" %(cla, exc))
return retval
def on_key_press(self, widget, event):
import gtk
if (event.keyval == gtk.keysyms.Home or
((event.keyval == gtk.keysyms.a and
event.get_state() & gtk.gdk.CONTROL_MASK))):
buffer = widget.get_buffer()
cursor_pos = buffer.get_property("cursor-position")
iter = buffer.get_iter_at_offset(cursor_pos)
line_cnt = iter.get_line()
start = buffer.get_iter_at_line(line_cnt)
start.forward_chars(2)
buffer.place_cursor(start)
return True
elif (event.keyval == gtk.keysyms.End or
(event.keyval == gtk.keysyms.e and
event.get_state() & gtk.gdk.CONTROL_MASK)):
buffer = widget.get_buffer()
end = buffer.get_end_iter()
buffer.place_cursor(end)
return True
elif event.keyval == gtk.keysyms.Return:
echo = False
buffer = widget.get_buffer()
cursor_pos = buffer.get_property("cursor-position")
iter = buffer.get_iter_at_offset(cursor_pos)
line_cnt = iter.get_line()
start = buffer.get_iter_at_line(line_cnt)
line_len = iter.get_chars_in_line()
buffer_cnt = buffer.get_line_count()
if (buffer_cnt - line_cnt) > 1:
line_len -= 1
echo = True
end = buffer.get_iter_at_line_offset(line_cnt, line_len)
line = buffer.get_text(start, end)
self.append_text("\n")
if line.startswith(">"):
line = line[1:].strip()
else:
self.append_text("> ")
return True
if echo:
self.append_text("> " + line)
end = buffer.get_end_iter()
buffer.place_cursor(end)
return True
# update states, in case of change:
self.env["dbstate"] = self.gui.dbstate
self.env["uistate"] = self.gui.uistate
_retval = None
if "_retval" in self.env:
del self.env["_retval"]
exp1 = """_retval = """ + line
exp2 = line.strip()
try:
_retval = eval(exp2, self.env)
except:
try:
exec exp1 in self.env
except:
try:
exec exp2 in self.env
except:
_retval = self.format_exception()
if "_retval" in self.env:
_retval = self.env["_retval"]
if _retval != None:
self.append_text("%s" % str(_retval))
self.append_text("\n> ")
end = buffer.get_end_iter()
buffer.place_cursor(end)
return True
return False
class TODOGramplet(Gramplet):
def init(self):
# GUI setup:
self.tooltip = _("Enter text")
self.gui.textview.set_editable(True)
self.append_text(_("Enter your TODO list here."))
def on_load(self):
self.load_data_to_text()
def on_save(self):
self.gui.data = [] # clear out old data
self.save_text_to_data()
def make_welcome_content(gui):
text = _(
'Welcome to GRAMPS!\n\n'
'GRAMPS is a software package designed for genealogical research.'
' Although similar to other genealogical programs, GRAMPS offers '
'some unique and powerful features.\n\n'
'GRAMPS is an Open Source Software package, which means you are '
'free to make copies and distribute it to anyone you like. It\'s '
'developed and maintained by a worldwide team of volunteers whose'
' goal is to make GRAMPS powerful, yet easy to use.\n\n'
'Getting Started\n\n'
'The first thing you must do is to create a new Family Tree. To '
'create a new Family Tree (sometimes called a database) select '
'"Family Trees" from the menu, pick "Manage Family Trees", press '
'"New" and name your database. For more details, please read the '
'User Manual, or the on-line manual at http://gramps-project.org.\n\n'
'You are currently reading from the "Gramplets" page, where you can'
' add your own gramplets.\n\n'
'You can right-click on the background of this page to add additional'
' gramplets and change the number of columns. You can also drag the '
'Properties button to reposition the gramplet on this page, and detach'
' the gramplet to float above GRAMPS. If you close GRAMPS with a gramplet'
' detached, it will re-opened detached the next time you start '
'GRAMPS.'
)
gui.set_text(text)
class NewsGramplet(Gramplet):
URL = "http://www.gramps-project.org/wiki/index.php?title=%s&action=raw"
def init(self):
self.tooltip = _("Read news from the GRAMPS wiki")
def main(self):
continuation = self.process('News')
retval = True
while retval:
retval, text = continuation.next()
self.cleanup(text)
yield True
self.cleanup(text)
yield False
def cleanup(self, text):
# final text
text = text.replace("<BR>", "\n")
while "\n\n\n" in text:
text = text.replace("\n\n\n", "\n\n")
text = text.strip()
self.set_text(text)
def process(self, title):
#print "processing '%s'..." % title
title = title.replace(" ", "_")
yield True, (_("Reading") + " '%s'..." % title)
fp = urllib.urlopen(self.URL % title)
text = fp.read()
#text = text.replace("\n", " ")
html = re.findall('<.*?>', text)
for exp in html:
text = text.replace(exp, "")
text = text.replace("\n", "<BR>")
fp.close()
pattern = '{{.*?}}'
matches = re.findall(pattern, text)
#print " before:", text
for match in matches:
page = match[2:-2]
oldtext = match
if "|" in page:
template, heading, body = page.split("|", 2)
if template.lower() == "release":
newtext = "GRAMPS " + heading + " released.<BR><BR>"
else:
newtext = heading + "<BR><BR>"
newtext += body + "<BR>"
text = text.replace(oldtext, newtext)
else: # a macro/redirect
continuation = self.process("Template:" + page)
retval = True
while retval:
retval, newtext = continuation.next()
yield True, newtext
text = text.replace(oldtext, newtext)
#print " after:", text
pattern = '#REDIRECT \[\[.*?\]\]'
matches = re.findall(pattern, text)
#print " before:", text
for match in matches:
page = match[12:-2]
oldtext = match
continuation = self.process(page)
retval = True
while retval:
retval, newtext = continuation.next()
yield True, newtext
text = text.replace(oldtext, newtext)
#print " after:", text
yield False, text
class AgeOnDateGramplet(Gramplet):
def init(self):
import gtk
# GUI setup:
self.tooltip = _("Enter a date, click Run")
vbox = gtk.VBox()
hbox = gtk.HBox()
# label, entry
description = gtk.TextView()
description.set_wrap_mode(gtk.WRAP_WORD)
buffer = description.get_buffer()
buffer.set_text(_("Enter a date in the entry below and click Run."
" This will compute the ages for everyone in your"
" Family Tree on that date. You can then sort by"
" the age column, and double-click the row to view"
" or edit."))
label = gtk.Label()
label.set_text(_("Date") + ":")
self.entry = gtk.Entry()
button = gtk.Button(_("Run"))
button.connect("clicked", self.run)
hbox.pack_start(label, False)
hbox.pack_start(self.entry, True)
vbox.pack_start(description, True)
vbox.pack_start(hbox, False)
vbox.pack_start(button, False)
self.gui.scrolledwindow.remove(self.gui.textview)
self.gui.scrolledwindow.add_with_viewport(vbox)
vbox.show_all()
def run(self, obj):
text = self.entry.get_text()
date = DateHandler.parser.parse(text)
run_quick_report_by_name(self.gui.dbstate,
self.gui.uistate,
'ageondate',
date)
register(type="gramplet",
name= "Top Surnames Gramplet",
tname=_("Top Surnames Gramplet"),
height=230,
content = TopSurnamesGramplet,
title=_("Top Surnames"),
)
register(type="gramplet",
name="Statistics Gramplet",
tname=_("Statistics Gramplet"),
height=230,
expand=True,
content = StatsGramplet,
title=_("Statistics"),
)
register(type="gramplet",
name="Session Log Gramplet",
tname=_("Session Log Gramplet"),
height=230,
data=['no'],
content = LogGramplet,
title=_("Session Log"),
)
register(type="gramplet",
name="Python Gramplet",
tname=_("Python Gramplet"),
height=250,
content = PythonGramplet,
title=_("Python Shell"),
)
register(type="gramplet",
name="TODO Gramplet",
tname=_("TODO Gramplet"),
height=300,
expand=True,
content = TODOGramplet,
title=_("TODO List"),
)
register(type="gramplet",
name="Welcome Gramplet",
tname=_("Welcome Gramplet"),
height=300,
expand=True,
content = make_welcome_content,
title=_("Welcome to GRAMPS!"),
)
register(type="gramplet",
name="Calendar Gramplet",
tname=_("Calendar Gramplet"),
height=200,
content = CalendarGramplet,
title=_("Calendar"),
)
register(type="gramplet",
name="News Gramplet",
tname=_("News Gramplet"),
height=300,
expand=True,
content = NewsGramplet,
title=_("News"),
)
register(type="gramplet",
name="Age on Date Gramplet",
tname=_("Age on Date Gramplet"),
height=200,
content = AgeOnDateGramplet,
title=_("Age on Date"),
)