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\persons.py Last Changed 2024-07-26
# persons.py
import tkinter as tk
import sqlite3
from PIL import Image, ImageTk
from base import tree_path, resize_scrolled_content, get_image_dir
from redraw import Redraw, get_name_from_id
from widgets import (
make_formats_dict, configall, get_color_scheme_id, ScrolledDialog,
open_message, run_statusbar_tooltips, RightClickMenu, Combobox,
LabelButtonText, Separator, LabelStay, TabBook)
from assertions import AssertionsDialog
from messages import persons_msg
from unigeds_queries import (
select_name_type_id, insert_name, insert_media_links_image_person,
insert_name_type, select_images_all, insert_event_birth_new_person,
delete_name_person, insert_name_and_type, select_count_name_id_sources,
select_name_id_by_person_id, delete_notes_links_person,
delete_notes_links_name, update_name_type_sorter, select_all_names,
update_couple_null1, update_couple_null2, insert_person_new,
delete_event_person, delete_person, update_assertion_delete_event,
select_event_person, insert_image_new, select_name_type_id_by_string,
delete_name_by_id, insert_name_placeholder, select_name_all_current,
insert_person, insert_event_couple, delete_assertion_by_event,
delete_assertion_by_name, delete_media_links_person, select_event_type_birth,
select_all_name_types)
import dev_tools as dt
from dev_tools import look, seeline
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_all_name_types(full_path_to_tree):
conn = sqlite3.connect(full_path_to_tree)
cur = conn.cursor()
cur.execute(select_all_name_types)
all_name_types = [i[0] for i in cur.fetchall()]
cur.close()
conn.close()
return all_name_types
def get_name_type_ids(file):
conn = sqlite3.connect(file)
cur = conn.cursor()
ids = []
for name_type in ("placeholder for name",):
cur.execute(
""" SELECT name_type_id
FROM name_type
WHERE name_type_text = ?
""",
(name_type,))
ids.append(cur.fetchone()[0])
cur.close()
conn.close()
return ids
def autocreate_unknown_person(
treebard, persid, person_col, other_person_col, conn, cur):
""" Used in persons.py and families.py. """
insert_couple_half = f'''
INSERT INTO couple ({person_col}, {other_person_col}) VALUES (?, ?)
'''
cur.execute(insert_person)
conn.commit()
other_person_id = cur.lastrowid
name, sort_order, name_type_id = treebard.PLACEHOLDER_NAME
cur.execute(select_event_type_birth)
birth_type_id = cur.fetchone()[0]
cur.execute(
''' INSERT INTO event (person_id, event_type_id, age)
VALUES (?, ?, '0')
''',
(other_person_id, birth_type_id))
conn.commit()
cur.execute(
insert_name_and_type,
(name, name_type_id, other_person_id, sort_order))
conn.commit()
new_name_id = cur.lastrowid
cur.execute(
"INSERT INTO main_tbd (name_id) VALUES (?)", (new_name_id,))
conn.commit()
cur.execute(insert_couple_half, (persid, other_person_id))
conn.commit()
return cur.lastrowid
def get_original(evt):
widg=evt.widget
widg.original = widg.get()
def delete_current_person(tree, main):
""" Remove all references to a person. Runs only from Elements menu. """
if len (tree.person_autofill_values) < 2:
return
conn = sqlite3.connect(f"{tree_path}/{tree.tree_id}/{tree.tree_id}.tbd")
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(
''' SELECT person_id
FROM current_tbd
WHERE current_tbd_id = 1
''')
persid = cur.fetchone()[0]
if persid == 1:
return
cur.execute(
''' UPDATE current_tbd
SET person_id = 1
WHERE current_tbd_id = 1
''')
conn.commit()
cur.execute(select_name_id_by_person_id, (persid,))
names = cur.fetchall()
for name_id in names:
cur.execute(delete_notes_links_name, name_id)
conn.commit()
cur.execute(delete_assertion_by_name, name_id)
conn.commit()
cur.execute(
''' DELETE FROM main_tbd
WHERE name_id = ?
''',
name_id)
conn.commit()
cur.execute(delete_notes_links_person, (persid,))
conn.commit()
cur.execute(delete_media_links_person, (persid,))
conn.commit()
cur.execute(delete_name_person, (persid,))
conn.commit()
cur.execute(update_couple_null1, (persid,))
conn.commit()
cur.execute(update_couple_null2, (persid,))
conn.commit()
cur.execute(select_event_person, (persid,))
events = cur.fetchall()
for event_id in events:
cur.execute(delete_assertion_by_event, event_id)
conn.commit()
cur.execute(delete_event_person, (persid,))
conn.commit()
cur.execute(delete_person, (persid,))
conn.commit()
tree.person_autofill_values = tree.update_person_autofill_values()
tree.persid = 1
rdw = Redraw(main=main, tree=tree)
rdw.redraw_person_tab()
main.names_tab.name_change_table = rdw.redraw_names_tab()
TabBook.resize_scrolled_dialog_with_tabbook(tree, main.canvas, main)
cur.close()
conn.close()
def open_new_person_dialog(tree, treebard, inwidg=None, inwidg2=None, got=None):
person_add = PersonAdd(tree, treebard, inwidg, inwidg2, got)
treebard.wait_window(person_add)
new_person_id = person_add.show()
return new_person_id
class PersonAdd(ScrolledDialog):
def __init__(
self, master, treebard, inwidg, inwidg2, got, *args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.tree = master
self.treebard = treebard
self.inwidg = inwidg
self.inwidg2 = inwidg2
self.tree.person_autofill_values = self.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.tree, self.treebard)
self.new_person_id = None
self.full_name = ""
self.name_type_id = None
self.make_dupe = False
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
self.names_only = self.check_for_dupes(cur)
self.geometry('+100+20')
self.make_inputs()
self.name_input.insert(0, self.xfr)
self.grab_set() # Can't do this, it disables the combobox dropdown.
self.focus_force()
self.name_input.focus_set()
cur.close()
conn.close()
ScrolledDialog.bind_canvas_to_mousewheel(self.canvas)
color_scheme_id = get_color_scheme_id(self.tree.tree_id)
formats = make_formats_dict(
self.tree.tree_id, color_scheme_id=color_scheme_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", formats=self.formats)
msg1[0].grab_set()
all_name_types = get_all_name_types(self.tree.file)
namelab = tk.Label(self.window, text="Full Name", anchor="w")
self.name_input = EntryAutoPerson(
self.window, self.tree, autofill=True, width=32,
values=self.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, tree=self.tree,
values=all_name_types, dialog_in=self)
self.type_input.entry.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")
rad_other = tk.Radiobutton(
radframe, value=4, variable=self.genvar, text="other")
dupes = tk.LabelFrame(
self.window,
text="If this name already exists, a notice will appear.")
self.dupelab = LabelStay(
dupes, None, text="", fg="yellow", wraplength=270, justify="left",
bg="black")
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.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")
rad_other.grid(column=0, row=3, padx=(0,0), pady=(0,0), sticky="w")
# children of dupes
dupes.columnconfigure(0, weight=1)
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, (self.name_type,))
conn.commit()
return cur.lastrowid
def save_new_person(self): # couple_id not going in when adding blank partner
conn = sqlite3.connect(self.tree.file)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
cur.execute(insert_person_new, (self.gender,))
conn.commit()
self.new_person_id = cur.lastrowid
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()
new_name_id = cur.lastrowid
cur.execute(
''' INSERT INTO main_tbd (name_id)
VALUES (?)
''',
(new_name_id,))
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, main_image = imagetup
image_name = ".".join(image)
image_id = all_images[image_name]
cur.execute(
insert_media_links_image_person,
(image_id, self.new_person_id))
conn.commit()
media_links_id = cur.lastrowid
if main_image == 1:
cur.execute(
"INSERT INTO main_tbd (media_links_id) VALUES (?)",
(media_links_id,))
conn.commit()
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))
conn.commit()
media_links_id = cur.lastrowid
if main_image == 1:
cur.execute(
"INSERT INTO main_tbd (media_links_id) VALUES (?)",
(media_links_id,))
conn.commit()
cur.execute(insert_event_birth_new_person, (self.new_person_id,))
conn.commit()
cur.close()
conn.close()
if self.inwidg:
self.inwidg.delete(0, 'end')
self.inwidg.insert(0, self.full_name)
self.tree.person_autofill_values = self.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.tree, self.tree.tree_id, self.ok_new_person,
self.new_images, self.existing_images, self.tree.file,
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", 4: "other"}
self.full_name = self.name_input.get()
self.sorted_name = self.autosort_input.get()
self.name_type = self.type_input.entry.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.tree.person_autofill_values = self.tree.update_person_autofill_values()
def cancel_add_person(self):
self.destroy()
class SelectImageDialog(ScrolledDialog):
def __init__(
self, master, ok_new_person, new_images, existing_images, treebard,
*args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.tree = master
self.ok_new_person = ok_new_person
self.new_images = new_images
self.existing_images = existing_images
self.treebard = treebard
color_scheme_id = get_color_scheme_id(self.tree.tree_id)
formats = make_formats_dict(
self.tree.tree_id, color_scheme_id=color_scheme_id)
self.rc_menu = RightClickMenu(self.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, 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.tree.file.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]
# The image index in the list is the radiobutton value.
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()
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, main, tree, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.treebard = treebard
self.formats = formats
self.main = main
self.tree = tree
self.name_types = get_all_name_types(self.tree.file)
self.name_change_table = None
self.make_inputs()
def make_inputs(self):
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
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, tree=self.tree,
values=self.name_types)
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()
bottom_frame = tk.LabelFrame(self, text="Select a display name")
cur.execute(select_name_all_current, (self.tree.persid,))
self.all_names = cur.fetchall()
values = [f"{i[1]}: ({i[2]})" for i in self.all_names]
self.main_name_chooser = Combobox(
bottom_frame, self.formats, tree=self.tree,
values=values)
choose_ok = tk.Button(
bottom_frame, text="SELECT", command=self.save_main_name)
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)
bottom_frame.grid(column=0, row=2, sticky="w", padx=12, pady=(0,6))
buttons.grid(
column=0, row=3, 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 bottom_frame
self.main_name_chooser.grid(column=0, row=0, padx=(12,0), pady=(6,12))
choose_ok.grid(column=1, row=0, padx=(6,12), pady=(6,12))
# 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")
cur.close()
conn.close()
def save_main_name(self):
conn = sqlite3.connect(
f"{tree_path}/{self.tree.tree_id}/{self.tree.tree_id}.tbd")
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
name = self.main_name_chooser.entry.get()
if len(name) == 0:
return
new_name_id = self.all_names[self.main_name_chooser.current][0]
current_main_tbd_id = None
for idnum in [i[0] for i in self.all_names]:
cur.execute(
''' SELECT main_tbd_id
FROM main_tbd
WHERE name_id = ?
''',
(idnum,))
result = cur.fetchone()
if result:
current_main_tbd_id = result[0]
break
if current_main_tbd_id:
cur.execute(
''' UPDATE main_tbd
SET name_id = ?
WHERE main_tbd_id = ?
''',
(new_name_id, current_main_tbd_id))
conn.commit()
else:
cur.execute("INSERT INTO main_tbd (name_id) VALUES (?)", (new_name_id,))
conn.commit()
self.main_name_chooser.entry.delete(0, "end")
cur.close()
conn.close()
def make_existing_names_table(self):
""" This also runs from `redraw_names_tab` in redraw.py. """
if self.name_change_table:
self.name_change_table.destroy()
self.name_change_table = None
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
cur.execute(select_name_all_current, (self.tree.persid,))
self.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 = Separator(self.name_change_table, self.formats)
sep.grid(column=1, row=1, sticky="ew", columnspan=5)
self.namevars = {}
margin_row = 2
indented_row = 3
for tup in self.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, tree=self.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
self.name_change_table.grid(column=0, row=1, columnspan=4, sticky="ew")
color_scheme_id = get_color_scheme_id(self.tree.tree_id)
self.formats = make_formats_dict(
self.tree.tree_id, color_scheme_id=color_scheme_id)
configall(self.name_change_table, self.formats)
cur.close()
conn.close()
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.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(
''' SELECT persid
FROM current_tbd
WHERE current_tbd_id = 1
''')
persid = cur.fetchone()[0]
position = None
if name_id in self.tree.open_assertions_dialogs_tracker["name_ids"]:
assertion_dialog = self.tree.open_assertions_dialogs_tracker[
"name_ids"][name_id]
position = assertion_dialog.close_dlg()
AssertionsDialog(
self.tree, self.treebard, self, widg, name_id=name_id,
position=position)
cur.close()
conn.close()
def update_name(self):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
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))
new_name = self.new_name_input.get()
name_type = self.new_name_type_cbo.entry.get()
self.tree.person_autofill_values = self.tree.update_person_autofill_values()
cur.execute(
''' SELECT person_id
FROM current_tbd
WHERE current_tbd_id = 1
''')
persid = cur.fetchone()[0]
self.main.current_person_name = get_name_from_id(persid, cur)
rdw = Redraw(main=self.main, tree=self.tree)
rdw.redraw_current_person_area()
if len(new_name) == 0 or len(name_type) == 0:
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,
persid,
self.new_name_sort_order.get()))
conn.commit()
self.new_name_input.delete(0, 'end')
self.new_name_type_cbo.entry.delete(0, 'end')
self.new_name_sort_order.delete(0, "end")
self.name_change_table = self.make_existing_names_table()
cur.execute(select_name_all_current, (persid,))
self.all_names = cur.fetchall()
new_values = [f"{i[1]}: ({i[2]})" for i in self.all_names]
self.main_name_chooser.config_values(new_values=new_values)
self.tree.persid = persid
rdw = Redraw(main=self.main, tree=self.tree)
rdw.redraw_names_tab()
cur.close()
conn.close()
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(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
if len(self.namevars) < 2:
cur.execute(insert_name_placeholder, (self.tree.persid,))
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()
cur.execute(select_name_all_current, (self.tree.persid,))
self.all_names = cur.fetchall()
new_values = [f"{i[1]}: ({i[2]})" for i in self.all_names]
self.main_name_chooser.config_values(new_values=new_values)
cur.close()
conn.close()
class EntryAutoPerson(tk.Entry):
""" After values change, run `update_person_autofill_values()`. """
def __init__(
self, master, tree, autofill=False, values=None, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
self.master = master
self.tree = tree
self.autofill = autofill
self.values = values
self.pos = 0
self.current_tbd_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=1, relief="sunken")
color_scheme_id = get_color_scheme_id(self.tree.tree_id)
self.formats = make_formats_dict(self.tree.tree_id, color_scheme_id=color_scheme_id)
self.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_tbd_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()
def match_string(self):
""" Match typed input to names already stored. """
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_tbd_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 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)
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, sticky="e")
dupe_name_ok.grid(column=0, row=0, sticky="e", padx=(0,12))
dupe_name_cancel.grid(column=1, row=0, sticky="e", padx=0)
configall(dupe_name_dlg, self.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, cur):
""" Get main name to fill in when ID is input. """
def err_done(self, msg):
self.delete(0, 'end')
msg[0].grab_release()
msg[0].destroy()
self.focus_set()
if idnum not in self.tree.person_autofill_values:
msg = open_message(
self, persons_msg[2], "Unknown Person ID",
"OK", formats=self.formats)
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, cur)
return name_from_id
def check_name(self, cur, redo=None, screen=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 redo and filled.startswith("#"):
if "+" in filled:
filled = filled.strip().strip("+").strip()
name_from_id = self.validate_id(int(filled.lstrip("#").strip()), cur)
if name_from_id is None:
if screen:
screen.destroy()
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]