places.py
Nov 25, 2022 17:55:54 GMT -8
Post by Uncle Buddy on Nov 25, 2022 17:55:54 GMT -8
<drive>:\treebard\app\python\places.py Last Changed 2024-04-16
# places.py
import tkinter as tk
import sqlite3
from datetime import datetime
from files import get_current_tree, appwide_db_path, current_drive
from redraw import (
redraw_place_tab, redraw_gui, PLACE_DETAIL_TYPES, redraw_events_table)
from utilities import center_dialog, resize_scrolled_content
from widgets import (
configall, make_formats_dict, ScrolledDialog, open_message,
run_statusbar_tooltips, RightClickMenu, make_rc_menus, EntryAutoSinglePlace)
from notes import NotesDialog
from messages_context_help import (
places_dialog_label_help_msg, places_dlg_help_msg,
places_dialog_radio_help_msg, places_dialog_hint_help_msg)
from messages import places_msg
from dates import ChangeDate
from query_strings import (
update_event_nested_place_unknown, insert_nested_place,
select_place_id_with_name, insert_place_new,
select_nested_place_inclusion, insert_place_name_main,
update_event_nested_place, update_current_nested_place,
select_nested_place_ids, delete_place, delete_place_name, select_place_hint,
select_current_nested_place_id, update_current_nested_place_to_1,
select_place_name_from_id_if_main, delete_nested_place,
select_place_ids_by_string, update_place_details,
select_nested_place_nesting_inclusion, select_place_names,
select_place_details, update_place_name_alt, update_place_name_main,
select_place_name_count, insert_place_name, select_event_nested_place_id_count,
insert_place_hint_check_dupes, select_place_check_dupes_from_name,
update_place_check_dupes, delete_notes_links_place_id,
update_event_nested_place_unknown_by_nested_place_id,
delete_notes_links_nesting, select_notes_links_place,
select_family_tree_path, select_all_family_trees,
select_nested_place_id_inclusion, select_nested_place_smallest_nest,
delete_note_place_name_by_place_id, delete_note_place,
update_nested_place_nests, select_nested_place_by_nest0,
)
import dev_tools as dt
from dev_tools import look, seeline
# Try to get rid of some class-level attributes that were only needed for overlays.
class EntryAutoPlace(tk.Entry):
def __init__(
self, master, family_tree, autofill=False, values=[], *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
self.master = master
self.family_tree = family_tree
self.autofill = autofill
self.values = values
self.family_tree.place_autofill_inputs.append(self)
self.autofilled = None
self.config(bd=0)
if autofill is True:
self.bind("<KeyPress>", self.detect_pressed)
self.bind("<KeyRelease>", self.get_typed)
self.bind("<FocusIn>", self.deselect, add="+")
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; filters out most non-alpha-numeric
keys; runs the functions not triggered by events.
"""
def do_it():
hits = self.match_string()
self.show_hits(hits, self.pos)
if self.autofill is False:
return
key = evt.keysym
# allow alphanumeric characters
if len(key) == 1:
do_it()
# allow hyphens and apostrophes
elif key in ('minus', 'quoteright'):
do_it()
# look for other chars that should be allowed in nested names
else:
pass
def match_string(self):
hits = []
got = self.get()
use_list = self.family_tree.place_autofill_values
for item in use_list:
if item.lower().startswith(got.lower()):
hits.append(item)
return hits
def show_hits(self, hits, pos):
cursor = pos + 1
if len(hits) != 0:
self.autofilled = hits[0]
self.delete(0, 'end')
self.insert(0, self.autofilled)
self.icursor(cursor)
def deselect(self, evt):
""" Since this is an autofill, replacement of selected text doesn't
work as expected, so clear the selection as a workaround.
"""
self.select_clear()
class PlacesTab(tk.Frame):
def __init__(
self, master, family_tree, family_tree_id, treebard, main,
formats, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.family_tree = family_tree
self.family_tree_id = family_tree_id
self.treebard = treebard
self.main = main
self.formats = formats
self.actionoptvar = tk.IntVar(None, 99)
self.selectplacevar = tk.IntVar(None, 99)
self.subject = None
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
self.current_file = get_current_tree(family_tree_id, cur)
self.current_tree = self.current_file[0]
cur.execute("ATTACH ? as tree", (self.current_tree,))
self.RADCOLS = ("ADD", "DELETE", "EDIT", "NOTE")
self.RADROWS = ("single place", "single place name", "nested place")
self.make_places_tab_inputs()
self.canvas = main.master
cur.execute("DETACH tree")
cur.close()
conn.close()
def make_places_tab_inputs(self):
controls = tk.Frame(self)
instrux = tk.Label(
controls,
text="Select an action to take in regards to a single or nested "
"place.\nInputs for the selected action will appear in a dialog.",
justify="left", bd=1, relief="raised")
select_action_radframe = tk.Frame(controls)
for idx, text in enumerate(self.RADCOLS, 1):
head = tk.Label(select_action_radframe, text=text, anchor="w", width=8)
head.grid(column=idx, row=0, sticky="ew")
for idx, text in enumerate(self.RADROWS, 1):
left = tk.Label(select_action_radframe, text=text, anchor="w")
left.grid(column=0, row=idx, sticky="ew", padx=(0,12))
value = 1
columns = 4
rows = 3
for row in range(rows):
for column in range(columns):
rad = tk.Radiobutton(
select_action_radframe, value=value, variable=self.actionoptvar,
anchor="w", command=self.open_place_edit_dialog)
rad.grid(row=row+1, column=column+1, sticky="ew")
value += 1
# children of self
self.columnconfigure(0, weight=1)
controls.grid(column=0, row=0, padx=12, sticky="ew")
# children of controls
controls.columnconfigure(0, weight=1)
instrux.grid(column=0, row=0, ipadx=9, ipady=9, pady=(0,12))
select_action_radframe.grid(column=0, row=1, pady=(0,12))
def open_place_edit_dialog(self):
self.place_edit_dialog = PlaceEdit(
self.family_tree, self.current_file, self.formats,
self.family_tree_id, self.selectplacevar, self.main,
current_action=self.actionoptvar.get(),
selectplacevargot=self.selectplacevar.get())
def make_new_place(name, check_dupes, conn, cur):
now = datetime.now()
datestamp = now.strftime("%Y%m%d%H%M")
cur.execute(insert_place_hint_check_dupes, (check_dupes, datestamp))
conn.commit()
new_place_id = cur.lastrowid
cur.execute(insert_place_name_main, (name, new_place_id))
conn.commit()
return new_place_id
class ValidatePlace():
""" Duplicate single places and duplicate nested places have to be checked
by the user viewing a dialog. Any place name that has no duplicates can
be opted out of the dupes dialog. OLD: Existing places
go into a list. New places go straight into database and into a list.
On OK the new places have already been input so just input the places
that already exist. On CANCEL delete the new places.
"""
opened_dialogs = 0
selections = []
event = None
inwidg = None
initial = ""
shrinking_place_list = []
full_place_list = []
single_place_values = {
"single_place_name": "",
"place_id": 0,
"make_new": False,
"check_dupes": 1}
def cancel_place_dialogs(dialog=None, cell=None):
if dialog:
dialog.destroy()
def __init__(
self, treebard, inwidg, initial, final, place_data, formats,
family_tree, family_tree_id, current_file, main,
event=None, place_tab=False):
self.treebard = treebard
ValidatePlace.treebard = self.treebard = treebard
ValidatePlace.inwidg = self.inwidg = inwidg
ValidatePlace.initial = self.initial = initial
self.final = final
self.place_data = place_data
self.formats = formats
self.family_tree = family_tree
self.family_tree_id = family_tree_id
self.current_file = current_file
self.main = main
ValidatePlace.event = self.event = event
self.place_tab = place_tab
self.new_places = []
self.single_place_values = dict(ValidatePlace.single_place_values)
self.validate_place()
def make_widgets(self):
self.duplicate_place_dialog.window.columnconfigure(0, weight=1)
self.duplicate_place_dialog.window.rowconfigure(0, weight=1)
self.content = tk.Frame(self.duplicate_place_dialog.window)
self.header = tk.Label(
self.content, text=f"Which {self.name}?",
justify='left', wraplength=600)
self.radframe = tk.Frame(self.content)
self.checkfrm = tk.LabelFrame(self.content)
buttons = tk.Frame(self.content)
okbutt = tk.Button(
buttons, text="OK", width=7, command=self.ok_place_dialogs)
cancelbutt = tk.Button(
buttons, text="CANCEL", width=7,
command=lambda dialog=self.duplicate_place_dialog:
ValidatePlace.cancel_place_dialogs(dialog))
# children of self.duplicate_place_dialog.window
self.duplicate_place_dialog.window.columnconfigure(0, weight=1)
self.duplicate_place_dialog.window.rowconfigure(1, weight=1)
self.content.grid(column=0, row=0, sticky="news")
# children of self.content
self.header.grid(
column=0, row=0, sticky='news', ipady=6, ipadx=6,
columnspan=2)#, padx=12, pady=12
self.radframe.grid(
column=0, row=1, sticky="news", columnspan=2)#, pady=(12,0)
self.checkfrm.grid(column=0, row=2, columnspan=2)#, padx=12
buttons.grid(column=1, row=3, sticky='se', pady=(12,6))#, padx=12, pady=12
# children of buttons
okbutt.grid(column=0, row=0)
cancelbutt.grid(column=1, row=0, padx=(3,0))
self.duplicate_place_dialog.maxsize(
int(self.duplicate_place_dialog.winfo_screenwidth() * 0.90),
int(self.duplicate_place_dialog.winfo_screenheight() * 0.90))
def make_inputs(self, cur):
""" Make a section of widgets for each duplicate of a place name. """
self.checkfrm.config(
text=f"Don't ask about duplicate places named '{self.name}'.")
# Dupe checking is off/1 by default.
self.checkvar = tk.IntVar(None, 1)
self.dupevar = tk.IntVar(None, 99)
self.radnew = tk.Radiobutton(
self.radframe,
text=f"New place named {self.name}",
variable=self.dupevar,
value=1000, )
self.radnew.config(command=lambda widg=self.radnew:
self.handle_radiobutton_click(widg, radio="new"))
self.radnew.grid(column=0, row=1, sticky="w")#, padx=12
row = 2
for num in self.dupe_ids:
cur.execute(select_place_hint, (num,))
hint = cur.fetchone()[0]
if hint:
text=f"{self.name} (hint: '{hint}')"
else:
text=self.name
frm = tk.LabelFrame(
self.radframe,
text=(f"Existing place named {self.name} and nested places "
f"which include this single place:"))
rad = tk.Radiobutton(
frm, text=text,
variable=self.dupevar,
value=row-2)
frm.grid(column=0, row=row, sticky="ew", pady=(0,12))
rad.grid(column=0, row=0, sticky="w")
rad.config(
command=lambda widg=rad: self.handle_radiobutton_click(
widg, radio="existing"))
self.show_examples(num, frm, cur)
row += 1
self.checkcheck = tk.Checkbutton(
self.checkfrm, onvalue=0, offvalue=1, variable=self.checkvar,
text="For non-duplicate place names. Change this option any "
"time in the places tab.")
self.checkcheck.grid(column=0, row=0, sticky="w")
self.checkcheck.config(command=self.handle_checkbutton_click)
self.duplicate_place_dialog.resize_scrolled_content(
self.duplicate_place_dialog, self.duplicate_place_dialog.canvas,
add_x=16, add_y=24)
def validate_place(self):
""" For more on why a '+' is added to new name input, see dev docs. """
conn = sqlite3.connect(appwide_db_path)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
self.current_tree, self.current_dir = get_current_tree(self.family_tree_id, cur)
cur.execute("ATTACH ? as tree", (self.current_tree,))
if self.initial:
lenstart = len(self.initial)
else:
lenstart = 0
lenfinal = len(self.final)
place_list1 = self.final.replace(",,", ",").replace(" ", " ").replace(
"+", "", -1)
place_list = place_list1.split(",")
ValidatePlace.full_place_list = [i.strip() for i in place_list]
ValidatePlace.shrinking_place_list = list(ValidatePlace.full_place_list)
self.nesting_length = len(ValidatePlace.full_place_list)
if self.initial == self.final:
return
if self.final.endswith("+"):
self.dispatch_tasks(cur, conn, new=True)
self.final = self.final.replace("+", "", -1)
ValidatePlace.final = self.final
elif self.initial is None:
self.initial = self.inwidg.get()
self.dispatch_tasks(
cur, conn, change_current=True,
current_file=self.current_file)
elif lenstart > 0 and lenfinal == 0:
self.dispatch_tasks(cur, conn, unlink=True)
elif lenstart > 0 and lenfinal > 0:
self.dispatch_tasks(cur, conn, replace=True)
elif lenstart == 0 and lenfinal > 0:
self.dispatch_tasks(
cur, conn, add=True, current_file=self.current_file)
else:
print("line", look(seeline()).lineno, "case not handled:")
cur.execute("DETACH tree")
cur.close()
conn.close()
def dispatch_tasks(
self, cur, conn, current_file=None, new=False, add=False,
replace=False, unlink=False, change_current=False):
if new:
self.open_dupe_place_dlg()
elif add or replace or change_current:
found = self.family_tree.place_autofill_values.count(self.final)
nested_place_id = self.get_nested_place_id_from_string(found, cur)
if add or replace:
cur.execute(
update_event_nested_place, (nested_place_id, ValidatePlace.event,))
conn.commit()
redraw_events_table(
self.main.events_table, self.main, cur, self.formats,
current_person_id=self.main.current_person_id)
cur.execute(select_current_nested_place_id)
current_nested_place_id = cur.fetchone()[0]
redraw_place_tab(
current_nested_place_id, self.family_tree, self.main, self.current_file,
cur, self.formats)
elif change_current:
cur.execute(update_current_nested_place, (nested_place_id,))
conn.commit()
redraw_place_tab(
nested_place_id, self.family_tree, self.main,
self.current_file, cur, self.formats)
elif unlink:
cur.execute(update_event_nested_place_unknown, (ValidatePlace.event,))
conn.commit()
print("line", look(seeline()).lineno, "running redraw_gui:")
redraw_gui(
main=self.main, family_tree=self.family_tree,
family_tree_id=self.family_tree_id,
current_file=self.current_file, conn=conn, cur=cur, formats=self.formats)
def get_nested_place_id_from_string(self, found, cur):
def run_inner_loop(dkt):
for k,v in dkt.items():
if self.final == k:
nested_place_id = v["nested_place_id"]
return nested_place_id
else:
continue
nested_place_id = None
if found == 0:
return
elif found > 1:
nestings = []
for dkt in self.family_tree.place_data:
nested_place_id = run_inner_loop(dkt)
if nested_place_id is None:
continue
else:
nestings.append(nested_place_id)
if len(nestings) == found:
break
self.open_dupe_nesting_dlg(nestings, cur)
elif found == 1:
for dkt in self.family_tree.place_data:
nested_place_id = run_inner_loop(dkt)
if nested_place_id is None:
continue
else:
nested_place_id = nested_place_id
break
if nested_place_id:
self.prepend_match(nested_place_id)
return nested_place_id
def open_dupe_nesting_dlg(self, nestings, cur):
single_places = self.final.split(", ")
single_place_hints = []
place_id_lists = []
for indx, nesting_id in enumerate(nestings):
cur.execute(select_nested_place_ids, (nesting_id,))
lst = [i for i in cur.fetchone() if i != 1]
place_id_lists.append(lst)
final_place_ids = list(zip(*place_id_lists))
final_final = []
for idx, tup in enumerate(final_place_ids):
if len(set(tup)) != 1:
for lst in place_id_lists:
place_id = lst[idx]
cur.execute(select_place_hint, (place_id,))
hint = cur.fetchone()[0]
final_final.append([place_id, single_places[idx], hint])
for idx, key in enumerate(nestings):
single_place_hints.append({key: [final_final[idx]]})
self.dupenestvar = tk.IntVar(None, 99)
self.duplicate_nesting_dialog = ScrolledDialog(self.treebard)
self.duplicate_nesting_dialog.protocol(
"WM_DELETE_WINDOW", self.cancel_dupe_nesting_dlg)
self.duplicate_nesting_dialog.title("Duplicate Nested Place")
header = tk.Label(
self.duplicate_nesting_dialog.window, text=places_msg[1],
justify='left', wraplength=850, bd=1, relief="raised")
radframe = tk.Frame(self.duplicate_nesting_dialog.window)
idx = 0
row = 0
for dkt in single_place_hints:
for k,v in dkt.items():
rad = tk.Radiobutton(
radframe, value=idx, variable=self.dupenestvar,
text=self.final, anchor="w")
hint_frame = tk.Frame(radframe)
rad.grid(column=0, row=row, sticky="ew")
hint_frame.grid(
column=0, row=row+1, sticky="w", pady=(0,12), padx=(28,0))
if idx == 0:
rad.select()
rad.focus_set()
for part,lst in enumerate(v):
text = f"Nest: {lst[1]}, hint: '{lst[2]}'"
lab = tk.Label(hint_frame, anchor="w", text=text)
lab.grid(column=0, row=part, sticky="ew")
idx += 1
row += 2
buttonbox = tk.Frame(self.duplicate_nesting_dialog.window)
button_yes = tk.Button(
buttonbox, text="OK", width=8,
command=lambda hints=single_place_hints: self.ok_dupe_nesting_dlg(hints))
button_no = tk.Button(
buttonbox, text="CANCEL", width=8, command=self.cancel_dupe_nesting_dlg)
header.grid(
column=0, row=0, sticky='news', padx=12, pady=12, ipadx=6, ipady=3)
radframe.grid(column=0, row=1, columnspan=2)
buttonbox.grid(column=0, row=2, padx=12, pady=12, sticky="e")
button_yes.grid(column=0, row=0)
button_no.grid(column=1, row=0, padx=(6,0))
configall(self.duplicate_nesting_dialog, self.formats)
self.duplicate_nesting_dialog.resize_scrolled_content(
self.duplicate_nesting_dialog, self.duplicate_nesting_dialog.canvas,
add_x=16, add_y=24)
def ok_dupe_nesting_dlg(self, single_place_hints):
idx = self.dupenestvar.get()
nested_place_id = list(single_place_hints[idx].keys())[0]
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(update_event_nested_place, (nested_place_id, ValidatePlace.event))
conn.commit()
self.duplicate_nesting_dialog.destroy()
self.inwidg.config(text=self.final)
cur.close()
conn.close()
def cancel_dupe_nesting_dlg(self):
self.duplicate_nesting_dialog.destroy()
self.inwidg.return_focus()
def show_examples(self, num, frm, cur):
""" List the nestings that use the current single place and occur most
commonly in the conclusions table in `commonest`. If `commonest`
is empty, list nestings that use the current single place and
exist but don't occur in the conclusions table in
`unused_nestings`. If `commonest` and `unused_nestings` are both
empty, change text to "".
"""
cur.execute(select_nested_place_inclusion, tuple([num] * 9,))
inclusions = [list(i) for i in cur.fetchall()]
copy = list(inclusions)
for indx, lst in enumerate(copy):
nested_place_id = lst[0]
lst = [i for i in lst[1:] if i != "unknown"]
inclusions[indx] = lst
examples = {}
for idx,lst in enumerate(copy):
examples[lst[0]] = inclusions[idx]
counted = []
cur.execute(select_nested_place_id_inclusion, tuple([num] * 9,))
for nested_place_id in cur.fetchall():
cur.execute(select_event_nested_place_id_count, nested_place_id)
counted.append(cur.fetchone())
counted = sorted(counted, key=lambda i: i[1], reverse=True)
commonest = [tup[0] for tup in counted[0:3] if tup[0]]
if len(commonest) == 0:
unused_nestings = self.get_unused_nestings(inclusions)
if len(unused_nestings) != 0:
for text in unused_nestings:
lab = tk.Label(frm, text=text, anchor="w")
lab.grid(column=0, row=idx+2, sticky="ew", padx=(36,0))
else:
# lab1.config(text="")
lab = tk.Label(frm, text="none", anchor="w")
lab.grid(column=0, row=idx+2, sticky="ew", padx=(36,0))
return
for idx,num in enumerate(commonest):
for k,v in examples.items():
if num == k:
lab = tk.Label(frm, text=", ".join(v), anchor="w")
lab.grid(column=0, row=idx+2, sticky="ew")#, padx=(36,0)
break
def get_unused_nestings(self, inclusions):
unused_nestings= []
for idx,lst in enumerate(list(inclusions)):
stg = ", ".join(lst)
unused_nestings.append(stg)
return unused_nestings
def stop_dupe_checks(self, widg, radio):
""" If the checkbutton is on/checked (0), dupe checking is off (False).
If the checkbutton is off/unchecked (1), dupe checking is on (True).
If no dupes exist yet, you can set dupe checking either way.
If one dupe already exists, and you choose to make a new one,
dupe checking turns on automatically (unchecked/1/False);
the checkbutton is automatically unchecked & disabled. [got rid of autocheck/uncheck 20230116]
If one dupe already exists, and you choose to use that one,
you can set dupe checking either way.
If two or more dupes already exist, you can't turn checking off;
the checkbutton is automatically unchecked & disabled.
"""
# The single place is not in the database:
if self.current_single_place_dupe_count == 0:
self.checkcheck.config(state="normal")
# One place matching the single place's spelling is in the db:
elif self.current_single_place_dupe_count == 1:
# A radiobutton was clicked:
if radio:
# It was the new place radiobutton:
if radio == "new":
self.checkcheck.config(state="disabled")
# It was an existing place radiobutton:
elif radio == "existing":
self.checkcheck.config(state="normal")
# The checkbutton was clicked:
else:
self.checkcheck.config(state="disabled")
# Two or more places matching the single place's spelling are in the db:
elif self.current_single_place_dupe_count > 1:
self.checkcheck.config(state="disabled")
else:
print("line", look(seeline()).lineno, "case not handled:")
if widg:
widg.focus_set()
else:
self.radnew.focus_set()
def handle_checkbutton_click(self):
""" This has to happen when the checkbutton is clicked, not just when
`handle_radiobutton_click()` runs, in order to get the intended value into
the dictionary. Normally when the radio is clicked, the checkbutton
hasn't been touched yet.
"""
self.single_place_values["check_dupes"] = self.checkvar.get()
def handle_radiobutton_click(self, widg=None, radio=None):
self.stop_dupe_checks(widg, radio)
dupevarget = self.dupevar.get()
if dupevarget == 99:
pass
elif dupevarget != 1000:
self.single_place_values["make_new"] = False
self.single_place_values["place_id"] = self.dupe_ids[dupevarget]
elif dupevarget == 1000:
self.single_place_values["make_new"] = True
self.single_place_values["place_id"] = 0
else:
print("line", look(seeline()).lineno, "dupevarget:", dupevarget)
def open_dupe_place_dlg(self):
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
self.name = ValidatePlace.shrinking_place_list.pop(0)
cur.execute(select_place_id_with_name, (self.name,))
self.dupe_ids = [i[0] for i in cur.fetchall()]
self.current_single_place_dupe_count = len(self.dupe_ids)
self.single_place_values["single_place_name"] = self.name
if len(ValidatePlace.inwidg.get("1.0", "end")) == 0:
# if len(ValidatePlace.overlay.edit_input.get()) == 0:
self.unlink_nested_place_from_event(conn, cur)
cur.close()
conn.close()
return
self.duplicate_place_dialog = ScrolledDialog(self.treebard)
self.duplicate_place_dialog.title(
f"Duplicate Place Dialog input: {self.final.replace('+', '', 1)}")
ValidatePlace.opened_dialogs += 1
self.duplicate_place_dialog.geometry("+120+24")
self.rc_menu = RightClickMenu(self.family_tree, self.treebard)
self.make_widgets()
self.make_inputs(cur)
check_dupes = self.handle_duplicate_names(cur)
if check_dupes == 0:
self.dupevar.set(0)
self.checkvar.set(0)
self.handle_radiobutton_click()
self.handle_checkbutton_click()
self.ok_place_dialogs()
elif check_dupes == 1 and len(ValidatePlace.inwidg.get("1.0", "end")) == 0:
# elif check_dupes == 1 and len(ValidatePlace.overlay.edit_input.get()) == 0:
# don't open dupe place dlg, just unlink the place from the event
print("line", look(seeline()).lineno, "check_dupes:", check_dupes)
cur.close()
conn.close()
ScrolledDialog.bind_canvas_to_mousewheel(self.duplicate_place_dialog.canvas)
configall(self.duplicate_place_dialog, self.formats)
self.duplicate_place_dialog.resize_scrolled_content(
self.duplicate_place_dialog, self.duplicate_place_dialog.canvas,
add_x=16, add_y=24)
self.duplicate_place_dialog.lift()
self.duplicate_place_dialog.focus_force() # don't delete this line
for child in self.duplicate_place_dialog.winfo_children():
if child["takefocus"] == 1:
child.focus_set()
break
def unlink_nested_place_from_event(self, conn, cur):
cur.execute(update_event_nested_place, (1, self.event))
conn.commit()
def handle_duplicate_names(self, cur):
""" The purpose of the user setting as to whether or not to check for
duplicate single place names is for example so Treebard won't ask
every time someone inputs a new nested place that includes USA,
whether the same old USA is intended, or a new one. This is a
custom setting for each single place. See dev_docs.txt for more info.
"""
cur.execute(select_place_check_dupes_from_name, (self.name,))
result = cur.fetchall()
if len(result) == 1:
check_dupes = result[0][0]
elif self.current_single_place_dupe_count > 1:
check_dupes = 1
elif self.current_single_place_dupe_count == 0:
# To prevent the dialog from opening if a first-time single name
# string is being input, use `check_dupes = 0`, but some
# redesign of the code will be needed to make that work.
check_dupes = 1
else:
check_dupes = 1
return check_dupes
def ok_place_dialogs(self):
def err_done1():
msg1[0].grab_release()
msg1[0].destroy()
self.checkcheck.focus_set()
if self.dupevar.get() == 99:
msg1 = open_message(
self.treebard,
places_msg[0],
"No Selection was Made",
"OK")
msg1[0].grab_set()
msg1[2].config(command=err_done1)
return
self.duplicate_place_dialog.destroy()
ValidatePlace.selections.append(dict(self.single_place_values))
self.single_place_values = dict(ValidatePlace.single_place_values)
if ValidatePlace.opened_dialogs == self.nesting_length:
self.update_db()
ValidatePlace.cancel_place_dialogs(
dialog=self.duplicate_place_dialog)
self.reset_class_vars()
subclass = type(self.inwidg).__name__
# if subclass == "LabelEntryDynamic":
# self.inwidg.return_focus()
if subclass == "CellAutoPlace": # the 2 subclasses shd be combined, they get the same response
self.main.new_current_place_input.focus_set()
elif subclass == "EntryAutoPlace":
self.main.new_current_place_input.focus_set()
else:
print("line", look(seeline()).lineno, "case_not_handled:")
else:
self.open_dupe_place_dlg()
def reset_class_vars(self):
""" Use class variables for one nesting, which uses a class instance
for each contained nest, then zero out the class variables' values.
"""
ValidatePlace.opened_dialogs = 0
ValidatePlace.selections = []
ValidatePlace.event = None
ValidatePlace.inwidg = None
ValidatePlace.initial = ""
ValidatePlace.shrinking_place_list = []
ValidatePlace.full_place_list = []
ValidatePlace.single_place_values = {
"single_place_name": "",
"place_id": 0,
"make_new": False,
"check_dupes": 1}
def update_db(self):
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (self.current_tree,))
for idx,dkt in enumerate(list(ValidatePlace.selections)):
if dkt["make_new"]:
new_place_id = make_new_place(
ValidatePlace.full_place_list[idx], dkt["check_dupes"],
conn, cur)
self.current_single_place_dupe_count += 1
ValidatePlace.selections[idx]["place_id"] = new_place_id
ValidatePlace.selections[idx]["make_new"] = False
nested_ids = []
for dkt in ValidatePlace.selections:
place_id = dkt["place_id"]
nested_ids.append(place_id)
cur.execute(update_place_check_dupes, (dkt["check_dupes"], place_id))
conn.commit()
length = len(nested_ids)
new_nesting = nested_ids + [1] * (9 - length)
cur.execute(insert_nested_place, tuple(new_nesting))
conn.commit()
new_nested_place_id = cur.lastrowid
cur.execute(
update_event_nested_place,
(new_nested_place_id, ValidatePlace.event,))
conn.commit()
self.family_tree.place_data = self.family_tree.get_place_values(
new_place=True, current_tree=self.current_tree)
self.family_tree.single_place_autofill_values = self.family_tree.create_single_place_lists(cur)
self.prepend_match(new_nested_place_id)
cur.execute("DETACH tree")
cur.close()
conn.close()
def prepend_match(self, new_nested_place_id):
def run_inner_loop(dkt):
for k,v in dkt.items():
if new_nested_place_id == v["nested_place_id"]:
prependee = k
return prependee
else:
continue
for dkt in self.family_tree.place_data:
prependee = run_inner_loop(dkt)
if prependee is None:
continue
else:
prependee = prependee
break
idx = self.family_tree.place_autofill_values.index(prependee)
popped = self.family_tree.place_autofill_values.pop(idx)
self.family_tree.place_autofill_values.insert(0, popped)
for widg in self.family_tree.place_autofill_inputs:
widg.values = self.family_tree.place_autofill_values
class PlacesExport(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.make_widgets()
def make_widgets(self):
headlab = tk.Label(
self,
text="Export places from one tree to another.\nSelect one tree from each column.",
justify="left", wraplength=600, bd=1, relief="raised")
from_column = tk.Frame(self, bd=1)
to_column = tk.Frame(self, bd=1)
from_head = tk.Label(from_column, text="FROM:", anchor="w")
to_head = tk.Label(to_column, text="TO:", anchor="w")
ok_button = tk.Button(
self, text="OK",
command=lambda from_column=from_column, to_column=to_column:
self.ok_export_places(from_column, to_column),
width=7)
self.rowconfigure(1, weight=1)
self.master.columnconfigure(0, weight=1)
headlab.grid(column=0, row=0, columnspan=2, pady=12, ipadx=6, ipady=6, padx=12)
from_column.grid(column=0, row=1, sticky="new")
to_column.grid(column=1, row=1, sticky="new", padx=(24,0))
ok_button.grid(column=1, row=2, sticky="e", pady=12)
from_head.grid(column=0, row=0, sticky="ew", padx=12)
to_head.grid(column=0, row=0, sticky="ew", padx=12)
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_all_family_trees)
self.all_trees = sorted([list(i) for i in cur.fetchall()], key=lambda i: i[0])
cur.close()
conn.close()
for column in from_column, to_column:
for lst in self.all_trees:
lab = tk.Label(column, text=lst[0], anchor="w")
lab.grid(sticky="new", padx=(12,0))
self.make_selectable(column)
def ok_export_places(self, from_column, to_column):
source = None
destiny = None
for child in from_column.winfo_children():
if child["bg"] == "black":
source = child
break
for child in to_column.winfo_children():
if child["bg"] == "black":
destiny = child
break
if source.cget("text") == destiny.cget("text"):
return
if source is None or destiny is None:
return
self.update_destination_db(source, destiny)
def update_destination_db(self, source, destiny):
source_id = None
destiny_id = None
source_title = source.cget("text")
destiny_title = destiny.cget("text")
for lst in self.all_trees:
if lst[0] == source_title:
source_id = lst[1]
elif lst[0] == destiny_title:
destiny_id = lst[1]
if source_id == destiny_id or source_id is None or destiny_id is None:
return
conn = sqlite3.connect(appwide_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
for idx, tree_id in enumerate((source_id, destiny_id)):
# Get full_path for both trees and attach them to the connection.
cur.execute(select_family_tree_path, (tree_id,))
full_path = f"{current_drive}/{cur.fetchone()[0]}"
if idx == 0:
cur.execute("ATTACH ? as source_alias", (full_path,))
elif idx == 1:
cur.execute("ATTACH ? as destiny_alias", (full_path,))
# Get all places, place_names, and nested_place_ids from source.
cur.execute("SELECT * FROM source_alias.place WHERE place_id != 1")
all_source_places = [list(i) for i in cur.fetchall()]
cur.execute("SELECT * FROM source_alias.place_name WHERE place_name_id != 1")
all_source_place_names = [list(i) for i in cur.fetchall()]
cur.execute("SELECT * FROM source_alias.nested_place WHERE nested_place_id != 1")
all_source_nested_places = [list(i) for i in cur.fetchall()]
# Get max IDs from destiny, three tables.
# Never available: place_id 1, place_name_id 1, nested_place_id 1.
cur.execute("SELECT max(place_id) from destiny_alias.place")
max_destiny_place_id = cur.fetchone()[0]
cur.execute("SELECT max(place_name_id) from destiny_alias.place_name")
max_destiny_place_name_id = cur.fetchone()[0]
cur.execute("SELECT max(nested_place_id) from destiny_alias.nested_place")
max_destiny_nested_place_id = cur.fetchone()[0]
# Create available IDs for imported data.
for lst in all_source_places:
lst[0] = lst[0] + max_destiny_place_id - 1
place_tups = [tuple(lst) for lst in all_source_places]
for lst in all_source_place_names:
lst[0] = lst[0] + max_destiny_place_name_id - 1
lst[2] = lst[2] + max_destiny_place_id - 1
place_name_tups = [tuple(lst) for lst in all_source_place_names]
for idx, lst in enumerate(all_source_nested_places):
lst[0] = lst[0] + max_destiny_nested_place_id - 1
lst = [lst[0]] + [i + max_destiny_place_id - 1 if i != 1 else i for i in lst[1:]]
all_source_nested_places[idx] = lst
nested_place_tups = [tuple(lst) for lst in all_source_nested_places]
# Insert places to destiny, three tables.
cur.executemany(
"INSERT INTO destiny_alias.place VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
place_tups)
conn.commit()
cur.executemany(
"INSERT INTO destiny_alias.place_name VALUES (?, ?, ?, ?)",
place_name_tups)
conn.commit()
cur.executemany(
"INSERT INTO destiny_alias.nested_place VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
nested_place_tups)
conn.commit()
for tree in ("source_alias", "destiny_alias"):
cur.execute(f"DETACH {tree}")
cur.close()
conn.close()
def select(self, evt, children):
widg = evt.widget
for label in children:
if label == widg:
widg["bg"] = "black"
else:
label["bg"] = "#414141"
def make_selectable(self, column):
children = column.winfo_children()
for child in children:
child.bind("<Button-1>", lambda evt, children=children:
self.select(evt, children))
class PlaceEdit(tk.Toplevel):
def __init__(
self, master, current_file, formats, family_tree_id, selectplacevar,
main, current_action=None,
selectplacevargot=None, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.family_tree = master
self.current_file = current_file
self.current_tree = self.current_file[0]
self.formats = formats
self.family_tree_id = family_tree_id
self.selectplacevar = selectplacevar
self.main = main
self.current_action = current_action
self.selectplacevargot = selectplacevargot
self.subject = None
self.title(f"Place Edit Dialog {self.main.user_tree_title}")
conn = sqlite3.connect(appwide_db_path)
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (self.current_tree,))
self.make_inputs()
cur.execute("DETACH tree")
cur.close()
conn.close()
configall(self, self.formats)
def update_place_lists(self, cur, new_place=False):
self.family_tree.place_data = self.family_tree.get_place_values(
new_place=new_place, current_tree=self.current_tree)
self.family_tree.create_single_place_lists(cur)
def make_inputs(self):
self.controls = tk.Frame(self)
self.controls.grid(column=0, row=0, sticky="news", padx=12, pady=12)
actions = {1: self.add_place, 2: self.delete_place}
for child in self.controls.winfo_children():
child.destroy()
for actionvar, funx in actions.items():
if self.current_action == actionvar:
funx()
break
def add_place(self):
def ok_add1():
name = add_single_input.get().strip()
if len(name) == 0 or "," in name:
self.destroy()
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(insert_place_new)
conn.commit()
new_place_id = cur.lastrowid
cur.execute(select_place_name_count, (new_place_id,))
count = cur.fetchone()[0]
if count == 0:
main_name = 1
else:
main_name = 0
cur.execute(insert_place_name, (name, new_place_id, main_name))
conn.commit()
cur.execute(insert_nested_place, (tuple([new_place_id] + [1] * 8)))
conn.commit()
nested_place_id = cur.lastrowid
self.update_place_lists(cur, new_place=True)
cur.close()
conn.close()
self.destroy()
def cancel_add1():
self.destroy()
self.title("Add a New Single Place")
add1 = tk.Label(
self.controls, text="Single place name (no commas):", anchor="w")
add_single_input = tk.Entry(self.controls, width=36)
buttons = tk.Frame(self.controls)
oker = tk.Button(buttons, text="OK", width=7, command=ok_add1)
noper = tk.Button(buttons, text="CANCEL", width=7, command=cancel_add1)
add1.grid(column=0, row=0, sticky="e")
add_single_input.grid(column=1, row=0, padx=(6,0), sticky="w")
buttons.grid(column=1, row=2, sticky="e", pady=(12,0))
oker.grid(column=0, row=0)
noper.grid(column=1, row=0, padx=(6,0))
add_single_input.focus_set()
def delete_place(self):
def ok_del1():
nesting = del_single_input.get().strip()
if len(nesting) == 0:
del_single_input.delete(0, "end")
del_single_input["bd"] = 3
del_single_input.focus_set()
return
conn = sqlite3.connect(self.current_tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
id_to_delete = self.get_id_to_delete(nesting, cur)
cur.execute(delete_note_place, (id_to_delete,))
conn.commit()
cur.execute(delete_note_place_name_by_place_id, (id_to_delete,))
conn.commit()
cur.execute(delete_place_name, (id_to_delete,))
conn.commit()
cur.execute(select_current_nested_place_id)
current_nested_place_id = cur.fetchone()[0]
cur.execute(select_nested_place_by_nest0, (id_to_delete,))
nestings = [i[0] for i in cur.fetchall()]
for nested_place_id in nestings:
if nested_place_id == current_nested_place_id:
cur.execute(update_current_nested_place_to_1)
conn.commit()
cur.execute(select_nested_place_ids, (nested_place_id,))
old_nests = cur.fetchone()
if old_nests.count(1) != 8:
new_nests = old_nests[1:] + (1, nested_place_id)
cur.execute(update_nested_place_nests, new_nests)
conn.commit()
else:
cur.execute(delete_nested_place, (nested_place_id,))
conn.commit()
cur.execute(delete_place, (id_to_delete,))
conn.commit()
self.update_place_lists(cur, new_place=True)
cur.close()
conn.close()
self.destroy()
def cancel_del1():
self.destroy()
del1 = tk.Label(self.controls, text="Place to delete:")
del_single_input = EntryAutoPlace(
self.controls, self.family_tree, autofill=True, width=48,
values=self.family_tree.place_autofill_values)
buttons = tk.Frame(self.controls)
oker = tk.Button(buttons, text="OK", width=7, command=ok_del1)
noper = tk.Button(buttons, text="CANCEL", width=7, command=cancel_del1)
del1.grid(column=0, row=0, sticky="ew")
del_single_input.grid(column=1, row=0, sticky="w", padx=(6,0))
buttons.grid(column=1, row=1, sticky="e", pady=(12,0))
oker.grid(column=0, row=0)
noper.grid(column=1, row=0, padx=(6,0))
del_single_input.focus_set()
def get_id_to_delete(self, nesting, cur):
nest_ids = []
for dkt in self.family_tree.place_data:
for nested_place_string, innerdkt in dkt.items():
if nesting == nested_place_string:
nest_ids.append(innerdkt["nested_place_id"])
if len(nest_ids) == 1:
cur.execute(select_nested_place_smallest_nest, (nest_ids[0],))
id_to_delete = cur.fetchone()[0]
return id_to_delete
elif len(nest_ids) > 1:
print("line", look(seeline()).lineno, "have to open dupes dialog")
else:
print("line", look(seeline()).lineno, "len(nest_ids)", len(nest_ids))
# DO LIST (old--maybe obsolete)
# redo the procedure for place autofill values. Instead of 2 collections there should be one. Instead of a procedure that works differently depending on a boolean, there should be a dedicated method for every unique procedure. The autofill should have access to the nested place id so we know which id was filled in and the dict shd also include the id of the smallest place in that nesting
# in delete single place, forgot to delete the place from the events table
# re: Jasper change curr nested place--why is there a duplicate nested place dialog and why is there a hint? This seems like overkill until you realize that a single place can also be a nesting, then this becomes necessary. Add an auto-hint (time stamp) and a hint column to the nested_place table if needed. Use in get_id_to_delete and other places.