families.py
Nov 25, 2022 3:06:48 GMT -8
Post by Uncle Buddy on Nov 25, 2022 3:06:48 GMT -8
<drive>:\treebard\families.py Last Changed 2024-07-25
# families.py
import tkinter as tk
import sqlite3
from base import tree_path
from redraw import Redraw, get_name_from_id
from base import center_dialog, resize_scrolled_content
from widgets import(
make_formats_dict, ScrolledDialog, configall, TabBook, Scrollbar,
run_statusbar_tooltips, EntryLabel, get_color_scheme_id, NEUTRAL_COLOR)
from persons import open_new_person_dialog, EntryAutoPerson
from messages_context_help import person_unlink_dlg_help_msg
from dates import (
format_stored_date, get_date_formats, validate_date, make_date_sorter)
from unigeds_queries import (
select_event_id_birth, update_person_gender, select_event_type_death,
insert_couple_new, update_event_couple, select_event_couple,
select_couple_partners, insert_couple_new_partner, select_event_id_death,
update_couple_new_person1, update_couple_new_person2,
select_event_death_date, update_event_date, insert_event_date,
select_person_gender_by_id, select_couple_id_by_partners, select_name_by_id,
select_event_alt_parents, select_couple_id_partner1, select_couple_id_partner2,
select_event_date_sorter, select_couple_partner1_not, select_couple_partner2_not,
update_event_couple_null_by_event_id, select_couple_id_by_partners2,
select_event_date_birth, select_couple_id_by_partners_r2d2,
)
import dev_tools as dt
from dev_tools import look, seeline
ALT_PARENT_TYPES = {
"adoption": "adoptive parent", "fosterage": "foster parent",
"guardianship": "guardian"}
select_children_data = '''
SELECT date_sorter, event_type_text, event.person_id, name_text, date,
event.couple_id, event_id
FROM person
JOIN name ON name.person_id = person.person_id
JOIN event ON event.person_id = person.person_id
JOIN couple ON event.couple_id = couple.couple_id
JOIN event_type ON event.event_type_id = event_type.event_type_id
JOIN main_tbd ON main_tbd.name_id = name.name_id
WHERE (person_id1 = ? OR person_id2 = ?)
AND event_type_text IN ('birth', 'adoption', 'fosterage', 'guardianship')
'''
class NuclearFamiliesTable(tk.Frame):
def __init__(
self, master, formats, tree, treebard,
events_table, right_panel, main=None, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.formats = formats
self.tree = tree
self.treebard = treebard
self.events_table = events_table
self.right_panel = right_panel
self.main = main
self.pardrads = tk.IntVar(None, 99)
self.original_text = None
self.date_prefs = get_date_formats()
self.make_widgets()
self.values = self.tree.person_autofill_values
self.dlg_is_open = False
self.config(bd=1)
def redraw(self, cur, current_person_changing=False):
rdw = Redraw(main=self.main, tree=self.tree)
rdw.redraw_person_tab()
if current_person_changing:
rdw.redraw_names_tab()
TabBook.resize_scrolled_dialog_with_tabbook(
self.tree, self.main.canvas, self.main)
def make_widgets(self):
self.nukefam_canvas = tk.Canvas(self, bd=0, highlightthickness=0)
ScrolledDialog.nested_canvases.append(self.nukefam_canvas)
self.nukefam_window = tk.Frame(self.nukefam_canvas)
self.nukefam_canvas.create_window(
0, 0, anchor="nw", window=self.nukefam_window)
nukefam_sbv = Scrollbar(
self, self.formats, command=self.nukefam_canvas.yview,
hideable=True)
self.nukefam_canvas.config(yscrollcommand=nukefam_sbv.set)
nukefam_sbh = Scrollbar(
self, self.formats, orient='horizontal',
command=self.nukefam_canvas.xview, hideable=True)
septor = tk.Frame(self)
self.nukefam_canvas.config(xscrollcommand=nukefam_sbh.set)
self.parents_table = tk.LabelFrame(
self.nukefam_window, text="Current Person's Parents")
self.progeny_table = tk.LabelFrame(
self.nukefam_window, text="Current Person's Partners & Children")
self.add_area = tk.Frame(self.nukefam_window)
# children of self
self.rowconfigure(0, weight=1)
self.nukefam_canvas.grid(column=0, row=0, sticky="news")
nukefam_sbv.grid(column=1, row=0, sticky="ns")
nukefam_sbh.grid(column=0, row=1, sticky="ew")
septor.grid(column=0, row=2, sticky="sew")#, columnspan=3
# children of self.nukefam_window
self.parents_table.grid(column=0, row=0, sticky="news")
self.progeny_table.grid(column=0, row=1, sticky="news", pady=12)
self.add_area.grid(column=0, row=2, sticky="ew")
def make_nukefam_inputs(self):
""" This is called in main.py and redraw.py. """
for row in range(self.nukefam_window.grid_size()[1]):
self.nukefam_window.rowconfigure(row, weight=1)
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
self.parents_data = self.get_parents_data(cur)
self.alt_parents_data = self.get_alt_parents_data(cur)
self.partners_data = self.get_partners_data(cur)
self.children_data = self.get_children_data(cur)
self.make_parent_widgets()
self.populate_inputs()
for idx, dkt in enumerate(self.alt_parents_data, 1):
self.make_alt_parents_row(idx, dkt)
self.make_partners_and_children_widgets()
self.make_add_area()
cur.close()
conn.close()
def populate_inputs(self):
pa, ma = self.parents_data
if pa["pa_id"]:
self.pa_input.delete(0, "end")
self.pa_input.insert(0, pa["pa_name"])
if ma["ma_id"]:
self.ma_input.delete(0, "end")
self.ma_input.insert(0, ma["ma_name"])
def get_parents_data(self, cur):
pa_id = None
pa_name = None
ma_id = None
ma_name = None
parent_ids = None
couple_id = None
event_id = None
cur.execute(select_event_couple, (self.tree.persid,))
result = cur.fetchone()
if result:
couple_id, event_id = result
cur.execute(select_couple_partners, (couple_id,))
parent_ids = cur.fetchone()
if parent_ids:
pa_id, ma_id = parent_ids
for idx, num in enumerate(parent_ids):
if not num:
continue
else:
name = get_name_from_id(num, cur)
if idx == 0:
pa_name = name
elif idx == 1:
ma_name = name
return [
{"pa_id": pa_id, "pa_name": pa_name, "couple_id": couple_id,
"event_id": event_id},
{"ma_id": ma_id, "ma_name": ma_name, "couple_id": couple_id,
"event_id": event_id}]
def get_alt_parents_data(self, cur):
parent_ids = None
alt_parents = []
cur.execute(select_event_alt_parents, (self.tree.persid,))
parentless_alt_parent_events = cur.fetchall()
for tup in parentless_alt_parent_events:
dkt = {}
date_string, dkt["event_id"], dkt["couple_id"], event_type = tup
dkt["label"] = ALT_PARENT_TYPES[event_type]
dkt["date_sorter"] = [int(i) for i in date_string.split(",")]
couple_id = dkt["couple_id"]
if couple_id:
cur.execute(select_couple_partners, (couple_id,))
parent_ids = cur.fetchone()
else:
parent_ids = (None, None)
dkt["left_parent_id"], dkt["right_parent_id"] = parent_ids
for idx, parent_id in enumerate(parent_ids):
if parent_id:
name = get_name_from_id(parent_id, cur)
if idx == 0:
dkt["left_parent_name"] = name
elif idx == 1:
dkt["right_parent_name"] = name
else:
if idx == 0:
dkt["left_parent_name"] = None
elif idx == 1:
dkt["right_parent_name"] = None
alt_parents.append(dkt)
alt_parents.sort(key = lambda i: i["date_sorter"])
return alt_parents
def get_partners_data(self, cur):
partners = []
cur.execute(select_couple_id_partner1, (self.tree.persid,))
person_ids1 = [list(i) for i in cur.fetchall()]
cur.execute(select_couple_id_partner2, (self.tree.persid,))
person_ids2 = [list(i) for i in cur.fetchall()]
partner_and_couple_ids = []
for lst in (person_ids1, person_ids2):
for tup in lst:
if tup not in partner_and_couple_ids:
partner_and_couple_ids.append(tup)
for lst in partner_and_couple_ids:
dkt = {"date_sorter": [10000,0,0]}
partner_id, couple_id = lst
dkt["partner_id"] = partner_id
dkt["couple_id"] = couple_id
cur.execute(select_event_date_sorter, (couple_id,))
event_ids_and_date_sorters = cur.fetchall()
for tup in event_ids_and_date_sorters:
event_id, date_string = tup
dkt["event_id"] = event_id
date_sorter = [int(i) for i in date_string.split(",")]
if date_sorter < dkt["date_sorter"]:
dkt["date_sorter"] = date_sorter
if dkt["date_sorter"] == [10000,0,0]:
dkt["date_sorter"] = [0,0,0]
partner_name = get_name_from_id(dkt["partner_id"], cur)
dkt["partner_name"] = partner_name
partners.append(dkt)
partners.sort(key=lambda i: i["date_sorter"])
return partners
def get_children_data(self, cur):
children = []
cur.execute(
select_children_data,
(self.tree.persid, self.tree.persid))
child_ids = [list(i) for i in cur.fetchall()]
for lst in child_ids:
dkt = {}
event_type = lst[1]
child_id = lst[2]
dkt["child_name"] = lst[3]
dkt["child_id"] = child_id
dkt["event_id"] = lst[6]
dkt["event_type"] = event_type
if event_type != 'birth':
alt_parentage_date = lst[4]
formatted_date = format_stored_date(
alt_parentage_date, date_prefs=self.date_prefs)
dkt["birth_date"] = formatted_date
dkt["date_sorter"] = lst[0]
else:
birth_date = lst[4]
formatted_birth_date = format_stored_date(
birth_date, date_prefs=self.date_prefs)
dkt["birth_date"] = formatted_birth_date
dkt["date_sorter"] = lst[0]
death_date = "-0000-00-00-------"
cur.execute(select_event_death_date, (child_id,))
result = cur.fetchone()
formatted_death_date = ""
if result:
formatted_death_date = format_stored_date(
result[0], date_prefs=self.date_prefs)
dkt["death_date"] = formatted_death_date
couple_id = lst[5]
dkt["couple_id"] = couple_id
cur.execute(
select_couple_partner1_not, (couple_id, self.tree.persid))
result1 = cur.fetchone()
if result1 is None:
cur.execute(
select_couple_partner2_not,
(couple_id, self.tree.persid))
result2 = cur.fetchone()
if result2:
parent_id = result2[0]
else:
parent_id = None
else:
parent_id = result1[0]
dkt["parent_id"] = parent_id
children.append(dkt)
children.sort(key=lambda i: i["date_sorter"])
return children
def get_birth_date(self, cur, child_id):
cur.execute(select_event_date_birth, (child_id,))
birth_date = cur.fetchone()[0]
formatted_date = format_stored_date(
birth_date, date_prefs=self.date_prefs)
date_sorter = make_date_sorter(birth_date)
return formatted_date, date_sorter
def show_id_tips(self, evt, row, column):
self.id_tip = None
idnum = None
widg = evt.widget
master = widg.master
if master == self.parents_table:
if row == 0:
if column == 1:
idnum = self.parents_data[0]["pa_id"]
elif column == 3:
idnum = self.parents_data [1]["ma_id"]
elif row > 0:
if column == 1:
for dkt in self.alt_parents_data:
if widg == dkt["left_input"]:
idnum = dkt["left_parent_id"]
break
elif column == 3:
for dkt in self.alt_parents_data:
if widg == dkt["right_input"]:
idnum = dkt["right_parent_id"]
break
elif master == self.progeny_table:
if column != 0:
return
else:
for dkt in self.children_data:
if widg == dkt["child_input"]:
idnum = dkt["child_id"]
break
elif master.winfo_class() == "Frame":
for dkt in self.partners_data:
if widg == dkt["partner_input"]:
idnum = dkt["partner_id"]
break
if idnum is None:
return
self.id_tip = tk.Toplevel(bd=1)
self.id_tip.wm_overrideredirect(1)
x, y = widg.winfo_pointerxy()
self.id_tip.geometry(f"+{str(x)}+{str(y+32)}")
lab = tk.Label(
self.id_tip, text=f"person ID #{idnum}",
bg=NEUTRAL_COLOR, fg="black")
# bg=self.formats["head_bg"], fg=self.formats["fg"])
lab.grid(sticky="news", ipadx=3, ipady=1)
def unshow_id_tips(self, evt):
if self.id_tip:
self.id_tip.destroy()
def make_parent_widgets(self):
pa_lab = tk.Label(self.parents_table, text="father:", anchor="e")
self.pa_input = EntryAutoPerson(
self.parents_table, self.tree,
cursor="hand2", width=32, autofill=True,
values=self.tree.person_autofill_values)
ma_lab = tk.Label(self.parents_table, text="mother:", anchor="e")
self.ma_input = EntryAutoPerson(
self.parents_table, self.tree,
cursor="hand2", width=32, autofill=True,
values=self.tree.person_autofill_values)
self.parents_table.columnconfigure(1, weight=1)
self.parents_table.columnconfigure(3, weight=1)
pa_lab.grid(column=0, row=0, sticky="ew", padx=(6,0))
self.pa_input.grid(column=1, row=0, sticky="ew", padx=(6,0))
ma_lab.grid(column=2, row=0, sticky="ew", padx=(18,0))
self.ma_input.grid(column=3, row=0, sticky="ew", padx=6)
spacer = tk.Frame(self.parents_table, height=6)
spacer.grid()
for widg in (self.pa_input, self.ma_input):
grid_info = widg.grid_info()
column = grid_info["column"]
row = grid_info["row"]
widg.bind(
"<Control-Button-1>", lambda evt, row=row, column=column:
self.change_current_person(evt, row, column), add="+")
widg.bind("<FocusIn>", self.get_initial, add="+")
column = widg.grid_info()["column"]
widg.bind(
"<FocusOut>", lambda evt, widg=widg, col=column:
self.get_final(evt, widg, col), add="+")
widg.bind(
"<Enter>", lambda evt, row=row, column=column:
self.show_id_tips(evt, row, column), add="+")
widg.bind("<Leave>", self.unshow_id_tips, add="+")
if column == 1:
dkt = self.parents_data[0]
dkt["pa_input"] = self.pa_input
elif column == 3:
dkt = self.parents_data[1]
dkt["ma_input"] = self.ma_input
def make_alt_parents_row(self, idx, dkt):
alt_parent_type = dkt["label"]
left_parent_id = dkt["left_parent_id"]
right_parent_id = dkt["right_parent_id"]
left_lab = tk.Label(
self.parents_table, text=f"{alt_parent_type}:", anchor="e")
left_input = EntryAutoPerson(
self.parents_table, self.tree, cursor="hand2", width=32,
autofill=True, values=self.tree.person_autofill_values)
right_lab = tk.Label(
self.parents_table, text=f"{alt_parent_type}:", anchor="e")
right_input = EntryAutoPerson(
self.parents_table, self.tree,
cursor="hand2", width=32, autofill=True,
values=self.tree.person_autofill_values)
left_input.delete(0, "end")
right_input.delete(0, "end")
if left_parent_id:
left_input.insert(0, dkt["left_parent_name"])
if right_parent_id:
right_input.insert(0, dkt["right_parent_name"])
left_lab.grid(column=0, row=idx, sticky="ew", padx=(6,0))
left_input.grid(column=1, row=idx, sticky="ew", padx=(6,0))
right_lab.grid(column=2, row=idx, sticky="ew", padx=(18,0))
right_input.grid(column=3, row=idx, sticky="ew", padx=6)
dkt["left_input"] = left_input
dkt["right_input"] = right_input
for widg in (left_input, right_input):
grid_info = widg.grid_info()
column = grid_info["column"]
row = grid_info["row"]
widg.bind(
"<Control-Button-1>", lambda evt, row=row, column=column:
self.change_current_person(evt, row, column), add="+")
widg.bind("<FocusIn>", self.get_initial, add="+")
widg.bind(
"<FocusOut>", lambda evt, widg=widg, col=column:
self.get_final(evt, widg, col), add="+")
widg.bind(
"<Enter>", lambda evt, row=row, column=column:
self.show_id_tips(evt, row, column), add="+")
widg.bind("<Leave>", self.unshow_id_tips, add="+")
def make_partners_and_children_widgets(self):
partner_count = 0
row_count = 0
for dkt in self.partners_data:
partner_id = dkt["partner_id"]
couple_id = dkt["couple_id"]
self.make_partner_row(row_count, dkt, partner_count, partner_id)
for child_count, dkkt in enumerate(self.children_data):
if dkkt["couple_id"] != couple_id:
continue
else:
row_count += 1
self.make_child_row(dkkt, row_count, child_count)
row_count += 1
partner_count += 1
spacer = tk.Frame(self.progeny_table)
spacer.grid(column=0, row=row_count, sticky="ew", pady=6)
def make_progeny_rows(self, child_rows):
spacer = tk.Frame(self.progeny_table)
spacer.grid(column=0, row=row_count, sticky="ew", pady=6)
def make_partner_row(self, row_count, dkt, partner_count, partner_id):
partner_name, couple_id = dkt["partner_name"], dkt["couple_id"]
self.progeny_table.columnconfigure(0, weight=1)
pardframe = tk.Frame(self.progeny_table)
pardframe.columnconfigure(1, weight=1)
rad = tk.Radiobutton(
pardframe, anchor="w", variable=self.pardrads,
value=partner_count)
ent = EntryAutoPerson(
pardframe, self.tree, autofill=True,
cursor="hand2", width=32,
values=self.tree.person_autofill_values)
ent.delete(0, "end")
if partner_name:
ent.insert(0, partner_name)
pardframe.grid(
column=0, row=row_count, sticky="ew", columnspan=5, padx=6)
rad.grid(column=0, row=row_count, sticky="w", padx=(6,0))
ent.grid(column=1, row=row_count, sticky="ew", padx=(6,0))
grid_info = ent.grid_info()
row = grid_info["row"]
ent.bind(
"<Control-Button-1>", lambda evt, row=row:
self.change_current_person(evt, row), add="+")
ent.bind("<FocusIn>", self.get_initial, add="+")
column = grid_info["column"]
ent.bind(
"<FocusOut>", lambda evt, widg=ent, col=column:
self.get_final(evt, widg, col), add="+")
ent.bind(
"<Enter>", lambda evt, row=row, column=column:
self.show_id_tips(evt, row, column), add="+")
ent.bind("<Leave>", self.unshow_id_tips, add="+")
dkt["partner_input"] = ent
def make_child_row(self, dkkt, row_count, child_count):
child_name, birth_date, death_date = (
dkkt["child_name"], dkkt["birth_date"], dkkt["death_date"])
ent = EntryAutoPerson(
self.progeny_table, self.tree, width=32,
autofill=True, cursor="hand2",
values=self.tree.person_autofill_values)
ent.delete(0, "end")
ent.insert(0, child_name)
birthlab = EntryLabel(self.progeny_table, self.formats, cursor="hand2")
birthlab.delete(0, "end")
birthlab.insert(0, birth_date)
lab = tk.Label(self.progeny_table, text="to", anchor="w")
deathlab = EntryLabel(self.progeny_table, self.formats, cursor="hand2")
deathlab.delete(0, "end")
deathlab.insert(0, death_date)
# children of self.progeny_table
ent.grid(column=0, row=row_count, sticky="ew", padx=(48,0))
birthlab.grid(column=1, row=row_count, sticky="ew", padx=(12,0))
lab.grid(column=2, row=row_count, sticky="ew", padx=(12,0))
deathlab.grid(column=3, row=row_count, sticky="ew", padx=(12,6))
ent.bind(
"<Control-Button-1>", lambda evt, row=row_count:
self.change_current_person(evt, row), add="+")
ent.bind(
"<Enter>", lambda evt, row=row_count, column=0:
self.show_id_tips(evt, row, column), add="+")
ent.bind("<Leave>", self.unshow_id_tips, add="+")
for widg in (ent, birthlab, deathlab):
widg.bind("<FocusIn>", self.get_initial, add="+")
column = widg.grid_info()["column"]
widg.bind(
"<FocusOut>", lambda evt, widg=widg, col=column:
self.get_final(evt, widg, col), add="+")
self.children_data[child_count]["child_input"] = ent
def get_initial(self, evt):
""" On focus into a table cell, store:
--a reference to the cell,
--the original cell contents.
"""
self.inwidg = widg = evt.widget
self.original_text = widg.get()
def get_final(self, evt, widg, col):
""" All without the use of an OK button:
--Compare the original_text and final_text of a table cell.
--Validate the new contents for dates/names.
--Open any dialogs needed:
--date clarification dialog for ambiguous dates
--duplicate names dialog
--Update the database if the new content is valid.
--Return the original content if new content is not valid.
"""
if self.original_text is None:
return
if widg.get() != self.original_text:
self.edit_ok(widg)
def change_current_person(self, evt, row, column=None):
conn = sqlite3.connect(self.tree.file)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
if self.id_tip:
self.id_tip.destroy()
width, height = self.tree.geometry().split("+", 1)[0].split("x")
screen = tk.Frame(self.tree, bg=self.formats["bg"], width=width, height=height)
screen.grid()
evt_widg = evt.widget
master = evt_widg.master
if len(evt_widg.get()) == 0:
screen.destroy()
return
# Append old value of persid to stack before updating
# persid to its new value.
self.tree.forward_stack = []
self.tree.backward_stack.append(self.tree.persid)
if master == self.parents_table and row == 0:
if column == 1:
parent_id = "pa_id"
dkt = self.parents_data[0]
elif column == 3:
parent_id = "ma_id"
dkt = self.parents_data[1]
self.tree.persid = dkt[parent_id]
elif master == self.parents_table and row and row > 0:
realrow = row - 1
if column == 1:
parent_id = "left_parent_id"
if column == 3:
parent_id = "right_parent_id"
self.tree.persid = self.alt_parents_data[realrow][parent_id]
elif master.master == self.progeny_table:
for dkt in self.partners_data:
if dkt["partner_input"] == evt_widg:
self.tree.persid = dkt["partner_id"]
break
elif master == self.progeny_table:
for dkt in self.children_data:
if dkt["child_input"] == evt_widg:
self.tree.persid = dkt["child_id"]
break
else:
screen.destroy()
return
self.redraw(cur, current_person_changing=True)
screen.destroy()
cur.close()
conn.close()
def update_child(
self, conn, cur, widg, column=None, new_child_id=None, event_id=None):
for dkt in self.children_data:
if widg == dkt["child_input"]:
couple_id, event_id = dkt["couple_id"], dkt["event_id"]
break
if new_child_id:
cur.execute(update_event_couple, (None, event_id))
cur.execute(select_event_id_birth, (new_child_id,))
birth_id = cur.fetchone()[0]
cur.execute(update_event_couple, (couple_id, birth_id))
conn.commit()
return
final_text = widg.get().strip()
if final_text == self.original_text:
return
if len(final_text) != 0:
return
cur.execute(update_event_couple_null_by_event_id, (event_id,))
conn.commit()
def update_date(self, conn, cur, inwidg, column, row):
event_id = None
input_date = inwidg.get().strip().lower()
for dkt in self.children_data:
if row == dkt["child_input"].grid_info()["row"]:
row_dkt = dkt
break
if column == 1:
event_id = row_dkt["event_id"]
elif column == 3:
child_id = dkt["child_id"]
cur.execute(select_event_id_death, (child_id,))
result = cur.fetchone()
if result:
event_id = result[0]
else:
cur.execute(select_event_type_death)
death_event_id = cur.fetchone()[0]
storable_date = validate_date(self.treebard, input_date, self.formats)
date_sorter = make_date_sorter(storable_date)
cur.execute(insert_event_date,
(storable_date, child_id, death_event_id, date_sorter))
conn.commit()
event_id = cur.lastrowid
formatted_date = format_stored_date(
storable_date, date_prefs=self.date_prefs)
inwidg.delete(0, "end")
inwidg.insert(0, formatted_date)
return
if event_id is None:
inwidg.delete(0, "end")
return
storable_date = validate_date(self.treebard, input_date, self.formats)
date_sorter = make_date_sorter(storable_date)
cur.execute(
update_event_date, (storable_date, date_sorter, event_id))
conn.commit()
formatted_date = format_stored_date(
storable_date, date_prefs=self.date_prefs)
inwidg.delete(0, "end")
inwidg.insert(0, formatted_date)
def unlink_parent(self, column, conn, cur, row):
""" If user deletes a name from a parent cell:
--Don't delete the person from the tree.
--Don't delete the person from the couple. Instead:
--If there's already no name in the other parent field, delete the
couple_id from the event row.
--Else search for a couple_id with only the remaining person in it.
--If that couple exists, use it in the event row.
--Else create such a couple and use it in the event row.
"""
if column == 1:
if row == 0:
dkt, otherdkt = self.parents_data
parent_id = dkt["pa_id"]
other_parent_id = otherdkt["ma_id"]
elif row > 0:
dkt = self.alt_parents_data[row-1]
parent_id = dkt["left_parent_id"]
other_parent_id = dkt["right_parent_id"]
couple_id, event_id = dkt["couple_id"], dkt["event_id"]
db_col = "person_id1"
other_db_col = "person_id2"
elif column == 3:
if row == 0:
otherdkt, dkt = self.parents_data
parent_id = dkt["ma_id"]
other_parent_id = otherdkt["pa_id"]
elif row > 0:
dkt = self.alt_parents_data[row-1]
parent_id = dkt["right_parent_id"]
other_parent_id = dkt["left_parent_id"]
couple_id, event_id = dkt["couple_id"], dkt["event_id"]
db_col = "person_id2"
other_db_col = "person_id1"
if other_parent_id is None:
cur.execute(update_event_couple_null_by_event_id, (event_id,))
conn.commit()
else:
cur.execute(
f''' SELECT couple_id
FROM couple
WHERE {db_col} IS null AND {other_db_col} = ?
''',
(other_parent_id,))
result = cur.fetchone()
if result:
cur.execute(update_event_couple, (result[0], dkt["event_id"]))
conn.commit()
else:
cur.execute(
f''' INSERT INTO couple ({other_db_col})
VALUES (?)
''',
(other_parent_id,))
conn.commit()
couple_id = cur.lastrowid
cur.execute(update_event_couple, (couple_id, event_id))
conn.commit()
def check_partner_options(
self, inwidg, grid_info, conn, cur, new_person_id=None):
def get_child_data(ddd, couple_id):
if ddd["couple_id"] == couple_id:
return ddd["event_type"], ddd["child_name"]
couple_id_fks = []
self.final_text = inwidg.get()
for dkt in self.partners_data:
if dkt["partner_input"] == inwidg:
couple_id, partner_id, partner_name = (
dkt["couple_id"], dkt["partner_id"], dkt["partner_name"])
for ddd in self.children_data:
if couple_id == ddd["couple_id"]:
event_type, child_name = get_child_data(ddd, couple_id)
couple_id_fks.append(
[ddd["event_id"], event_type, child_name])
break
if self.original_text != self.final_text:
existing_couple_id = None
cur.execute(select_couple_id_by_partners_r2d2,
(self.tree.persid, new_person_id, self.tree.persid, new_person_id))
result = cur.fetchone()
if result:
existing_couple_id = result[0]
self.change_to_existing_couple(existing_couple_id, inwidg, conn, cur)
else:
self.update_partner(new_person_id, inwidg, conn, cur)
elif len(self.original_text) == 0:
return
elif len(self.final_text) == 0:
self.update_partner(None, inwidg, conn, cur)
def change_to_existing_couple(
self, existing_couple_id, inwidg, conn, cur):
event_ids = []
for dkt in self.partners_data:
if dkt["partner_input"] == inwidg:
old_couple_id = dkt["couple_id"]
break
for dkt in self.children_data:
if dkt["couple_id"] == old_couple_id:
event_ids.append(dkt["event_id"])
for event_id in event_ids:
cur.execute(update_event_couple, (existing_couple_id, event_id))
conn.commit()
def update_parent(self, new_parent_id, conn, cur, column, row, inwidg):
def innerloop(dkt, column, key, other_parent_id):
if dkt.get(key) and dkt[key] == inwidg:
event_id = dkt["event_id"]
if key in ("pa_input", "left_input"):
tup = (
new_parent_id, other_parent_id, other_parent_id,
other_parent_id)
elif key in ("ma_input", "right_input"):
tup = (
other_parent_id, new_parent_id, other_parent_id,
other_parent_id)
cur.execute(select_couple_id_by_partners2, tup)
result = cur.fetchone()
if result:
couple_id = result[0]
else:
cur.execute(insert_couple_new_partner, tup[0:2])
conn.commit()
couple_id = cur.lastrowid
return couple_id, event_id
return None, None
if row == 0:
collection = self.parents_data
if column == 1:
key = "pa_input"
other_parent_id = self.parents_data[1]["ma_id"]
elif column == 3:
key = "ma_input"
other_parent_id = self.parents_data[0]["pa_id"]
elif row > 0:
collection = self.alt_parents_data
if column == 1:
key = "left_input"
other_parent_id = self.alt_parents_data[row-1]["right_parent_id"]
elif column == 3:
key = "right_input"
other_parent_id = self.alt_parents_data[row-1]["left_parent_id"]
for dkt in collection:
couple_id, event_id = innerloop(dkt, column, key, other_parent_id)
if couple_id and event_id:
break
if couple_id is None:
if column == 1:
cur.execute(insert_couple_new_partner, (new_parent_id, None))
elif column == 3:
cur.execute(insert_couple_new_partner, (None, new_parent_id))
conn.commit()
couple_id = cur.lastrowid
else:
if column == 1:
cur.execute(
update_couple_new_person1, (new_parent_id, couple_id))
elif column == 3:
cur.execute(
update_couple_new_person2, (new_parent_id, couple_id))
conn.commit()
cur.execute(update_event_couple, (couple_id, event_id))
conn.commit()
def update_partner(self, new_partner_id, inwidg, conn, cur):
for dkt in self.partners_data:
if dkt["partner_input"] == inwidg:
couple_id = dkt["couple_id"]
break
cur.execute(select_couple_partners, (couple_id,))
partners = cur.fetchone()
pards = ("person_id1", "person_id2")
for idx, person_id in enumerate(partners):
if person_id != self.tree.persid:
pard = pards[idx]
break
update_couple_one_partner = f'''
UPDATE couple
SET {pard} = ?
WHERE couple_id = ?
'''
cur.execute(update_couple_one_partner, (new_partner_id, couple_id))
conn.commit()
def make_add_area(self):
plab = tk.Label(
self.add_area, text="Add partner to current person:", anchor="e")
self.new_partner_input = EntryAutoPerson(
self.add_area, self.tree, width=24,
autofill=True, values=self.tree.person_autofill_values)
partner_adder = tk.Button(
self.add_area, text="ADD PARTNER",
command=self.add_partner, width=13)
clab = tk.Label(
self.add_area,
text="Add child to selected partner:",
anchor="e")
self.new_child_input = EntryAutoPerson(
self.add_area, self.tree, width=24,
autofill=True, values=self.tree.person_autofill_values)
child_adder = tk.Button(
self.add_area, text="ADD CHILD",
command=self.add_child, width=13)
buttons = tk.Frame(self.add_area)
self.partner_linker = tk.Button(
buttons, text="LINK SELECTED PARTNER: CLICK HERE, THEN EVENT, THEN OK",
command=self.link_partner_to_event)
self.pardlink_ok = tk.Button(
buttons, text="OK", width=3, state="disabled")
# children of self.add_area
self.add_area.columnconfigure(1, weight=1)
plab.grid(column=0, row=0, sticky="ew", pady=(0,6), padx=(6,0))
self.new_partner_input.grid(
column=1, row=0, padx=(6,0), sticky="ew", pady=(0,6))
partner_adder.grid(column=2, row=0, padx=6, sticky="w", pady=(0,6))
clab.grid(column=0, row=1, sticky="ew", padx=(6,0))
self.new_child_input.grid(column=1, row=1, padx=(6,0), sticky="ew")
child_adder.grid(column=2, row=1, padx=6, sticky="w", pady=(0,6))
buttons.grid(column=0, row=2, pady=6, sticky="e", columnspan=3)
self.partner_linker.grid(column=0, row=0, pady=6, sticky="ew")
self.pardlink_ok.grid(column=1, row=0, padx=(3,0))
def link_partner_to_event(self):
def link():
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
pard_idx = self.pardrads.get()
if pard_idx == 99:
return
event_id = int(self.partner_linker["text"].split("#")[1])
couple_id = self.partners_data[pard_idx]["couple_id"]
cur.execute(update_event_couple, (couple_id, event_id))
conn.commit()
self.redraw(cur)
cur.close()
conn.close()
def change_button_text(evt):
cell = evt.widget
self.partner_linker["text"] = (f"LINK SELECTED PARTNER TO "
f"{couple_widgets[cell]['text']} EVENT #{str(couple_widgets[cell]['id'])}")
couple_widgets = {}
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
cur.execute(
''' SELECT event_type_text
FROM event_type
JOIN traits_tbd
ON traits_tbd.event_type_id = event_type.event_type_id
WHERE couple = 1
''')
couple_event_types = [i[0] for i in cur.fetchall()]
event_col = self.events_table.grid_slaves(column=0)
for widg in event_col:
if widg.winfo_class() in ("Frame", "Label"):
continue
elif widg.get("1.0", "end-1c") in couple_event_types:
text = widg.get("1.0", "end-1c")
widg["bg"] = self.formats["head_bg"]
widg.bind("<Button-1>", change_button_text, add="+")
couple_widgets[widg] = {}
couple_widgets[widg]["text"] = text
couple_widgets[widg]["id"] = widg.event_id
self.pardlink_ok["command"] = link
self.pardlink_ok["state"] = "normal"
cur.close()
conn.close()
def add_partner(self):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_partner_id = self.get_new_person_id(self.new_partner_input, cur)
if new_partner_id is None:
return
partners = self.get_partner_order(
[self.tree.persid, new_partner_id], cur, conn)
if partners is None:
return
person_id1, person_id2, got = partners
cur.execute(insert_couple_new, (person_id1, person_id2))
conn.commit()
new_couple_id = cur.lastrowid
if got is not None:
if got == 0:
cur.execute(update_person_gender, ("male", person_id1))
elif got == 1:
cur.execute(update_person_gender, ("female", person_id1))
elif got == 4:
cur.execute(update_person_gender, ("male", person_id2))
elif got == 5:
cur.execute(update_person_gender, ("female", person_id2))
conn.commit()
self.new_partner_input.delete(0, "end")
self.redraw(cur)
cur.close()
conn.close()
def get_partner_order(self, partners, cur, conn):
order = {}
for pard in partners:
gender = "unknown"
cur.execute(select_person_gender_by_id, (pard,))
result = cur.fetchone()
if result:
gender = result[0]
order[pard] = gender
partner1, partner2 = partners
if order[partner1] == "male" or order[partner2] == "female":
return partner1, partner2, None
elif order[partner2] == "male" or order [partner1] == "female":
right, left = partners
return left, right, None
else:
get_a_clue = LeftRightInputDialog(
self.tree, self.tree.tree_id, partners)#.get_partner_pos()
self.wait_window(get_a_clue)
if get_a_clue.ok_was_pressed is False:
return None
got = get_a_clue.get_partner_pos()
if got in (0, 2, 5, 7):
person_id1, person_id2 = partners
elif got in (1, 3, 4, 6):
person_id1, person_id2 = (partners[1], partners[2])
return person_id1, person_id2, got
def add_child(self):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_child_id = self.get_new_person_id(self.new_child_input, cur)
if new_child_id is None:
return
pardrow = self.pardrads.get()
partner_id = self.partners_data[pardrow]["partner_id"]
cur.execute(
select_couple_id_by_partners,
(self.tree.persid, partner_id, partner_id,
self.tree.persid, self.tree.persid,
self.tree.persid))
result = cur.fetchone()
if result:
couple_id = result[0]
cur.execute(select_event_id_birth, (new_child_id,))
birth_event_id = cur.fetchone()[0]
cur.execute(update_event_couple, (couple_id, birth_event_id))
conn.commit()
self.new_child_input.delete(0, "end")
self.redraw(cur)
cur.close()
conn.close()
def edit_ok(self, inwidg):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
master = inwidg.master
master_grid_info = master.grid_info()
master_column = master_grid_info["column"]
master_row = master_grid_info["row"]
grids = inwidg.grid_info()
column = grids["column"]
row = grids["row"]
grid_info = (column, row, master_column, master_row)
if master.master == self.progeny_table and column == 1:
new_person_id = self.get_new_person_id(inwidg, cur)
self.check_partner_options(
inwidg, grid_info, conn, cur, new_person_id=new_person_id)
elif master == self.parents_table and column in (1, 3):
new_person_id = self.get_new_person_id(inwidg, cur)
if row == 0 and new_person_id: # parent
self.update_parent(
new_person_id, conn, cur, column, row, inwidg)
elif row == 0 and new_person_id is None:
self.unlink_parent(column, conn, cur, row)
elif new_person_id: # alt parent
self.update_parent(
new_person_id, conn, cur, column, row, inwidg)
elif new_person_id is None:
self.unlink_parent(column, conn, cur, row)
elif master == self.progeny_table and column == 0:
new_person_id = self.get_new_person_id(inwidg, cur)
self.update_child(conn, cur, inwidg, new_child_id=new_person_id)
elif master == self.progeny_table and column in (1, 3):
self.update_date(conn, cur, inwidg, column, row)
if self.dlg_is_open is False:
self.redraw(cur)
cur.close()
conn.close()
def get_new_person_id(self, inwidg, cur):
got = inwidg.get()
if len(got) == 0:
return None
name_data = inwidg.check_name(cur)
if name_data is None:
redo = f"{got}+"
name_data = inwidg.check_name(cur, redo=redo)
if name_data == "add_new_person":
new_person_id = open_new_person_dialog(
self.tree, self.treebard, inwidg=inwidg, got=got)
self.tree.person_autofill_values = self.tree.update_person_autofill_values()
else:
new_person_id = name_data[1]
return new_person_id
class LeftRightInputDialog(tk.Toplevel):
def __init__(self, master, tree_id, partners, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
""" Input `partners` is `(persid, new_partner_id)`, but without
gender info, so that order is arbitrary. Get the user to provide one
useful fact to confirm or reverse this order.
"""
self.tree = master
self.partners = partners
self.title("Where to Place New Partner in Parents Area")
self.pardgenvar = tk.IntVar(None, 99)
self.ok_was_pressed = False
self.make_widgets()
color_scheme_id = get_color_scheme_id(tree_id)
formats = make_formats_dict(tree_id, color_scheme_id=color_scheme_id)
configall(self, formats)
def make_widgets(self):
def ok():
self.ok_was_pressed = True
self.destroy()
def cancel():
self.destroy()
self.headlab = tk.Label(
self, text="Select one of the eight options below.",
wraplength=480, justify="left", bd=1, relief="raised")
self.content = tk.Frame(self)
self.make_inputs()
buttons = tk.Frame(self)
ok_butt = tk.Button(buttons, text="OK", width=7, command=ok)
cancel_butt = tk.Button(buttons, text="CANCEL", width=7, command=cancel)
self.headlab.grid(
column=0, row=0, ipadx=6, ipady=6, sticky="ew", padx=12, pady=12)
self.content.grid(column=0, row=1, sticky="news")
buttons.grid(column=0, row=2, sticky="e", padx=12, pady=12)
ok_butt.grid(column=0, row=0, sticky="e")
cancel_butt.grid(column=1, row=0, sticky="e", padx=(6,0))
def make_inputs(self):
HEAD1 = ("", "", "", "In Parents Area:")
HEAD2 = ("", "male", "female", "on left", "on right")
column = 0
for head in HEAD1:
lab = tk.Label(
self.content, text=head)
lab.grid(column=column, row=0, sticky="ew")
if column == 3:
lab.grid_configure(columnspan=2)
column += 1
column = 0
for head in HEAD2:
lab = tk.Label(self.content, text=head, anchor="w")
lab.grid(column=column, row=1, sticky="ew", padx=6)
if column == 4:
lab.grid_configure(padx=12)
column += 1
name1 = self.tree.person_autofill_values[
self.partners[0]][0]["name"]
name2 = self.tree.person_autofill_values[
self.partners[1]][0]["name"]
p1 = tk.Label(self.content, text=f"{name1}", anchor="w")
p2 = tk.Label(self.content, text=f"{name2}", anchor="w")
p1.grid(column=0, row=2, sticky="ew", padx=12)
p2.grid(column=0, row=3, sticky="ew", padx=12)
value = 0
row = 2
for person in self.partners:
column = 1
for i in range(4):
rad = tk.Radiobutton(
self.content, value=value,
variable=self.pardgenvar)
rad.grid(column=column, row=row, sticky="ew")
column += 1
value += 1
row += 1
center_dialog(self, frame=self.content)
def get_partner_pos(self):
return self.pardgenvar.get()