|
Post by Uncle Buddy on Sept 29, 2020 6:54:27 GMT -8
# treebard_root_017 (this is the main app and current person stuff)
# _016 had roles and notes dialogs pretty much finished, and right-click menu and statusbar tooltips refactored and working well, getting ready to finally finish the last 3 columns on person tab events table
import files import tkinter as tk from PIL import Image, ImageTk import sqlite3 import styles as st import utes import widgets as wdg import names import notes import dates import gallery as gl import colorizer as clz import right_click_menu as rcm import message_strings as ms from os import path, rename import messages as msg import pedigree_chart as pc
formats = st.make_formats_dict() current_file = files.get_current_file()[0]
class SplashScreen(wdg.Toplevel): def __init__(self, *args, **kwargs): wdg.Toplevel.__init__(self, *args, **kwargs)
root.iconify() self.overrideredirect(True) width = self.winfo_screenwidth() height = self.winfo_screenheight() splash_file = 'images/splash_small.gif' pil_img = Image.open(splash_file) tk_img = ImageTk.PhotoImage(pil_img) splash_canvas = wdg.Canvas( self, height=height*0.33, width=width*0.33, bg=formats['bg']) splash_canvas.create_image(width*0.33/2, height*0.33/2, image=tk_img) splash_canvas.pack()
dlg_pos = utes.center_dialog(self) self.geometry('{}x{}+{}+{}'.format( str(int(width*0.33)), str(int(height*0.33)), dlg_pos[0], dlg_pos[1]))
self.after(500, self.destroy) root.wait_window(self) root.deiconify()
class Treebard(wdg.Frame): def __init__(self, parent, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs) self.parent = parent self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(2, weight=1)
self.files = files.Files(self) self.open_treebard()
self.icon_menu = IconMenu(self)
grip = wdg.Sizer(parent) grip.place(relx=1.0, rely=1.0, anchor='se') grip.bind('<B1-Motion>', grip.resize_se_corner)
self.vscrollbar = wdg.AutoScrollbar(self) self.hscrollbar = wdg.AutoScrollbar(self, orient='horizontal')
self.root_statusbar = wdg.StatusbarTooltips(self)
self.rc_menu = rcm.RightClickMenu(self)
# Main has to instantiate after AutoScrollbar and StatusBarTooltips self.main = Main(self, root) self.main.canvas.grid_remove()
self.top_menu = TopMenu(self)
self.top_menu.grid(column=0, row=0) self.icon_menu.grid(column=0, row=1, sticky='ew') self.main.grid(column=0, row=2, sticky='news') self.vscrollbar.grid(column=1, row=2, sticky='ns') self.hscrollbar.grid(column=0, row=3, sticky='ew')
self.root_statusbar.grid(column=0, row=4, sticky='ew')
ST.create_custom_theme() self.resize_scrollbar() self.resize_window()
self.open_tbard.grab_set() self.open_tbard.lift()
def open_treebard(self):
''' This opening window is important because you never want to open a tree automatically. If the user has two trees that are similar, he might start working on the wrong tree without realizing it. Or if the wrong tree opens automatically and it's really huge, he might have to wait for it to open just so he can close it. So this opening dialog might be a bit of a nuisance to deal with on each loading of the app, but it's not optional. '''
self.open_tbard = wdg.Toplevel() self.open_tbard.title('Open, Create, or Copy a Tree')
open = wdg.Button( self.open_tbard, text='OPEN TREE', command=lambda: self.files.open_tree(root, dialog=self.open_tbard)) open.grid(column=0, row=0, padx=24, pady=24) open.focus_set()
new = wdg.Button( self.open_tbard, text='NEW TREE', command=lambda: self.files.make_tree(root, dialog=self.open_tbard)) new.grid(column=1, row=0, padx=24, pady=24)
saveas = wdg.Button( self.open_tbard, text='SAVE AS', command=lambda: self.files.save_as(root)) saveas.grid(column=2, row=0, padx=24, pady=24)
savecopyas = wdg.Button( self.open_tbard, text='SAVE COPY AS', command=lambda: self.files.save_copy_as()) savecopyas.grid(column=3, row=0, padx=24, pady=24)
rename = wdg.Button( self.open_tbard, text='RENAME TREE', command=lambda: self.files.rename_tree(root)) rename.grid(column=4, row=0, padx=24, pady=24)
ST.config_generic(self.open_tbard)
dlg_pos = utes.center_dialog(self.open_tbard) self.open_tbard.geometry("+{}+{}".format(dlg_pos[0], dlg_pos[1]))
def resize_scrollbar(self): root.update_idletasks() self.main.canvas.config( scrollregion=self.main.canvas.bbox("all"))
def resize_window(self): root.update_idletasks() page_x = self.main.content.winfo_reqwidth() page_y = self.main.content.winfo_reqheight()+24 root.geometry('{}x{}'.format(page_x, page_y))
class IconMenu(wdg.Frame): def __init__(self, parent, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs) self.parent = parent
menu_names = ( 'open', 'cut', 'copy', 'paste', 'print', 'home', 'first', 'previous', 'next', 'last', 'search', 'add', 'settings', 'note', 'back', 'forward')
ribbon = {}
r = 0 for name in menu_names: file = 'images/{}.gif'.format(name) pil_img = Image.open(file) tk_img = ImageTk.PhotoImage(pil_img) icon = wdg.Button( self, image=tk_img, command=placeholder, takefocus=0) icon.image = tk_img icon.grid(column=r, row=0) utes.create_tooltip(icon, name.title()) ribbon[name] = icon r += 1 ribbon['open'].config( command=lambda: files.open_tree(root)) ribbon['home'].config(command=root.quit)
class Main(wdg.Frame): def __init__(self, parent, root, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent self.root = root self.tab_names = (('person', 0), ('place', 1), ('source', 0), ('names', 0), ('chart', 0), ('report', 0), ('project', 6), ('contact', 1), ('graphics', 0), ('preferences', 3))
self.tabbook_x = 300 self.tabbook_y = 300
self.make_widgets()
def make_widgets(self):
# containers
self.canvas = wdg.Canvas( self, bd=0, highlightthickness=0) self.canvas.grid(column=0, row=2, sticky='news') self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(2, weight=1)
self.content = wdg.Frame(self.canvas) self.tabs = wdg.Tabs(self.content, self.tab_names) self.tabs.grid(column=0, row=1, sticky='news') self.tabs.enable_traversal()
# preferences tabbed notebook
pref_tab_names = ( ('general', 1), ('date', 0), ('color scheme', 10), ('types', 1)) self.pref_tabs = wdg.Tabs( self.tabs.store['preferences'], pref_tab_names) self.pref_tabs.grid()
self.pref_tabs.enable_traversal()
# scrollbars
self.canvas.config( yscrollcommand=self.parent.vscrollbar.set, xscrollcommand=self.parent.hscrollbar.set)
self.parent.vscrollbar.config(command=self.canvas.yview) self.parent.hscrollbar.config(command=self.canvas.xview)
# change-current-person area
current = wdg.Frame(self.content) current.grid(column=0, row=0, pady=(24, 0))
self.curr_per = wdg.LabelH3( current, text='Current Person (ID):', anchor='e') self.curr_per.grid(column=1, row=0, padx=12, pady=12)
self.curr_per_displ = wdg.LabelH3( current, anchor='w') self.curr_per_displ.grid(column=2, row=0, padx=12, pady=12)
self.change_curr_per = wdg.LabelH3( current, text='Change Current Person to:') self.change_curr_per.grid(column=3, row=0, padx=(36,12), pady=12)
self.new_person_fill = wdg.AutofillEntry(current) self.new_person_fill.grid(column=4, row=0, padx=12, pady=12) self.new_person_fill.focus_set() self.new_person_fill.config(textvariable=self.new_person_fill.var) self.new_person_fill.values = names.make_values_list_for_person_select() self.new_person_fill.fix_max_width()
# The binding to Return also works for Ctrl-Return accidentally but OK self.new_person_fill.bind('<Key-Return>', self.select_new_current_person) self.new_person_fill.bind( '<Control-Key-space>', self.select_new_current_person) self.new_person_fill.bind( '<Control-Key-Tab>', self.select_new_current_person)
search_opener = wdg.Button( current, text='Find/Create a Person/Name', command=self.open_person_search_dialog) search_opener.grid(column=5, row=0, padx=12, pady=12)
# persons tab id = get_current_person()[0] current_person_birthname = names.get_name_with_id(id) self.persons = PersonsTab( self.tabs.store['person'], self, root, self.new_person_fill, current_person_birthname) self.persons.grid(column=0, row=0)
# galleries in Places and Sources tabs and Persons tab gallery dialog
place_gallery = gl.Gallery( self.tabs.store['place'], self.tabs, self.tabs.store['graphics'], root, self.canvas)
source_gallery = gl.Gallery( self.tabs.store['source'], self.tabs, self.tabs.store['graphics'], root, self.canvas)
# names tab
self.names = NamesTab( self.tabs.store['names'], root, self.new_person_fill) self.names.grid(column=0, row=0)
# dates tab
date_widgets = DatePrefsWidgets(self.pref_tabs.store['date'], root) date_widgets.grid(column=0, row=0)
# colors tab
colorizer = clz.Colorizer( self.pref_tabs.store['color scheme'], self.pref_tabs, root, self.persons.right_panel) colorizer.grid(column=0, row=0)
# types tab
types_settings_instrux = wdg.LabelH3( self.pref_tabs.store['types'], justify='left', text='For each type choose whether to show, delete or hide it\n' 'so it won\'t be available to select. Built-in types can\'t\n' 'be deleted, just hidden. User-made types can be deleted\n' 'or hidden, except for types that are being used in your tree,\n' 'in which case that type can\'t be deleted or hidden.\n\n' 'Types which can be created, hidden and deleted by the user\n' 'include event types, role types and color schemes.') types_settings_instrux.grid()
# footer
footer = wdg.Frame(self.content) footer.columnconfigure(0, weight=1) footer.rowconfigure(0, weight=1) footer.grid(column=0, row=2, sticky='news') self.foot_label = wdg.LabelBoilerplate( footer, text="Treebard GPS is free, portable, open-source software " "written in Python, Tkinter and Sqlite.\nTreebard's purpose " "is to showcase functionalities that could inspire developers " "and users of genealogy database software to expect a " "better user experience.\nGPS stands for " "'Genieware Pattern Simulation' because GPS is here to show " "the way.\nCreated 2014 - 2021 by Scott Robertson. Email: " "stumpednomore-at-gmail.com. Donations: " "http://gofundme/whearly", justify='center') self.foot_label.grid(column=0, row=0, pady=24)
ST.config_generic(root)
# statusbar tooltips for root
visited = ( (self.new_person_fill, "Change Current Person", "Select someone or add a name."), (place_gallery.thumbstrip, "Thumbnail Views", "Click thumbnail to display. Hover below to see " "file name. If radio, click to make main image."), (place_gallery.pic_canvas, "Image", "Arrow keys change image when it's in focus."), (place_gallery.previous_img, "Left Button", "Click with mouse or when highlighted click with spacebar."), (place_gallery.next_img, "Right Button", "Click with mouse or when highlighted click with spacebar."), (source_gallery.thumbstrip, "Thumbnail Views", "Click thumbnail to display. Hover below to see " "file name. If radio, click to make main image."), (source_gallery.pic_canvas, "Image", "Arrow keys change image when it's in focus."), (source_gallery.previous_img, "Left Button", "Click with mouse or when highlighted click with spacebar."), (source_gallery.next_img, "Right Button", "Click with mouse or when highlighted click with spacebar.") ) wdg.run_statusbar_tooltips( visited, self.parent.root_statusbar.status_label, self.parent.root_statusbar.tooltip_label)
root.config(bg=formats['bg'])
self.canvas.bind_all( '<MouseWheel>', lambda evt, canvas=self.canvas: utes.scroll_on_mousewheel( evt, canvas))
self.canvas.create_window(0, 0, anchor=tk.NW, window=self.content)
def show_top_pic(self, top_pic_button): current_file_tup = files.get_current_file() current_file = current_file_tup[0] image_dir = current_file_tup[1] conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute(''' SELECT images FROM images_entities JOIN person ON images_entities.person_id = person.person_id JOIN current ON current.person_id = person.person_id JOIN image ON image.image_id = images_entities.image_id WHERE images_entities.main_image = 1''')
top_pic = cur.fetchone() cur.close() conn.close()
if top_pic: img_stg = ''.join(top_pic) new_stg = '{}treebard_gps/data/{}/images/{}'.format( st.root_drive, image_dir, img_stg)
top = Image.open(new_stg) img1 = ImageTk.PhotoImage(top)
top_pic_button.config(image=img1) top_pic_button.image = img1
self.tabbook_x = top.width self.tabbook_y = top.height
def open_person_search_dialog(self): person_search = Search( self, # Main root, self.new_person_fill, self.tabs.store['names'], self.persons.findings_table.top_pic_button) person_search.make_search_dialog()
def select_new_current_person(self, evt): if len(self.new_person_fill.get()) != 0: curr_person = self.new_person_fill.get() curr_person = curr_person.split(' #') subject_id = curr_person[1] self.new_current_person = curr_person[0] replace_findings_table( self.persons, self.persons.attributes_content, self.new_person_fill, self.persons.top_pic_button, self, self.tabs.store['person'], subject_id)
class TopMenu(wdg.Frame): def __init__(self, parent, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs) self.parent = parent
self.make_menu()
def make_menu(self):
menubar = tk.Menu(self.parent) menubar = menubar
menubar_items = { 'file': ( ('New Tree', lambda: self.parent.files.make_tree(root), 'Ctrl+N'), ('Open...', lambda: self.parent.files.open_tree(root), 'Ctrl+O'), ('Save', self.parent.main.persons.findings_table.save, 'Ctrl-S'), ('Save As...', lambda: self.parent.files.save_as(root), 'Ctrl+Alt+S'), ('Save Copy As...', self.parent.files.save_copy_as, 'Ctrl+Alt+C'), ('Rename...', lambda: self.parent.files.rename_tree(root), ''), ('Close', placeholder, ''), ('Exit', root.quit, '')), 'edit': ( ('Cut', placeholder, 'Ctrl+X'), ('Copy', placeholder, 'Ctrl+C'), ('Paste', placeholder, 'Ctrl+V'), ('Edit', placeholder, '')), 'entities': ( ('Add Person, Place, or Source', placeholder, ''), ('Person', placeholder, ''), ('Place', placeholder, ''), ('Event', placeholder, ''), ('Attribute', placeholder, ''), ('Media', placeholder, '')), 'output': ( ('Charts', placeholder, ''), ('Reports', placeholder, '')), 'research': ( ('Do List', placeholder, ''), ('Research Goals', placeholder, ''), ('Contacts', placeholder, ''), ('Correspondence', placeholder, '')), 'utilities': ( ('Your Profile', placeholder, ''), ('Appearance Settings', placeholder, ''), ('Relationship Calculator', placeholder, ''), ('Date Calculator', placeholder, ''), ('Duplicates & Matches Detector', placeholder, ''), ('U-R-Your-Own-Grampaw Detector', placeholder, ''), ('Sure Thing Calculator', placeholder, ''), ('Go Figure Planmaker', placeholder, '')), 'window': ( ('Arrange All', placeholder, ''), ('Tile Horizontal', placeholder, ''), ('Tile Vertical', placeholder, ''), ('Close All But Home Window', placeholder, ''), ('Close Top Window', placeholder, '')), 'help': (('About Treebard', placeholder, ''), ("Users' Manual", placeholder, ''), ('Genealogy Tips & Tricks', placeholder, ''), ('Treebard Website', placeholder, ''), ('Treebard Forum', placeholder, ''), ('Help Contents, Search, & Index', placeholder, ''), ('Contact Treebard', placeholder, ''), ('Feature Requests', placeholder, ''), ('Source Code', placeholder, ''), ('Donations', placeholder, ''))}
for k,v in menubar_items.items(): menoo = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label=k.title(), menu=menoo) for tup in v: menoo.add_command(label=tup[0], command=tup[1], accelerator=tup[2])
# keystroke bindings for menu command accelerators
# display the menu root.config(menu=menubar)
class PersonsTab(wdg.Frame): def __init__( self, parent, main, root, new_person_fill, current_person_birthname, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs)
self.person_tab = parent self.main = main self.root = root self.new_person_fill = new_person_fill self.current_person_birthname = current_person_birthname self.rc_menu = rcm.RightClickMenu(self.root) self.make_widgets()
def make_widgets(self):
def position_attributes(evt=None): if evt: # recalculate notebook position if window is moved self.right_panel.posx = self.right_panel.winfo_rootx() self.right_panel.posy = self.right_panel.winfo_rooty() a = self.right_panel.posx b = self.main.tabbook_x c = self.right_panel.notebook.winfo_reqwidth()
d = self.right_panel.posy e = self.main.tabbook_y f = self.right_panel.notebook.winfo_reqheight()
xx = a - b + c yy = d - e + f
w = max( self.right_panel.notebook.winfo_reqwidth(), self.main.tabbook_x) h = max( self.right_panel.notebook.winfo_reqheight(), self.main.tabbook_y)
x = min(xx, self.right_panel.posx) y = min(yy, self.right_panel.posy)
self.right_panel.store['attributes'].config( width=self.main.tabbook_x, height=self.main.tabbook_y + 7) self.att.geometry('{}x{}+{}+{}'.format(w, h, x, y))
def open_attributes(evt): ''' Open mock-up of a notebook tab when attributes tab is clicked. Can be resized to overlap the pedigree area because it is a toplevel window and not a real notebook tab (not a frame). ''' position_attributes() self.att.deiconify() def close_attributes(evt):
self.att.withdraw()
def start_edge_sizer(evt):
self.init_geometry = self.att.geometry() self.click_down_x = self.att.winfo_pointerx() self.orig_pos_x = self.att.winfo_rootx() self.orig_pos_y = self.att.winfo_rooty()
def stop_edge_sizer(evt):
self.click_up_x = self.click_down_x
new_pos_x = self.orig_pos_x
xy = self.init_geometry.split('+') wh = xy.pop(0).split('x')
orig_wd = int(wh[0]) orig_ht = int(wh[1]) self.new_w = orig_wd self.new_h = orig_ht
self.click_up_x = self.att.winfo_pointerx()
dx = self.click_down_x - self.click_up_x
self.new_w = orig_wd + dx new_pos_x = self.orig_pos_x - dx new_pos_y = self.orig_pos_y
if self.new_w < 10: self.new_w = 10
self.att.geometry('{}x{}+{}+{}'.format( self.new_w, self.new_h, new_pos_x, new_pos_y))
pedigree = wdg.Frame(self) pedigree.grid(column=0, row=0, padx=12, pady=16)
# draggable canvas in frame in labelframe in notebook tab
self.pedigree_pan = wdg.Frame(pedigree) self.pedigree_pan.grid(column=0, row=0) self.pedigree_pan.rowconfigure(0, weight=1)
# # mockup/placeholder till the real one comes self.make_pedigree_chart()
# right panel notebook toggles picture and attributes chart
panel_tabs = [('images', 'I'), ('attributes', 'A')]
minx=self.main.tabbook_x/self.master.winfo_screenwidth() miny=self.main.tabbook_y/self.master.winfo_screenheight()
self.right_panel = wdg.TabBook( self, root=self.root, tabwidth=11, selected='images', case='title', side='se', minx=minx, miny=miny, tabs=panel_tabs) self.right_panel.grid(column=1, row=0, padx=24, pady=24) self.right_panel.store['images'].grid_columnconfigure(0, weight=1) self.right_panel.store['images'].grid_rowconfigure(0, weight=1) self.top_pic_button = wdg.ButtonPlain( self.right_panel.store['images'], command=self.open_person_gallery)
self.main.show_top_pic(self.top_pic_button)
self.top_pic_button.grid(column=0, row=0, sticky='news', padx=3, pady=3)
# attributes tab is empty but when it opens, # a dialog opens to cover it, allowing this # apparent tab to overlap the pedigree chart # and easily be resized since it's not a frame.
self.right_panel.store['attributes'].bind('<Expose>', open_attributes) self.right_panel.store['images'].bind('<Expose>', close_attributes)
self.att = wdg.Toplevel(self.right_panel.store['attributes']) self.att.withdraw() self.att.wm_overrideredirect(True)
self.root.bind('<Configure>', position_attributes)
self.att.grid_columnconfigure(1, weight=1) self.att.grid_rowconfigure(0, weight=1)
left_grip = wdg.FrameHilited1( self.att, width=6, cursor='sb_h_double_arrow') left_grip.grid(column=0, row=0, sticky='news') left_grip.bind('<Button-1>', start_edge_sizer) left_grip.bind('<B1-Motion>', stop_edge_sizer) left_grip.bind('<ButtonRelease-1>', stop_edge_sizer)
vscroll = wdg.AutoScrollbar( self.att, orient='vertical') hscroll = wdg.AutoScrollbar( self.att, orient='horizontal')
self.attributes_canvas = wdg.CanvasHilited( self.att, highlightthickness=0, bd=0, yscrollcommand=vscroll.set, xscrollcommand=hscroll.set) self.attributes_canvas.grid(column=1, row=0, sticky='news', pady=3, padx=3)
vscroll.config(command=self.attributes_canvas.yview) hscroll.config(command=self.attributes_canvas.xview)
vscroll.grid(column=2, row=0, sticky='ns') hscroll.grid(column=1, row=1, sticky='ew')
# create canvas contents
self.attributes_content = wdg.FrameHilited3( self.attributes_canvas, padx=3, pady=3) self.attributes_content.grid_rowconfigure(1, weight=1) self.attributes_content.grid_columnconfigure(1, weight=1)
# Creation of the attributes table and events table begins # in the files module where make_findings_table() # is called once for each table. self.attributes_table = FindingsTable( self.attributes_content, self.new_person_fill, self.top_pic_button, self.main, self.main.tabs.store['person'])
# opening position of scrollbars self.attributes_canvas.create_window( 0, 0, anchor='nw', window=self.attributes_content)
self.attributes_content.update_idletasks() self.attributes_canvas.config( scrollregion=self.attributes_canvas.bbox("all"))
# findings table instances are gridded in make_header_row() self.findings_table = FindingsTable( self, self.new_person_fill, self.top_pic_button, self.main, self.person_tab)
# # - - - context-sensitive right-click menu - - - # rcm_widgets = (self.top_pic_button,) rcm.make_rc_menus( rcm_widgets, self.rc_menu, ms.persons_tab_msg)
def make_pedigree_chart(self):
self.pedigree_chart = pc.PedigreeChart( self.pedigree_pan, self.current_person_birthname, self.root, width=950, height=300, takefocus=1, bd=1, highlightthickness=1, highlightbackground=formats['highlight_bg'], bg=formats['bg'])
def open_person_gallery(self):
person_gallery_dlg = wdg.Toplevel()
person_gallery_dlg.grid_columnconfigure(0, weight=1) person_gallery_dlg.grid_rowconfigure(0, weight=1)
person_gallery = gl.Gallery( person_gallery_dlg, self.main.tabs, self.main.tabs.store['graphics'], root, self.main.canvas)
person_gallery_statusbar = wdg.StatusbarTooltips(person_gallery_dlg) person_gallery_statusbar.grid(column=0, row=2, sticky='ew') wdg.run_statusbar_tooltips( person_gallery.visited, person_gallery_statusbar.status_label, person_gallery_statusbar.tooltip_label)
ST.config_generic(person_gallery_dlg) # ****************** SEARCH CLASSES ******************************
COL_HEADS = ('ID', 'Name', 'Birth', 'Death', 'Mother', 'Father')
NAMER_HEADS = ('Name Type', 'First Name', 'Middle Names', 'Surname', 'Suffix') NAME_TYPES = ( 'birth name', 'nickname', 'also known as', 'legally changed name', 'pseudonym', 'adoptive name', 'married name', 'alternate spelling', 'mis-spelling', 'call name', 'anglicized name', 'religious order name', 'stage name') NAME_TYPE_IDS = (1, 12, 2, 10, 3, 5, 6, 8, 9, 13, 15, 17, 11)
NONPRINT_KEYS = ( 'Return', 'Tab', 'Shift_L', 'Shift_R', 'Escape', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Caps_Lock', 'Control_L', 'Control_R', 'Win_L', 'Win_R', 'Alt_R', 'Alt_L', 'App', 'space', 'Up', 'Down', 'Left', 'Right', 'Num_Lock', 'Home', 'Prior', 'End', 'Next', 'Insert', 'Pause')
new_tables = [] new_events_table = None new_attributes_table = None
def replace_findings_table( events_table_parent, attributes_table_parent, person_autofill, top_pic_button, main, person_tab, subject_id):
''' Findings table is instantiated automatically on load. In this function it's destroyed and replaced when changing current person or other times such as redrawing the table by running save(). Events table and attributes table are both attributes of the same instance of the PersonsTab class but gridded in different parents. '''
global new_tables global new_events_table global new_attributes_table
if not subject_id: return
if len(new_tables) > 0: for table in new_tables: table.destroy() new_tables = [] events_table_parent.findings_table.destroy() events_table_parent.attributes_table.destroy()
new_events_table = FindingsTable( events_table_parent, person_autofill, top_pic_button, main, person_tab)
new_events_table.distinguish_evt_att(person_id=subject_id)
new_tables.append(new_events_table) new_events_table.config(bg=formats['bg']) new_events_table.grid(column=0, row=1, sticky='ew')
new_attributes_table = FindingsTable( attributes_table_parent, person_autofill, top_pic_button, main, person_tab) new_tables.append(new_attributes_table) new_attributes_table.config(bg=formats['bg']) new_attributes_table.grid(column=0, row=1, sticky='ew')
new_attributes_table.current_person_attributes = new_events_table.current_person_attributes new_events_table.make_findings_table( new_events_table.current_person_events) new_attributes_table.make_findings_table( new_attributes_table.current_person_attributes) new_attributes_table.update_current_person(subject_id, new_attributes_table)
person_autofill.delete(0, 'end')
GENDER_TYPES = ('unknown (default if left blank)', 'female', 'male', 'other')
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_current_person():
current_file = files.get_current_file()[0] conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute( ''' SELECT name.person_id, names FROM name JOIN person ON person.person_id = name.person_id JOIN current ON name.person_id = current.person_id ''') current_person = cur.fetchone() if not current_person: return else: current_person_id = current_person[0] current_person = current_person[1] return current_person_id, current_person
class NamesTab(wdg.Frame): def __init__(self, parent, root, autofill, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent self.root = root self.autofill = autofill
self.current_person = get_current_person()[0]
self.NAMES_TABLE_HEADS = ( 'Name or ID Number', 'Name Type', 'Sort Order', 'Used By')
self.full_name = '' self.order = []
self.new_or_current = tk.StringVar() self.new_or_current.set('current')
self.make_widgets()
self.toggle_name_var()
def make_widgets(self):
self.announce = wdg.LabelH2(self)
self.names_table = wdg.Table(self, self.NAMES_TABLE_HEADS)
n = 0 for head in self.NAMES_TABLE_HEADS: lab = wdg.LabelColumn( self.names_table.mainframe, text=self.NAMES_TABLE_HEADS[n]) lab.grid(column=n, row=0, sticky='ew', ipadx=12) n += 1
self.fill_names_table()
choose = wdg.FrameHilited(self) self.choose1 = wdg.RadiobuttonHilited( choose, text='Add Name for Current Person', command=self.toggle_name_var, variable=self.new_or_current, value='current')
self.choose2 = wdg.RadiobuttonHilited( choose, text='Create a New Person', command=self.toggle_name_var, variable=self.new_or_current, value='new')
self.person_add = PersonAdd( self, self.autofill, self.root)
self.announce.grid( column=0, row=0, padx=6, pady=6, columnspan=5, sticky='w') self.names_table.grid( column=0, row=1, padx=24, pady=24, columnspan=2) choose.grid(column=0, row=2, columnspan=2, padx=12, pady=12, sticky='w') self.choose1.grid(column=0, row=0, padx=6, pady=6, sticky='w') self.choose2.grid(column=1, row=0, padx=6, pady=6, sticky='w')
self.person_add.grid(column=0, row=3, columnspan=5)
def toggle_name_var(self): ''' Radiobuttons toggle a boolean so the new name is assigned to either a new person or the current person. '''
if self.new_or_current.get() == 'new': self.person_add.add_butt.config( command=self.person_add.add_person) elif self.new_or_current.get() == 'current': self.person_add.add_butt.config(command=self.add_name)
def add_name(self): self.current_person = get_current_person()[0] self.person_add.get_entered_values() self.person_add.make_sort_order() self.person_add.save_new_name(self.current_person) self.fill_names_table()
def fill_names_table(self): ''' Add names data for current person to names table each time a new name is made for current person and each time current person is changed. ''' self.current_person = get_current_person()[0] self.clear_table()
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute( ''' SELECT names, name_types, sort_order, used_by FROM name LEFT JOIN name_type ON name.name_type_id = name_type.name_type_id WHERE person_id = ? ''', (self.current_person,)) current_names = cur.fetchall() cur.close() conn.close() current_names = [list(i) for i in current_names] for lst in current_names: if not lst[3]: lst[3] = ''
row = 2 for lst in current_names: column=0 for stg in lst: cell = wdg.EntryLabel( self.names_table.mainframe, self.use_input, text=stg) cell.grid(column=column, row=row, ipadx=12, ipady=6) column += 1 row += 1
def clear_table(self): ''' Prepare to update table. ''' for child in self.names_table.mainframe.winfo_children(): if child.grid_info()['row'] > 1: child.destroy()
# do not delete this seemingly unused def, unless you know exactly what you're doing: def use_input(self): pass
def reset(self):
self.new_or_current.set('current') self.toggle_name_var() self.person_add.reset()
class Search(wdg.Toplevel):
def __init__( self, main, root, person_autofill, names_tab, top_pic_button, *args, **kwargs):
wdg.Toplevel.__init__(self, *args, **kwargs)
self.main = main self.root = root self.result_rows = [] self.hilit_row = None self.unhilit_row = None
self.person_autofill = person_autofill self.sent_text = '' self.new_current_id = None self.new_current_person = None
self.all_matches = [] self.tkvars = {} self.sort_by = None
self.widget = None self.nametip = None self.x = self.y = 0
self.nametip_text = None self.name_list = []
self.pointed_to = None self.names_tab = names_tab self.person_id = None
self.top_pic_button = top_pic_button self.ma_id = None self.pa_id = None
self.rc_menu = rcm.RightClickMenu(self.root)
def open_search(self, evt): ''' This recreates a results table when a character is typed into or removed from the input entry at top of search dialog. '''
if evt.keysym in NONPRINT_KEYS: return elif evt.keysym.isalnum() is False: return
for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] not in (0, 1): child.destroy()
self.all_matches = get_matches(self.search_input) self.make_search_dialog_cells()
def make_search_results_header_row(self):
for col in range(0, 6): var = tk.StringVar() self.tkvars[col] = var lab = wdg.LabelColumn( self.search_results.mainframe, text=COL_HEADS[col], cursor='hand2') lab.grid(column=col, row=0, sticky='ew', ipadx=12) lab.bind('<Button-1>', self.track_column_state)
def open_names_tab(self):
xfr = self.search_input.get() self.destroy() self.main.tabs.select(self.names_tab) self.main.names.person_add.name_input.input.delete(0, 'end') self.main.names.person_add.name_input.input.insert(0, xfr) self.main.names.person_add.name_input.input.config( font=formats['input_font']) self.main.names.new_or_current.set('new') self.main.names.toggle_name_var()
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute( ''' SELECT gender FROM person JOIN name ON person.person_id = name.person_id WHERE names = ? AND name_type_id = 1 ''',
(xfr,)) first_match = cur.fetchone() cur.close() conn.close()
if first_match: first_match = first_match[0] self.main.names.gender_input.input.config(state='normal') self.main.names.gender_input.input.delete(0, 'end') self.main.names.gender_input.input.insert(0, first_match) self.main.names.gender_input.input.config(state='readonly') self.main.names.gender = first_match
self.main.canvas.yview_moveto(0.0)
def make_search_dialog(self):
self.title('Person Search') self.geometry('+1+1') # if u do this u have to deal with rcm being not on top and unturnoffable: # self.grab_set()
self.canvas = wdg.Canvas(self) self.content = wdg.Frame(self.canvas)
self.protocol('WM_DELETE_WINDOW', self.close_search_dialog)
self.canvas.config( bd=0, highlightthickness=0) self.canvas.grid( column=0, row=0, sticky='news')
self.content.grid_columnconfigure(0, weight=1) self.content.grid_rowconfigure(0, weight=1)
self.vscrollbar = wdg.AutoScrollbar(self) self.vscrollbar.grid(column=1, row=0, sticky='ns') self.hscrollbar = wdg.AutoScrollbar(self, orient='horizontal') self.hscrollbar.grid(column=0, row=1, sticky='ew')
self.canvas.config( yscrollcommand=self.vscrollbar.set, xscrollcommand=self.hscrollbar.set)
self.vscrollbar.config(command=self.canvas.yview) self.hscrollbar.config(command=self.canvas.xview)
self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
header = wdg.Frame(self.content) header.grid(column=0, row=0, sticky='ew')
self.search_dlg_heading = wdg.LabelH2( header, text='Person Search') self.search_dlg_heading.grid(column=0, row=0, pady=(24,0))
instrux = wdg.Label( header, text='Search for person by name(s) or id number:') instrux.grid(column=0, row=1, sticky='e', padx=24, pady=12)
self.sent_text = self.person_autofill.get()
self.search_input = wdg.Entry(header) self.search_input.grid(column=1, row=1, sticky='w', padx=12, pady=12) self.search_input.insert(0, self.sent_text) self.search_input.focus_set()
self.search_input.bind('<KeyRelease>', self.open_search)
self.person_adder = wdg.Button( header, text='Add New Person', command=self.open_names_tab)
self.person_adder.grid(column=2, row=1, padx=12, pady=12)
self.search_results = wdg.Table(self.content, COL_HEADS) self.search_results.grid( column=0, row=1, sticky='news', padx=48, pady=48)
search_closer = wdg.Button( self.content, text='Cancel', command=self.close_search_dialog) search_closer.grid(column=2, row=2, padx=6, pady=6, sticky='e')
# STATUSTIPS person_search_statusbar = wdg.StatusbarTooltips(self) person_search_statusbar.grid(column=0, row=3, sticky='ew') visited = ( (self.search_input.ent, "Person Search Input", "Type any part of any name or ID number; table will fill " "with matches."), (self.search_results.mainframe, "Person Search Table", "Select highlighted row with Enter or Space key to change " "current person, or double-click any row.")) wdg.run_statusbar_tooltips( visited, person_search_statusbar.status_label, person_search_statusbar.tooltip_label)
# right click context help doesn't work yet for person search dialog # maybe because of the convoluted instaniation process which takes # place in the Main class; this should be simplified so adding rcm # to Search class works the same as adding rcm to any other class. rcm_widgets = (self.search_input.ent, self.search_dlg_heading) rcm.make_rc_menus( rcm_widgets, self.rc_menu, ms.search_person_msg)
self.make_search_results_header_row()
self.canvas.create_window(0, 0, anchor='nw', window=self.content) ST.config_generic(self) self.resize_scrollbar() self.resize_window()
def make_search_dialog_cells(self):
self.result_rows = []
c = 0 for person_row in self.all_matches: self.make_row_list_for_search_results_table(person_row) row_list = self.row_list self.result_rows.append(row_list) c += 1
for i in range(0, 6): if i == 0: init_sort = i self.tkvars[init_sort].set('clicked_once') else: no_sort = i self.tkvars[no_sort].set('not_clicked') self.result_rows = sorted( self.result_rows, key=lambda q: q[init_sort])
row = 2 for lst in self.result_rows: col = 0 for val in lst: if col in (0,1,4,5): text = lst[col] lst[col] = val elif col in (2,3): text = val[1] else: break
lab = wdg.LabelSearch( self.search_results.mainframe, cursor='hand2') lab.grid(column=col, row=row, sticky='ew', ipadx=12) lab.config(text=text)
if lab.grid_info()['row'] != 0: if lab.grid_info()['column'] in (0, 1): self.widget = lab self.make_nametip() col += 1 row += 1
for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] not in (0, 1): child.bind( '<Double-Button-1>', self.select_searched_current_person) child.bind('<Return>', self.select_searched_current_person) child.bind('<Key-space>', self.select_searched_current_person) child.bind('<FocusIn>', self.highlight_on_focus) child.bind('<FocusOut>', self.unhighlight_on_unfocus) child.bind('<Key-Up>', self.go_up) child.bind('<Key-Down>', self.go_down) if child.grid_info()['column'] == 0: child.config(takefocus=1) ST.config_generic(self) self.resize_scrollbar() self.resize_window()
self.search_input.focus_set()
def select_searched_current_person(self, evt):
if evt.widget.grid_info()['row'] == 0: return
self.hilit_row = evt.widget.grid_info()['row'] for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] in (0, 1): pass elif (child.grid_info()['row'] == self.hilit_row and child.grid_info()['column'] == 1): self.new_current_person = child['text']
# handle the double-click event if evt.type == '4': if (evt.widget.grid_info()['column'] == 0 and evt.widget.grid_info()['row'] == self.hilit_row): self.new_current_id = evt.widget['text'] elif (evt.widget.grid_info()['column'] in (1,2,3,4,5) and evt.widget.grid_info()['row'] == self.hilit_row): for child in self.search_results.mainframe.winfo_children(): if (child.grid_info()['row'] == self.hilit_row and child.grid_info()['column'] == 0): self.new_current_id = child['text'] if evt.type != '4': self.new_current_id = evt.widget['text'] self.new_current_person = names.get_name_with_id(self.new_current_id) self.close_search_dialog() replace_findings_table( self.main.persons, self.main.persons.attributes_content, self.person_autofill, self.top_pic_button, self.main, self.main.tabs.store['person'], self.new_current_id)
self.off()
def close_search_dialog(self): self.destroy()
def go_up(self, evt): next = evt.widget.tk_focusPrev() next.focus_set()
def go_down(self, evt): prior = evt.widget.tk_focusNext() prior.focus_set()
def highlight_on_focus(self, evt): self.hilit_row = evt.widget.grid_info()['row'] for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] in (0, 1): pass elif child.grid_info()['row'] == self.hilit_row: child.config(bg=formats['highlight_bg'])
def unhighlight_on_unfocus(self, evt): self.unhilit_row = evt.widget.grid_info()['row'] for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] in (0, 1): pass elif child.grid_info()['row'] == self.unhilit_row: child.config(bg=formats['bg'])
def resize_scrollbar(self): self.update_idletasks() self.canvas.config(scrollregion=self.canvas.bbox("all"))
def resize_window(self): self.update_idletasks() page_x = self.content.winfo_reqwidth()+24 page_y = self.content.winfo_reqheight()+48 self.geometry('{}x{}'.format(page_x, page_y))
def track_column_state(self, evt): ''' Bound to column head labels. Each column uses its own tkinter variable to track one of two possible states: 1st click or no click. If on being clicked the column was the last one clicked, its state is 'clicked_once' so it sorts descending. Otherwise, the column's state is 'not_clicked' so it sorts ascending. On changing columns the newly clicked column always sorts ascending. ID column autosorts ascending on load. '''
sortcol = evt.widget.grid_info()['column']
keycols = (0, 6, 10, 11, 7, 8)
a = 0 for value in keycols: if a == sortcol: self.sortkey = value a += 1
self.sort_by = evt.widget.grid_info()['column'] ascending = sorted(self.result_rows, key=lambda f: f[self.sortkey]) descending = sorted( self.result_rows, key=lambda f: f[self.sortkey], reverse=True)
if self.tkvars[self.sort_by].get() == 'not_clicked': for k,v in self.tkvars.items(): if v.get() == 'clicked_once': v.set('not_clicked') self.tkvars[self.sort_by].set('clicked_once') self.row_list = ascending
elif self.tkvars[self.sort_by].get() == 'clicked_once': self.tkvars[self.sort_by].set('not_clicked') self.row_list = descending self.reorder_column()
def reorder_column(self): ''' Reconfigure labels in table. '''
cells = [] for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] > 1: cells.append([child])
new_text = [] for row in self.row_list: new_text.extend( (row[0], row[1], row[2][1], row[3][1], row[4], row[5]))
a = 0 for lst in cells: lst.append(new_text[a]) a += 1
for lst in cells: lst[0].config(text=lst[1])
def make_row_list_for_search_results_table(self, unique_match): ''' Gets a tuple (person_id, other_names) for one person at a time from the Search class which has collected matches from a search input. The unique person data is used to create a row list which will be used to create a sortable search results table with one person per row. The other_names value is a list of names by results table row which will be displayed in a nametip. ''' self.row_list = [] self.found_person = unique_match[0] self.row_list.append(self.found_person) self.other_names = unique_match[1]
self.display_name = names.get_name_with_id(self.found_person)
self.row_list.append(self.display_name) ext = [[], [], '', '', '', '', '', []] self.row_list.extend(ext[0:])
# this has to run last self.get_values_of_searched()
def get_values_of_searched(self): ''' ''' self.get_death() self.get_birth() if self.other_names: self.get_other_names() else: self.row_list[9] = '' self.make_sorters()
def make_sorters(self): ''' self.ma_id & self.pa_id don't exist yet at this point and if there is no birth/offspring event they won't. '''
self.row_list[6] = self.get_sort_names( self.row_list[0]) self.row_list[10] = self.make_sorter_for_formatted_dates( self.row_list, 2) self.row_list[11] = self.make_sorter_for_formatted_dates( self.row_list, 3)
def make_sorter_for_formatted_dates(self, row, date_index): ''' Add a sortable date to the search results row lists, for birth column or death column, whichever was clicked. Prefix is stripped out. The iso date string is converted to a list of integers [y,m,d] which extends the existing date key's value for use to sort the table by the date column; BC year integers are made negative. CHANGE THIS SO IT MAKES SORTERS FOR ONE ROW AT A TIME '''
sorter = row[date_index][0] for pfx in dates.INPUT_PFX: if sorter.startswith(pfx) is True: sorter = sorter.lstrip(pfx)
if sorter.startswith('?') is True: sorter = sorter.replace('?-to-', '')
int_sort = sorter.split('-') nums = [] for item in int_sort: if len(item) == 4: idx = int_sort.index(item) nums.append(item) break nums.extend([int_sort[idx+1], int_sort[idx+2], int_sort[idx+3]])
ints = [] if nums[3] == 'bc': ints.append(-int(nums[0])) ints.extend([int(nums[1]), int(nums[2])]) else: del nums[3] for num in nums: ints.append(int(num)) sorter = ints row.append(sorter) return sorter
def get_sort_names(self, subject_id): ''' '''
conn = sqlite3.connect(current_file) cur = conn.cursor()
cur.execute(''' SELECT sort_order FROM name JOIN person ON person.person_id = name.person_id
WHERE name.person_id = ? AND name_type_id = 1 ''', (subject_id,)) sort_name = cur.fetchone() cur.close() conn.close() # only looking for birth names and there might not be one if sort_name: sort_name = sort_name[0].lower() elif not sort_name: sort_name = '' return sort_name
def get_death(self): ''' '''
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute(''' SELECT
date
FROM person JOIN finding ON finding.person_id = person.person_id JOIN event_type ON finding.event_type_id = event_type.event_type_id
WHERE finding.person_id = ? AND finding.event_type_id == 4 ''', (self.found_person,)) death_date = cur.fetchone() cur.close() conn.close()
self.death_date = ['-0000-00-00-', '']
if not death_date: self.row_list[3] = self.death_date return
iso_date = death_date[0] self.death_date = [iso_date, dates.format_misc_date(iso_date)]
self.row_list[3] = self.death_date
def get_birth(self): ''' '''
conn = sqlite3.connect(current_file) cur = conn.cursor()
cur.execute(''' SELECT finding_id, date
FROM person JOIN finding ON finding.person_id = person.person_id JOIN event_type ON finding.event_type_id = event_type.event_type_id
WHERE finding.person_id = ? AND finding.event_type_id == 1 ''', (self.found_person,)) birth_date = cur.fetchone() cur.close() conn.close()
self.birth_date = ['-0000-00-00-', '']
if not birth_date: self.row_list[2] = self.birth_date return
iso_date = birth_date[1] self.birth_date = [iso_date, dates.format_misc_date(iso_date)] self.row_list[2] = self.birth_date
self.offspring_event = birth_date[0] self.get_ma() self.get_pa()
self.row_list[7] = self.get_sort_names(self.ma_id) self.row_list[8] = self.get_sort_names(self.pa_id)
def get_ma(self): ''' '''
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute(''' SELECT name.person_id, names
FROM person LEFT JOIN findings_persons ON person.person_id = findings_persons.person_id LEFT JOIN kin_type ON findings_persons.kin_type_id = kin_type.kin_type_id LEFT JOIN name ON person.person_id = name.person_id WHERE findings_persons.kin_type_id = 1 AND name_type_id = 1 AND findings_persons.finding_id = ? ''', (self.offspring_event,))
self.mother = cur.fetchall() cur.close() conn.close()
if len(self.mother) != 0: self.mother = self.mother[0] ma = None
self.ma_id = self.mother[0]
ma = names.get_name_with_id(self.ma_id)
self.row_list[4] = ma
def get_pa(self): ''' ''' conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute(''' SELECT name.person_id, names
FROM person LEFT JOIN findings_persons ON person.person_id = findings_persons.person_id LEFT JOIN kin_type ON findings_persons.kin_type_id = kin_type.kin_type_id LEFT JOIN name ON person.person_id = name.person_id WHERE findings_persons.kin_type_id = 2 AND name_type_id = 1 AND findings_persons.finding_id = ? ''', (self.offspring_event,))
self.father = cur.fetchall() cur.close() conn.close()
if len(self.father) != 0: pa = None self.pa_id = self.father[0][0] pa = names.get_name_with_id(self.pa_id)
self.row_list[5] = pa
def get_other_names(self): ''' For nametips. '''
tip_names = [] for lst in self.other_names: name = lst[0] name_type = lst[1] tip_names.append([name_type, name])
name_tips = [] for lst in tip_names: sub = ': '.join(lst) name_tips.append(sub) name_tips = '\n'.join(name_tips)
for lst in self.other_names: name = lst[0] name_type = lst[1] name_kv = '{}: {}'.format(name_type, name) if lst[2]: used_by = lst[2] else: used_by = 'unknown' usedby_kv = 'name used by: {}'.format(used_by)
self.row_list[9] = name_tips
# NAMETIPS
def show_nametip(self): ''' Some rows in the search results table have no name because the displayed findings only show birth names. The nametips will point out that there may be no birth name stored for the person. Or the user might type "Daisy" and get "Alice". The nametip will show that Alice's nickname is Daisy. ''' maxvert = self.winfo_screenheight()
if self.nametip or not self.nametip_text: return x, y, cx, cy = self.widget.bbox('insert')
self.nametip = d_tip = wdg.Toplevel(self.widget)
label = wdg.LabelNegative( d_tip, text=self.nametip_text, justify='left', relief='solid', bd=1, bg=formats['highlight_bg']) 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.nametip self.nametip = None if d_tip: d_tip.destroy()
def make_nametip(self): ''' Runs once on widget construction. '''
self.widget.bind('<Enter>', self.handle_enter) self.widget.bind('<Leave>', self.on_leave)
def handle_enter(self, evt): ''' Get person id from text in column 0 of pointed row. Find that person id as row[id] in search results dicts. In that dict get row[other names]. '''
self.pointed_to = evt.widget pointed_row = self.pointed_to.grid_info()['row'] for child in self.search_results.mainframe.winfo_children(): if child.grid_info()['row'] in (0, 1): pass elif (child.grid_info()['column'] == 0 and child.grid_info()['row'] == pointed_row): self.person_id = child['text'] for row in self.result_rows: if row[0] == self.person_id: pointed_dict = row self.nametip_text = pointed_dict[9]
if self.nametip_text: self.show_nametip()
def on_leave(self, evt): self.other_names = [] self.off()
def get_matches(search_input): got = search_input.get() conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute( ''' SELECT DISTINCT person.person_id FROM person JOIN name ON person.person_id = name.person_id WHERE names LIKE ? OR person.person_id LIKE ? ''', ('%'+got+'%', '%'+got+'%')) all_matches = cur.fetchall() all_matches = [list(i) for i in all_matches]
for lst in all_matches: person_id = lst[0] cur.execute(''' SELECT names, name_types, used_by FROM name LEFT JOIN name_type ON name.name_type_id = name_type.name_type_id WHERE person_id = ?''', (person_id,)) other_names = cur.fetchall() other_names = [list(tup) for tup in other_names] if other_names: lst.append(other_names) elif not other_names: lst.append('')
cur.close() conn.close() return all_matches
def get_values(current_person): ''' Value-getting functions are nested in this function in order that the whole findings table can be built with one database connection. The list FindingsTable.findings is a class variable instead of an instance variable because both instances of the FindingsTable class (i.e. the events table and the attributes table) are made from the same list. Both tables exist at the same time and are replaced/updated at the same time. Return values are used in a chain from query function to query function since the variable has to be updated several times by several separate queries during each update process, and if the update functions are run in the wrong order, the results will be wrong. So using return values to call the query functions exactly once in exactly the right place ensures that the right query function will be run at the right time. Also the class variable can be referenced outside the FindingsTable class which is handy and allows this large collection of nested functions, get_values(), to reside outside the class also. ''' def get_finding_ids(): ''' Get ids for each row in findings table and start dict rows. '''
FindingsTable.findings = [] cur.execute(''' SELECT finding_id FROM finding WHERE person_id = ? ''', (current_person,)) ids1 = cur.fetchall()
cur.execute(''' SELECT finding_id FROM findings_persons WHERE person_id = ? ''',
(current_person,)) ids2 = cur.fetchall()
all_ids = ids1 + ids2 all_ids = [i[0] for i in all_ids]
for id in all_ids: finding_row = [ id, [], [], [], [], [], [''], [{}], [[]], 0, [0,0,0]] FindingsTable.findings.append(finding_row) return FindingsTable.findings
def get_generic_events(): ''' The value 'unknown' for place fields is used instead of nulls in the database where there is no known value for place. This allows use of a default value of 1 for the place_id fk in the database finding table. This is the id for the place 'unknown'. The code that builds the findings table in the GUI doesn't like nulls and maybe at the time I decided it would be better to store 'unknown' for place no. 1 instead of an empty string in case I forgot why I did it. I tried making it number 999999 assuming that many places would never be needed, but then the next place I added was auto-numbered 1000000 by sqlite which was annoying so I used place_id 1 instead. '''
FindingsTable.findings = get_finding_ids()
cur.execute(''' SELECT finding_id, event_types, date, places, particulars, age FROM finding JOIN event_type ON event_type.event_type_id = finding.event_type_id JOIN place ON place.place_id = finding.place_id WHERE person_id = ? AND finding.event_type_id != 1''', (current_person,)) generic_events = cur.fetchall() for tup in generic_events: for row in FindingsTable.findings: if tup[0] == row[0]:
lists = [[i] for i in tup[1:]] row[1:6] = lists
return FindingsTable.findings
def get_birth_events(): ''' Birth events in which current person is the child. '''
FindingsTable.findings = get_generic_events()
cur.execute(''' SELECT finding_id, date, places, particulars FROM finding JOIN event_type ON finding.event_type_id = event_type.event_type_id JOIN place ON finding.place_id = place.place_id WHERE person_id = ? AND finding.event_type_id = 1''', (current_person,)) birth_event = cur.fetchone() if birth_event: birth_event = list(birth_event) birth_event_id = birth_event[0] cur.execute(''' SELECT findings_persons.person_id FROM findings_persons JOIN finding ON findings_persons.finding_id = finding.finding_id WHERE findings_persons.finding_id = ? AND findings_persons.person_id != ?''', (birth_event_id, current_person)) parents = cur.fetchall() if len(parents) == 0: pa_ma = [] else: pa_ma = [] for id in parents: id = id[0] name = names.get_name_with_id(id) pa_ma.append([name, id])
for row in FindingsTable.findings: if row[0] == birth_event[0]: birth_event.insert(1, 'birth') if birth_event[3] == 'unknown': birth_event[3] = '' birth_event.append('0') # age at birth lists = [[i] for i in birth_event[1:6]] fixed = [] for lst in pa_ma: lst.append(None) fixed.append(lst) if len(fixed) > 0: lists.append(fixed) else: lists.append([[]]) row[1:7] = lists
return FindingsTable.findings def get_offspring_events(): ''' Birth events in which current person is a parent. '''
FindingsTable.findings = get_birth_events()
cur.execute(''' SELECT findings_persons.finding_id, date, places, particulars, findings_persons.age, finding.person_id FROM finding JOIN findings_persons ON finding.finding_id = findings_persons.finding_id JOIN place ON finding.place_id = place.place_id WHERE kin_type_id in (1, 2) AND findings_persons.person_id = ?''', (current_person,)) children = cur.fetchall()
children = [list(i) for i in children] if len(children) != 0:
kidvars = [] for lst in children: id = lst[5] name = names.get_name_with_id(id) kidvar = [[name, id, None]] kidvars.append(kidvar)
for row in FindingsTable.findings: for lst in children: if row[0] == lst[0]: lst.insert(1, 'offspring') if lst[3] == 'unknown': lst[3] = '' lists = [[i] for i in lst[1:6]] for item in kidvars: if item[0][1] == lst[6]: lists.append(item) row[1:7] = lists return FindingsTable.findings
def get_couple_events(): ''' '''
FindingsTable.findings = get_offspring_events()
cur.execute(''' SELECT finding.finding_id, event_types, date, places, particulars, findings_persons.age FROM finding LEFT JOIN findings_persons ON finding.finding_id = findings_persons.finding_id LEFT JOIN event_type ON finding.event_type_id = event_type.event_type_id LEFT JOIN person ON findings_persons.person_id = person.person_id LEFT JOIN place ON finding.place_id = place.place_id WHERE findings_persons.person_id = ? AND findings_persons.kin_type_id = 9''', (current_person,)) couple_events = cur.fetchall() couple_events = [list(i) for i in couple_events]
for lst in couple_events: name = '' finding_id = lst[0] cur.execute(''' SELECT person_id FROM findings_persons WHERE finding_id = ? AND person_id != ? ''', (finding_id, current_person)) spouse_id = cur.fetchone()
if spouse_id: id = spouse_id[0] name = names.get_name_with_id(id) lst.append([name, id]) else: lst.append(['']) for row in FindingsTable.findings: for lst in couple_events: if lst[0] == row[0]:
fix_me = lst[6] fix_me.append(None) if lst[3] == 'unknown': lst[3] = '' lists = [[i] for i in lst[1:6]] lists.append([fix_me]) row[1:7] = lists
return FindingsTable.findings
def get_generic_event_roles(): ''' Example: employer in an Occupation event is generic role. Flower girl in a Wedding event is couple-event role because it will be linked to both spouses. ''' cur.execute('''
SELECT finding.finding_id, findings_roles.role_type_id, findings_roles.person_id FROM finding
JOIN findings_roles ON findings_roles.finding_id = finding.finding_id JOIN person ON person.person_id = findings_roles.person_id JOIN role_type ON role_type.role_type_id = findings_roles.role_type_id
WHERE finding.person_id = ?''', (current_person,))
generic_event_roles = cur.fetchall()
generic_event_roles = [list(i) for i in generic_event_roles]
return generic_event_roles
def get_couple_event_ids(): ''' For use in getting couple event roles and couple event notes. '''
cur.execute(''' SELECT finding_id FROM findings_persons WHERE person_id = ?''', (current_person,)) couple_event_ids = cur.fetchall() couple_event_ids = [i[0] for i in couple_event_ids] qmarks = len(couple_event_ids)
return couple_event_ids, qmarks
def get_couple_event_roles():
couple_event_ids = get_couple_event_ids()
cur.execute(''' SELECT findings_roles.finding_id, findings_roles.role_type_id, findings_roles.person_id
FROM finding JOIN findings_roles ON findings_roles.finding_id = finding.finding_id JOIN person ON person.person_id = findings_roles.person_id JOIN role_type ON role_type.role_type_id = findings_roles.role_type_id WHERE findings_roles.finding_id IN ({}) '''.format(','.join('?' * couple_event_ids[1])), couple_event_ids[0])
couple_event_roles = cur.fetchall()
couple_event_roles = [list(i) for i in couple_event_roles]
return couple_event_roles
def make_roles_from_array(): ''' Gets all roles for current person's finding rows. When dots are clicked in findings table roles column, the dialog opens showing the right roles for that person and that role. '''
FindingsTable.findings = get_couple_events()
generic_event_roles = get_generic_event_roles() couple_event_roles = get_couple_event_roles() all_roles = generic_event_roles + couple_event_roles unique_ids = [] for lst in all_roles: if lst[0] not in unique_ids: unique_ids.append(lst[0]) all_roles_by_finding = [] for lst in all_roles: for id in unique_ids: roles_per_finding = [] if lst[0] == id: roles_per_finding.append(lst) for item in roles_per_finding: if len(item) > 0: all_roles_by_finding.append(item) final = [] for i in range(len(unique_ids)): final.append([]) t = 0 for id in unique_ids: for lst in all_roles_by_finding: if lst[0] == id: final[t].append(lst) t += 1
for row in FindingsTable.findings: for lst in final: if row[0] == lst[0][0]: row[7] = [lst]
return FindingsTable.findings
def get_notes():
FindingsTable.findings = make_roles_from_array()
couple_event_ids = get_couple_event_ids()
cur.execute('''
SELECT finding.finding_id, notes
FROM finding JOIN findings_notes ON finding.finding_id = findings_notes.finding_id JOIN note ON findings_notes.note_id = note.note_id JOIN person ON finding.person_id = person.person_id
WHERE finding.person_id = ?''', (current_person,))
generic_event_notes = cur.fetchall() generic_event_notes = [list(i) for i in generic_event_notes] cur.execute('''
SELECT findings_notes.finding_id, notes
FROM finding JOIN findings_notes ON finding.finding_id = findings_notes.finding_id JOIN note ON findings_notes.note_id = note.note_id
WHERE findings_notes.finding_id IN ({}) '''.format(','.join('?' * couple_event_ids[1])), couple_event_ids[0])
couple_event_notes = cur.fetchall() couple_event_notes = [list(i) for i in couple_event_notes]
all_notes = generic_event_notes + couple_event_notes
for row in FindingsTable.findings: notes = [[]] for lst in all_notes: if lst[0] == row[0]: notes[0].append(lst[1]) row[8] = notes
return FindingsTable.findings
def get_source_count():
FindingsTable.findings = get_notes()
source_count = []
for tup in FindingsTable.findings: id = tup[0]
cur.execute( ''' SELECT COUNT(finding_id) FROM claims_findings WHERE finding_id = ?''', (id,)) src_count = cur.fetchone() source_count.append((id, src_count[0])) for tup in source_count: for row in FindingsTable.findings: if tup[0] == row[0]: row[9] = tup[1]
return FindingsTable.findings
def make_formatted_dates_sortable():
''' Add a sortable date key to the findings row dicts: prefix is stripped out; date string is converted to list of integers which is stored as the "sortable_date" key's value; BC year integers are made negative. '''
FindingsTable.findings = get_source_count()
for row in FindingsTable.findings: sorter = row[2][0] for pfx in dates.INPUT_PFX: if sorter.startswith(pfx) is True: sorter = sorter.lstrip(pfx)
if sorter.startswith('?') is True: sorter = sorter.replace('?-to-', '') int_sort = sorter.split('-') nums = [] for item in int_sort: if len(item) == 4: idx = int_sort.index(item) nums.append(item) break nums.extend([int_sort[idx+1], int_sort[idx+2], int_sort[idx+3]])
ints = [] if nums[3] == 'bc': ints.append(-int(nums[0])) ints.extend([int(nums[1]), int(nums[2])]) else: del nums[3] for num in nums: ints.append(int(num)) sorter = ints row[10] = sorter
return FindingsTable.findings conn = sqlite3.connect(current_file) cur = conn.cursor() FindingsTable.findings = make_formatted_dates_sortable() cur.close() conn.close()
return FindingsTable.findings
FINDING_TABLE_HEADS = ( 'Event', 'Date', 'Place', 'Particulars', 'Age', 'Kin', 'Roles', 'Notes', 'Sources')
listx = list(FINDING_TABLE_HEADS[0:5])
FINDING_TABLE_TEXT_COLS = tuple(listx) FINDING_TABLE_DOT_COLS = FINDING_TABLE_HEADS[6:8]
# replace with query from db so user can add his own couple events: couple_event_types = ['marriage', 'divorce', 'honeymoon', 'annulment', 'marriage banns', 'wedding', 'separation', 'wedding anniversary celebration']
class FindingsTable(wdg.Table): ''' Make events and attributes tables on load or on changing current person or executing Save command. '''
findings = [] def __init__( self, master, person_autofill, top_pic_button, main, person_tab, heads=FINDING_TABLE_HEADS, **options):
wdg.Table.__init__(self, master, heads, **options)
self.master = master # PersonsTab instance self.person_autofill = person_autofill self.top_pic_button = top_pic_button self.main = main self.person_tab = person_tab # person tab of main notebook self.master.grid_rowconfigure(1, weight=0) self.finding_id = None self.string_input = '' self.new_row = 0 self.dlg_type = '' self.role_ids = [] self.note_string = [] self.col_header = '' self.cell_valuer = '' self.kin_id = None
self.subject_birthname = '' self.source_count = 0 self.from_db = True self.generic_event_roles = [] self.couple_event_roles = [] self.table_cells_parent = self.mainframe
self.current_person_events = [] self.current_person_attributes = [] self.valdor = dates.ValidDate()
self.bind_all('<Control-S>', self.save) self.bind_all('<Control-s>', self.save)
def distinguish_evt_att(self, person_id=None): ''' Runs many queries to get all findings for the current person. Separates them into two lists depending on whether or not the finding is dated. Also called in methods of the Files class. '''
if person_id: current_person = person_id else: current_person = get_current_person()[0]
FindingsTable.findings = get_values(current_person) self.subject_birthname = names.get_name_with_id(current_person)
for row in FindingsTable.findings: if (row[1][0] in ('birth', 'death', 'burial') or row[2][0] != '-0000-00-00-'): self.current_person_events.append(row) else: self.current_person_attributes.append(row)
def make_findings_table(self, evt_or_att): ''' This is called in methods of the Files class. '''
def make_header_row():
self.grid(column=0, row=2, sticky='ew', columnspan=2) g = 0 for name in FINDING_TABLE_HEADS: lab = wdg.LabelColumn( self.table_cells_parent, text=name) lab.grid(column=g, row=0, padx=0, sticky='w') g += 1
if evt_or_att is self.current_person_events: self.order_event_rows() make_header_row() self.make_table_cells(parent=self.table_cells_parent) self.make_event_selector() ST.config_generic(self.person_tab) elif evt_or_att is self.current_person_attributes: self.order_attribute_rows() make_header_row() self.make_table_cells(parent=self.table_cells_parent) ST.config_generic(self.main.persons.att) self.resize_scrollbar() self.resize_window()
self.main.persons.right_panel.make_active() # below config_generic()
self.valdor.value_from_db = None
self.fix_tab_traversal() self.display_curr_per()
def resize_scrollbar(self): self.update_idletasks() # self.main.persons is PersonsTab instance self.main.persons.attributes_canvas.config( scrollregion=self.main.persons.attributes_canvas.bbox("all"))
def resize_window(self): self.update_idletasks() page_x = self.main.persons.attributes_content.winfo_reqwidth() page_y = self.main.persons.attributes_content.winfo_reqheight() self.main.persons.att.geometry('{}x{}'.format(page_x, page_y))
def update_current_person(self, new_current_id, findings_table):
findings_table.person_autofill.delete(0, 'end') conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( 'UPDATE current SET person_id = ? WHERE current_id = 1', (new_current_id,)) conn.commit() cur.close() conn.close() self.main.show_top_pic(self.top_pic_button)
findings_table.display_curr_per()
self.main.canvas.yview_moveto(0.0)
def display_curr_per(self):
conn = sqlite3.connect(current_file) cur = conn.cursor()
cur.execute( ''' SELECT current.person_id FROM person JOIN current ON person.person_id = current.person_id WHERE current_id = 1 ''')
curr_id = cur.fetchone() if curr_id: curr_id = curr_id[0] else: return
cur.execute( ''' SELECT gender FROM person WHERE person_id = ? ''', (curr_id,)) curr_gen = cur.fetchone()[0] cur.close() conn.close()
self.subject_birthname = names.get_name_with_id(curr_id) self.main.curr_per_displ.config( text='{} ({})'.format(self.subject_birthname, curr_id)) self.main.names.announce.config( text='Names for {}'.format(self.subject_birthname)) self.main.names.person_add.gender_input.input.delete(0, 'end') self.main.names.person_add.gender_input.input.insert(0, curr_gen)
self.main.names.fill_names_table()
self.main.master.resize_scrollbar() self.main.master.resize_window()
def order_event_rows(self):
FIXED_ORDER_EVENTS = {'birth' : 0, 'death' : 1, 'burial' : 2}
fixed_rows = [] sortable_rows = [] for row in self.current_person_events: if row[1][0] == 'birth': fixed_rows.append(row) elif row[1][0] == 'death': fixed_rows.append(row) elif row[1][0] == 'burial': fixed_rows.append(row) else: sortable_rows.append(row)
fixed_rows.sort(key=lambda j: FIXED_ORDER_EVENTS[j[1][0]])
sortable_rows.sort(key = lambda i: i[10]) p = 1 for dikt in sortable_rows: fixed_rows.insert(p, dikt) p += 1
self.ordered_rows = fixed_rows
def order_attribute_rows(self):
sortable_rows = [] for row in self.current_person_attributes: if row[1][0] in ('birth', 'death', 'burial'): pass elif row[2][0] != '-0000-00-00-': pass else: sortable_rows.append(row) sortable_rows.sort(key = lambda i: i[1]) self.ordered_rows = sortable_rows
def make_role_dots(self, parent, dlg_header, holder=[[]]): dots = wdg.LabelDots( parent, RolesDialog, self.subject_birthname, dlg_header, self.finding_id, self.role_ids, root=self.main.root, holder=[[]]) if len(self.role_ids) != 0: dots.config(text=' ... ') else: dots.config(text=' ')
dots.grid( column=6, row=self.new_row, sticky='w', pady=3, padx=6)
def make_note_dots(self, parent, dlg_header, holder=[[]]):
dots = wdg.LabelDots( parent, notes.NotesDialog, self.subject_birthname, dlg_header, self.finding_id, self.note_string, holder=[[]])
if len(self.note_string) != 0: dots.config(text=' ... ') else: dots.config(text=' ')
dots.grid( column=7, row=self.new_row, sticky='w', pady=3, padx=6)
def make_kin_label(self):
population = len(self.coupler.winfo_children()) cell = wdg.LabelGoTo( self.coupler, table=self, change_person=replace_findings_table, subject_id=self.kin_id)
if self.from_db is True: self.insert_values_from_db(cell)
g = 0 for col_head in FINDING_TABLE_TEXT_COLS: if self.col_header == col_head: cell.grid( column=g, row=population, sticky='ew', pady=(3,0), padx=6) if population == 1: cell.grid_configure(pady=0) g += 1
def make_table_cells(self, parent): ''' Each row list has a [cell value] to which a widget reference will be appended. '''
col_num = 1 for heading in FINDING_TABLE_TEXT_COLS: self.new_row = 2 for table_row in self.ordered_rows: self.col_header = heading evt_type = table_row[1][0]
self.cell_valuer = table_row[col_num][0] self.make_label(parent, holder=table_row[col_num]) self.from_db = True self.new_row += 1 col_num += 1
self.new_row = 2 for table_row in self.ordered_rows: kinvars = table_row[6] if len(kinvars[0]) == 0: self.new_row += 1 continue self.coupler = wdg.Frame(self.mainframe) self.coupler.grid( column=5, row=self.new_row, sticky='ew') self.col_header = 'Kin'
p = 0 for lst in kinvars: self.cell_valuer = lst[0] self.kin_id = lst[1]
self.make_kin_label() self.from_db = True p += 1 self.new_row += 1
for dlg_type in FINDING_TABLE_DOT_COLS:
self.new_row = 2 for table_row in self.ordered_rows: self.finding_id = table_row[0] date = dates.format_misc_date(table_row[2][0])
self.dlg_header = ( table_row[1][0], date, table_row[3][0], table_row[4][0]) self.dlg_type = dlg_type if dlg_type == 'Roles': self.role_ids = table_row[7][0] self.make_role_dots( parent, self.dlg_header, holder=table_row[7]) elif dlg_type == 'Notes': self.note_string = table_row[8][0] self.make_note_dots( parent, self.dlg_header, holder=table_row[8]) self.new_row += 1
self.new_row = 2 for table_row in self.ordered_rows: self.source_count = table_row[9] self.make_source_button(parent) self.new_row += 1
def make_source_button(self, parent):
butt = wdg.LabelButtonText(parent) butt.config(text=self.source_count) butt.grid(column=8, row=self.new_row, padx=6, pady=3, sticky='w')
def make_label(self, parent, holder=[]): ''' holder is [cell's display value] '''
cell = wdg.EntryLabel( parent, self.use_input, table=self, list_list=FindingsTable.findings, change_person=replace_findings_table, subject_id=self.kin_id) holder.append(cell)
if self.from_db is True: self.insert_values_from_db(cell)
g = 0 for col_head in FINDING_TABLE_TEXT_COLS: if self.col_header == col_head: if (g != 5 or (g == 5 and len(holder[0]) == 0) or parent.winfo_name() != 'coupler'): cell.grid( column=g, row=self.new_row, sticky='ew', pady=3, padx=6) elif g == 5: cell.grid( column=g, row=row, sticky='ew', pady=3, padx=6) g += 1
self.kin_id = None
def insert_values_from_db(self, cell):
if self.col_header == 'Date': if ( '-and-' not in self.cell_valuer and '-to-' not in self.cell_valuer): formatted_date = self.valdor.format_date_for_table( self.cell_valuer, widg=cell) cell.config(text=formatted_date)
elif '-and-' in self.cell_valuer: self.valdor.and_to = ' and ' elif '-to-' in self.cell_valuer: self.valdor.and_to = ' to ' if (self.valdor.and_to is not None and len(self.valdor.and_to) != 0): formatted_date = self.valdor.select_function( self.cell_valuer, widg=cell) cell.config(text=formatted_date)
else: cell.config(text=self.cell_valuer)
# validation methods e.g. "get_user_input..." are temporary, # to be deleted or replaced later
def get_user_input_string(self, input, widg, finding_id=None): ''' If user focuses into EntryLabel in transit or to edit then focuses out w/out making changes, no validation will be done. As for general string validation rules, I don't know what to disallow so will add validation rules later as needed; for now anything that can be typed into a general string Entry is valid. '''
if widg['state'] == 'readonly': return conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor()
cur.execute(''' UPDATE finding SET particulars = ? WHERE finding_id = ?''', (input, self.finding_id)) conn.commit() cur.close() conn.close()
def get_user_input_place(self, evt): ''' ''' pass
def get_user_input_event(self, evt=None, finding_id=None): ''' ''' pass
def get_user_input_age(self, input, widg, finding_id=None): ''' Allow positive whole number only for now. These are strings, not integers. One purpose of using strings is for possible application of age expressed as year-month-day, e.g. '99y 8m 14d'. '''
if widg['state'] == 'readonly': return
if input.isnumeric() is False: widg.clear_bad_data( "non-numeric value", "Only positive whole numbers and zero are used.") return
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor()
# only works for generic events cur.execute( ''' UPDATE finding SET age = ? WHERE finding_id = ? ''', (input, self.finding_id)) conn.commit()
# only works for couple events current_person = get_current_person()[0] cur.execute( ''' UPDATE findings_persons SET age = ? WHERE finding_id = ? AND person_id = ? ''', (input, self.finding_id, current_person)) conn.commit()
cur.close() conn.close()
def get_user_input_kin(self, evt=None, finding_id=None): ''' ''' pass
def get_event_types(self):
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT event_type_id, event_types FROM event_type') evt_types_ids = cur.fetchall() cur.close() conn.close() return evt_types_ids
def create_new_event(self, evt): ''' '''
self.make_blank_row(parent=self.table_cells_parent)
new_evt = self.new_event_input.get() # # cover the combobox with an event save button: row = self.new_event_input.grid_info()['row'] self.new_event_input.grid_forget() self.new_event_input.delete(0, 'end') self.new_event_input.grid(column=0, row=row+1)
save_event = wdg.Button( self.mainframe, text='Save New Event', command=self.save) save_event.grid(column=0, row=row+1, sticky='ew')
new_evt_lab = wdg.EntryLabel( self.mainframe, self.use_input, table=self, list_list=FindingsTable.findings) new_evt_lab.grid( column=0, row=row, sticky='ew', pady=3, ipadx=12, padx=(12,0))
new_evt_lab.bind( '<FocusIn>', new_evt_lab.set_current_finding, add='+') new_evt_lab.bind( '<Button-1>', new_evt_lab.set_current_finding, add='+') new_evt_lab.config(text=new_evt)
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' SELECT event_type_id FROM event_type WHERE event_types = ? ''', (new_evt,)) new_event_type = cur.fetchone()[0]
current_person = get_current_person()[0] cur.execute( ''' INSERT INTO finding (finding_id, person_id, event_type_id) VALUES (null, ?, ?) ''', (current_person, new_event_type,)) conn.commit()
cur.execute( ''' SELECT seq FROM SQLITE_SEQUENCE WHERE name = 'finding' ''') new_finding_id = cur.fetchone()[0] new_row = [ new_finding_id, [new_evt, new_evt_lab], ['', ''], ['', ''], ['', ''], ['', ''], ['', ''], [{}], [[]], 0, [0,0,0]] FindingsTable.findings.append(new_row)
self.finding_id = new_finding_id
cur.close() conn.close() # format the new event label ST.config_generic(self.person_tab)
def make_event_selector(self): ''' '''
evt_types_ids = self.get_event_types() evt_types = [] for tup in evt_types_ids: evt_types.append(tup[1]) evt_types = sorted(evt_types) self.new_event_input = wdg.Grombo( self.mainframe, 'add event', values=evt_types) self.new_event_input.grid( column=0, row=self.new_row, sticky='ew', pady=3, padx=6) self.new_event_input.bind( '<<ComboboxSelected>>', self.create_new_event)
def make_blank_row(self, parent): ''' self.new_row is where the new row grids and +1 accounts for the Separator. '''
for heading in FINDING_TABLE_TEXT_COLS[1:7]: self.col_header = heading self.from_db = False self.make_label(parent) for heading in FINDING_TABLE_DOT_COLS: if heading == 'Roles': self.dlg_type = 'Roles' self.role_ids = [] self.make_role_dots(parent, 'Enter new role for event.') elif heading == 'Notes': self.dlg_type = 'Notes' self.make_note_dots(parent, 'Enter new note for event.')
self.make_source_button(parent) # format the new event row ST.config_generic(self.person_tab)
def fix_tab_traversal(self):
def third_and_second_items(pos): return pos[2], pos[1]
row_fixer = [] for child in self.mainframe.winfo_children(): if child.grid_info()['row'] != 0: row_fixer.append(( child, child.grid_info()['column'], child.grid_info()['row'])) row_fixer_2 = sorted(row_fixer, key=third_and_second_items)
widgets = [] for tup in row_fixer_2: widgets.append(tup[0])
for widg in widgets: widg.lift()
def use_input(self, input, widg): ''' See notes in EntryLabel class definition. ''' col = widg.grid_info()['column']
funx_per_col = { 1 : self.valdor.validate_date_or_not, 3 : self.get_user_input_string, 4 : self.get_user_input_age}
for k,v in funx_per_col.items(): if col == k: v(input, widg, finding_id=self.finding_id)
# # ************************* def save(self, evt=None): ''' Redraws the findings table, for example after a new event is added, it won't appear interfiled in the table by date until save() is run or the app is reloaded or anything that causes the current person to change or reload (such as resetting the current person to the already current person). Even if only the current person's main image is changed, this always redraws the whole table so that if the user wants to redraw the person tab with the newly chosen image, all he has to do is press Ctrl-S. ''' conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT person_id FROM current') current_person = cur.fetchone()[0] cur.close() conn.close()
if self.person_tab.focus_get() is self.person_autofill: self.person_tab.focus_set() replace_findings_table( self.main.persons, self.main.persons.attributes_content, self.person_autofill, self.top_pic_button, self.main, self.main.tabs.store['person'], current_person)
class DatePrefsWidgets(wdg.Frame): def __init__(self, parent, root, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs)
self.root = root self.rc_menu = rcm.RightClickMenu(self.root) self.make_widgets()
def make_widgets(self):
self.test_frm = wdg.Frame(self) self.test_frm.grid(column=0, row=0, pady=24)
self.test1 = wdg.LabelH3( self.test_frm, text="Date Entry Demo (doesn't affect your tree)") self.test1.grid(column=0, row=0)
self.tester = wdg.Frame(self.test_frm) self.tester.grid(column=0, row=1, sticky='w') self.tester.grid_columnconfigure(0, weight=1) self.tester.grid_columnconfigure(1, weight=1) self.tester.grid_columnconfigure(2, weight=1)
date_entries = ['Date Input I', 'Date Input II', 'Date Input III']
self.date_test = dates.ValidDate() self.tester_widgets = {} g = 0 for lab in date_entries: lbl = wdg.Label(self.tester, text=date_entries[g]) lbl.grid(column=0, row= g+1, padx=24, sticky='e') dent = wdg.EntryLabel( self.tester, self.use_input) dent.grid(column=1, row=g+1, sticky='ew') dent.config(width=56) self.tester_widgets[lab] = dent g += 1
self.date_prefs = wdg.Frame(self) self.date_prefs.grid(column=0, row=2, pady=24)
self.general = wdg.FrameHilited(self.date_prefs) self.general.grid( column=0, row=3, columnspan=3, rowspan=2, padx=(12,0), pady=(0,18))
self.pref_lab = wdg.LabelH2( self.date_prefs, text='Set Date Display Preferences') self.pref_lab2 = wdg.Label( self.date_prefs, text='first value in each dropdown list is default') self.section1 = wdg.LabelHilited( self.general, text='Select where all the following changes will apply:') self.section2 = wdg.LabelH3(self.date_prefs, text='Prefixes') self.section3 = wdg.LabelH3(self.date_prefs, text='Suffixes') self.section4 = wdg.LabelH3(self.date_prefs, text='Compound Dates')
options = ( "General", "Estimated", "Approximate", "Calculated", "Before/After", "Epoch", "Julian/Gregorian", "From...To...", "Between...And...")
self.date_pref_heads = {} p = 0 for heading in options: lab = wdg.LabelH3(self.date_prefs, text=options[p]) self.date_pref_heads[heading] = lab combo = wdg.ClearableReadonlyCombobox( self.date_prefs, values=dates.DATE_PREF_COMBOS[p], font=(formats['input_font'])) dates.date_pref_combos[heading] = combo p += 1
self.this_tree_or_all = tk.StringVar()
self.date_radios = {} d = 0 radios = ('This Tree', 'All Trees') for choice in radios: rad = wdg.RadiobuttonHilited( self.general, text=choice, variable=self.this_tree_or_all, command=dates.get_rad_val) rad.grid(column=d, row=1) self.date_radios[choice] = rad d += 1
self.date_radios['This Tree'].config(value='local') self.date_radios['All Trees'].config(value='global') self.this_tree_or_all.set('local')
self.pref_lab.grid(column=0, row=0, columnspan=4, sticky='we') self.pref_lab2.grid(column=0, row=1, columnspan=4, sticky='we', pady=(0, 24))
self.section1.grid(column=0, row=0, sticky='w', pady=12, padx=24, columnspan=3)
self.date_pref_heads['General'].grid(column=3, row=3, padx=12) dates.date_pref_combos['General'].grid(column=3, row=4, padx=12, pady=(0,18))
self.section2.grid(column=0, row=5, sticky='w', pady=12, padx=24)
self.date_pref_heads['Estimated'].grid(column=0, row=6, padx=12) dates.date_pref_combos['Estimated'].grid(column=0, row=7, padx=12, pady=(0,18))
self.date_pref_heads['Approximate'].grid(column=1, row=6, padx=12) dates.date_pref_combos['Approximate'].grid(column=1, row=7, padx=12, pady=(0,18))
self.date_pref_heads['Calculated'].grid(column=2, row=6, padx=12) dates.date_pref_combos['Calculated'].grid(column=2, row=7, padx=12, pady=(0,18))
self.date_pref_heads['Before/After'].grid(column=3, row=6, padx=12) dates.date_pref_combos['Before/After'].grid(column=3, row=7, padx=12, pady=(0,18))
self.section3.grid(column=0, row=8, sticky='w', pady=12, padx=24)
self.date_pref_heads['Epoch'].grid(column=0, row=9, padx=12) dates.date_pref_combos['Epoch'].grid(column=0, row=10, padx=12, pady=(0,12))
self.date_pref_heads['Julian/Gregorian'].grid(column=1, row=9, padx=12) dates.date_pref_combos['Julian/Gregorian'].grid( column=1, row=10, padx=12, pady=(0,12))
self.section4.grid(column=2, row=8, sticky='w', pady=12, padx=24)
self.date_pref_heads['From...To...'].grid(column=2, row=9, padx=12) dates.date_pref_combos['From...To...'].grid(column=2, row=10, padx=12, pady=(0,12))
self.date_pref_heads['Between...And...'].grid(column=3, row=9, padx=12) dates.date_pref_combos['Between...And...'].grid( column=3, row=10, padx=12, pady=(0,12))
self.bbox = wdg.Frame(self) self.bbox.grid(column=0, row=3, pady=24)
self.submit = wdg.Button( self.bbox, text='SUBMIT PREFERENCES', command=lambda tk_var=self.this_tree_or_all: submit_date_prefs(tk_var), width=30)
self.submit.grid(column=0, row=0, padx=(0,24))
self.revert = wdg.Button( self.bbox, text='REVERT TO DEFAULT VALUES', command=lambda tk_var=self.this_tree_or_all: revert_to_default(tk_var), width=30) self.revert.grid(column=1, row=0, padx=(24,0))
self.dates_visited = ( (self.test_frm, "", "Use top area to test input; bottom area for display settings."), (self.tester, "Demo Date Entry Inputs", "Enter date parts in any order. " "Right-click any area for more info."), (self.general, "", "Select whether a format should affect " "all your trees or just the current one."), (dates.date_pref_combos['General'], "General Date Format", "Select a general type of date display."), (dates.date_pref_combos['Estimated'], "Estimated Date Prefix", "Use estimated dates for unsourced guessed dates."), (dates.date_pref_combos['Approximate'], "Approximate Date Prefix", "Use approximated dates for sourced imprecise dates."), (dates.date_pref_combos['Calculated'], "Calculated Date Prefix", "Calculated dates derived from other data such as age."), (dates.date_pref_combos['Before/After'], "Before or After Date Prefix", "When you know something happened before or after some date."), (dates.date_pref_combos['Epoch'], "Epoch Date Suffix", "'BC' and 'AD' now have more politically correct variations."), (dates.date_pref_combos['Julian/Gregorian'], "Calendar Era Date Suffix", "Mark dates 'old style' or 'new style' for events during " "calendar transition times."), (dates.date_pref_combos['From...To...'], "Format Two Dates in a Span", "Something started at one time and lasted till another time."), (dates.date_pref_combos['Between...And...'], "Format Two Dates in a Range", "Something happened somewhere within a range between two dates."), (self.submit, "Submit Changes", "The changes you selected will be saved."), (self.revert, "Revert to Defaults", "Date formats will revert to Treebard's out-of-the-box defaults."))
rcm_widgets = ( self.test1, self.tester_widgets['Date Input I'], self.tester_widgets['Date Input II'], self.tester_widgets['Date Input III'], self.pref_lab, self.general, dates.date_pref_combos['General'], dates.date_pref_combos['Estimated'], dates.date_pref_combos['Approximate'], dates.date_pref_combos['Calculated'], dates.date_pref_combos['Before/After'], dates.date_pref_combos['Epoch'], dates.date_pref_combos['Julian/Gregorian'], dates.date_pref_combos['From...To...'], dates.date_pref_combos['Between...And...'], self.submit, self.revert) rcm.make_rc_menus( rcm_widgets, self.rc_menu, ms.dates_prefs_msg)
def use_input(self, input, widg): ''' This is what makes EntryLabel work. No finding_id needed for date tester widgets in date preferences tab. ''' self.date_test.validate_date_or_not(input, widg)
class PersonAdd(wdg.Frame): def __init__(self, parent, autofill, root, *args, **kwargs): wdg.Frame.__init__(self, parent, *args, **kwargs)
self.root = root self.autofill = autofill
self.gender = 'unknown'
self.new_person_id = None
self.role_person_edited = False self.findings_roles_id = None
self.rc_menu = rcm.RightClickMenu(self.root) self.make_widgets()
def make_widgets(self):
self.gender_input = wdg.LabelEntryPair(self, input_type='readonly') self.gender_input.label.config(text='Gender:') self.gender_input.input.config(values=GENDER_TYPES) all_pics = self.get_all_pics() self.image_input = wdg.LabelEntryPair(self, input_type='clickany') self.image_input.input.config(values=all_pics) self.image_input.label.config(text='Main Image:')
self.name_type_input = wdg.LabelEntryPair( self, input_type='readonly') self.name_type_input.input.config(values=self.get_name_types()) self.name_type_input.label.config(text='Name Type:')
self.name_input = wdg.LabelEntryPair(self) self.name_input.input.config(width=65) self.name_input.label.config(text='Full Name')
self.how = wdg.LabelH3( self, text='Alphabetize name: after clicking auto-sort, tab into ' 'auto-filled name fields to modify\nsort order with ' 'arrow keys or if sort order is correct, just click ADD.')
autosort = wdg.Button( self, text='AUTOSORT', command=self.show_sort_order)
self.order_frm = wdg.Frame(self)
s = 0 for stg in range(20): mov = wdg.LabelMovable(self.order_frm) mov.grid(column=s, row=0, padx=3) s += 1
self.buttonbox = wdg.Frame(self) self.add_butt = wdg.Button(self.buttonbox, text='ADD', width=8)
self.gender_input.grid( column=0, row=3, padx=12, pady=12, sticky='e') self.image_input.grid(column=1, row=3, padx=12, pady=12) self.name_type_input.grid( column=0, row=4, padx=12, pady=12, sticky='e') self.name_input.grid(column=1, row=4, padx=12, pady=12) self.how.grid(column=0, row=5, padx=6, pady=6, columnspan=4) autosort.grid(column=0, row=6, padx=6, pady=6) self.order_frm.grid(column=1, row=6, columnspan=4, pady=24) self.buttonbox.grid(column=1, row=7, sticky='e') self.add_butt.grid(column=0, row=0, padx=6, pady=6, sticky='e')
self.preset()
rcm_widgets = (self.name_input.input, self.name_type_input.input) rcm.make_rc_menus( rcm_widgets, self.rc_menu, ms.person_add_msg)
def preset(self):
self.gender_input.input.config(state='normal') self.gender_input.input.delete(0, 'end') self.gender_input.input.config(state='readonly') self.image_input.input.delete(0, 'end') self.image_input.input.insert(0, 'no_photo_001.gif') self.name_type_input.input.config(state='normal') self.name_type_input.input.delete(0, 'end') self.name_type_input.input.insert(0, 'birth name') self.name_type_input.input.config(state='readonly')
def reset(self): self.preset() self.name_input.input.delete(0, 'end') for child in self.order_frm.winfo_children(): child['text'] = '' self.dupe_check = True
def add_person(self, findings_roles_id=None): self.get_entered_values() self.findings_roles_id = findings_roles_id self.check_for_dupes()
def get_entered_values(self): ''' ''' if len(self.gender_input.input.get()) != 0: self.gender = self.gender_input.input.get() self.full_name = self.name_input.input.get() self.selected_image = self.image_input.input.get() self.name_type = self.name_type_input.input.get()
def check_for_dupes(self): ''' If birth name already exists in database, open dialog. ''' conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT person_id FROM person') all_people = cur.fetchall() cur.close() conn.close() all_people = [[i[0]] for i in all_people]
names_only = []
for id in all_people: display_name = names.get_name_with_id(id[0]) names_only.append(display_name) id.insert(0, display_name)
people_vals = [] for lst in all_people: if not lst[0]: lst[0] = '' people_vals.append(' #'.join([lst[0], str(lst[1])]))
if self.full_name not in names_only: self.make_new_person() self.make_sort_order() self.save_new_name(self.new_person_id) self.reset() else: self.dupe_check = msg.YesNoMessage( self, title='Duplicate Check', message="This birth name already exists. To create a " "new person by the same name, click SUBMIT. The " "two persons can be merged later if desired.", input=self.full_name).show() if self.dupe_check is True: self.make_new_person() self.make_sort_order() self.save_new_name(self.new_person_id) self.reset() else: self.reset()
def make_new_person(self): conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' INSERT INTO person VALUES (null, ?) ''', (self.gender,)) conn.commit() cur.execute( ''' SELECT seq FROM SQLITE_SEQUENCE WHERE name = 'person' ''') new_person_id = cur.fetchone() if new_person_id: self.new_person_id = new_person_id[0]
if self.role_person_edited is True: cur.execute( ''' UPDATE findings_roles SET person_id = ? WHERE findings_roles_id = ? ''', (self.new_person_id, self.findings_roles_id)) conn.commit() self.role_person_edited = False cur.close() conn.close()
def make_sort_order(self):
self.order = []
for child in self.order_frm.winfo_children(): text = child['text'] self.order.append(text)
self.order = ' '.join(self.order) self.order = self.order.replace(' , ', ', ') self.order = self.order.strip(', ')
def save_new_name(self, subject_id): conn = sqlite3.connect(current_file) cur = conn.cursor() conn.execute('PRAGMA foreign_keys = 1') cur.execute( 'SELECT image_id FROM image WHERE images = ?', (self.selected_image,)) img_id = cur.fetchone()[0]
cur.execute(''' INSERT INTO images_entities (image_id, main_image, person_id) VALUES (?, 1, ?) ''', (img_id, subject_id)) conn.commit() cur.execute( ''' SELECT name_type_id FROM name_type WHERE name_types = ? ''', (self.name_type,)) name_type_id = cur.fetchone() name_type_id = name_type_id[0]
cur.execute(''' INSERT INTO name VALUES (null, ?, ?, ?, ?, null)''', (subject_id, self.full_name, name_type_id, self.order)) conn.commit()
new_list = names.make_values_list_for_person_select()
self.autofill.values = new_list cur.close() conn.close()
self.image_input.input.delete(0, 'end') self.image_input.input.insert(0, 'no_photo_001.gif')
for widg in (self.name_type_input, self.name_input): widg.input.config(state='normal') widg.input.delete(0, 'end') self.name_type_input.input.config(state='readonly')
for child in self.order_frm.winfo_children(): child.config(text='') self.gender_input.input.config(state='normal') self.gender_input.input.delete(0, 'end') self.gender_input.input.config(state='readonly') self.gender_input.input.focus_set()
def show_sort_order(self):
self.got = self.name_input.input.get() self.new_name = self.got self.got = self.got.split() if len(self.got) == 0: return else: length = len(self.got)-1 word = self.got[length].lower() self.got.insert(0, ',') length += 1 if word not in NAME_SUFFIXES: self.got.insert(0, self.got.pop()) elif word in NAME_SUFFIXES and self.got[length].lower() == word: self.got.insert(0, self.got.pop()) self.got.insert(0, self.got.pop())
for child in self.order_frm.winfo_children(): child.config(text='')
v = 0 for name in self.got: self.order_frm.winfo_children()[v].config(text=name) v += 1
def get_name_types(self):
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT name_types FROM name_type ORDER BY name_types') name_types = cur.fetchall() cur.close() conn.close() name_types = [i[0] for i in name_types]
return name_types
def get_all_pics(self):
conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT images FROM image') picvals = cur.fetchall() cur.close() conn.close() return picvals
class RolesDialog(wdg.Toplevel): def __init__( self, parent, subject, role_ids, dlg_header, finding_id=None, root=None, *args, **kwargs): wdg.Toplevel.__init__(self, *args, **kwargs)
self.parent = parent self.title('{} ({})'.format('Roles for an Event', subject)) self.role_ids = role_ids self.dlg_header = dlg_header self.finding_id = finding_id self.root = root self.autofill = parent.master.person_autofill
self.original_role_type = '' self.original_role_person = ''
self.user_input_person = ''
self.role_types = [] self.findings_roles_id = 0 self.edited_role_id = 0
self.got_row = 0
self.role_types = [] self.persons = get_all_persons()
self.roles_per_finding = []
self.rc_menu = rcm.RightClickMenu(self.root) self.make_widgets() self.protocol("WM_DELETE_WINDOW", self.close_roles_dialog) def make_widgets(self):
# auto-scrollbar
self.role_canvas = wdg.Canvas( self, bd=0, highlightthickness=0) self.role_canvas.grid(column=0, row=0, sticky='news')
self.role_content = wdg.Frame(self.role_canvas)
self.role_content.grid_columnconfigure(0, weight=1) self.role_content.grid_columnconfigure(1, weight=0) self.role_content.grid_columnconfigure(2, weight=1) self.role_content.grid_rowconfigure(1, weight=1)
vscroll = wdg.AutoScrollbar(self, width=16) vscroll.grid(column=1, row=0, sticky='ns') hscroll = wdg.AutoScrollbar(self, orient='horizontal', width=16) hscroll.grid(column=0, row=2, sticky='ew')
self.role_canvas.config( yscrollcommand=vscroll.set, xscrollcommand=hscroll.set)
vscroll.config(command=self.role_canvas.yview) hscroll.config(command=self.role_canvas.xview)
self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
# widgets
roles_table = wdg.Frame(self.role_content) new_roles_area = wdg.Frame(self.role_content)
self.roles_dialog_header = wdg.Frame(self.role_content) self.rolfrm = wdg.Frame(self.role_content)
self.make_roles_table()
new_roles_header = wdg.LabelH3(new_roles_area, text='Create New Role')
self.role_type_input = wdg.ClickAnywhereCombo( new_roles_area, values=self.role_types)
self.person_input = wdg.AutofillEntry(new_roles_area) self.person_input.values = self.persons self.person_input.config(textvariable=self.person_input.var) self.person_input.fix_max_width()
self.add_butt = wdg.Button( new_roles_area, text='Add', command=self.get_add_state) self.done_butt = wdg.Button( new_roles_area, text='Done', command=self.add_and_close) self.close_butt = wdg.Button( new_roles_area, text='Close', command=self.close_roles_dialog)
roles_statusbar = wdg.StatusbarTooltips(self) roles_statusbar.grid(column=0, row=2, sticky='ew')
roles_table.grid(column=0, row=0) new_roles_area.grid(column=0, row=2)
self.roles_dialog_header.grid(column=0, row=0, ipadx=12, padx=24, pady=(36,12)) self.rolfrm.grid(column=0, row=1, columnspan=2) self.rolfrm.grid_columnconfigure(4, weight=1)
new_roles_header.grid(column=0, row=0, padx=6, pady=6)
self.role_type_input.grid(column=0, row=1, padx=(24,6), pady=6) self.person_input.grid(column=1, row=1, padx=6, pady=6) self.add_butt.grid(column=2, row=1, padx=6, pady=6) self.done_butt.grid(column=3, row=1, padx=6, pady=6) self.close_butt.grid(column=4, row=2, padx=(6,24), pady=(6,24))
self.make_edit_row() self.get_role_types()
visited = ( (self.roles_dialog_header, '', 'Type, date, place & particulars of this event if known.'), (self.edit_role_type, 'Edit Role Type Input', 'Existing roletype can be changed to a different type.'), (self.edit_role_person, '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, add new one, or leave blank.'), (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.'))
wdg.run_statusbar_tooltips( visited, roles_statusbar.status_label, roles_statusbar.tooltip_label)
rcm_widgets = ( self.role_type_input, self.person_input, self.add_butt, self.done_butt, self.close_butt) rcm.make_rc_menus( rcm_widgets, self.rc_menu, ms.role_dlg_msg, header_parent=self.roles_dialog_header, dlg_header=self.dlg_header, which_dlg='roles')
self.role_canvas.create_window( 0, 0, anchor=tk.NW, window=self.role_content) ST.config_generic(self) self.resize_window() self.resize_scrollbar()
def make_roles_list(self):
conn = sqlite3.connect(current_file) cur = conn.cursor()
cur.execute( ''' SELECT findings_roles_id, role_types, person_id, findings_roles.role_type_id FROM role_type JOIN findings_roles ON role_type.role_type_id = findings_roles.role_type_id WHERE finding_id = ? ''', (self.finding_id,)) roles_per_finding = cur.fetchall() if roles_per_finding: self.roles_per_finding = [list(i) for i in roles_per_finding]
for lst in self.roles_per_finding: name = names.get_name_with_id(lst[2]) lst.append(lst[2]) lst[2] = name
cur.close() conn.close()
def make_roles_table(self):
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()
first_butt = None n = 0 for lst in self.roles_per_finding: labelr = wdg.Label(self.rolfrm, text=lst[1], anchor='e') sep = wdg.Label(self.rolfrm, text='|', anchor='w') labelp = wdg.Label(self.rolfrm, text=lst[2], anchor='w') editx = wdg.ButtonQuiet( self.rolfrm, width=2, command=self.grid_edit_row) 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] = ms.gen_edit_role_rows n += 1 if first_butt: first_butt.focus_set()
def grid_edit_row(self): self.edited_role_id = self.roles_per_finding[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_finding[self.got_row][1] elif child.grid_info()['column'] == 2: self.original_role_person = self.roles_per_finding[self.got_row][2] self.edit_row.grid(row=self.got_row)
self.edit_role_type.delete(0, 'end') self.edit_role_person.delete(0, 'end') self.edit_role_type.insert(0, self.roles_per_finding[self.got_row][1]) self.edit_role_type.focus_set()
chosen_person_id = self.roles_per_finding[self.got_row][4] if chosen_person_id is None: self.edit_role_person.insert(0, '')
else: self.edit_role_person.insert( 0, '{} #{}'.format( self.roles_per_finding[self.got_row][2], str(chosen_person_id)))
self.edit_row.lift() self.resize_scrollbar() self.resize_window()
def make_edit_row(self):
def cancel_edit_row(): self.edit_row.grid_remove() self.resize_scrollbar() self.resize_window()
self.edit_row = wdg.Frame(self.rolfrm)
self.edit_role_type = wdg.ClickAnywhereCombo( self.edit_row, values=self.role_types)
self.edit_role_person = wdg.AutofillEntry(self.edit_row) self.edit_role_person.values = self.persons self.edit_role_person.config(textvariable=self.edit_role_person.var) self.edit_role_person.fix_max_width()
self.ok_butt = wdg.Button( self.edit_row, text='OK', command=self.get_edit_state) self.cancel_butt = wdg.Button( self.edit_row, text='Cancel', command=cancel_edit_row) self.delete_butt = wdg.Button( self.edit_row, text='Delete', command=self.delete_role) 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.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 get_edit_state(self): ''' Detect and respond to changes in existing roles on OK button. ''' edited_role_type = self.edit_role_type.get() edited_role_person = self.edit_role_person.get() if edited_role_type in self.role_types: if edited_role_type != self.original_role_type: self.update_role_type(edited_role_type) else: self.make_new_role_type(edited_role_type) self.update_role_type(edited_role_type) if edited_role_person in self.persons: if edited_role_person != self.original_role_person: self.change_role_person(edited_role_person) elif len(edited_role_person) == 0: findings_roles_id = self.roles_per_finding[self.got_row][0] self.set_role_person_unknown(findings_roles_id) else: self.make_new_person(from_edit=True, edited_role_person=edited_role_person) edited_person_id = self.person_add.new_person_id self.update_role_person(edited_person_id) self.original_role_type = edited_role_type self.original_role_person = self.edit_role_person.get() self.edit_row.grid_remove()
def get_add_state(self):
if len(self.role_type_input.get()) == 0: self.role_type_input.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: self.make_new_role_type(chosen_role_type) self.make_new_role(chosen_role_type)
def make_new_role(self, role_type):
if len(self.user_input_person) == 0: role_person_id = None elif self.user_input_person not in self.persons: self.make_new_person() role_person_id = self.person_add.new_person_id else: role_person_id = self.person_input.get().split(' #')[1]
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' SELECT role_type_id FROM role_type WHERE role_types = ? ''', (role_type,)) role_type_id = cur.fetchone()[0]
cur.execute( ''' INSERT INTO findings_roles VALUES (null, ?, ?, ?) ''', (self.finding_id, role_type_id, role_person_id)) conn.commit() cur.close() conn.close() 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() self.role_type_input.delete(0, 'end') self.person_input.delete(0, 'end') self.resize_scrollbar() self.resize_window() self.make_roles_list()
def make_new_person(self, from_edit=False, edited_role_person=None): ''' Open PersonAdd dialog. '''
def close_dialog(): self.new_role_person_dialog.destroy()
self.new_role_person_dialog = wdg.Toplevel(self) self.person_add = PersonAdd( self.new_role_person_dialog, self.person_input, self.root) self.person_add.grid() self.person_add.name_input.input.delete(0, 'end') if from_edit is False: self.person_add.name_input.input.insert(0, self.user_input_person) else: self.person_add.name_input.input.insert(0, edited_role_person)
self.person_add.add_butt.config(command=self.person_add.add_person)
self.close_butt = wdg.Button( self.person_add.buttonbox, text='Close', width=8, command=close_dialog) self.close_butt.grid(column=1, row=0, padx=6, pady=6)
self.new_role_person_dialog.protocol("WM_DELETE_WINDOW", close_dialog) self.person_add.gender_input.input.focus_set() ST.config_generic(self.new_role_person_dialog) self.new_role_person_dialog.wait_window() self.persons = get_all_persons()
def change_role_person(self, edited_role_person): ''' Change role person to a person that already exists. '''
new_person_data = edited_role_person.split(' #') new_person_id = new_person_data[1] new_person_name = new_person_data[0] conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' UPDATE findings_roles SET person_id = ? WHERE findings_roles_id = ? ''', (new_person_id, self.edited_role_id)) conn.commit() cur.close() conn.close()
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) self.resize_scrollbar() self.resize_window() self.make_roles_list()
def make_new_role_type(self, edited_role_type): ''' Get user pref w/ dialog. Connect to db, insert new role_type. '''
if len(edited_role_type) == 0: return conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' INSERT INTO role_type VALUES(null, ?) ''', (edited_role_type,)) conn.commit() cur.close() conn.close()
self.get_role_types()
def update_role_type(self, edited_role_type):
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor()
cur.execute( ''' SELECT role_type_id FROM role_type WHERE role_types = ? ''', (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 findings_roles SET role_type_id = ? WHERE findings_roles_id = ? ''', (chosen_role_type_id, self.edited_role_id)) conn.commit()
cur.close() conn.close()
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_scrollbar() self.resize_window()
self.get_role_types() self.make_roles_list()
def update_role_person(self, edited_person_id): ''' Change person in existing role to newly created person. '''
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' UPDATE findings_roles SET person_id = ? WHERE findings_roles_id = ? ''', (edited_person_id, self.edited_role_id)) conn.commit() cur.close() conn.close()
edited_role_person_name = names.get_name_with_id(edited_person_id)
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=edited_role_person_name) self.resize_scrollbar() self.resize_window() self.make_roles_list()
def set_role_person_unknown(self, findings_roles_id):
conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' UPDATE findings_roles SET person_id = null WHERE findings_roles_id = ? ''', (findings_roles_id,)) conn.commit() cur.close() conn.close()
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='')
self.resize_scrollbar() self.resize_window() self.make_roles_list()
def delete_role(self):
def delete_role_from_db(): conn = sqlite3.connect(current_file) conn.execute('PRAGMA foreign_keys = 1') cur = conn.cursor() cur.execute( ''' DELETE FROM findings_roles WHERE findings_roles_id = ? ''', (self.edited_role_id,)) conn.commit() cur.close() conn.close() self.make_roles_list()
delete_role_from_db() 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_scrollbar() self.resize_window()
def add_and_close(self): self.get_add_state() self.close_roles_dialog()
def close_roles_dialog(self, evt=None): self.destroy()
def get_role_types(self):
self.role_types = []
conn = sqlite3.connect(current_file) cur = conn.cursor()
cur.execute( ''' SELECT role_types FROM role_type ''') role_types = cur.fetchall() cur.close() conn.close() role_types = [i[0] for i in role_types]
role_types.sort()
self.role_types = role_types
for widg in (self.edit_role_type, self.role_type_input): widg.config(values=self.role_types)
def resize_scrollbar(self): self.update_idletasks() self.role_canvas.config(scrollregion=self.role_canvas.bbox("all"))
def resize_window(self): self.update_idletasks() page_x = self.role_content.winfo_reqwidth() + 24 page_y = self.role_content.winfo_reqheight() + 48 self.geometry('{}x{}'.format(page_x, page_y))
def get_all_persons(): conn = sqlite3.connect(current_file) cur = conn.cursor() cur.execute('SELECT person_id FROM person') person_ids = cur.fetchall() person_ids = [i[0] for i in person_ids] cur.close() conn.close() persons = [] for id in person_ids: name = names.get_name_with_id(id) if len(name) != 0: name = '{} #{}'.format(name, id) persons.append(name) return persons
def placeholder(): print('menu test')
if __name__ == "__main__":
root = tk.Tk() ST = st.ThemeStyles(app=root) Treebard(root).pack(side='top', fill='both', expand=True)
root.iconbitmap(default='favicon.ico') root.title('Treebard Genieware Pattern Simulation') root.withdraw() splash = SplashScreen(root)
root.geometry('+0+0') root.mainloop()
|
|