roles.py
Nov 25, 2022 18:04:30 GMT -8
Post by Uncle Buddy on Nov 25, 2022 18:04:30 GMT -8
<drive>:\treebard_gps\app\python\roles.py Last Changed 2023-11-15
# roles.py
import tkinter as tk
import sqlite3
from files import get_current_tree, appwide_db_path
from widgets import (
configall, make_formats_dict, ScrolledDialog, open_message, Combobox,
run_statusbar_tooltips, RightClickMenu, make_rc_menus, get_colors_type_id,
NEUTRAL_COLOR)
from persons import get_original, open_new_person_dialog, EntryAutoPerson
from messages import roles_msg
from messages_context_help import roles_dlg_help_msg, role_edit_help_msg
from dates import ChangeDate
from query_strings import (
select_roles_links_roles, select_role_types, select_role_type_id,
update_roles_links_role_type, update_roles_links_person_id,
insert_roles_links_role_person, delete_events_role,
select_count_events_roles, insert_role_type)
import dev_tools as dt
from dev_tools import look, seeline
class RolesDialog(ScrolledDialog):
def __init__(
self, master, family_tree_id, treebard, event_id, header,
current_person_id, inwidg=None, person_autofill_values=None,
linked_element=None, *args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.family_tree = master
self.family_tree_id = family_tree_id
self.treebard = treebard
self.event_id = event_id
self.header = header
self.current_person_id = current_person_id
self.inwidg = inwidg
self.protocol("WM_DELETE_WINDOW", self.close_roles_dialog)
self.role_types = []
self.roles_per_event = []
self.widget = None
self.idtip = None
self.idtip_text = None
self.idtip_bindings = {"on_enter": [], "on_leave": []}
self.person_inputs = []
self.current_name = self.family_tree.person_autofill_values[
self.current_person_id][0]["name"]
self.title(
f"Roles Dialog Current Person: {self.current_name}, ID #{self.current_person_id}")
self.rc_menu = RightClickMenu(self.family_tree, self.treebard)
self.rc_menu.style = "rightclickmenu"
self.make_widgets()
self.make_inputs()
ScrolledDialog.bind_canvas_to_mousewheel(self.canvas)
colors_type_id = get_colors_type_id(family_tree_id)
self.formats = make_formats_dict(colors_type_id=colors_type_id)
configall(self, self.formats)
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
def make_widgets(self):
self.columnconfigure(1, weight=1)
self.window.columnconfigure(2, weight=1)
self.window.rowconfigure(1, minsize=60)
self.window.columnconfigure(0, weight=1)
self.window.rowconfigure(0, weight=1)
self.header_msg = tk.Label(self.window, text=self.header, justify="left")
self.header_msg.style = "label"
def make_inputs(self):
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.role_types = self.get_role_types(cur)
self.rolfrm = tk.Frame(self.window)
new_roles_area = tk.Frame(self.window)
for widg in (self.rolfrm, new_roles_area):
widg.style = "frame"
self.make_roles_table(conn, cur)
new_roles_header = tk.Label(new_roles_area, text='Create New Role')
new_roles_header.style = "labelh3"
self.role_type_input = Combobox(
new_roles_area, self.formats, self.family_tree, values=self.role_types)
self.role_type_input.style = "combobox"
self.role_type_input.entry.focus_set()
self.person_input = EntryAutoPerson(
new_roles_area, self.family_tree_id, self.family_tree, width=32,
autofill=True, values=self.family_tree.person_autofill_values)
self.person_input.style = "entry"
self.person_input.bind("<FocusIn>", get_original, add="+")
self.family_tree.person_autofills.append(self.person_input)
self.add_butt = tk.Button(
new_roles_area,
text='ADD',
command=self.get_add_state)
self.done_butt = tk.Button(
new_roles_area,
text='DONE',
command=self.add_and_close)
self.close_butt = tk.Button(
new_roles_area,
text='CANCEL',
command=self.close_roles_dialog)
for widg in (self.add_butt, self.done_butt, self.close_butt):
widg.style = "button"
self.bind('<Escape>', self.close_roles_dialog)
# children of self.window
self.header_msg.grid(column=0, row=0, ipadx=9, ipady=9)
self.rolfrm.grid(column=0, row=1, columnspan=2)
new_roles_area.grid(column=0, row=2)
# children of new_roles_area
new_roles_header.grid(column=0, row=0)
self.role_type_input.grid(column=0, row=1)
self.person_input.grid(column=1, row=1, padx=(6,0))
self.add_butt.grid(column=2, row=1, padx=(6,0))
self.done_butt.grid(column=3, row=1, padx=(6,0))
self.close_butt.grid(column=4, row=1, padx=(6,0))
self.make_edit_row()
self.make_idtips()
cur.execute("DETACH tree")
cur.close()
conn.close()
visited = (
(self.header_msg,
'',
'Event-type, date, place & particulars of this conclusion if known.'),
(self.edit_role_type,
'Edit Role Type Input',
'Existing role type can be changed to a different type.'),
(self.edit_role_person_input,
'Edit Role Person Input',
'Existing role person can be changed or new person made.'),
(self.ok_butt,
'Edit Role OK Button',
'Submit changes to this role.'),
(self.cancel_butt,
'Edit Role Cancel Button',
'Close role edit row, make no changes.'),
(self.delete_butt,
'Edit Role Delete Button',
'Delete this role but not the role type or role person.'),
(self.role_type_input,
'New Role: Type Input',
'Select role type or create a new one.'),
(self.person_input,
'New Role: Person Input',
'Select role person or add new person.'),
(self.add_butt,
'Add New Role Button',
'Make a new role and leave dialog open.'),
(self.done_butt,
'Add New Role & Close Dialog Button',
'Make a new role and close dialog.'),
(self.close_butt,
'Close Dialog Button',
'Close dialog without making another role.'))
run_statusbar_tooltips(
visited,
self.statusbar.status_label,
self.statusbar.tooltip_label, self)
rcm_widgets = (
self.role_type_input, self.person_input, self.add_butt,
self.done_butt, self.close_butt)
make_rc_menus(
rcm_widgets,
self.rc_menu,
roles_dlg_help_msg)
def make_roles_table(self, conn, cur):
def get_clicked_row(evt):
self.got_row = evt.widget.grid_info()['row']
def on_hover(evt):
evt.widget.config(text='EDIT')
def on_unhover(evt):
evt.widget.config(text='')
self.make_roles_list(cur)
self.rolfrm.columnconfigure(4, weight=1)
first_butt = None
n = 0
for idx, lst in enumerate(list(self.roles_per_event)):
if type(lst[2]) is tuple:
self.roles_per_event[idx][2] = "({}) {}".format(lst[2][1], lst[2][0])
labelr = tk.Label(self.rolfrm, text=lst[1], anchor='e')
sep = tk.Label(self.rolfrm, text='|', anchor='w')
labelp = tk.Label(self.rolfrm, text=lst[4], anchor='w')
editx = tk.Button(
self.rolfrm,
width=3, text='', overrelief=tk.GROOVE,
command=self.grid_edit_row)
editx.style = "buttonquiet"
labelr.grid(column=0, row=n, padx=6, pady=3, sticky='ew')
sep.grid(column=1, row=n)
labelp.grid(column=2, row=n, padx=6, pady=3, sticky='ew')
editx.grid(column=3, row=n, padx=6, pady=3)
if n == 0:
first_butt = editx
editx.bind('<Enter>', on_hover)
editx.bind('<Leave>', on_unhover)
editx.bind('<Button-1>', get_clicked_row)
editx.bind('<space>', get_clicked_row)
self.rc_menu.loop_made[editx] = role_edit_help_msg
for widg in (labelr, sep, labelp):
widg.style = "label"
n += 1
if first_butt:
first_butt.focus_set()
configall(self.rolfrm, self.formats)
def make_roles_list(self, cur):
cur.execute(select_roles_links_roles, (self.event_id,))
roles_per_event = cur.fetchall()
if roles_per_event:
self.roles_per_event = [list(i) for i in roles_per_event]
for idx,lst in enumerate(list(self.roles_per_event)):
iD = lst[2]
name = self.family_tree.person_autofill_values[iD][0]["name"]
self.roles_per_event[idx].append(name)
def grid_edit_row(self):
self.edited_role_id = self.roles_per_event[self.got_row][0]
for child in self.rolfrm.winfo_children():
if child.winfo_class() != 'Label':
pass
elif child.grid_info()['row'] == self.got_row:
if child.grid_info()['column'] == 0:
self.original_role_type = self.roles_per_event[
self.got_row][1]
elif child.grid_info()['column'] == 2:
self.original_role_person = self.roles_per_event[
self.got_row][2]
self.edit_row.grid(row=self.got_row)
self.edit_role_type.delete(0, 'end')
self.edit_role_person_input.delete(0, 'end')
self.edit_role_type.insert(0, self.roles_per_event[self.got_row][1])
self.edit_role_type.focus_set()
chosen_person_id = self.roles_per_event[self.got_row][4]
if chosen_person_id is None:
self.edit_role_person_input.insert(0, '')
else:
self.edit_role_person_input.insert(0, self.roles_per_event[self.got_row][4])
self.edit_row.lift()
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
def get_role_types(self, cur):
cur.execute(select_role_types)
role_types = sorted([i[0] for i in cur.fetchall()])
return role_types
def get_add_state(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 widg in (self.role_type_input.entry, self.person_input):
if len(widg.get()) == 0:
widg.focus_set()
return
chosen_role_type = self.role_type_input.get()
self.user_input_person = self.person_input.get()
if chosen_role_type not in self.role_types:
chosen_role_type = self.make_new_role_type(chosen_role_type, conn, cur)
self.make_new_role(chosen_role_type, conn, cur)
cur.execute("DETACH tree")
cur.close()
conn.close()
def make_new_role_type(self, new_role_type, conn, cur):
cur.execute(insert_role_type, (new_role_type,))
conn.commit()
return new_role_type
def add_and_close(self):
self.get_add_state()
self.close_roles_dialog()
def close_roles_dialog(self, evt=None):
self.destroy()
def make_edit_row(self):
def cancel_edit_row():
self.edit_row.grid_remove()
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
self.edit_row = tk.Frame(self.rolfrm)
self.edit_row.style = "frame"
self.edit_role_type = Combobox(
self.edit_row, self.formats, family_tree=self.family_tree,
values=self.role_types)
self.edit_role_type.style = "combobox"
self.edit_role_person_input = EntryAutoPerson(
self.edit_row, self.family_tree_id, self.family_tree, width=32,
autofill=True, values=self.family_tree.person_autofill_values)
self.edit_role_person_input.style = "entryhilited"
self.edit_role_person_input.bind("<FocusIn>", get_original, add="+")
self.family_tree.person_autofills.append(self.edit_role_person_input)
self.ok_butt = tk.Button(
self.edit_row,
text='OK',
command=self.get_edit_state)
self.cancel_butt = tk.Button(
self.edit_row,
text='Cancel',
command=cancel_edit_row)
self.delete_butt = tk.Button(
self.edit_row,
text='Delete',
command=self.delete_role)
for widg in (self.ok_butt, self.cancel_butt, self.delete_butt):
widg.style = "button"
self.edit_row.grid(column=0, row=0, columnspan=5, sticky='ew')
self.edit_role_type.grid(column=0, row=0, padx=6, pady=6)
self.edit_role_person_input.grid(column=1, row=0, padx=6, pady=6)
self.ok_butt.grid(column=2, row=0, padx=6, pady=6)
self.cancel_butt.grid(column=3, row=0, padx=6, pady=6)
self.delete_butt.grid(column=4, row=0, padx=6, pady=6)
self.edit_row.grid_remove()
def make_new_role(self, role_type, conn, cur):
""" For more info on why a '+' is added to new name input, see dev docs. """
got = self.person_input.get()
if len(self.user_input_person) == 0:
self.person_input.focus_set()
return
name_data = self.person_input.check_name()
if name_data is None:
redo = f"{got}+"
name_data = self.person_input.check_name(redo=redo)
if name_data == "add_new_person":
role_person_id = open_new_person_dialog(
self.family_tree, self.family_tree_id, self.treebard,
inwidg=self.person_input, got=got)
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
else:
role_person_id = name_data[1]
cur.execute(select_role_type_id, (role_type,))
role_type_id = cur.fetchone()[0]
cur.execute(
insert_roles_links_role_person,
(self.event_id, role_type_id, role_person_id))
conn.commit()
ChangeDate(self.current_person_id, tag="person", conn=conn, cur=cur)
dont_destroy = []
for child in self.rolfrm.winfo_children():
if child.winfo_class() == 'Frame':
edit_row_frame = child
dont_destroy.append(edit_row_frame)
break
for child in self.rolfrm.winfo_children():
if child.master == edit_row_frame:
dont_destroy.append(child)
for child in self.rolfrm.winfo_children():
if child not in dont_destroy:
child.destroy()
self.make_roles_table(conn, cur)
self.inwidg.config(text=' ... ')
self.role_type_input.delete(0, 'end')
self.person_input.delete(0, 'end')
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
self.make_roles_list(cur)
def get_edit_state(self):
""" Detect and respond to changes in existing roles on OK button. For
more info 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()
cur.execute("ATTACH ? as tree", (self.current_tree,))
got = self.edit_role_person_input.get()
edited_role_type = self.edit_role_type.get()
if edited_role_type in self.role_types:
if edited_role_type != self.original_role_type:
self.update_role_type(edited_role_type, conn, cur)
else:
self.make_new_role_type(edited_role_type, conn, cur)
self.update_role_type(edited_role_type, conn, cur)
edited_role_person = self.edit_role_person_input.get()
name_data = self.edit_role_person_input.check_name()
if name_data is None:
redo = f"{got}+"
name_data = self.edit_role_person_input.check_name(redo=redo)
if name_data == "add_new_person":
role_person_id = open_new_person_dialog(
self.family_tree, self.family_tree_id, self.treebard,
inwidg=self.edit_role_person_input, got=got)
self.family_tree.person_autofill_values = self.family_tree.update_person_autofill_values()
self.change_role_person(edited_role_person, role_person_id, conn, cur)
else:
role_person_id = name_data[1]
new_name = self.family_tree.person_autofill_values[role_person_id][0]["name"]
self.change_role_person(new_name, role_person_id, conn, cur)
self.original_role_type = edited_role_type
self.original_role_person = edited_role_person
self.edit_row.grid_remove()
cur.execute("DETACH tree")
cur.close()
conn.close()
def delete_role(self):
def delete_role_from_db():
nonlocal role_count_per_event
conn = sqlite3.connect(appwide_db_path)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (self.current_tree,))
cur.execute(delete_events_role, (self.edited_role_id,))
conn.commit()
cur.execute(select_count_events_roles, (self.event_id,))
role_count_per_event = cur.fetchone()[0]
self.make_roles_list(cur)
cur.execute("DETACH tree")
cur.close()
conn.close()
role_count_per_event = 0
delete_role_from_db()
if role_count_per_event == 0:
self.inwidg.config(text=' ')
self.edit_row.grid_remove()
for child in self.rolfrm.winfo_children():
if child.winfo_class() == 'Frame':
pass
elif child.grid_info()['row'] == self.got_row:
child.destroy()
elif child.grid_info()['row'] > self.got_row:
row = child.grid_info()['row']
child.grid(row=row-1)
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
def update_role_type(self, edited_role_type, conn, cur):
cur.execute(select_role_type_id, (edited_role_type,))
chosen_role_type_id = cur.fetchone()
if chosen_role_type_id:
chosen_role_type_id = chosen_role_type_id[0]
cur.execute(
update_roles_links_role_type,
(chosen_role_type_id, self.edited_role_id))
conn.commit()
for child in self.rolfrm.winfo_children():
if child.winfo_class() == 'Frame':
pass
elif child.grid_info()['row'] == self.got_row:
if child.grid_info()['column'] == 0:
child.config(text=edited_role_type)
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
self.role_types = self.get_role_types(cur)
self.make_roles_list(cur)
def change_role_person(self, new_person_name, new_person_id, conn, cur):
cur.execute(
update_roles_links_person_id, (new_person_id, self.edited_role_id))
conn.commit()
ChangeDate(new_person_id, tag="person", conn=conn, cur=cur)
for child in self.rolfrm.winfo_children():
if child.winfo_class() == 'Frame':
pass
elif child.grid_info()['row'] == self.got_row:
if child.grid_info()['column'] == 2:
child.config(text=new_person_name.strip("+"))
self.resize_scrolled_content(self, self.canvas, add_x=16, add_y=24)
self.make_roles_list(cur)
def show_idtip(self, iD, name_type):
maxvert = self.winfo_screenheight()
if self.idtip or not self.idtip_text:
return
x, y, cx, cy = self.widget.bbox('insert')
self.idtip = d_tip = tk.Toplevel(self.widget)
label = tk.Label(
d_tip, bg=NEUTRAL_COLOR, fg="white",
text=self.idtip_text,
justify='left',
relief='solid',
bd=1)
label.pack(ipadx=6, ipady=3)
mouse_at = self.winfo_pointerxy()
tip_shift = 48
if mouse_at[1] < maxvert - tip_shift * 2:
x = mouse_at[0] + tip_shift
y = mouse_at[1] + tip_shift
else:
x = mouse_at[0] + tip_shift
y = mouse_at[1] - tip_shift
d_tip.wm_overrideredirect(1)
d_tip.wm_geometry('+{}+{}'.format(x, y))
def off(self):
d_tip = self.idtip
self.idtip = None
if d_tip:
d_tip.destroy()
def handle_enter(self, evt):
row = evt.widget.master.grid_info()['row']
iD, name_type = self.person_inputs[row][1:]
if len(name_type) == 2:
name_type = list(name_type)
name_type[1] = "({})".format(name_type[1])
name_type = " ".join(name_type)
self.idtip_text = "ID #{}: {}".format(iD, name_type)
if self.idtip_text:
self.show_idtip(iD, name_type)
def on_leave(self, evt):
self.off()
def make_idtips(self):
row = 0
for lst in self.roles_per_event:
iD = lst[2]
name_type = self.family_tree.person_autofill_values[iD][0]["name type"]
widg = self.edit_role_person_input
self.person_inputs.append((widg, iD, name_type))
row += 1
widg = self.edit_role_person_input
name_in = widg.bind("<Enter>", self.handle_enter)
name_out = widg.bind("<Leave>", self.on_leave)
self.widget = widg
self.idtip_bindings["on_enter"].append([widg, name_in])
self.idtip_bindings["on_leave"].append([widg, name_out])
def get_original(self, evt):
widg=evt.widget
widg.original = widg.get()