persons.py
Nov 25, 2022 17:46:51 GMT -8
Post by Uncle Buddy on Nov 25, 2022 17:46:51 GMT -8
<drive>:\treebard\app\python\persons.py Last Changed 2024-4-16
# persons.py
import tkinter as tk
import sqlite3
from PIL import Image, ImageTk
from files import get_current_tree, appwide_db_path, get_image_dir
from type_elements import get_all_name_types
from redraw import redraw_current_person_area
from utilities import resize_scrolled_content
from widgets import (
make_formats_dict, configall, get_colors_type_id, ScrolledDialog,
open_message, run_statusbar_tooltips, RightClickMenu,
make_rc_menus, Combobox, LabelButtonText)
from assertions import AssertionsDialog
from messages_context_help import person_add_help_msg
from messages import persons_msg
from dates import ChangeDate
from query_strings import (
select_name_type_id, insert_name, insert_media_links_image_person,
select_all_name_types, insert_person_new, update_current_person_to_1,
insert_name_type_new, select_images_all, insert_event_birth_new_person,
select_current_person_id, delete_name_person, insert_image_new,
select_name_id_by_person_id, delete_notes_links_person,
delete_notes_links_name, update_name_type_sorter,
update_couple_null1, update_couple_null2,
delete_event_person, delete_person, update_assertion_delete_event,
select_all_names, select_event_person, select_count_name_id_sources,
delete_name_by_id, insert_name_placeholder, select_name_all_current,
select_name_type_id_by_string, insert_name_and_type)
import dev_tools as dt
from dev_tools import look, seeline
GENDER_TYPES = ('unknown', 'female', 'male')
NAME_SUFFIXES = (
'jr.', 'sr.', 'jr', 'sr', 'junior', 'senior',
'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii',
'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv')
def get_name_from_id(idnum, family_tree):
name_from_id = None
for k,v in family_tree.person_autofill_values.items():
if idnum == k:
name_from_id = (v[0], k)
break
return name_from_id
def get_original(evt):
widg=evt.widget
widg.original = widg.get()
def delete_current_person(evt, current_tree):
""" Remove all references to a person.
[see if still true, fix, update:]Due to import circles, the user will
have to change current person manually after deleting the current
person. That is not an emergency but what if he tries to do something
on the current person conclusions or families table?
"""
if len (self.family_tree.person_autofill_values) < 2:
return
conn = sqlite3.connect(current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_current_person_id)
current_person_id = cur.fetchone()[0]
if current_person_id == 1:
return
cur.execute(update_current_person_to_1)
conn.commit()
cur.execute(select_name_id_by_person_id, (current_person_id,))
names = cur.fetchall()
for name_id in names:
cur.execute(delete_notes_links_name, name_id)
conn.commit()
cur.execute(delete_notes_links_person, (current_person_id,))
conn.commit()
cur.execute(delete_name_person, (current_person_id,))
conn.commit()
# When removing a partner from a couple, check to see if the couple_id is
# being used anywhere, (like event table) and if not, delete the whole
# couple row.
cur.execute(update_couple_null1, (current_person_id,))
conn.commit()
cur.execute(update_couple_null2, (current_person_id,))
conn.commit()
cur.execute(select_event_person, (current_person_id,))
events = cur.fetchall()
for event_id in events:
cur.execute(update_assertion_delete_event, event_id)
conn.commit()
cur.execute(delete_event_person, (current_person_id,))
conn.commit()
conn.commit()
cur.execute(delete_person, (current_person_id,))
conn.commit()
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
cur.close()
conn.close()
# can't run change_person() here
msg = open_message(
None,
persons_msg[4],
"Current Person Deleted",
"OK")
msg[0].grab_set()
def open_new_person_dialog(
family_tree, family_tree_id, treebard, inwidg=None, inwidg2=None, got=None):
person_add = PersonAdd(
family_tree, family_tree_id, treebard, inwidg, inwidg2, got)
treebard.wait_window(person_add)
new_person_id = person_add.show()
return new_person_id
class PersonAdd(ScrolledDialog):
""" For more info on why a '+' is added to new name input, see dev docs. """
def __init__(
self, master, family_tree_id, treebard, inwidg, inwidg2, got,
*args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.family_tree = master
self.family_tree_id = family_tree_id
self.treebard = treebard
self.inwidg = inwidg
self.inwidg2 = inwidg2
self.family_tree.person_autofill_values = self.family_tree.make_all_names_dict_for_person_select()
self.title("Add Person Dialog")
self.full_name = ""
self.sorted_name = ""
self.name_type = ""
self.xfr = ""
if got:
self.xfr = got
elif self.inwidg:
self.xfr = self.inwidg.get()
if "+" in self.xfr:
self.xfr = self.xfr.strip().strip("+").strip()
self.new_images = []
self.existing_images = []
self.genvar = tk.IntVar(None, 3)
self.role_person_edited = False
self.rc_menu = RightClickMenu(self.family_tree, self.treebard)
self.new_person_id = None
self.full_name = ""
self.name_type_id = None
self.make_dupe = False
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
self.current_tree = get_current_tree(self.family_tree_id, cur)[0]
cur.execute("ATTACH ? as tree", (self.current_tree,))
self.names_only = self.check_for_dupes(cur)
self.geometry('+100+20')
self.grab_set()
self.make_inputs()
self.name_input.insert(0, self.xfr)
self.focus_force()
self.name_input.focus_set()
cur.execute("DETACH tree")
cur.close()
conn.close()
ScrolledDialog.bind_canvas_to_mousewheel(self.canvas)
colors_type_id = get_colors_type_id(self.family_tree_id)
formats = make_formats_dict(colors_type_id=colors_type_id)
configall(self, formats)
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=32)
def make_inputs(self):
def instruct_user():
msg1 = open_message(
dupe_exists,
persons_msg[3],
"Person Already Exists",
"OK")
msg1[0].grab_set()
all_name_types = get_all_name_types(self.current_tree)
namelab = tk.Label(self.window, text="Full Name", anchor="w")
self.name_input = EntryAutoPerson(
self.window, self.family_tree_id, self.family_tree, autofill=True, width=32,
values=self.family_tree.person_autofill_values)
dupetext = ("Press OK to make another person with the existing name. The "
"persons can be merged later if desired.")
# rerun this on focus out of name input if content changes and again on OK:
self.name_input.bind(
"<FocusOut>",
lambda evt, dupetext=dupetext: self.show_dupe_msg(evt, dupetext), add="+")
autosorter = tk.Button(
self.window, text="AUTOSORT", command=self.sort_name)
self.autosort_input = tk.Entry(self.window, width=32)
typelab = tk.Label(self.window, text="Name Type", anchor="w")
self.type_input = Combobox(
self.window, self.formats, family_tree=self.family_tree,
values=all_name_types)
self.type_input.insert(0, "birth name")
radframe = tk.Frame(self.window)
rad_male = tk.Radiobutton(
radframe, value=1, variable=self.genvar, text="male")
rad_female = tk.Radiobutton(
radframe, value=2, variable=self.genvar, text="female")
rad_unknown = tk.Radiobutton(
radframe, value=3, variable=self.genvar, text="unknown")
dupes = tk.LabelFrame(
self.window,
text="If this name already exists, a notice will appear.")
self.dupelab = tk.Label(
dupes, None,
text="",
wraplength=270, justify="left")
image_select = tk.Button(
self.window, text="SELECT IMAGE(S) FOR THIS PERSON",
command=self.open_image_dlg)
buttons = tk.Frame(self.window)
self.ok_new_person = tk.Button(
buttons, text="OK", width=8, command=self.ok_add_person)
cancel_new_person = tk.Button(
buttons, text="CANCEL", width=8,
command=self.cancel_add_person)
visited = (
(autosorter,
"Autosort Button",
"Creates an alphabetical-order name; you can change it manually."),
)
run_statusbar_tooltips(
visited,
self.statusbar.status_label,
self.statusbar.tooltip_label,
self)
# self.columnconfigure(1, weight=1)
self.window.columnconfigure(0, weight=1)
self.window.rowconfigure(0, weight=1)
# children of self
self.statusbar.grid(column=1, row=3, sticky='ew')
# children of self.window
namelab.grid(column=0, row=0, padx=(3,0), sticky="ew", pady=(3,0))
self.name_input.grid(column=1, row=0, padx=(6,0), pady=(3,0), sticky="w")
autosorter.grid(column=2, row=0, padx=(6,0), pady=(3,0))
self.autosort_input.grid(column=3, row=0, padx=(6,6), pady=(3,0))
typelab.grid(column=0, row=1, padx=(3,0), pady=(6,0), sticky="ew")
self.type_input.grid(column=1, row=1, padx=(6,0), pady=(0,0), sticky="w")
radframe.grid(column=3, row=1, padx=(6,0), pady=(6,0), rowspan=3)
dupes.grid(column=0, row=2, padx=(3,0), pady=12, columnspan=3)
image_select.grid(column=0, row=3, padx=12, pady=12, columnspan=3)
buttons.grid(column=3, row=6, padx=12, pady=(12,0), sticky="e")
# children of radframe
rad_male.grid(column=0, row=0, padx=(0,0), pady=(0,0), sticky="w")
rad_female.grid(column=0, row=1, padx=(0,0), pady=(0,0), sticky="w")
rad_unknown.grid(column=0, row=2, padx=(0,0), pady=(0,0), sticky="w")
# children of dupes
self.dupelab.grid(column=0, row=0, sticky="news", ipadx=12, ipady=12)
# children of buttons
self.ok_new_person.grid(column=0, row=0, padx=(0,0), pady=(0,0))
cancel_new_person.grid(column=1, row=0, padx=(6,0), pady=(0,0))
def make_new_name_type(self, cur, conn):
cur.execute(insert_name_type_new, (self.name_type,))
conn.commit()
return cur.lastrowid
def save_new_person(self):
conn = sqlite3.connect(appwide_db_path)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (self.current_tree,))
cur.execute(insert_person_new, (self.gender,))
conn.commit()
self.new_person_id = cur.lastrowid
ChangeDate(self.new_person_id, tag="person", conn=conn, cur=cur)
cur.execute(select_name_type_id, (self.name_type,))
result = cur.fetchone()
if result:
self.name_type_id = result[0]
else:
self.name_type_id = self.make_new_name_type(cur, conn)
cur.execute(
insert_name,
(self.new_person_id, self.full_name,
self.name_type_id, self.sorted_name))
conn.commit()
cur.execute(select_images_all)
all_images = {i[0]:i[1] for i in cur.fetchall()}
if len(self.new_images) == 0 and len(self.existing_images) == 0:
pass
elif len(self.new_images) == 0:
for imagetup in self.existing_images:
image_name, main_image = imagetup
image_name = ".".join(image_name)
image_id = all_images[image_name]
cur.execute(
insert_media_links_image_person,
(image_id, self.new_person_id, main_image))
conn.commit()
ChangeDate(image_id, tag="media", conn=conn, cur=cur)
for imagetup in self.new_images:
filename, caption, main_image = imagetup
if filename not in all_images:
cur.execute(insert_image_new, (filename, caption))
conn.commit()
image_id = cur.lastrowid
cur.execute(
insert_media_links_image_person,
(image_id, self.new_person_id, main_image))
conn.commit()
ChangeDate(image_id, tag="media", conn=conn, cur=cur)
cur.execute(insert_event_birth_new_person, (self.new_person_id,))
conn.commit()
ChangeDate(self.new_person_id, tag="person", conn=conn, cur=cur)
cur.execute("DETACH tree")
cur.close()
conn.close()
if self.inwidg:
self.inwidg.delete(0, 'end')
self.inwidg.insert(0, self.full_name)
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
def sort_name(self):
namelist = self.name_input.get().strip().split()
surname = namelist.pop()
self.sorted_name = "{}, {}".format(surname, " ".join(namelist))
self.autosort_input.delete(0, "end")
self.autosort_input.insert(0, self.sorted_name)
def open_image_dlg(self):
image_dialog = SelectImageDialog(
self.family_tree, self.family_tree_id, self.ok_new_person,
self.new_images, self.existing_images, self.current_tree,
self.treebard)
def check_for_dupes(self, cur):
cur.execute(select_all_names)
names_only = [i[0] for i in cur.fetchall()]
return names_only
def show(self):
return self.new_person_id
def show_dupe_msg(self, evt, dupetext):
if self.name_input.get() in self.names_only:
self.dupelab.config(text=dupetext)
else:
self.dupelab.config(text="")
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=32)
def ok_add_person(self):
gendermap = {1: "male", 2: "female", 3: "unknown"}
self.full_name = self.name_input.get()
self.sorted_name = self.autosort_input.get()
self.name_type = self.type_input.get()
gender = self.genvar.get()
for k,v in gendermap.items():
if gender == k:
self.gender = v
break
for stg in (self.full_name, self.sorted_name, self.name_type):
if len(stg) == 0:
return
self.save_new_person()
self.destroy()
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
def cancel_add_person(self):
self.destroy()
class SelectImageDialog(ScrolledDialog):
def __init__(
self, master, family_tree_id, ok_new_person, new_images,
existing_images, current_tree, treebard, *args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.family_tree = master
self.family_tree_id = family_tree_id
self.ok_new_person = ok_new_person
self.new_images = new_images
self.existing_images = existing_images
self.current_tree = current_tree
self.treebard = treebard
colors_type_id = get_colors_type_id(self.family_tree_id)
formats = make_formats_dict(colors_type_id=colors_type_id)
self.rc_menu = RightClickMenu(self.family_tree, self.treebard)
self.title("Select Images Dialog")
self.sizers = []
self.picvar = tk.IntVar()
self.make_widgets()
self.make_inputs()
self.make_user_input_widgets()
configall(self, self.formats)
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=32)
def make_widgets(self):
self.columnconfigure(1, weight=1)
self.window.columnconfigure(2, weight=1)
self.window.rowconfigure(1, weight=1)
self.grab_set()
def make_inputs(self):
def ok_selected_images():
save_path = f"{'/'.join(self.current_tree.split('/')[0:4])}/images/"
main_pic = self.picvar.get()
for idx, ent in enumerate(self.sizers):
main_image = 0
(resize_input, (orig_w, orig_h), name,
picorig, saveas_input, caption_input) = ent
ratio = resize_input.get()
if ratio.isnumeric():
ratio = int(ratio) / 100
else:
ratio = 1
new_width = int(orig_w * ratio)
new_height = int(orig_h * ratio)
ext = name.split(".")[-1]
new_file = f"{saveas_input.get()}.{ext}"
filename = f"{save_path}{new_file}"
source_path = filename.split('/')[0:5]
if idx == main_pic:
main_image = 1
if source_path != save_path.split('/')[0:5] or ratio != 1:
treepic = picorig.resize((new_width, new_height))
treepic.save(filename)
self.new_images.append((new_file, caption_input.get(), main_image))
else:
self.existing_images.append((name.split(".")[-2:], main_image))
self.ok_new_person.focus_set()
self.destroy()
def cancel_selected_images():
self.destroy()
msg = (f"Choose one image to be the main image for this element.\n\nThe "
f"image(s) will be copied to the tree's `images` folder at a "
f"percentage of current size. The original image will not be moved "
f"or changed.\n\nThe size of this computer's screen is "
f"{self.winfo_screenwidth()} pixels wide by {self.winfo_screenheight()} "
f"pixels high.")
lab = tk.Label(
self.window, text=msg, wraplength=650, justify="left",
bd=1, relief="raised")
self.picframe = tk.Frame(self.window)
buttons = tk.Frame(self.window)
ok_savepics = tk.Button(
buttons, text="OK", width=8, command=ok_selected_images)
cancel_savepics = tk.Button(
buttons, text="CANCEL", width=8, command=cancel_selected_images)
# children of self.window
lab.grid(column=0, row=0, ipadx=6, ipady=6)
self.picframe.grid(column=0, row=1, sticky="news")
buttons.grid(column=0, row=2, padx=12, pady=12, sticky="e")
# children of buttons
ok_savepics.grid(column=0, row=0, padx=(0,0), pady=(0,0))
cancel_savepics.grid(column=1, row=0, padx=(0,0), pady=(0,0))
def make_user_input_widgets(self):
imagedir = get_image_dir(self.family_tree_id)
filetypes = [
("ALL Files", "*.*"),
('JPG Files', '*.jpg'),
('PNG Files', '*.png'),
('GIF Files', '*.gif'),
('BMP Files', '*.bmp')]
filenames = tk.filedialog.askopenfilename(
multiple=True, filetypes=filetypes, initialdir=imagedir)
for idx, file in enumerate(filenames):
rad = tk.Radiobutton(self.picframe, value=idx, variable=self.picvar)
picorig = Image.open(file)
orig_w, orig_h = picorig.size
show = picorig.resize((100,100))
img1 = ImageTk.PhotoImage(show)
lab = tk.Label(self.picframe)
lab.image = img1
lab['image'] = img1
sourcelab = tk.Label(
self.picframe, text=file, anchor="w",
wraplength=360, justify="left")
sizelab = tk.Label(
self.picframe,
text="({} x {} pixels)".format(orig_w, orig_h), anchor="w")
resizelab = tk.Label(self.picframe, text="resize %:")
resize_input = tk.Entry(self.picframe, width=6)
resize_input.insert(0, "100")
filelab = tk.Label(self.picframe, text="Save As:")
saveas_input = tk.Entry(self.picframe, width=32)
captionlab = tk.Label(self.picframe, text="Caption:")
caption_input = tk.Entry(self.picframe, width=36)
name = file.split("/")[-1]
self.sizers.append(
(resize_input, (orig_w, orig_h), name,
picorig, saveas_input, caption_input))
# children of self.picframe
rad.grid(column=0, row=idx)
lab.grid(column=1, row=idx)
sourcelab.grid(column=2, row=idx, sticky="ew", padx=(9,0))
sizelab.grid(column=3, row=idx, sticky="ew")
resizelab.grid(column=4, row=idx, sticky="e", padx=(9,0))
resize_input.grid(column=5, row=idx, sticky="w")
filelab.grid(column=6, row=idx, sticky="e", padx=(9,0))
saveas_input.grid(column=7, row=idx, sticky="w")
captionlab.grid(column=8, row=idx, sticky="e", padx=(9,0))
caption_input.grid(column=9, row=idx, sticky="w")
HEADS = ("FULL NAME", "NAME TYPE", "AUTOSORT", "SORTED ORDER", "SOURCES")
class NamesTab(tk.Frame):
def __init__(
self, master, treebard, formats, family_tree_id, main, family_tree,
current_person_id, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.treebard = treebard
self.formats = formats
self.family_tree_id = family_tree_id
self.main = main
self.family_tree = family_tree
self.current_person_id = current_person_id
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
self.current_tree = get_current_tree(self.family_tree_id, cur)[0]
self.name_types = get_all_name_types(self.current_tree)
self.name_change_table = None
self.make_inputs()
cur.close()
conn.close()
def make_inputs(self):
top_frame = tk.Frame(self)
self.head_label = tk.Label(
top_frame, text="Change Current Person Name", anchor="w")
self.new_name_label = tk.Label(
top_frame,
text="New name for the current person:", anchor="w")
self.new_name_input = tk.Entry(top_frame, width=25)
self.new_name_type_cbo = Combobox(
top_frame, self.formats, family_tree=self.family_tree,
values=self.name_types, entry_width=19)
self.sortbutt = tk.Button(
top_frame, text="Alphabetize as:",
command=lambda: self.make_default_sort_order(
ent=self.new_name_input, sortent=self.new_name_sort_order))
self.new_name_sort_order = tk.Entry(top_frame, width=25)
self.name_change_table = self.make_existing_names_table(
self.current_person_id)
buttons = tk.Frame(self)
self.namer = tk.Button(
buttons, text="SAVE NEW NAMES & EDIT CHECKED NAMES",
command=self.update_name)
self.name_deleter = tk.Button(
buttons, text="DELETE CHECKED NAMES",
command=self.delete_name)
# children of self
top_frame.grid(
column=0, row=0, padx=(15,12), pady=(24,12), sticky="w", columnspan=2)
buttons.grid(
column=0, row=2, padx=(0,12), pady=12, sticky="e", columnspan=4)
# children of top_frame
self.head_label.grid(column=0, row=0, sticky="w", columnspan=2)
self.new_name_label.grid(column=0, row=1, sticky="w", columnspan=2)
self.new_name_input.grid(
column=0, row=2, padx=(30,0), pady=(6,0), sticky="w")
self.new_name_type_cbo.grid(
column=1, row=2, padx=(6,0), pady=(6,0), sticky="w")
self.sortbutt.grid(
column=2, row=2, padx=(6,0), pady=(6,0), sticky="w")
self.new_name_sort_order.grid(
column=3, row=2, padx=(6,0), pady=(6,0), sticky="w")
# children of buttons
self.namer.grid(column=0, row=0, sticky="e")
self.name_deleter.grid(column=1, row=0, padx=(6,0), sticky="e")
def make_existing_names_table(self, current_person_id):
if self.name_change_table:
self.name_change_table.destroy()
self.name_change_table = None
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
self.current_file = get_current_tree(self.family_tree_id, cur)
self.current_tree = self.current_file[0]
cur.execute("ATTACH ? as tree", (self.current_tree,))
cur.execute(select_name_all_current, (current_person_id,))
all_names = cur.fetchall()
self.name_change_table = tk.Frame(self)
for idx, head in enumerate(HEADS, 1):
lab = tk.Label(self.name_change_table, anchor="w", text=head)
lab.grid(column=idx, row=0, padx=(6,0), pady=(12,3), sticky="ew")
sep = tk.Frame(self.name_change_table)
sep.grid(column=1, row=1, sticky="ew", columnspan=5)
self.namevars = {}
margin_row = 2
indented_row = 3
for tup in all_names:
var = tk.IntVar()
name_id, name, name_type = tup
chk = tk.Checkbutton(self.name_change_table, variable=var)
lab = tk.Label(
self.name_change_table,
anchor="w",
text=f"Change name '{name}' (name type '{name_type}') to:")
ent = tk.Entry(self.name_change_table, width=25)
cbo = Combobox(
self.name_change_table, self.formats, family_tree=self.family_tree,
values=self.name_types, entry_width=19)
sortent = tk.Entry(self.name_change_table, width=25)
cur.execute(select_count_name_id_sources, (name_id,))
source_count = cur.fetchone()[0]
sourcer = LabelButtonText(
self.name_change_table, width=8, anchor='w', text=source_count)
sourcer.bind(
"<Button-1>",
lambda evt, name_id=name_id,
widg=sourcer: self.open_assertions_dialog(
evt, name_id, widg))
self.namevars[name_id] = [var, ent, cbo, sortent, chk, lab]
sorting_button = tk.Button(
self.name_change_table, text="Alphabetize as:",
command=lambda ent=ent, sortent=sortent:
self.make_default_sort_order(ent, sortent))
for child in (lab, ent, cbo, sorting_button, sortent, sourcer):
child.lift()
chk.grid(column=0, row=margin_row, padx=(12,0))
lab.grid(column=1, row=margin_row, sticky="ew", columnspan=4)
ent.grid(
column=1, row=indented_row, padx=(3,0), pady=(0,12), sticky="w")
cbo.grid(
column=2, row=indented_row, padx=(6,0), pady=(0,12), sticky="w")
sorting_button.grid(
column=3, row=indented_row, padx=(6,0), pady=(0,12), sticky="w")
sortent.grid(
column=4, row=indented_row, padx=(6,0), pady=(0,12), sticky="w")
sourcer.grid(
column=5, row=indented_row, padx=(6,12), pady=(0,12), sticky="w")
margin_row += 2
indented_row += 2
cur.execute("DETACH tree")
cur.close()
conn.close()
self.name_change_table.grid(column=0, row=1, columnspan=4, sticky="ew")
colors_type_id = get_colors_type_id(self.family_tree_id)
self.formats = make_formats_dict(colors_type_id=colors_type_id)
configall(self.name_change_table, self.formats)
return self.name_change_table # DON'T DELETE THIS
def open_assertions_dialog(self, evt=None, name_id=None, widg=None):
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_current_person_id)
current_person_id = cur.fetchone()[0]
position = None
if name_id in self.family_tree.open_assertions_dialogs_tracker["name_ids"]:
assertion_dialog = self.family_tree.open_assertions_dialogs_tracker[
"name_ids"][name_id]
position = assertion_dialog.close_dlg()
AssertionsDialog(
self.family_tree, self.family_tree_id, self.treebard, self,
widg, current_person_id, name_id=name_id, position=position)
cur.close()
conn.close()
def update_name(self):
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute('ATTACH ? as tree', (self.current_tree,))
for k,v in self.namevars.items():
if v[0].get() == 1:
ent, cbo, sortent, chk, lab = v[1:]
for widg in (ent, cbo.entry, sortent):
if len(widg.get()) == 0:
return
name = ent.get()
name_id = k
name_type = cbo.entry.get()
sorter = sortent.get()
cur.execute(select_name_type_id_by_string, (name_type,))
name_type_id = cur.fetchone()[0]
cur.execute(
update_name_type_sorter,
(name, name_type_id, sorter, name_id))
conn.commit()
ent.delete(0, "end")
cbo.entry.delete(0, "end")
sortent.delete(0, "end")
chk.deselect()
lab.config(text="Change name '{}' (name type '{}') to:".format(
name, name_type))
ChangeDate(
self.current_person_id, tag="person", conn=conn, cur=cur)
new_name = self.new_name_input.get()
name_type = self.new_name_type_cbo.get()
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
self.main.current_person_name = get_name_from_id(
self.current_person_id, self.family_tree)[0]["name"]
redraw_current_person_area(
self.current_person_id, self.main,
self.main.current_person_name, self.current_file)
if len(new_name) == 0 or len(name_type) == 0:
cur.execute('DETACH tree')
cur.close()
conn.close()
return
cur.execute(select_name_type_id_by_string, (name_type,))
name_type_id = cur.fetchone()[0]
cur.execute(
insert_name_and_type, (
new_name, name_type_id,
self.current_person_id,
self.new_name_sort_order.get()))
conn.commit()
ChangeDate(self.current_person_id, tag="person", conn=conn, cur=cur)
self.new_name_input.delete(0, 'end')
self.new_name_type_cbo.delete(0, 'end')
self.new_name_sort_order.delete(0, "end")
cur.execute('DETACH tree')
cur.close()
conn.close()
self.name_change_table = self.make_existing_names_table(
self.current_person_id)
def make_default_sort_order(self, ent, sortent):
new_name = ent.get().split()
last = new_name.pop()
last = "{},".format(last)
new_name.insert(0, last)
new_name = " ".join(new_name)
sortent.delete(0, "end")
sortent.insert(0, new_name)
def delete_name(self):
""" Delete selected names. Insert placeholder if the last name is deleted. """
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (self.current_tree,))
if len(self.namevars) < 2:
cur.execute(insert_name_placeholder, (self.current_person_id,))
conn.commit()
for k,v in self.namevars.items():
if v[0].get() == 1:
cur.execute(delete_name_by_id, (k,))
conn.commit()
self.name_change_table = self.make_existing_names_table(self.current_person_id)
cur.execute("DETACH tree")
cur.close()
conn.close()
class EntryAutoPerson(tk.Entry):
""" Unlike the EntryAuto class, this one does not prepend the last-used
value to the values list. A lot of time was wasted trying to make that
work with a dict, but really the same person name won't be used over
and over enough to justify the effort. After values change, run
`update_person_autofill_values()`.
"""
def __init__(
self, master, family_tree_id, family_tree,
autofill=False, values=None, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
self.master = master
self.family_tree_id = family_tree_id
self.family_tree = family_tree
self.autofill = autofill
self.values = values
self.pos = 0
self.current_id = None
self.filled_name = None
self.hits = None
if autofill is True:
self.bind("<KeyPress>", self.detect_pressed)
self.bind("<KeyRelease>", self.get_typed)
self.bind("<FocusIn>", self.deselect, add="+")
self.config(bd=0)
self.family_tree.person_autofills.append(self)
def detect_pressed(self, evt):
""" Run on every key press. """
if self.autofill is False:
return
key = evt.keysym
if len(key) == 1:
self.pos = self.index('insert')
keep = self.get()[0:self.pos].strip("+")
self.delete(0, 'end')
self.insert(0, keep)
def get_typed(self, evt):
""" Run on every key release. Filter out most non-alpha-numeric
keys. Run the functions not triggered by events.
"""
def do_it():
hits = self.match_string()
self.show_hits(hits, self.pos)
self.current_id = None
if self.autofill is False:
return
key = evt.keysym
# Allow alphanumeric characters.
if len(key) == 1:
do_it()
# Allow number signs, hyphens and apostrophes (#, -, ').
elif key in ('numbersign', 'minus', 'quoteright'):
do_it()
# elif key in ('plus'):
# postpend()
def match_string(self):
""" Match typed input to names already stored in hierarchical order. """
hits = []
got = self.get()
for k, v in self.values.items():
for dkt in v:
if dkt["name"].lower().startswith(got.lower()):
if (dkt, k) not in hits:
hits.append((dkt, k))
return hits
def show_hits(self, hits, pos):
cursor = pos + 1
if len(hits) != 0:
self.current_id = hits[0][1]
self.filled_name = hits[0][0]["name"]
self.delete(0, 'end')
self.insert(0, self.filled_name)
self.icursor(cursor)
self.hits = hits
def open_dupe_dialog(self, hits):
def ok_dupe_name():
self.delete(0, "end")
dupe_name_dlg.destroy()
def search_dupe_name():
self.delete(0, "end")
dupe_name_dlg.destroy()
def cancel_dupe_name():
nonlocal dupe_name_dlg_cancelled
dupe_name_dlg_cancelled = True
self.delete(0, "end")
dupe_name_dlg.destroy()
dupe_name_dlg_cancelled = False
self.right_id = tk.IntVar()
dupe_name_dlg = tk.Toplevel(self)
dupe_name_dlg.protocol("WM_DELETE_WINDOW", cancel_dupe_name)
frame = tk.Frame(dupe_name_dlg)
headlab = tk.Label(frame, bd=1, relief="raised", justify="left")
radfrm = tk.Frame(frame)
r = 0
for hit in hits:
dkt, iD = hit
name, name_type, used_by, dupe_name = (
dkt["name"], dkt["name type"], dkt["used by"], dkt["dupe name"])
if len(used_by) != 0:
used_by = ", name used by {}".format(used_by)
rdo = tk.Radiobutton(
radfrm, text=f"person #{iD} {name} ({name_type}){used_by} ",
variable=self.right_id, value=r, anchor="w")
rdo.grid(column=0, row=r, sticky="ew")
if r == 0:
rdo.select()
rdo.focus_set()
headlab.config(text=f"Which {name}?")
r += 1
buttons = tk.Frame(frame)
dupe_name_ok = tk.Button(buttons, text="OK", command=ok_dupe_name, width=7)
search_name = tk.Button(
buttons, text="SEARCH DUPE NAME", command=search_dupe_name)
dupe_name_cancel = tk.Button(
buttons, text="CANCEL", command=cancel_dupe_name, width=7)
dupe_name_dlg.title("Duplicate Stored Names")
frame.grid(column=0, row=0, sticky="ew")
headlab.grid(column=0, row=0, ipadx=6, ipady=6, padx=12, pady=12)
radfrm.grid(column=0, row=1)
buttons.grid(column=0, row=2, pady=12, padx=12)
dupe_name_ok.grid(column=0, row=0, sticky="e", padx=(0,12))
search_name.grid(column=1, row=0, sticky="e", padx=(0,12))
dupe_name_cancel.grid(column=2, row=0, sticky="e", padx=0)
colors_type_id = get_colors_type_id(self.family_tree_id)
formats = make_formats_dict(colors_type_id=colors_type_id)
configall(dupe_name_dlg, formats)
self.wait_window(dupe_name_dlg)
if dupe_name_dlg_cancelled is False:
selected = self.right_id.get()
return hits[selected]
def deselect(self, evt):
""" Since this is an autofill, replacement of selected text doesn't
work as expected, so clear the selection.
"""
self.select_clear()
def validate_id(self, idnum):
""" Get a name to fill in when ID is input. Unlike name autofill, the best
name to fill in is determined by hierarchy column in name_type table so
names are put into the dict in the right order so the first name
found will be the most suitable one according to the name types hierarchy.
E.G. birth name trumps pseudonym and pseudonym trumps nickname.
"""
def err_done(self, msg):
self.delete(0, 'end')
msg[0].grab_release()
msg[0].destroy()
self.focus_set()
if idnum not in self.family_tree.person_autofill_values:
msg = open_message(
self,
persons_msg[2],
"Unknown Person ID",
"OK")
msg[0].grab_set()
msg[2].config(
command=lambda entry=self, msg=msg: err_done(
self, msg))
else:
name_from_id = get_name_from_id(idnum, self.family_tree)
return name_from_id
def check_name(self, redo=None):
""" Used in families.py, main.py, roles.py. """
name_from_id = None
if redo is None:
filled = self.get().strip()
else:
filled = redo
if filled.startswith("#"):
filled = filled
elif filled.startswith("+") or filled.endswith("+"):
filled = filled
else:
filled = self.filled_name
if filled is None:
return None
elif len(filled) == 0:
return None
elif filled.startswith("#"):
if "+" in filled:
filled = filled.strip().strip("+").strip()
name_from_id = self.validate_id(int(filled.lstrip("#").strip()))
if name_from_id is None:
return None
else:
self.delete(0, 'end')
return name_from_id
elif filled.startswith("+") or filled.endswith("+"):
return "add_new_person"
dupes = []
for hit in self.hits:
the_one = hit[0]
if the_one["dupe name"] is False or the_one["name"] != self.filled_name:
continue
else:
dupes.append(hit)
if len(dupes) > 1:
right_dupe = self.open_dupe_dialog(dupes)
return right_dupe
elif len(self.hits) > 0:
pass
else:
self.delete(0, 'end')
if len(self.hits) != 0:
return self.hits[0]