Skip to content

Commit 45c2dff

Browse files
committed
Merge branch 'gramps51'
2 parents ec431a4 + 7b43e9b commit 45c2dff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+5473
-7302
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#
2+
# Gramps - a GTK+/GNOME based genealogy program
3+
#
4+
# Copyright (C) 2019 Matthias Kemmer <[email protected]>
5+
#
6+
# This program is free software; you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation; either version 2 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
#
20+
21+
register(TOOL,
22+
id = 'AvatarGenerator',
23+
name = _("Avatar Generator Tool"),
24+
description = _("A tool to add avatar pictures to your family tree"),
25+
version = '1.0.1',
26+
gramps_target_version = "5.0",
27+
status = STABLE,
28+
fname = 'AvatarGenerator.py',
29+
authors = ["Matthias Kemmer"],
30+
authors_email = ["[email protected]"],
31+
category = TOOL_DBPROC,
32+
toolclass = 'AvatarGeneratorWindow',
33+
optionclass = 'AvatarGeneratorOptions',
34+
tool_modes = [TOOL_MODE_GUI],
35+
)

AvatarGenerator/AvatarGenerator.py

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
#
2+
# Gramps - a GTK+/GNOME based genealogy program
3+
#
4+
# Copyright (C) 2019 Matthias Kemmer
5+
#
6+
# This program is free software; you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation; either version 2 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
20+
# -------------------------------------------------
21+
#
22+
# GRAMPS modules
23+
#
24+
# -------------------------------------------------
25+
from gramps.gui.plug import MenuToolOptions, PluginWindows
26+
from gramps.gen.plug.menu import (FilterOption, MediaOption, PersonOption,
27+
BooleanOption)
28+
from gramps.gen.db import DbTxn
29+
import gramps.gen.plug.report.utils as ReportUtils
30+
from gramps.gui.dialog import OkDialog
31+
from gramps.gen.filters import GenericFilterFactory, rules
32+
from gramps.gen.lib import MediaRef
33+
from gramps.gen.errors import HandleError
34+
35+
from gramps.gen.const import GRAMPS_LOCALE as glocale
36+
try:
37+
_trans = glocale.get_addon_translator(__file__)
38+
except ValueError:
39+
_trans = glocale.translation
40+
_ = _trans.gettext
41+
42+
43+
# -------------------------------------------------
44+
#
45+
# Tool Classes
46+
#
47+
# -------------------------------------------------
48+
class AvatarGeneratorOptions(MenuToolOptions):
49+
def __init__(self, name, person_id=None, dbstate=None):
50+
self.__db = dbstate.get_database()
51+
MenuToolOptions.__init__(self, name, person_id, dbstate)
52+
53+
def add_menu_options(self, menu):
54+
"""
55+
Add all menu options to the tool window.
56+
"""
57+
# generate list for category menu option
58+
words = [_("male"), _("female"), _("unknown"),
59+
_("Option"), _("single image mode")]
60+
itm1 = "{} 1: {}".format(words[3], words[4])
61+
itm2 = "{} 2: {}/{}/{}".format(words[3], words[0], words[1], words[2])
62+
category_names = [(0, itm1), (1, itm2)]
63+
64+
# add all menu options
65+
self.__category = FilterOption(_("Category"), 0)
66+
text = _("Choose how many images you'd like to use.")
67+
self.__category.set_help(text)
68+
menu.add_option(_("Options"), "category",
69+
self.__category)
70+
self.__category.set_items(category_names)
71+
self.__category.connect('value-changed', self.__update_options)
72+
73+
self.__media1 = MediaOption(_("Unknown"))
74+
self.__media1.set_help(_("Image for people with unknown gender."))
75+
menu.add_option(_("Options"), "media1", self.__media1)
76+
77+
self.__media2 = MediaOption(_("Male"))
78+
self.__media2.set_help(_("Image for males."))
79+
menu.add_option(_("Options"), "media2", self.__media2)
80+
81+
self.__media3 = MediaOption(_("Female"))
82+
self.__media3.set_help(_("Image for females"))
83+
menu.add_option(_("Options"), "media3", self.__media3)
84+
85+
self.__person_filter = FilterOption(_("Person Filter"), 0)
86+
self.__person_filter.set_help(_("Select filter to restrict people"))
87+
menu.add_option(_("Options"), "person_filter", self.__person_filter)
88+
self.__person_filter.connect('value-changed', self.__filter_changed)
89+
90+
self.__pid = PersonOption(_("Center Person"))
91+
self.__pid.set_help(_("The center person for the filter"))
92+
menu.add_option(_("Options"), "pid", self.__pid)
93+
self.__pid.connect('value-changed', self.__update_filters)
94+
95+
self.__remove = BooleanOption(_("Remove images from people"), False)
96+
txt = _("Remove selected image(s).")
97+
self.__remove.set_help(txt)
98+
menu.add_option(_("Options"), "remove", self.__remove)
99+
100+
def __update_filters(self):
101+
"""
102+
Update filter list based on the selected person.
103+
"""
104+
gid = self.__pid.get_value()
105+
person = self.__db.get_person_from_gramps_id(gid)
106+
filter_list = ReportUtils.get_person_filters(person, False)
107+
self.__person_filter.set_filters(filter_list)
108+
109+
def __filter_changed(self):
110+
"""
111+
Handle filter change. If the filter is not specific to a person,
112+
disable the person option.
113+
"""
114+
filter_value = self.__person_filter.get_value()
115+
if filter_value in [1, 2, 3, 4]:
116+
self.__pid.set_available(True)
117+
else:
118+
self.__pid.set_available(False)
119+
120+
def __update_options(self):
121+
"""
122+
Update the availability of media options in the menu depending on
123+
what the user selects in menu option"category".
124+
"""
125+
self.__media2.set_available(False)
126+
self.__media3.set_available(False)
127+
if self.__category.get_value() == 1:
128+
self.__media2.set_available(True)
129+
self.__media3.set_available(True)
130+
131+
132+
class AvatarGeneratorWindow(PluginWindows.ToolManagedWindowBatch):
133+
def get_title(self):
134+
return _("Avatar Generator") # tool window title
135+
136+
def initial_frame(self):
137+
return _("Options") # tab title
138+
139+
def run(self):
140+
"""
141+
Main function running the Avatar Generator Tool.
142+
"""
143+
self.__db = self.dbstate.get_database()
144+
self.__get_menu_options()
145+
146+
def __get_menu_options(self):
147+
"""
148+
General menu option processing.
149+
"""
150+
menu = self.options.menu
151+
152+
# moo = MenuOption object
153+
category_moo = menu.get_option_by_name('category')
154+
category_value = category_moo.get_value()
155+
156+
remove_moo = menu.get_option_by_name('remove')
157+
remove = remove_moo.get_value()
158+
159+
iter_people = self.dbstate.db.iter_person_handles()
160+
filter_options = menu.get_option_by_name('person_filter')
161+
person_filter = filter_options.get_filter()
162+
people = person_filter.apply(self.dbstate.db, iter_people)
163+
164+
media_handles = self.__get_media_list(category_value)
165+
media_handles_ok = self.__check_media_list(media_handles)
166+
167+
if media_handles_ok and remove:
168+
self.__remove_media_from_people(media_handles, people)
169+
elif media_handles_ok and category_value == 0 and not remove:
170+
self.__avatar_gen(media_handles, people, category_value)
171+
elif media_handles_ok and category_value == 1 and not remove:
172+
people_sorted = self.__people_sorted(people)
173+
self.__avatar_gen(media_handles, people_sorted, category_value)
174+
175+
def __remove_media_from_people(self, person_media_handles, people):
176+
"""
177+
Remove selected media from selected people when menu option "remove"
178+
was selected.
179+
"""
180+
count_media = 0
181+
count_person = 0
182+
handle_error = 0
183+
with DbTxn(_("Avatar Generator"), self.db, batch=True) as self.trans:
184+
self.db.disable_signals()
185+
num_people = len(people)
186+
self.progress.set_pass(_('Remove avatar images...'),
187+
num_people)
188+
189+
for person_handle in people:
190+
person = self.__db.get_person_from_handle(person_handle)
191+
media_removed = False
192+
for mediaRef_obj in person.get_media_list():
193+
ref = mediaRef_obj.get_referenced_handles()
194+
try:
195+
media = self.__db.get_media_from_handle(ref[0][1])
196+
media_handle = media.get_handle()
197+
if media_handle in person_media_handles:
198+
person.remove_media_references(media_handle)
199+
self.db.commit_person(person, self.trans)
200+
count_media += 1
201+
media_removed = True
202+
except HandleError:
203+
handle_error += 1
204+
if media_removed:
205+
count_person += 1
206+
self.progress.step()
207+
self.db.enable_signals()
208+
self.db.request_rebuild()
209+
210+
if count_media == 0 and count_person == 0:
211+
OkDialog(_("INFO"), _("No media was removed."), parent=self.window)
212+
else:
213+
info_text = _("{} media references were removed from {} persons.")
214+
info_text = info_text.format(count_media, count_person)
215+
if handle_error > 0:
216+
info_text2 = _("\n{} HandleError occured, but were ignored.")
217+
info_text2 = info_text2.format(handle_error)
218+
info_text = info_text + info_text2
219+
OkDialog(_("INFO"), info_text, parent=self.window)
220+
221+
def __apply_filter(self, people, filter_rule):
222+
"""
223+
Apply a filter rule on a list of people. Return the filtered list of
224+
people handles.
225+
"""
226+
FilterClass = GenericFilterFactory('Person')
227+
filter_obj = FilterClass()
228+
filter_obj.add_rule(filter_rule)
229+
filtered_people = filter_obj.apply(self.dbstate.db, people)
230+
return filtered_people
231+
232+
def __people_sorted(self, people):
233+
"""
234+
Sort people depending on their gender and return a sorted list of
235+
unknown, male and female person handles.
236+
"""
237+
ismale = rules.person.IsMale([])
238+
isfemale = rules.person.IsFemale([])
239+
isunknown = rules.person.HasUnknownGender([])
240+
people_sorted = []
241+
242+
males = self.__apply_filter(people, ismale)
243+
females = self.__apply_filter(people, isfemale)
244+
unknown = self.__apply_filter(people, isunknown)
245+
246+
people_sorted.append(unknown)
247+
people_sorted.append(males)
248+
people_sorted.append(females)
249+
250+
return people_sorted
251+
252+
def __avatar_gen(self, media_handles, people, value):
253+
"""
254+
Add the image(s) chosen in the menu options to the people.
255+
"""
256+
counter = 0
257+
name_txt = _("Avatar Generator")
258+
259+
if value == 0: # Single image mode
260+
with DbTxn(name_txt, self.db, batch=True) as self.trans:
261+
self.db.disable_signals()
262+
num_people = len(people)
263+
self.progress.set_pass(_('Add avatar images...'), num_people)
264+
for person_handle in people: # people = list of people handles
265+
person = self.__db.get_person_from_handle(person_handle)
266+
if person.get_media_list() == []:
267+
mediaref = MediaRef()
268+
mediaref.ref = media_handles[0]
269+
person.add_media_reference(mediaref)
270+
self.db.commit_person(person, self.trans)
271+
counter += 1
272+
self.progress.step()
273+
self.db.enable_signals()
274+
self.db.request_rebuild()
275+
276+
elif value == 1: # male/female/unknown
277+
with DbTxn(name_txt, self.db, batch=True) as self.trans:
278+
self.db.disable_signals()
279+
num_people = len(people[0]) + len(people[1]) + len(people[2])
280+
self.progress.set_pass(_('Add avatar images...'), num_people)
281+
# people = contains 3 lists here
282+
# each containing people handels of unknown, males or females
283+
for person_handle in people[0]: # unknown people handles
284+
person = self.__db.get_person_from_handle(person_handle)
285+
if person.get_media_list() == []:
286+
mediaref = MediaRef()
287+
mediaref.ref = media_handles[0]
288+
person.add_media_reference(mediaref)
289+
self.db.commit_person(person, self.trans)
290+
counter += 1
291+
self.progress.step()
292+
for person_handle in people[1]: # male people handles
293+
person = self.__db.get_person_from_handle(person_handle)
294+
if person.get_media_list() == []:
295+
mediaref = MediaRef()
296+
mediaref.ref = media_handles[1]
297+
person.add_media_reference(mediaref)
298+
self.db.commit_person(person, self.trans)
299+
counter += 1
300+
self.progress.step()
301+
for person_handle in people[2]: # female people handles
302+
person = self.__db.get_person_from_handle(person_handle)
303+
if person.get_media_list() == []:
304+
mediaref = MediaRef()
305+
mediaref.ref = media_handles[2]
306+
person.add_media_reference(mediaref)
307+
self.db.commit_person(person, self.trans)
308+
counter += 1
309+
self.progress.step()
310+
self.db.enable_signals()
311+
self.db.request_rebuild()
312+
313+
if counter > 0:
314+
info_text = _("{} avatar images were sucessfully added.")
315+
info_text2 = info_text.format(counter)
316+
OkDialog(_("INFO"), info_text2, parent=self.window)
317+
else:
318+
txt1 = _("There was no avatar image to add. ")
319+
txt2 = _("All persons already had one.")
320+
OkDialog(_("INFO"), (txt1 + txt2), parent=self.window)
321+
322+
def __check_media_list(self, media_list):
323+
"""
324+
Return 'True' if media list is valid and contains media handles only.
325+
"""
326+
if False in media_list:
327+
OkDialog("INFO", "Select images first.", parent=self.window)
328+
return False # Invalid, because user didn't selected a image
329+
else:
330+
return True # valid list of handles
331+
332+
def __get_media_list(self, value):
333+
"""
334+
Media menu option processing. Returns a list of media handles.
335+
"""
336+
menu = self.options.menu
337+
name = ['media1', 'media2', 'media3']
338+
media_list = []
339+
340+
if value == 0: # Option 1: single image
341+
menu_option = menu.get_option_by_name(name[0])
342+
media_handle = self.__get_media_handle(menu_option)
343+
media_list.append(media_handle)
344+
return media_list
345+
elif value == 1: # Option 2: male/female/unknown
346+
for i in range(3):
347+
menu_option = menu.get_option_by_name(name[i])
348+
media_handle = self.__get_media_handle(menu_option)
349+
media_list.append(media_handle)
350+
return media_list
351+
352+
def __get_media_handle(self, menu_option):
353+
"""
354+
Return media handle from media menu option or 'False' if no image was
355+
selected.
356+
"""
357+
gid = menu_option.get_value()
358+
if gid == "":
359+
return False
360+
else:
361+
media_obj = self.__db.get_media_from_gramps_id(gid)
362+
media_handle = media_obj.get_handle()
363+
return media_handle

0 commit comments

Comments
 (0)