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\app\python\families.py Last Changed 2024-04-15
# families.py
import tkinter as tk
import sqlite3
from files import appwide_db_path, get_current_tree
from redraw import redraw_person_tab, redraw_families_table, redraw_names_tab
from utilities import center_dialog, resize_scrolled_content
from widgets import(
make_formats_dict, ScrolledDialog, open_message, configall,
Scrollbar, LabelEntryDynamic, RightClickMenu, make_rc_menus,
get_colors_type_id, Combobox, run_statusbar_tooltips, TabBook)
from persons import open_new_person_dialog, EntryAutoPerson
from messages import families_msg
from messages_context_help import person_unlink_dlg_help_msg
from dates import (
format_stored_date, get_date_formats, OK_MONTHS, validate_date,
make_date_sorter)
from dates import ChangeDate
from query_strings import (
select_person_gender_by_id, select_all_event_types_id_couple,
update_person_gender, select_event_id_birth, select_all_event_types_couple,
insert_couple_new, update_event_couple, select_event_couple_alt_parents,
select_couple_event_progeny, select_person_event_gender_birth_date,
select_couple_partners, select_event_parents, insert_couple_new_partner,
update_couple_new_father, update_couple_new_mother,
select_couple_by_person_id, delete_couple_by_id, select_event_count_couple,
update_couple_null1, update_couple_null2,
delete_notes_links_couple, select_event_death_date, update_event_date,
update_event_couple_null_by_couple_id, update_change_date_by_couple_id)
import dev_tools as dt
from dev_tools import look, seeline
from timeit import default_timer as timer
class NuclearFamiliesTable(tk.Frame):
""" Alt parentage refers to adopting, being a guardian, or fostering a child.
Alt birth refers to being adopted, being a ward, or being fostered.
"""
def __init__(
self, master, formats, family_tree, family_tree_id, treebard, current_person_id,
events_table, right_panel, current_file=None, main=None, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.formats = formats
self.family_tree = family_tree
self.family_tree_id = family_tree_id
self.treebard = treebard
self.current_person_id = current_person_id
self.events_table = events_table
self.right_panel = right_panel
self.main = main
self.current_file = current_file
self.pardrads = tk.IntVar(None, 99)
self.current_widget = None
self.progeny = []
self.parents = {
"parents": {
"couple_id": None,
"birth_event_id": None,
"father": {"name": None, "id": None},
"mother": {"name": None, "id": None}},
"alt_parents": []}
self.editable_cells = []
self.name_widgets = {}
self.couple_cells = {}
self.parent_cells = {}
self.child_cells = {}
self.gender_cells = {}
self.birth_date_cells = {}
self.death_date_cells = {}
self.date_prefs = get_date_formats()
self.original_text = ""
self.final_text = ""
self.alt_parent_types = {
48: "guardian", 83: "adoptive parent", 95: "foster parent"}
self.make_widgets()
self.values = self.family_tree.person_autofill_values
def redraw(self, current_person_changing=False, current_grid_info=None):
redraw_person_tab(
main=self.main, family_tree_id=self.family_tree_id,
current_person_id=self.current_person_id,
family_tree=self.family_tree, current_file=self.current_file,
current_grid_info=current_grid_info)
if current_person_changing:
redraw_names_tab(self.current_person_id, self.main)
TabBook.resize_scrolled_dialog_with_tabbook(
self.family_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(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
self.current_tree = get_current_tree(self.family_tree_id, cur)[0]
cur.execute("ATTACH ? as tree", (self.current_tree,))
self.make_parent_widgets()
self.get_parents_data(cur)
self.get_alt_parents_data(cur)
self.get_progeny_data(cur)
self.make_add_area()
self.make_edit_frame()
self.bind_person_cells()
cur.execute("DETACH tree")
cur.close()
conn.close()
def bind_person_cells(self):
for widg in self.editable_cells:
if widg.master != self.progeny_table:
widg.bind(
"<Control-Button-1>",
self.change_current_person, add="+")
elif (widg.master == self.progeny_table and
widg.grid_info()["column"] == 0):
widg.bind(
"<Control-Button-1>",
self.change_current_person, add="+")
widg.bind("<Double-Button-1>", self.grid_edit_frame, add="+")
def make_parent_widgets(self):
pa_lab = tk.Label(self.parents_table, text="father:", anchor="e")
self.pa_input = LabelEntryDynamic(
self.parents_table,
cursor="hand2",
anchor="w", takefocus=1, wraplength=self.main.wraplength,
justify="left")
ma_lab = tk.Label(self.parents_table, text="mother:", anchor="e")
self.ma_input = LabelEntryDynamic(
self.parents_table, cursor="hand2", anchor="w", takefocus=1,
wraplength=self.main.wraplength, justify="left")
for widg in (self.pa_input, self.ma_input):
self.editable_cells.append(widg)
self.name_widgets[widg] = None
self.parent_cells[widg] = None
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")
ma_lab.grid(column=2, row=0, sticky="ew", padx=(18,0))
self.ma_input.grid(column=3, row=0, sticky="ew", padx=(0,6))
spacer = tk.Frame(self.parents_table, height=6)
spacer.grid()
def get_parents_data(self, cur):
if self.current_person_id is None:
return
parent_id_left = None
parent_id_right = None
left_parent_name = None
right_parent_name = None
couple_id = None
birth_event_id = None
cur.execute(select_event_parents, (self.current_person_id,))
results = cur.fetchone()
if results:
parent_id_left, parent_id_right, couple_id, birth_event_id = results
for idx, tup in enumerate((parent_id_left, parent_id_right)):
left_parent_name = ""
right_parent_name = ""
if idx == 0:
if parent_id_left:
left_parent_name = self.family_tree.person_autofill_values[
parent_id_left][0]["name"]
self.pa_input.config(text=left_parent_name)
self.name_widgets[self.pa_input] = parent_id_left
self.parent_cells[self.pa_input] = (couple_id, birth_event_id)
elif idx == 1:
if parent_id_right:
right_parent_name = self.family_tree.person_autofill_values[
parent_id_right][0]["name"]
self.ma_input.config(text=right_parent_name)
self.name_widgets[self.ma_input] = parent_id_right
self.parent_cells[self.ma_input] = (couple_id, birth_event_id)
self.parents["parents"]["couple_id"] = couple_id
self.parents["parents"]["birth_event_id"] = birth_event_id
self.parents["parents"]["father"]["id"] = parent_id_left
self.parents["parents"]["father"]["name"] = left_parent_name
self.parents["parents"]["mother"]["id"] = parent_id_right
self.parents["parents"]["mother"]["name"] = right_parent_name
def get_alt_parents_data(self, cur):
cur.execute(select_event_couple_alt_parents, (self.current_person_id,))
alt_parents = [list(i) for i in cur.fetchall()]
for lst in alt_parents:
lst[5] = lst[5].split(",")
lst[5] = [int(i) for i in lst[5]]
alt_parents.sort(key=lambda i: i[5])
for idx, tup in enumerate(alt_parents, 1):
self.make_alt_parents_row(idx, tup[0:-1], alt_parents, cur)
def make_alt_parents_row(self, idx, tup, alt_parents, cur):
(left_person_id, right_person_id, couple_id, alt_birth_event_id,
event_type_id) = tup
for k,v in self.alt_parent_types.items():
if k == event_type_id:
alt_parent_type = v
break
left_lab = tk.Label(
self.parents_table, text=f"{alt_parent_type}:", anchor="e")
left_input = LabelEntryDynamic(
self.parents_table,
cursor="hand2",
anchor="w", takefocus=1, wraplength=self.main.wraplength,
justify="left")
right_lab = tk.Label(
self.parents_table, text=f"{alt_parent_type}:", anchor="e")
right_input = LabelEntryDynamic(
self.parents_table,
cursor="hand2",
anchor="w", takefocus=1, wraplength=self.main.wraplength,
justify="left")
alt_parent_names = []
tup = 0
for widg, person_id in (
(left_input, left_person_id),
(right_input, right_person_id)):
name = ""
if person_id:
name = self.family_tree.person_autofill_values[person_id][0]["name"]
widg.config(text=name)
alt_parent_names.append(name)
self.editable_cells.append(widg)
self.name_widgets[widg] = person_id
if tup == 0:
person_id1 = person_id
name1 = name
elif tup == 1:
person_id2 = person_id
name2 = name
tup += 1
left_lab.grid(column=0, row=idx, sticky="ew", padx=(6,0))
left_input.grid(column=1, row=idx, sticky="ew")
right_lab.grid(column=2, row=idx, sticky="ew", padx=(18,0))
right_input.grid(column=3, row=idx, sticky="ew", padx=(0,6))
for lab in (left_input, right_input):
self.parent_cells[lab] = (couple_id, alt_birth_event_id)
self.save_alt_parents_data(
couple_id, alt_birth_event_id, person_id1, name1,
person_id2, name2, cur)
def save_alt_parents_data(
self, couple_id, alt_birth_event_id, person_id1, name1,
person_id2, name2, cur):
ALT_PARENTS_MODEL = {
"couple_id": None,
"alt_birth_event_id": None,
"on_left": {"id": None, "name": None},
"on_right": {"id": None, "name": None}}
dkt = dict(ALT_PARENTS_MODEL)
dkt["couple_id"] = couple_id
dkt["alt_birth_event_id"] = alt_birth_event_id
dkt["on_left"]["id"] = person_id1
dkt["on_left"]["name"] = name1
dkt["on_right"]["id"] = person_id2
dkt["on_right"]["name"] = name2
self.parents["alt_parents"].append(dkt)
def get_progeny_data(self, cur):
cur.execute(select_couple_event_progeny, (
self.current_person_id, self.current_person_id))
self.progeny = [list(i) for i in cur.fetchall()]
self.get_baseless_couples(cur)
self.get_progeny_strings_by_id(cur)
self.partner_rows, child_rows = (self.collect_progeny_data(cur))
self.make_progeny_rows(child_rows)
def get_baseless_couples(self, cur):
cur.execute(
select_couple_by_person_id,
(self.current_person_id, self.current_person_id))
baseless_couples = [list(i) for i in cur.fetchall()]
baseless_couples = [
[i[0], None, i[1], i[2], None, None] for i in baseless_couples]
existing_couple_ids = [i[0] for i in self.progeny]
for lst in baseless_couples:
if lst[0] not in existing_couple_ids:
self.progeny.append(lst)
def make_progeny_rows(self, child_rows):
partner_count = 0
row_count = 0
for partner_row in self.partner_rows:
(couple_id, partner_name, partner_id,
event_id) = partner_row
right_kids = []
self.make_partner_row(
row_count, partner_name, partner_id,
couple_id, event_id, partner_count)
for child_row in child_rows:
if child_row[0] == couple_id:
right_kids.append((child_row[1:]))
for tup in right_kids:
(child_name, gender, birth_date, death_date, event_type_id,
child_id, birth_event_id, death_event_id) = tup
row_count += 1
self.make_child_row(
row_count, child_name, gender, birth_date, death_date,
child_id, couple_id, birth_event_id, death_event_id)
row_count += 1
partner_count += 1
spacer = tk.Frame(self.progeny_table)
spacer.grid(column=0, row=row_count, sticky="ew", pady=6)
def get_progeny_strings_by_id(self, cur):
for idx, event in enumerate(list(self.progeny)):
[couple_id, child_id, person_id1, person_id2, event_type_id,
event_id] = event
child_name = ""
if child_id:
child_name = self.family_tree.person_autofill_values[
child_id][0]["name"]
if person_id1 == self.current_person_id:
partner_id = person_id2
elif person_id2 == self.current_person_id:
partner_id = person_id1
partner_name = ""
if partner_id:
partner_name = self.family_tree.person_autofill_values[
partner_id][0]["name"]
self.progeny[idx] = [
couple_id, partner_name, event_type_id,
child_name, child_id, partner_id, event_id]
def collect_progeny_data(self, cur):
partner_rows = []
child_rows = []
no_birth_date = "(birth date)"
no_death_date = "(death date)"
death_event_id = None
for idx, event in enumerate(list(self.progeny)):
(couple_id, partner_name, event_type_id,
child_name, child_id, partner_id, event_id) = event
if couple_id not in [i[0] for i in partner_rows]:
partner_rows.append(
(couple_id, partner_name, partner_id, event_id))
else:
indx = [i[0] for i in partner_rows].index(couple_id)
if event_type_id in (1, 48, 83, 95):
if child_id is None:
continue
cur.execute(
select_person_event_gender_birth_date,
(child_id, ))
gender, stored_birth_date, birth_event_id = cur.fetchone()
if stored_birth_date == "-0000-00-00-------":
birth_date = no_birth_date
else:
birth_date = format_stored_date(
stored_birth_date, date_prefs=self.date_prefs)
stored_death_date = None
cur.execute(select_event_death_date, (child_id,))
results = cur.fetchone()
if results:
stored_death_date, death_event_id = results
if (stored_death_date is None or
stored_death_date == "-0000-00-00-------"):
death_date = no_death_date
else:
death_date = format_stored_date(
stored_death_date, date_prefs=self.date_prefs)
child_rows.append(
(couple_id, child_name, gender, birth_date, death_date,
event_type_id, child_id, birth_event_id, death_event_id))
return partner_rows, child_rows
def make_partner_row(
self, row_count, partner_name, partner_id,
couple_id, event_id, partner_count):
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)
lab = LabelEntryDynamic(
pardframe,
anchor="w", text=partner_name,
takefocus=1, wraplength=self.main.wraplength*2, justify="left",
cursor="hand2")
pardframe.grid(column=0, row=row_count, sticky="ew", columnspan=5, padx=6)
rad.grid(column=0, row=row_count, sticky="w")
lab.grid(column=1, row=row_count, sticky="ew")
self.name_widgets[lab] = partner_id
self.editable_cells.append(lab)
self.couple_cells[lab] = couple_id
def make_child_row(
self, row_count, child_name, gender, birth_date,
death_date, child_id, couple_id, birth_event_id, death_event_id):
kidlab = LabelEntryDynamic(
self.progeny_table,
text=child_name, cursor="hand2",
anchor="w", takefocus=1, wraplength=self.main.wraplength,
justify="left")
genlab = LabelEntryDynamic(
self.progeny_table,
text=gender, cursor="hand2",
anchor="w", takefocus=1, width=7, justify="left")
birthlab = LabelEntryDynamic(
self.progeny_table,
text=birth_date, cursor="hand2",
anchor="w", takefocus=1, wraplength=int(self.main.wraplength*0.66),
justify="left")
lab = tk.Label(
self.progeny_table, text="to", anchor="w")
deathlab = LabelEntryDynamic(
self.progeny_table,
text=death_date, cursor="hand2",
anchor="w", takefocus=1, wraplength=int(self.main.wraplength*0.66),
justify="left")
# children of self.progeny_table
kidlab.grid(column=0, row=row_count, sticky="ew", padx=(48,0))
genlab.grid(column=1, row=row_count, sticky="ew", padx=(12,0))
birthlab.grid(column=2, row=row_count, sticky="ew", padx=(12,0))
lab.grid(column=3, row=row_count, sticky="ew", padx=(12,0))
deathlab.grid(column=4, row=row_count, sticky="ew", padx=(12,6))
for widg in (kidlab, genlab, birthlab, deathlab):
self.editable_cells.append(widg)
self.name_widgets[kidlab] = child_id
self.child_cells[kidlab] = (couple_id, birth_event_id)
self.gender_cells[genlab] = child_id
self.birth_date_cells[birthlab] = birth_event_id
self.death_date_cells[deathlab] = death_event_id
def change_current_person(self, evt):
# start = timer()
width, height = self.family_tree.geometry().split("+", 1)[0].split("x")
screen = tk.Frame(self.family_tree, bg=self.formats["bg"], width=width, height=height)
screen.grid()
evt_widg = evt.widget
master = evt_widg.master
if len(evt_widg.cget("text")) == 0:
return
grid_info = evt_widg.grid_info()
column = grid_info["column"]
row = grid_info["row"]
for k,v in self.name_widgets.items():
if k == evt_widg:
self.current_person_id = v
break
self.redraw(current_person_changing=True)
screen.destroy()
# end = timer()
# print("change_current_person in families.py takes", end - start, " seconds.")
def make_edit_frame(self):
self.edit_frame = tk.Frame(self.master)
self.edit_input = EntryAutoPerson(
self.edit_frame, self.family_tree_id, self.family_tree,
autofill=True, values=self.family_tree.person_autofill_values)
self.child_input = tk.Entry(self.edit_frame)
self.ok_edit = tk.Button(self.edit_frame, text="OK")
self.edit_not = tk.Button(self.edit_frame, text="CANCEL")
self.edit_input.grid(column=0, row=0, sticky="w", padx=(0,6))
self.ok_edit.grid(column=1, row=0, sticky="e", padx=(0,6))
self.edit_not.grid(column=2, row=0, sticky="e", padx=(0,6))
self.child_input.grid(column=0, row=0, sticky="w", padx=(0,6))
self.edit_input.grid_remove()
self.child_input.grid_remove()
def return_focus(self, current_grid_info):
column, row, master_column, master_row, grancestor = current_grid_info
for child in grancestor.winfo_children():
grid_info = child.grid_info()
col = grid_info["column"]
row = grid_info["row"]
if col == master_column and row == master_row:
master = child
break
for child in master.winfo_children():
grinfo = child.grid_info()
(c, r) = (grinfo["column"], grinfo["row"])
if (c, r) == (column, row):
child.focus_set()
break
def ungrid_edit_frame(self):
self.edit_frame.grid_forget()
self.edit_input.delete(0, "end")
self.edit_input.grid_remove()
self.child_input.delete(0, "end")
self.child_input.grid_remove()
def cancel_edit_frame(self, grid_info):
self.ungrid_edit_frame()
self.return_focus(grid_info)
def grid_edit_frame(self, evt):
self.widget_being_edited = evt.widget
master = self.widget_being_edited.master
grancestor = master.master
master_grid_info = master.grid_info()
master_column = master_grid_info["column"]
master_row = master_grid_info["row"]
self.original_text = self.widget_being_edited.cget("text")
grids = self.widget_being_edited.grid_info()
column = grids["column"]
row = grids["row"]
self.ok_edit.config(
command=lambda:
self.edit_ok(
grid_info=(column, row, master_column, master_row, grancestor)))
self.edit_not.config(
command=lambda:
self.cancel_edit_frame(
grid_info=(column, row, master_column, master_row, grancestor)))
self.edit_frame.grid(
column=column, row=row, in_=master, columnspan=4, sticky="ew")
if master == self.progeny_table and column > 0:
self.edit_input.grid_remove()
self.child_input.grid()
self.child_input.focus_set()
else:
self.edit_input.grid()
self.edit_input.focus_set()
configall(self.edit_frame, self.formats)
def edit_ok(self, grid_info):
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_person_id = self.get_new_person_id(self.edit_input)
column, row = grid_info[0:2]
if self.widget_being_edited.master.winfo_class() == "Frame":
self.check_partner_options(grid_info, new_person_id=new_person_id)
elif self.widget_being_edited.master == self.parents_table and column in (
1, 3):
if row == 0 and new_person_id: # parent
self.update_parent(new_person_id, conn, cur, column)
elif row == 0 and new_person_id is None:
self.unlink_parent(column, conn, cur)
elif new_person_id: # alt parent
self.update_parent(new_person_id, conn, cur, column)
elif new_person_id is None:
self.unlink_parent(column, conn, cur)
elif column == 0:
self.update_child(conn, cur, new_child_id=new_person_id)
elif column in (1, 2, 4):
self.update_child(conn, cur, column)
self.ungrid_edit_frame()
self.redraw(current_grid_info=grid_info)
cur.close()
conn.close()
def update_child(self, conn, cur, column, new_child_id=None, event_id=None):
if new_child_id:
for k,v in self.child_cells.items():
if k == self.widget_being_edited:
couple_id, orig_birth_event_id = v
break
cur.execute(update_event_couple, (None, orig_birth_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()
else:
got = self.child_input.get().strip().lower()
if column == 1:
for k,v in self.gender_cells.items():
if k == self.widget_being_edited:
child_id = v
break
if got.startswith("u"):
gender = "unknown"
elif got.startswith("f"):
gender = "female"
elif got.startswith("m"):
gender = "male"
else:
return
cur.execute(update_person_gender, (gender, child_id))
conn.commit()
elif column in (2, 4):
if column == 2:
for k,v in self.birth_date_cells.items():
if k == self.widget_being_edited:
event_id = v
break
elif column == 4:
for k,v in self.death_date_cells.items():
if k == self.widget_being_edited:
event_id = v
break
storable_date = validate_date(
self.treebard, self.child_input, got, self.formats)
date_sorter = make_date_sorter(storable_date)
cur.execute(
update_event_date, (storable_date, date_sorter, event_id))
conn.commit()
ChangeDate(child_id, tag="couple", conn=conn, cur=cur)
def unlink_parent(self, column, conn, cur):
for k,v in self.parent_cells.items():
if self.widget_being_edited == k:
couple_id, event_id = v
break
if column == 1:
cur.execute(update_couple_null1, (couple_id,))
elif column == 3:
cur.execute(update_couple_null2, (couple_id,))
conn.commit()
ChangeDate(couple_id, tag="couple", conn=conn, cur=cur)
def check_partner_options(self, grid_info, new_person_id=None):
self.final_text = self.edit_input.get()
for k,v in self.couple_cells.items():
if self.widget_being_edited == k:
couple_id = v
break
selected_progeny = {}
for lst in list(self.progeny):
if lst[0] == couple_id:
partner_name = lst[1]
selected_progeny[lst[6]] = lst[2:4]
partner_id = lst[5]
if len(self.original_text) == 0 and len(self.final_text) != 0:
self.name_missing_partner(couple_id, new_person_id)
elif len(self.original_text) == 0:
return False
elif len(self.final_text) == 0:
unlink_dialog = PersonUnlinkSelectDialog(
self.family_tree, self.family_tree_id, self.treebard,
self.current_person_id, couple_id,
grid_info, selected_progeny=selected_progeny,
partner_id=partner_id, partner_name=partner_name,
unlink_type="partner", unlink_partner=True,
inwidg=self.widget_being_edited)
elif self.original_text != self.final_text:
unlink_dialog = PersonUnlinkSelectDialog(
self.family_tree, self.family_tree_id, self.treebard,
self.current_person_id, couple_id,
grid_info, selected_progeny=selected_progeny,
partner_id=partner_id, partner_name=partner_name,
unlink_type="partner", inwidg=self.widget_being_edited)
def name_missing_partner(self, couple_id, new_person_id):
""" Handle if missing partner is a new person, if missing partner is an
existing person.
"""
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_couple_partners, (couple_id,))
result = cur.fetchone()
if result[0] is None:
query = update_couple_new_father
elif result[1] is None:
query = update_couple_new_mother
cur.execute(query, (new_person_id, couple_id))
conn.commit()
cur.close()
conn.close()
def update_parent(self, new_parent_id, conn, cur, column):
for k,v in self.parent_cells.items():
if k == self.widget_being_edited:
couple_id, event_id = v
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_father, (new_parent_id, couple_id))
elif column == 3:
cur.execute(update_couple_new_mother, (new_parent_id, couple_id))
conn.commit()
cur.execute(update_event_couple, (couple_id, event_id))
conn.commit()
ChangeDate(couple_id, tag="couple", conn=conn, cur=cur)
def update_partner(self, new_partner_id, conn, cur):
for k,v in self.couple_cells.items():
if k == self.widget_being_edited:
couple_id = v
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.current_person_id:
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()
ChangeDate(couple_id, tag="couple", conn=conn, cur=cur)
def get_new_person_id(self, inwidg):
""" For more info on why a '+' is added to new name input, see dev docs. """
got = inwidg.get()
if len(got) == 0:
return None
name_data = inwidg.check_name()
if name_data is None:
redo = f"{got}+"
name_data = inwidg.check_name(redo=redo)
if name_data == "add_new_person":
new_person_id = open_new_person_dialog(
self.family_tree, self.family_tree_id, self.treebard,
inwidg=inwidg, got=got)
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
else:
new_person_id = name_data[1]
return new_person_id
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.family_tree_id, self.family_tree, width=24,
autofill=True, values=self.family_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.family_tree_id, self.family_tree, width=24,
autofill=True,
values=self.family_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.current_tree)
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])
for idx,row in enumerate(self.partner_rows):
if idx == pard_idx:
couple_id = row[0]
break
cur.execute(update_event_couple, (couple_id, event_id))
conn.commit()
ChangeDate(couple_id, tag="couple", conn=conn, cur=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.current_tree)
cur = conn.cursor()
cur.execute(select_all_event_types_couple)
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() == "Frame" or widg["text"] == "EVENT":
continue
elif widg["text"] in couple_event_types:
text = widg["text"]
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.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_partner_id = self.get_new_person_id(self.new_partner_input)
partners = self.get_partner_order(
[self.current_person_id, new_partner_id], cur, conn)
print("line", look(seeline()).lineno, "new_partner_id, partners", new_partner_id, partners)
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()
ChangeDate(new_couple_id, tag="couple", conn=conn, cur=cur)
self.new_partner_input.delete(0, "end")
self.redraw()
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.family_tree, self.family_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.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_child_id = self.get_new_person_id(self.new_child_input)
pardrow = self.pardrads.get()
couple_id = self.partner_rows[pardrow][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()
ChangeDate(couple_id, tag="couple", conn=conn, cur=cur)
self.new_child_input.delete(0, "end")
self.redraw()
cur.close()
conn.close()
class PersonUnlinkSelectDialog(ScrolledDialog):
""" Undecided whether this will be needed to select what to link; just
using it to do unlinking. The parameter `unlink_partner` exists in
case both linking & unlinking will be done. The parameter `unlink_type`
exists in case a dialog ends up being used for parents and children,
but for now, this class is just used for unlinking partners.
"""
def __init__(
self, master, family_tree_id, treebard, current_person_id,
couple_id, grid_info,
selected_progeny=None, partner_id=None, partner_name=None,
unlink_partner=False, inwidg=None, original_text=None,
final_text=None, unlink_type=None, *args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.family_tree = master
self.family_tree_id = family_tree_id
self.treebard = treebard
self.current_person_id = current_person_id
self.couple_id = couple_id
self.grid_info = grid_info
self.inwidg = inwidg
self.original_text = original_text
self.final_text = final_text
if unlink_type == "partner":
self.selected_progeny = selected_progeny
self.partner_id = partner_id
self.partner_name = partner_name
self.unlink_partner = unlink_partner
(title_link, instrux_link, title_unlink, self.instrux_unlink,
self.link_all, self.unlink_all, self.couple_event_types,
self.BIRTH_EVENT_TYPES) = customize_labels("partner")
self.rc_menu = RightClickMenu(self, self.treebard)
if self.unlink_partner is False:
self.title(title_link)
self.headlab.config(text=instrux_link)
else:
self.title(title_unlink)
self.checkvars = {}
self.make_inputs()
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=24)
def make_inputs(self):
def cancel_unlink():
self.destroy()
self.headlab = tk.Label(
self.window, text="", wraplength=600, justify="left", bd=1,
relief="raised")
self.content = tk.Frame(self.window)
self.allvar = tk.IntVar(None, 1)
self.chk_all = tk.Checkbutton(
self.content, variable=self.allvar, command=self.toggle_all)
self.checkframe = tk.Frame(self.content)
check_widgets = []
for event_id, values in self.selected_progeny.items():
text = ""
event_type_id, child_name = values
if event_type_id in self.BIRTH_EVENT_TYPES:
event_type = self.BIRTH_EVENT_TYPES[event_type_id]
text = f"{event_type} event of child {child_name}"
elif event_type_id in self.couple_event_types:
event_type = self.couple_event_types[event_type_id]
text = f"{event_type} event with partner {self.partner_name}"
if len(text) != 0:
var = tk.IntVar(None, 1)
chk = tk.Checkbutton(
self.checkframe, text=text, anchor="w", variable=var,
state="disabled")
chk.grid(sticky="ew")
self.checkvars[chk] = var
check_widgets.append(chk)
buttons = tk.Frame(self.window)
ok_button = tk.Button(buttons, text="OK", width=7, command=self.ok_unlink)
cancel_button = tk.Button(
buttons, text="CANCEL", width=7, command=cancel_unlink)
self.adjust_text()
# Probably the reason this won't start out in focus despite the following
# calls is that return_focus() runs when the edit frame closes, which
# doesn't account for this dialog. Return focus is more important but
# it could be made more complicated than it already is by loaning focus
# here first and then return focus again to the widget being edited.
self.grab_set()
self.lift()
self.chk_all.focus_set()
# children of self.window
self.window.columnconfigure(0, weight=1)
self.window.rowconfigure(1, weight=1)
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)
# children of self.content
self.checkframe.rowconfigure(1, weight=1)
self.chk_all.grid(column=0, row=0, sticky="w", pady=(12,0))
self.checkframe.grid(column=0, row=1, sticky="news", padx=12, pady=12)
self.chk_all.focus_set()
# children of buttons
ok_button.grid(column=0, row=0)
cancel_button.grid(column=1, row=0, padx=(6,0))
visited = (
(self.chk_all,
'Delete Relationship Select',
'Delete the entire relationship.'),
(self.checkframe,
'Marital Events & Children Select',
'Select marital events and children to unlink from this partner.'))
run_statusbar_tooltips(
visited,
self.statusbar.status_label,
self.statusbar.tooltip_label, self.family_tree)
rcm_widgets = (
self.chk_all, self.checkframe)
make_rc_menus(
rcm_widgets,
self.rc_menu,
person_unlink_dlg_help_msg)
def ok_unlink(self):
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
current_tree = get_current_tree(self.family_tree_id, cur)[0]
cur.execute("ATTACH ? as tree", (current_tree,))
checkvals = []
for var in self.checkvars.values():
got = var.get()
checkvals.append(got)
if self.unlink_partner:
self.unlink_events(checkvals, conn, cur)
cur.execute("DETACH tree")
cur.close()
conn.close()
self.destroy()
redraw_families_table(
self.grid_info, self.formats, self.family_tree,
main=self.family_tree.main)
def unlink_events(self, checkvals, conn, cur):
cur.execute(select_couple_partners, (self.couple_id,))
partners = list(cur.fetchone())
both_partners = ("person_id1", "person_id2")
partner_on_left = False
partner_string = both_partners[1]
if self.current_person_id == partners[1]:
partner_on_left = True
partner_string = both_partners[0]
if self.allvar.get() == 1:
cur.execute(select_event_count_couple, (self.couple_id,))
event_count = cur.fetchone()[0]
if event_count > 1:
cur.execute(
f'''UPDATE couple SET {partner_string} = null
WHERE couple_id = ?''',
(self.couple_id,))
conn.commit()
ChangeDate(self.couple_id, tag="couple", conn=conn, cur=cur)
else:
cur.execute(delete_notes_links_couple, (self.couple_id,))
cur.execute(update_event_couple_null_by_couple_id, (self.couple_id,))
cur.execute(update_change_date_by_couple_id, (self.couple_id,))
conn.commit()
cur.execute(delete_couple_by_id, (self.couple_id,)) # fk constraing
conn.commit()
elif self.allvar.get() == 0:
to_unlink = []
event_id_per_checkvals = list(
zip(list(self.selected_progeny.keys()), checkvals))
for lst in event_id_per_checkvals:
if lst[1] == 1:
to_unlink.append(lst[0])
if partner_on_left:
cur.execute(insert_couple_new, (None, self.current_person_id))
else:
cur.execute(insert_couple_new, (self.current_person_id, None))
conn.commit()
new_couple_id = cur.lastrowid
for event_id in to_unlink:
cur.execute(update_event_couple, (new_couple_id, event_id))
conn.commit()
ChangeDate(new_couple_id, tag="couple", conn=conn, cur=cur)
def adjust_text(self):
if self.unlink_partner:
text = self.instrux_unlink.replace(
"the partner who is being unlinked from the current person",
self.partner_name).replace(
"the unlinked partner", self.partner_name)
if self.partner_id:
self.chk_all.config(text=self.unlink_all.replace(
"this partner", self.partner_name))
self.headlab.config(text=text)
else:
self.chk_all.config(text=self.unlink_all)
else:
self.chk_all.config(text=self.link_all)
self.headlab.config(text=self.instrux_unlink)
def toggle_all(self):
got = self.allvar.get()
if got == 0:
state = "normal"
elif got == 1:
state = "disabled"
for chk, var in self.checkvars.items():
chk.config(state="normal")
var.set(got)
chk.config(state=state)
def customize_labels(unlink_type):
def make_event_types_dict():
event_types = {}
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
cur.execute(select_all_event_types_id_couple)
couple_event_types = cur.fetchall()
cur.close()
conn.close()
for tup in couple_event_types:
event_types[tup[1]] = tup[0]
return event_types
if unlink_type == "partner":
title_link = "Link Couple Events & Children to New Partner"
instrux_link = ("Which couple events and children of the current person "
"also belong to the partner being added?")
title_unlink = "Unlink Couple Events & Children from Unlinked Partner"
instrux_unlink = (
"Which couple events and children of the current person "
"should be unlinked from the partner who is being unlinked from the "
"current person? (To delete the unlinked partner from the tree, "
"make "
"them the current person and use the DELETE PERSON menu item.)")
link_all = ("Link the new partner to all the children and couple "
"events below.")
unlink_all = ("Unlink this partner from the current person "
"completely and delete the relationship.")
couple_event_types = make_event_types_dict()
BIRTH_EVENT_TYPES = {
1: 'offspring', 48: 'guardianship', 83: 'adoption', 95: 'fosterage'}
return (
title_link, instrux_link, title_unlink, instrux_unlink, link_all,
unlink_all, couple_event_types, BIRTH_EVENT_TYPES)
class LeftRightInputDialog(tk.Toplevel):
def __init__(self, master, family_tree_id, partners, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
""" Input `partners` is `(current_person_id, 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.family_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()
colors_type_id = get_colors_type_id(family_tree_id)
formats = make_formats_dict(colors_type_id=colors_type_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.family_tree.person_autofill_values[
self.partners[0]][0]["name"]
name2 = self.family_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) # frame is none by default, try that
def get_partner_pos(self):
return self.pardgenvar.get()
if __name__ == "__main__":
def change_current_person():
global current_person_id
if len(cbo.get()) == 0:
return
nukefam_table.current_person_id = current_person_id = int(cbo.get())
redraw_families_table()
def redraw_families_table():
print(current_person_id)
formats = make_formats_dict(root=True)
current_person_id = 51
values = ('6', '5', '1', '4', '51')
treebard = tk.Tk()
current_person_name = self.family_tree.person_autofill_values[
current_person_id][0]["name"]
treebard.title(f"Current Person Tab for {current_person_name}")
treebard.geometry("+500+150")
master = tk.Frame(treebard)
master.grid()
from opening import make_combobox_dropdown
(treebard.combobox_dropdown,
treebard.combobox_dropdown.canvas,
treebard.combobox_dropdown.sbv,
treebard.combobox_dropdown.content,
treebard.combobox_dropdown.scrollbar_width) = make_combobox_dropdown(formats)
cbo = Combobox(master, formats, family_tree=treebard, values=values)
right_panel = tk.Frame(master, width=500, height=500)
events_table = tk.Frame(master, width=1000, height=250)
butt = tk.Button(
master, text="CHANGE CURRENT PERSON", command=change_current_person)
cbo.grid(column=0, row=0)
right_panel.grid(column=1, row=1, sticky="news")
events_table.grid(column=0, row=2, columnspan=2, sticky="news")
butt.grid(column=1, row=4, sticky="e")
nukefam_table = NuclearFamiliesTable(
master, formats, 1, treebard, current_person_id, events_table, right_panel)
nukefam_table.grid(column=0, row=1, sticky="news", padx=12, pady=12)
treebard.config(bg="black")
treebard.mainloop()