Post by Uncle Buddy on Aug 20, 2021 22:46:46 GMT -8
For more information see this thread: treebard.proboards.com/thread/79/doing-nested-places-right
Here is the first version of places.py that has passed the tests it was subjected to.
Here is the first version of places.py that has passed the tests it was subjected to.
'''
Validation and input to database has been tested for these cases:
ALL KNOWN PLACES, NO DUPES: dialog doesn't open;
ALL KNOWN, SOME OUTER DUPES
ALL KNOWN, SOME INNER DUPES
ALL KNOWN, SOME INNER & SOME OUTER DUPES
SOME NEW & SOME KNOWN, NO DUPES
SOME NEW & SOME KNOWN, SOME OUTER DUPES
SOME NEW & SOME KNOWN, SOME INNER DUPES
SOME NEW & SOME KNOWN, SOME INNER & SOME OUTER DUPES
ALL NEW, NO DUPES
ALL NEW, SOME OUTER DUPES
ALL NEW, SOME INNER DUPES
ALL NEW, SOME INNER & SOME OUTER DUPES
insert final parent (end of nesting)
insert first child (start of nesting)
insert intermediate child (mid-nesting)
'''
import tkinter as tk
from widgets import (
Toplevel, Frame, Button, Label, RadiobuttonBig, MessageHilited,
Entry, ButtonQuiet)
from place_autofill import EntryAuto
from toykinter_widgets import Separator
from nested_place_strings import (
make_all_nestings, ManyManyRecursiveQuery)
from query_strings import (
select_place_id_hint, insert_place_new, update_finding_places_null,
select_place_hint, select_all_places, select_all_places_places,
select_finding_places_nesting, update_place_hint, select_place_id2,
select_max_place_id, select_place_id1, update_finding_places,
insert_places_places_new, insert_finding_places, select_all_place_ids,
select_finding_places_id, select_places_places_id)
from files import current_file
from window_border import Border
from scrolling import Scrollbar, resize_scrolled_content
from error_messages import open_error_message, places_err
import dev_tools as dt
from dev_tools import looky, seeline
import sqlite3
def get_all_places_places():
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(select_all_places_places)
places_places = cur.fetchall()
cur.close()
conn.close()
return places_places
place_strings = make_all_nestings(select_all_place_ids)
places_places = get_all_places_places()
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(select_all_places)
all_place_names = [i[0] for i in cur.fetchall()]
cur.close()
conn.close()
unique_place_names = set(all_place_names)
class NewPlaceDialog():
def __init__(
self,
parent,
place_dicts,
message,
title,
button_labels,
treebard,
do_on_ok=None,
selection=None
):
self.parent = parent
self.place_dicts = place_dicts
self.message = message
self.title = title
self.button_labels = button_labels
self.treebard = treebard
self.do_on_ok = do_on_ok
self.got_row = 0
self.got_nest = None
self.edit_hint_id = 0
self.hint_to_edit = None
self.edit_rows = {}
self.make_widgets()
self.add_new_place_option = False
self.error = False
def make_widgets(self):
def show_message():
window.columnconfigure(1, weight=1)
window.rowconfigure(1, weight=1)
lab = MessageHilited(
window, text=self.message, justify='left', aspect=500)
lab.grid(column=1, row=1, sticky='news', ipady=18)
def ok():
if self.do_on_ok:
self.do_on_ok()
if self.error is False:
cancel()
def cancel():
self.new_places_dialog.destroy()
size = (
self.parent.winfo_screenwidth(), self.parent.winfo_screenheight())
self.new_places_dialog = Toplevel(self.parent)
self.new_places_dialog.geometry("+120+24")
self.new_places_dialog.maxsize(
width=int(size[0] * 0.85), height=int(size[1] * 0.95))
self.new_places_dialog.columnconfigure(1, weight=1)
self.new_places_dialog.rowconfigure(4, weight=1)
canvas = Border(self.new_places_dialog, size=3) # don't hard-code size
canvas.title_1.config(text=self.title)
canvas.title_2.config(text='')
window = Frame(canvas)
canvas.create_window(0, 0, anchor='nw', window=window)
scridth = 16
scridth_n = Frame(window, height=scridth)
scridth_w = Frame(window, width=scridth)
scridth_n.grid(column=0, row=0, sticky='ew')
scridth_w.grid(column=0, row=1, sticky='ns')
# DO NOT DELETE THESE LINES, UNCOMMENT IN REAL APP
# self.treebard.scroll_mouse.append_to_list([canvas, window])
# self.treebard.scroll_mouse.configure_mousewheel_scrolling()
window.vsb = Scrollbar(
self.new_places_dialog,
hideable=True,
command=canvas.yview,
width=scridth)
window.hsb = Scrollbar(
self.new_places_dialog,
hideable=True,
width=scridth,
orient='horizontal',
command=canvas.xview)
canvas.config(
xscrollcommand=window.hsb.set,
yscrollcommand=window.vsb.set)
window.vsb.grid(column=2, row=4, sticky='ns')
window.hsb.grid(column=1, row=5, sticky='ew')
buttonbox = Frame(window)
b1 = Button(buttonbox, text=self.button_labels[0], width=7, command=ok)
b2 = Button(buttonbox, text=self.button_labels[1], width=7, command=cancel)
scridth_n.grid(column=0, row=0, sticky='ew')
scridth_w.grid(column=0, row=1, sticky='ns')
window.columnconfigure(2, weight=1)
window.rowconfigure(1, minsize=60)
buttonbox.grid(column=1, row=3, sticky='se', pady=6)
b1.grid(column=0, row=0)
b2.grid(column=1, row=0, padx=(2,0))
self.frm = Frame(window)
self.frm.grid(column=1, row=2, sticky='news', pady=12)
show_message()
self.lay_out_radios()
resize_scrolled_content(self.new_places_dialog, canvas, window)
self.new_places_dialog.focus_set()
def ok_hint(self):
edit_row = self.edit_rows[self.got_nest]
new_hint = edit_row.ent.get()
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(update_place_hint, ((new_hint, self.edit_hint_id)))
conn.commit()
cur.close()
conn.close()
self.hint_to_edit.config(text="hint: {}".format(new_hint))
edit_row.remove_edit_row()
def make_edit_row(self, parent, row=None):
edit_row = EditRow(parent, self.ok_hint)
self.edit_rows[parent] = edit_row
def grid_edit_row(self, hint):
edit_row = self.edit_rows[self.got_nest]
edit_row.grid(column=0, row=self.got_row, sticky='ew', columnspan=2)
edit_row.lift()
for child in self.got_nest.winfo_children():
if child.grid_info()['column'] == 0:
if child.grid_info()['row'] == self.got_row - 1:
self.edit_hint_id = int(child.cget('text').split(': ')[0])
elif child.grid_info()['column'] == 1:
if child.grid_info()['row'] == self.got_row:
if child.winfo_class() == 'Label':
self.hint_to_edit = child
edit_row.ent.delete(0, 'end')
edit_row.ent.insert(0, hint)
edit_row.ent.focus_set()
def get_clicked_row(self, evt):
self.got_row = evt.widget.grid_info()['row']
self.got_nest = evt.widget.master
def on_hover(self, evt):
evt.widget.config(text='Edit')
def on_unhover(self, evt):
evt.widget.config(text='')
def lay_out_radios(self):
'''
Make a new place dialog the same way for every place, then for
certain qualifying (unambiguous) cases, don't open the dialog.
'''
self.radvars = []
i = 0
for dd in self.place_dicts:
self.var = tk.IntVar()
self.radvars.append(self.var)
i += 1
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.close()
conn.close()
self.bullet = len(self.place_dicts) - 1
self.rowdx = 0
self.vardx = 0
for dkt in self.place_dicts:
self.make_radiobuttons(dkt)
def make_radiobuttons(self, dkt):
if len(dkt["id"]) > 0:
nest_ids = dkt["id"]
else:
nest_ids = dkt["temp_id"]
place_string = '{}: {}, place ID #{}'.format(
self.bullet, dkt["input"], nest_ids)
lab = Label(self.frm, text=place_string)
lab.grid(column=0, row=self.rowdx, sticky='w')
self.nest_level = Frame(self.frm)
self.nest_level.grid(column=0, row=self.rowdx+1, sticky='w', padx=(0,3), columnspan=2)
self.nest_level.columnconfigure(0, minsize=48)
self.make_edit_row(self.nest_level)
self.radx = 0
row = 0
for hint in dkt["hint"]:
if len(dkt["id"]) > 0:
new_id = dkt["temp_id"]
last_idx = len(dkt["id"])
if self.radx == last_idx:
self.current_id = new_id
self.select_first_radio()
rad_string = "{}: {} (new place and new place ID)".format(
self.current_id, dkt["input"])
else:
self.current_id = dkt["id"][self.radx]
self.select_first_radio()
nesting = ManyManyRecursiveQuery(initial_id=self.current_id).radio_text
rad_string = "{}: {}".format(self.current_id, nesting)
elif len(dkt["id"]) == 0:
self.current_id = dkt["temp_id"]
self.select_first_radio()
rad_string = "{}: {} (new place and new place ID)".format(
self.current_id, dkt["input"])
else:
print("line", looky(seeline()).lineno, "case not handled")
rad = RadiobuttonBig(
self.nest_level,
variable=self.radvars[self.vardx],
value=self.current_id,
text=rad_string,
anchor="w")
lab = Label(
self.nest_level,
text="hint: {}".format(hint),
anchor='w', bg='red')
editx = ButtonQuiet(
self.nest_level,
width=2,
command=lambda hint=hint: self.grid_edit_row(hint))
self.nest_level.columnconfigure(1, weight=1)
rad.grid(column=0, row=row, sticky='we', columnspan=2)
lab.grid(column=1, row=row+1, sticky='w', padx=6)
editx.grid(column=0, row=row+1, pady=(0,3), sticky='e')
editx.bind('<Enter>', self.on_hover)
editx.bind('<Leave>', self.on_unhover)
editx.bind('<Button-1>', self.get_clicked_row)
editx.bind('<space>', self.get_clicked_row)
editx.bind('<FocusIn>', self.on_hover)
editx.bind('<FocusOut>', self.on_unhover)
self.radx += 1
row += 2
sep = Separator(self.frm, 3)
sep.grid(column=0, row=self.rowdx+2, sticky='ew',
columnspan=3, pady=(3,0))
self.rowdx += 3
self.vardx += 1
self.bullet -= 1
def select_first_radio(self):
if self.radx == 0:
self.radvars[self.vardx].set(self.current_id)
class EditRow(Frame):
def __init__(self, master, command, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.ent = Entry(self, width=36)
spacer = Label(self, width=3)
ok_butt = Button(
self,
text='OK',
command=command)
cancel_butt = Button(
self,
text='CANCEL',
command=self.remove_edit_row)
spacer.grid(column=0, row=0)
self.ent.grid(column=1, row=0, padx=3, pady=3)
ok_butt.grid(column=2, row=0, padx=6, pady=6)
cancel_butt.grid(column=3, row=0, padx=6, pady=6)
def remove_edit_row(self):
self.grid_forget()
class ValidatePlace():
def __init__(self, root, treebard, place_input, finding, nested_place):
self.root = root
self.treebard = treebard
self.place_input = place_input
self.finding = finding
self.nested_place = nested_place
self.place_list = []
self.place_dicts = []
self.new = False
self.dupes = False
self.see_whats_needed()
def see_whats_needed(self):
def get_matching_ids_hints(nest):
cur.execute(select_place_id_hint, (nest,))
ids_hints = cur.fetchall()
if len(ids_hints) == 0: self.new = True
elif len(ids_hints) > 1: self.dupes = True
return ids_hints
conn = sqlite3.connect(current_file)
cur = conn.cursor()
if len(self.place_input) == 0:
cur.execute(update_finding_places_null, (self.finding,))
conn.commit()
return
self.place_list = self.place_input.split(",")
self.place_list = [self.place_list[i].strip() for i in range(len(self.place_list))]
self.length = len(self.place_list)
for nest in self.place_list:
ids_hints = [list(i) for i in get_matching_ids_hints(nest)]
ids = []
hints = []
for tup in ids_hints:
ids.append(tup[0])
hints.append(tup[1])
hints.append("")
self.place_dicts.append({
"id" : ids,
"input" : nest,
"hint" : hints})
cur.close()
conn.close()
self.make_new_places()
if self.new is True or self.dupes is True:
self.new_place_dialog = NewPlaceDialog(
self.root,
self.place_dicts,
"Clarify place selections where there is not exactly one ID "
"number.\n\nPress the EDIT button to add or edit hints for "
"duplicate place names or any place name.\n\nWhen entering "
"new place names, ID numbers have been assigned which you can "
"just OK.\n\nIf the right options are not listed, press CANCEL "
"and use the Places Tab to create or edit the place.",
"New and Duplicate Places Dialog",
("OK", "CANCEL"),
self.treebard,
do_on_ok=self.collect_place_ids)
else:
for dkt in self.place_dicts:
dkt["id"] = dkt["id"][0]
self.input_to_db()
def make_new_places(self):
'''
A temp_id is assigned to each nest in case the user needs to make
a new place by the name input, even if some place(s) by that name
already exist in the database.
The temp_ids all have to be assigned at the same time so that each
number is unique, then entry to the database has to be done all
at the same time while this information is still correct. After
the max ID is obtained from the database, no db transactions can
occur which insert to any of the place tables till these temp IDs
are either used or discarded.
'''
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(select_max_place_id)
temp_id = cur.fetchone()[0] + 1
for dkt in self.place_dicts:
dkt["temp_id"] = temp_id
temp_id += 1
cur.close()
conn.close()
def collect_place_ids(self):
def reset_error(evt):
self.new_place_dialog.error = False
r = 0
for dkt in self.place_dicts:
dkt["id"] = self.new_place_dialog.radvars[r].get()
r += 1
seen = set()
for dkt in self.place_dicts:
val = dkt['id']
if val in seen:
msg = open_error_message(
self.root,
places_err[0],
"Duplicate Place IDs",
"OK")
msg[1].config(aspect=400)
msg[0].bind("<Destroy>", reset_error)
self.new_place_dialog.error = True
return
seen.add(val)
self.input_to_db()
def input_to_db(self):
conn = sqlite3.connect(current_file)
cur = conn.cursor()
ids = []
for dkt in self.place_dicts:
ids.append(dkt["id"])
qty = len(self.place_dicts)
nulls = 9 - qty
ids = ids + [None] * nulls
ids.append(self.finding)
last = len(self.place_dicts) - 1
q = 0
for dkt in self.place_dicts:
child = dkt["id"]
if q < last:
parent = self.place_dicts[q+1]["id"]
else:
parent = None
if child == dkt["temp_id"]:
cur.execute(insert_place_new, (child, dkt["input"]))
conn.commit()
cur.execute(insert_places_places_new, (child, parent))
conn.commit()
else:
if (child, parent) not in places_places:
places_places.append((child, parent))
cur.execute(insert_places_places_new, (child, parent))
conn.commit()
q += 1
cur.execute(update_finding_places, tuple(ids))
conn.commit()
place_strings.insert(0, self.place_input)
EntryAuto.create_lists(place_strings)
EntryAuto.final_items.insert(0, self.place_input)
cur.close()
conn.close()