Post by Uncle Buddy on Nov 25, 2022 3:51:09 GMT -8
D:\treebard_gps\app\python\main.py Last Changed 2022-12-07
# main.py
import tkinter as tk
from tkinter import filedialog
import sqlite3
from PIL import Image, ImageTk
from files import current_drive, get_current_file, global_db_path, get_image_dir
from widgets import (
Frame, LabelH2, LabelH3, Label, Button, Canvas, ButtonBigPic, Toplevel,
Radiobutton, LabelFrame, Border, TabBook, Scrollbar, fix_tab_traversal,
EntryAutoPerson, EntryAutoPersonHilited, FontPicker, redraw_person_tab,
open_message, Separator, Entry, Checkbutton, Combobox, EntryAutoPlace,
LabelHeader, redraw_current_person_area)
from right_click_menu import RightClickMenu, make_rc_menus
from toykinter_widgets import run_statusbar_tooltips
from families import NuclearFamiliesTable
from findings_table import FindingsTable
from dates import DatePreferences, OK_MONTHS, get_date_formats
from gallery import Gallery
from colorizer import Colorizer
from images import MediaTab
from do_list import DoList
from messages import main_msg
from messages_context_help import main_help_msg
from persons import check_name, get_original, get_name_from_id, open_new_person_dialog
from search import PersonSearch
from graphics import CropTool, BorderTextTool, ResizeTool, CaptionTool
from query_strings import (
select_links_links_main_image_person, select_current_person_nested_place_source,
select_finding_id_birth, select_person_gender_by_id, update_name_type_sorter,
select_person_id_finding, select_date_finding, select_finding_event_type,
select_finding_id_death, select_name_all_current, select_all_name_types,
select_name_type_id_by_string, insert_name_and_type, select_all_event_types,
insert_event_type, select_links_links_main_image_nested_place,
select_links_links_main_image_source, update_current_image_directory,
select_app_setting_default_images, delete_name, insert_name_placeholder,
select_preferences, select_gender, select_current_image_directory,
)
import dev_tools as dt
from dev_tools import looky, seeline
MAIN_TABS = (
("person", "P"), ("assertions", "A"), ("names", "N"), ("places", "L"),
("sources", "S"), ("reports", "R"), ("charts", "Z"), ("projects", "J"),
("graphics", "G"), ("links", "K"), ("search", "H"), ("types", "T"),
("contacts", "Q"), ("preferences", "E"))
RIGHT_PANEL_TABS = (("images", "I"), ("do list", "O"))
PREFS_TABS = (
("general", "X"), ("colors", "C"), ("fonts", "F"), ("dates", "D"),
("media", "M"), ("where?", "W"))
NUKEFAM_HEADS = ("NAME OF CHILD", "GENDER", "DATE OF BIRTH", "DATE OF DEATH")
def get_current_elements():
current_file = get_current_file()[0]
conn = sqlite3.connect(current_file)
cur = conn.cursor()
cur.execute(select_current_person_nested_place_source)
current_elements = cur.fetchone()
cur.close()
conn.close()
return current_elements
from widgets import FrameStay
class Main(FrameStay):
def __init__(self, master, root, treebard, *args, **kwargs):
FrameStay.__init__(self, master, *args, **kwargs)
self.master = master # the main canvas (instance of Border class)
self.root = root
self.treebard = treebard
tree = get_current_file()[0]
conn = sqlite3.connect(global_db_path)
cur = conn.cursor()
cur.execute("ATTACH ? AS tree", (tree,))
cur.execute(select_all_name_types)
self.name_types = [" ".join(i) for i in cur.fetchall()]
self.current_person, self.current_nested_place, self.current_source = get_current_elements()
self.current_person_name = ""
self.current_gender = self.get_current_person_gender(self.current_person)
self.edit_image = None
self.to_destroy = []
self.male_image = ""
self.female_image = ""
self.unisex_image = ""
self.nested_place_image = ""
self.source_image = ""
self.current_image = None
self.names_table = None
self.place_data = EntryAutoPlace.get_place_values()
self.tabbook_x = 300
self.tabbook_y = 300
self.SCREEN_SIZE = []
self.SCREEN_SIZE.append(self.winfo_screenwidth())
self.SCREEN_SIZE.append(self.winfo_screenheight())
self.rc_menu = RightClickMenu(self.root, treebard=self.treebard)
cur.execute(select_preferences)
use_default_images = cur.fetchone()[0]
if use_default_images == 0:
self.use_default_images = True
elif use_default_images == 1:
self.use_default_images = False
self.make_widgets(cur, conn, tree)
self.get_current_values()
self.nukefam_table.make_nukefam_inputs(on_load=True)
cur.execute("DETACH tree")
cur.close()
conn.close()
def make_scrollbars(self):
self.vsb = Scrollbar(
self.root,
hideable=True,
command=self.master.yview,
width=20)
self.hsb = Scrollbar(
self.root,
hideable=True,
width=20,
orient='horizontal',
command=self.master.xview)
self.master.config(
xscrollcommand=self.hsb.set,
yscrollcommand=self.vsb.set)
self.vsb.grid(column=2, row=4, sticky='ns')
self.hsb.grid(column=1, row=5, sticky='ew')
def make_widgets(self, cur, conn, tree):
self.make_scrollbars()
scridth = 20
scridth_n = Frame(self, height=scridth)
scridth_w = Frame(self, width=scridth)
current_person_area = Frame(self)
self.main_tabs = TabBook(
self, root=self.root, tabs=MAIN_TABS,
selected='person', case='upper', miny=0.66, takefocus=0)
persons_tab = Frame(self.main_tabs.store["person"])
places_tab = Frame(self.main_tabs.store["places"], name="placetab")
sources_tab = Frame(self.main_tabs.store["sources"], name="sourcetab")
self.names_tab = Frame(self.main_tabs.store["names"])
self.graphics_tab = Frame(self.main_tabs.store["graphics"])
prefs_tab = Frame(self.main_tabs.store["preferences"])
types_tab = Frame(self.main_tabs.store["types"])
self.current_person_label = LabelH2(current_person_area)
change_current_person = LabelH3(
current_person_area, text="Change current person to:")
self.person_entry = EntryAutoPersonHilited(
current_person_area,
width=36,
autofill=True)
EntryAutoPerson.person_autofills.append(self.person_entry)
person_change = Button(
current_person_area, text="OK", command=self.change_person)
person_search = Button(
current_person_area,
text="ADD/FIND",
command=self.open_person_search)
minx = self.tabbook_x/self.SCREEN_SIZE[0]
miny = self.tabbook_y/self.SCREEN_SIZE[1]
self.right_panel = TabBook(
persons_tab, root=self.root, tabs=RIGHT_PANEL_TABS,
side="se", selected='images', case='upper', miny=0.25, minx=0.20,
takefocus=0)
self.top_pic_button = ButtonBigPic(
self.right_panel.store['images'],
command=self.open_person_gallery,
text="Add images to the current person\nin Preferences > Media tab.")
self.do_list = DoList(self.right_panel.store["do list"], tree, self.right_panel, self.treebard)
self.do_list.grid(column=0, row=0, sticky="news")
self.findings_table = FindingsTable(
persons_tab,
self.root,
self.treebard,
self,
self.current_person,
self.place_data)
self.nukefam_table = NuclearFamiliesTable(
persons_tab,
self.root,
self.treebard,
self.current_person,
self.findings_table,
self.right_panel)
fix_tab_traversal(self.nukefam_table, self.findings_table)
septor = Separator(persons_tab)
tree, current_dir = get_current_file()
self.get_default_images(cur)
# this does not run on redraw_person_tab, just on load
if len(current_dir) != 0:
self.show_top_pic(tree, current_dir, self.current_person)
self.main_tabs.store["assertions"].columnconfigure(0, weight=1)
self.main_tabs.store["assertions"].rowconfigure(0, weight=1)
announcement = Label(
self.main_tabs.store["assertions"],
text="Click any SOURCES button to display assertions here.")
announcement.grid()
self.top_pic_place_button = ButtonBigPic(
places_tab, command=self.open_place_gallery,
text="Add images to the current place\nin Preferences > Media tab.")
if len(current_dir) != 0:
self.show_top_pic_nested_place(
tree, current_dir, self.current_nested_place)
self.top_pic_source_button = ButtonBigPic(
sources_tab, command=self.open_source_gallery,
text="Add images to the current source\nin Preferences > Media tab.")
if len(current_dir) != 0:
self.show_top_pic_source(
tree, current_dir, self.current_source)
lab665 = LabelH3(self.names_tab, text="Change Current Person Name")
lab472 = Label(
self.names_tab,
text="Make new name for the current person:", anchor="e")
self.new_name_input = Entry(self.names_tab, width=25)
self.new_name_type_cbo = Combobox(self.names_tab, self.root, values=self.name_types)
self.new_name_sort_order = Entry(self.names_tab, width=25) # FIX TAB ORDER
sortbutt = Button(
self.names_tab, text="Alphabetize as:",
command=lambda ent=self.new_name_input, sortent=self.new_name_sort_order: self.make_default_sort_order(
ent, sortent))
for child in (
self.new_name_input, self.new_name_type_cbo,
sortbutt, self.new_name_sort_order):
child.lift()
self.names_table = self.make_names_table(self.names_tab)
namer = Button(
self.names_tab, text="OK", width=8, command=self.update_name)
name_deleter = Button(
self.names_tab, text="DELETE CHECKED NAMES",
command=self.delete_name)
graphics_buttons = Frame(self.graphics_tab)
self.cropper = Button(
graphics_buttons, text="CROP", width=18,
command=lambda tool="cropper": self.open_graphics_tool(tool))
self.texter = Button(
graphics_buttons, text="ADD BORDER & TEXT", width=18,
command=lambda tool="texter": self.open_graphics_tool(tool))
self.resizer = Button(
graphics_buttons, text="COPY/RESIZE", width=18,
command=lambda tool="resizer": self.open_graphics_tool(tool))
self.captioner = Button(
graphics_buttons, text="EDIT CAPTION", width=18,
command=lambda tool="captioner": self.open_graphics_tool(tool))
clearbutt = Button(
graphics_buttons, text="CLEAR THIS TAB",
command=self.clear_image_frame, width=18)
# children of self.graphics_tab
graphics_buttons.grid(column=0, row=0, padx=12, pady=12, sticky="ew")
# children of graphics_buttons
self.cropper.grid(column=0, row=0, padx=(0,6))
self.texter.grid(column=1, row=0, padx=(0,6))
self.resizer.grid(column=2, row=0, padx=(0,6))
self.captioner.grid(column=3, row=0, padx=(0,6))
clearbutt.grid(column=4, row=0, padx=(0,6))
options_tabs = TabBook(
prefs_tab, root=self.root, tabs=PREFS_TABS,
side="se", selected='general', case='upper', miny=0.5, minx=0.66)
cur.execute(select_all_event_types)
all_event_types = ", ".join([i[0] for i in cur.fetchall()])
event_types_display = Label(
types_tab, text=all_event_types, justify="left", wraplength=750)
lab_event_type = Label(types_tab, text="Add event type if not in above list:")
self.new_event_type_input = Entry(types_tab, width=32)
radframe = Frame(types_tab)
l6 = Label(radframe, text="Couple event?")
self.couple_rad = tk.IntVar(None, 0)
couple_rad1 = Radiobutton(radframe, variable=self.couple_rad, value=1, text="yes")
couple_rad2 = Radiobutton(radframe, variable=self.couple_rad, value=0, text="no")
l7 = Label(radframe, text="Marital event?")
self.marital_rad = tk.IntVar(None, 0)
marital_rad1 = Radiobutton(radframe, variable=self.marital_rad, value=1, text="yes")
marital_rad2 = Radiobutton(radframe, variable=self.marital_rad, value=0, text="no")
l8 = Label(radframe, text="After death event?")
self.after_death_rad = tk.IntVar(None, 0)
after_death_rad1 = Radiobutton(
radframe, variable=self.after_death_rad, value=1, text="yes")
after_death_rad2 = Radiobutton(
radframe, variable=self.after_death_rad, value=0, text="no")
# children of types_tab
event_types_display.grid(column=0, row=0, columnspan=2)
lab_event_type.grid(column=0, row=1, sticky="e")
self.new_event_type_input.grid(column=1, row=1, pady=12, sticky="w")
radframe.grid(column=0, row=2, columnspan=2)
# children of radframe
couple_rad1.grid(column=1, row=0)
couple_rad2.grid(column=2, row=0)
marital_rad1.grid(column=1, row=1)
marital_rad2.grid(column=2, row=1)
after_death_rad1.grid(column=1, row=2)
after_death_rad2.grid(column=2, row=2)
l6.grid(column=0, row=0, sticky="w")
l7.grid(column=0, row=1, sticky="w")
l8.grid(column=0, row=2, sticky="w")
save_event_type = Button(types_tab, text="SAVE", command=self.save_new_event_type)
save_event_type.grid(column=2, row=3, pady=12, sticky="e")
# radios for couple, marital, after death
general = Frame(options_tabs.store['general'])
general.columnconfigure(0, weight=1)
general.rowconfigure(0, weight=1)
about = LabelHeader(
general,
text="Treebard GPS is free, portable, open-source, public domain "
"software written in Python, Tkinter and SQLite. Treebard's "
"purpose is to showcase functionalities that could inspire "
"developers and users of genealogy database software to expect a "
"better user experience. GPS stands for "
"'Genieware Pattern Simulation' because GPS is here to show "
"the way. Created 2015 - 2023 by Scott Robertson. Email: "
"stumpednomore-at-gmail.com. forum/blog/code repository: "
"http://treebard.proboards.com. website: http://treebard.com.",
justify='left',
wraplength=960)
general.grid(column=0, row=0, sticky='news', ipadx=6, ipady=6)
about.grid(column=0, row=0, sticky='news', padx=12, pady=12)
colorizer = Colorizer(
options_tabs.store['colors'],
self.root,
self.rc_menu,
tabbook=self.right_panel)
colorizer.grid(column=0, row=0)
self.fontpicker = FontPicker(options_tabs.store['fonts'], self.root, self)
self.fontpicker.grid(column=0, row=0)
self.date_options = DatePreferences(
options_tabs.store['dates'])
self.date_options.grid(column=0, row=0)
media_tab = MediaTab(options_tabs.store['media'], self.root, self)
media_tab.columnconfigure(0, weight=1)
media_tab.rowconfigure(0, weight=1)
media_tab.grid(column=0, row=0, sticky="news", padx=12, pady=12)
default_directories = Frame(options_tabs.store['where?'])
default_directories.columnconfigure(0, weight=1)
default_directories.rowconfigure(0, weight=1)
dirlab = LabelHeader(
default_directories,
text="Default directory for image source:\n"
"(will open in file explorer)")
self.dirent = Entry(default_directories, width=60)
imagedir = get_image_dir()
self.dirent.insert(0, imagedir)
browse = Button(default_directories, text=" . . . ", command=self.change_image_dir)
browse.focus_set()
default_directories.grid(column=0, row=0, sticky='news', ipadx=6, ipady=6)
dirlab.grid(column=0, row=0, sticky='news', padx=(12,0), pady=12)
self.dirent.grid(column=1, row=0, sticky="w", padx=(12,0), pady=12)
browse.grid(column=2, row=0, sticky="w", padx=12, pady=12)
# children of self.master i.e. root
# top_menu & ribbon_menu etc. are gridded in window_border.py
# children of self
scridth_n.grid(column=0, row=0, sticky='ew')
scridth_w.grid(column=0, row=1, sticky='ns')
self.rowconfigure(2, weight=1)
current_person_area.grid(
column=1, row=1, sticky='w', padx=(0,24), pady=(0,12))
self.main_tabs.grid(column=1, row=2, sticky='ew')
# children of main tabs
persons_tab.grid(column=0, row=0, sticky='news')
places_tab.grid(column=0, row=0, sticky='news')
sources_tab.grid(column=0, row=0, sticky='news')
self.names_tab.grid(column=0, row=0, sticky='news')
self.graphics_tab.grid(column=0, row=0, sticky='news')
prefs_tab.grid(column=0, row=0, sticky='news')
types_tab.grid(column=0, row=0, sticky='news')
# children of persons_tab
self.nukefam_table.grid(column=0, row=0, sticky="news", padx=12, pady=12)
septor.grid(column=0, row=1, sticky="we", padx=(12,0))
self.right_panel.grid(column=1, row=0, sticky='e', padx=12, pady=12)
self.findings_table.grid(
column=0, row=2, columnspan=2, padx=12, pady=12, sticky="w")
# children of self.nukefam_table gridded in families.py
persons_tab.columnconfigure(1, weight=1)
# children of places tab
self.top_pic_place_button.grid(column=0, row=0)
# children of sources tab
self.top_pic_source_button.grid(column=0, row=0)
# children of self.names_tab
lab665.grid(column=0, row=0, padx=12, pady=12)
lab472.grid(column=0, row=1, sticky="e", padx=(12,0), pady=12)
self.new_name_input.grid(column=1, row=1, sticky="w", padx=(6,0))
self.new_name_type_cbo.grid(column=2, row=1, padx=(12,0))
sortbutt.grid(column=3, row=1, padx=(12,0))
self.new_name_sort_order.grid(column=4, row=1, padx=12)
namer.grid(column=1, row=3, pady=12)
name_deleter.grid(column=2, row=3, pady=12)
# children of preferences tab
options_tabs.grid(column=0, row=0, sticky="news", padx=12, pady=12)
# children of current_person_area
self.current_person_label.pack(side="left", fill="x", expand="0", padx=(0,12))
change_current_person.pack(side="left", padx=(0,12))
self.person_entry.pack(side="left", padx=(0,12))
person_change.pack(side="left", padx=(0,12))
person_search.pack(side="right")
# children of images tab
self.right_panel.store['images'].columnconfigure(0, weight=1)
self.right_panel.store['images'].rowconfigure(0, weight=1)
self.top_pic_button.grid(column=0, row=0, sticky='news', padx=3, pady=12)
# self.top_pic_button.grid(column=0, row=0, sticky='news', padx=3, pady=3)
# children of do list tab
self.right_panel.store['do list'].columnconfigure(0, weight=1)
self.right_panel.store['do list'].rowconfigure(0, weight=1)
self.do_list.grid(column=0, row=0, sticky='news', padx=12, pady=12)
visited = (
(self.person_entry,
"New Current Person Entry",
"Name of a change-to current person will auto-fill when you "
"start typing. Start or end a new person with a plus sign."),
(person_change,
"New Current Person OK Button",
"Press OK to change current person as per input to the left."),
(person_search,
"Person Search Button",
"Any name or ID of any person in the tree can be searched, "
"or a new person can be created."),
(self.top_pic_button,
"Current Person Main Image",
"The current person's main image can be clicked to open a "
"gallery of all that person's linked images."),
(self.findings_table.finding_input,
"New Conclusion Input",
"Input for new conclusions including new event types."),
(self.findings_table.add_finding_button,
"New Conclusion Input Button",
"Press to submit new conclusion indicated to the left."),
(self.fontpicker.output_sample,
"",
"Sample text using selected output font."),
(self.fontpicker.label_sample,
"",
"Sample text using selected output font."),
(self.fontpicker.preview_button,
"Font Preview Button",
"Apply selections to sample text only."),
(self.fontpicker.font_sizer,
"Font Size Select",
"Arrow keys or mouse selects the default text size."),
(self.fontpicker.cbo.entry,
"Font Family Select",
"Select the font family for output text."),
(colorizer.current_display,
"",
"Click ID to highlight currently applied swatch."),
(colorizer.swatch_window,
"Color Scheme Samples",
"Click color scheme to try."),
(colorizer.copy_button,
"Copy Color Scheme Button",
"Press to fill inputs with selected color scheme; "
"change one or more copied color."),
(colorizer.apply_button,
"Apply Color Scheme Button",
"Apply selected color scheme to everything."),
(colorizer.add_button,
"New Color Scheme Button",
"Save new color scheme using colors filled into the "
"four inputs."),
(colorizer.bg1,
"Background Color 1 Input",
"Type hex color string or double-click to open color chooser."),
(colorizer.bg2,
"Background Color 2 Input",
"Type hex color string or double-click to open color chooser."),
(colorizer.bg3,
"Background Color 3 Input",
"Type hex color string or double-click to open color chooser."),
(colorizer.fg1,
"Font Color Input",
"Type hex color string or double-click to open color chooser."),
(self.findings_table.headers[0],
"",
"Press delete key to delete this conclusion row."),
(self.findings_table.headers[1],
"",
"Enter simple or compound date in free order with text for "
"month, e.g.: '28 f 1845 to 1846 mar 31'."),
(self.findings_table.headers[2],
"",
"Existing places will auto-fill when you start typing, "
"starting with places used most recently."),
(self.findings_table.headers[3],
"",
"Use for short notes. Press button in Notes column to input "
"longer notes."),
(self.findings_table.headers[4],
"",
"Age at time of event, in any format."),
(self.findings_table.headers[5],
"",
"Create, add and edit roles adjunct to this event."),
(self.findings_table.headers[6],
"",
"Create, add and edit notes regarding this conclusion."),
(self.findings_table.headers[7],
"",
"View and edit sources, citations and assertions linked to "
"this conclusion."),
(self.date_options.test_frm,
"",
"Use top area to test input; bottom area for display settings."),
(self.date_options.prefcombos['General'],
"General Date Format",
"Select a general type of date display."),
(self.date_options.prefcombos['Estimated'],
"Estimated Date Prefix",
"Use estimated dates for unsourced guessed dates."),
(self.date_options.prefcombos['Approximate'],
"Approximate Date Prefix",
"Use approximated dates for sourced imprecise dates."),
(self.date_options.prefcombos['Calculated'],
"Calculated Date Prefix",
"Calculated dates derived from other data such as age."),
(self.date_options.prefcombos['Before/After'],
"Before or After Date Prefix",
"When you know something happened before or after some date."),
(self.date_options.prefcombos['Epoch'],
"Epoch Date Suffix",
"'BC' and 'AD' now have more politically correct variations."),
(self.date_options.prefcombos['Julian/Gregorian'],
"Calendar Era Date Suffix",
"Mark dates 'old style' or 'new style' for events during "
"calendar transition times."),
(self.date_options.prefcombos['From...To...'],
"Format Two Dates in a Span",
"Something started at one time and lasted till another time."),
(self.date_options.prefcombos['Between...And...'],
"Format Two Dates in a Range",
"Something happened within a range between two dates."),
(self.date_options.submit,
"Submit Changes",
"The changes you selected will be saved."),
(self.date_options.revert,
"Revert to Defaults",
"Date formats will revert to defaults."),
(media_tab.add_input,
"Add Image",
"Select an image to add to the tree's files."),
(media_tab.add_caption,
"Add Caption",
"Describe/explain/annotate the image."),
(media_tab.ok_new_image,
"OK New Image",
"Add the image to the tree."),
(media_tab.image_type,
"Image Type",
"Will the image be linked to a person, a place, or a source?"),
(media_tab.link_input,
"Link or Unlink Image",
"Select an image from the tree's files to link or unlink to an element."),
(media_tab.radframe,
"Select Action",
"What will be done with the selected image?"),
# (media_tab.ask_main_image,
# "Main Image?",
# "Will this be the linked element's main image?"),
(media_tab.ok_link_pic,
"OK Link/Unlink",
"Link or unlink the image from the current person, place, or source."),
(media_tab.no_image,
"Use Default Images?",
"Use default images for elements with no linked image?"),
(media_tab.user_input,
"Add/Change User-defined Default Images",
"Empty field OK for 'no user-defined default image'."),
(media_tab.ok_default_image,
"OK Default Image",
"Add, change or delete a user-defined default image."),
(media_tab.reset,
"Reset Default Images",
"Delete all user-defined default images."))
run_statusbar_tooltips(
visited,
self.master.statusbar.status_label,
self.master.statusbar.tooltip_label)
rcm_widgets = (
self.person_entry,
person_change,
person_search,
self.top_pic_button,
self.findings_table.finding_input,
self.findings_table.headers[0],
self.findings_table.headers[1],
self.findings_table.headers[2],
self.findings_table.headers[3],
self.findings_table.headers[4],
self.findings_table.headers[5],
self.findings_table.headers[6],
self.findings_table.headers[7],
self.fontpicker.output_sample,
self.fontpicker.label_sample,
self.fontpicker.preview_button,
self.fontpicker.font_sizer,
self.fontpicker.cbo.entry,
colorizer.header,
colorizer.current_display,
colorizer.copy_button,
colorizer.apply_button,
colorizer.add_button,
colorizer.bg1,
colorizer.fg1,
self.date_options.tester_head,
self.date_options.date_test['Date Input I'],
self.date_options.date_test['Date Input II'],
self.date_options.date_test['Date Input III'],
self.date_options.pref_head,
self.date_options.prefcombos['General'].entry,
self.date_options.prefcombos['Estimated'].entry,
self.date_options.prefcombos['Approximate'].entry,
self.date_options.prefcombos['Calculated'].entry,
self.date_options.prefcombos['Before/After'].entry,
self.date_options.prefcombos['Epoch'].entry,
self.date_options.prefcombos['Julian/Gregorian'].entry,
self.date_options.prefcombos['From...To...'].entry,
self.date_options.prefcombos['Between...And...'].entry,
self.date_options.submit, self.date_options.revert,
media_tab.add_input,
media_tab.add_caption,
media_tab.image_type.entry,
media_tab.link_input,
media_tab.radframe_children[0],
media_tab.radframe_children[1],
media_tab.radframe_children[2],
media_tab.radframe_children[3],
media_tab.no_image,
media_tab.user_input,
media_tab.reset)
make_rc_menus(rcm_widgets, self.rc_menu, main_help_msg)
def make_names_table(self, parent):
if self.names_table:
self.names_table.destroy()
tree = get_current_file()[0]
conn = sqlite3.connect(global_db_path)
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (tree,))
cur.execute(select_name_all_current, (self.current_person,))
all_names = cur.fetchall()
frm = Frame(parent)
self.namevars = {}
for idx, tup in enumerate(all_names):
var = tk.IntVar()
name_id, name, name_type = tup
chk = Checkbutton(frm, variable=var)
lab = Label(
frm,
anchor="e",
text="Change name '{}' (name type '{}') to:".format(
name, name_type))
ent = Entry(frm, width=25)
cbo = Combobox(frm, self.root, values=self.name_types)
sortent = Entry(frm, width=25) # FIX TAB ORDER
self.namevars[name_id] = [var, ent, cbo, sortent, chk, lab]
bqw = Button(
frm, text="Alphabetize as:",
command=lambda ent=ent, sortent=sortent: self.make_default_sort_order(ent, sortent))
chk.grid(column=0, row=idx, padx=(12,0))
lab.grid(column=1, row=idx, sticky="ew")
ent.grid(column=2, row=idx, padx=(6,0))
cbo.grid(column=3, row=idx, padx=(12,0))
bqw.grid(column=4, row=idx, padx=(12,0))
sortent.grid(column=5, row=idx, padx=12)
for child in (ent, cbo, bqw, sortent):
child.lift()
cur.execute("DETACH tree")
cur.close()
conn.close()
frm.grid(column=0, row=2, columnspan=5)
return frm
def delete_name(self):
""" Delete selected names. Insert placeholder if the last name is deleted. """
tree = get_current_file()[0]
conn = sqlite3.connect(tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
if len(self.namevars) < 2:
# return INSTEAD OF DOING THIS, INSERT _____ _____ AS A NAME FOR THE NOW NAMELESS PERSON
cur.execute(insert_name_placeholder, (self.current_person,))
conn.commit()
for k,v in self.namevars.items():
print("line", looky(seeline()).lineno, "k, v[0].get():", k, v[0].get())
if v[0].get() == 1:
cur.execute(delete_name, (k,))
conn.commit()
self.make_names_table(self.names_tab)
cur.close()
conn.close()
def open_graphics_tool(self, tool=None, img_id=None):
def do_it(img):
if tool == "cropper":
CropTool(self.graphics_tab, self.root, self.treebard,
self.main_tabs, img)
elif tool == "texter":
BorderTextTool(self.graphics_tab, self.root, self.treebard,
self.main_tabs, img, in_path=self.edit_image)
elif tool == "resizer":
ResizeTool(self.graphics_tab, self.root, self.treebard,
self.main_tabs, img, in_path=self.edit_image)
elif tool == "captioner":
CaptionTool(self.graphics_tab, self.root, self.treebard,
self.main_tabs, img, img_id=self.current_image,
in_path=self.edit_image)
if self.edit_image:
img = Image.open(self.edit_image)
else:
tree = get_current_file()[0]
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_current_image_directory)
result = cur.fetchone()
if result:
openpic_dir = result[0]
title = "Select Image to Edit"
path = filedialog.askopenfilename(
initialdir=openpic_dir,
title=title,
defaultextension=".jpg",
filetypes=(
('', '*.jpg'),
('', '*.png'),
('', '*.gif'),
('', '*.bmp'),
('all files', '*.*')))
cur.close()
conn.close()
# Prevent Attribute Error in case user closes the file dialog with X on title bar.
if len(path) == 0:
return
img = Image.open(path)
self.edit_image = path
do_it(img)
def clear_image_frame(self):
for widg in self.to_destroy:
widg.destroy()
self.to_destroy = []
self.edit_image = None
def get_current_values(self):
self.current_person_name = EntryAutoPerson.person_autofill_values[self.current_person][0]["name"]
self.current_person_label.config(
text="Current Person (ID): {} ({})".format(
self.current_person_name, self.current_person))
self.current_person_label.bind("<Enter>", self.show_gender)
self.current_person_label.bind("<Leave>", self.show_id)
self.person_entry.values = EntryAutoPerson.person_autofill_values
self.person_entry.focus_force()
def show_gender(self, evt):
self.current_person_label.config(
text="Current Person (ID): {} ({})".format(
self.current_person_name, self.current_gender))
def get_current_person_gender(self, current_person):
tree = get_current_file()[0]
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_gender, (current_person,))
gender = cur.fetchone()[0]
if gender == "male":
gender = "M"
elif gender == "female":
gender = "F"
elif gender == "unknown":
gender = "U"
cur.close()
conn.close()
return gender
def show_id(self, evt):
self.current_person_label.config(
text="Current Person (ID): {} ({})".format(
self.current_person_name, self.current_person))
def open_person_search(self):
person_search_dialog = PersonSearch(
self,
self.root,
self.treebard,
self.person_entry,
self.findings_table,
self.show_top_pic)
def get_default_images(self, cur):
cur.execute(select_app_setting_default_images)
default_images = [i for i in cur.fetchone()]
for idx, img in enumerate(list(default_images[0:5])):
if len(img) == 0:
default_images[idx] = default_images[idx + 5]
(self.male_image, self.female_image, self.unisex_image,
self.nested_place_image, self.source_image) = default_images[0:5]
def change_person(self):
def err_done1(entry, msg):
entry.delete(0, 'end')
msg1[0].grab_release()
msg1[0].destroy()
entry.focus_set()
name_data = check_name(ent=self.person_entry)
print("line", looky(seeline()).lineno, "name_data:", name_data)
if name_data is None:
msg1 = open_message(
self,
main_msg[0],
"Person Name Unknown",
"OK")
msg1[0].grab_set()
msg1[2].config(
command=lambda entry=self.person_entry, msg=msg1: err_done1(
entry, msg))
return
elif name_data == "add_new_person":
old_current_person = self.current_person
self.current_person = open_new_person_dialog(
self, self.root, self.treebard, inwidg=self.person_entry)
if self.current_person is None:
self.current_person = old_current_person
else:
EntryAutoPerson.person_autofill_values = EntryAutoPerson.update_person_autofill_values()
self.current_person_name = get_name_from_id(self.current_person)[0]["name"]
else:
self.current_person_name = name_data[0]["name"]
self.current_person = name_data[1]
self.findings_table.current_person = self.current_person
redraw_person_tab(
main_window=self,
current_person=self.current_person,
current_name=self.current_person_name)
self.names_table = self.make_names_table(self.names_tab)
def change_image_dir(self):
imagedir = filedialog.askdirectory()
self.dirent.delete(0, "end")
self.dirent.insert(0, imagedir)
tree = get_current_file()[0]
conn = sqlite3.connect(tree)
conn.execute('PRAGMA foreign_keys = 1')
cur = conn.cursor()
cur.execute(update_current_image_directory, (imagedir,))
conn.commit()
cur.close()
conn.close()
def open_person_gallery(self):
person_gallery_dlg = Toplevel(self.root)
gallery_canvas = Border(person_gallery_dlg, self.root)
person_gallery = Gallery(
gallery_canvas,
self.main_tabs,
self.main_tabs.store['graphics'],
self.root,
self.treebard,
self.SCREEN_SIZE,
dialog=person_gallery_dlg,
current_person=self.current_person,
current_person_name=self.current_person_name)
def open_place_gallery(self):
place_gallery_dlg = Toplevel(self.root)
place_gallery_canvas = Border(place_gallery_dlg, self.root)
place_gallery = Gallery(
place_gallery_canvas,
self.main_tabs,
self.main_tabs.store['graphics'],
self.root,
self.treebard,
self.SCREEN_SIZE,
dialog=place_gallery_dlg,
current_nested_place=self.current_nested_place)
def open_source_gallery(self):
source_gallery_dlg = Toplevel(self.root)
source_gallery_canvas = Border(source_gallery_dlg, self.root)
source_gallery = Gallery(
source_gallery_canvas,
self.main_tabs,
self.main_tabs.store['graphics'],
self.root,
self.treebard,
self.SCREEN_SIZE,
dialog=source_gallery_dlg,
current_source=self.current_source)
def resize_top_pic(self, top, new_stg):
new_width = width = top.width
new_height = height = top.height
aspect_ratio = width / height
if height != 294:
new_height = 294
new_width = int(294 * aspect_ratio)
top = top.resize((new_width, new_height))
app_path, bare = new_stg.split("images/")
bare = bare.rsplit(".", 1)[0]
# bare = bare.split(".")[0]
thumb_path = '{}images/thumbnails/{}_resized_top_pic.png'.format(app_path, bare)
# overwrites file by same name if it exists
top.save(thumb_path)
return top
def show_top_pic(self, tree, current_dir, current_person):
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_links_links_main_image_person, (current_person,))
top_pic = cur.fetchone()
if top_pic:
img_stg = ''.join(top_pic)
new_stg = '{}treebard_gps/data/{}/images/{}'.format(
current_drive, current_dir, img_stg)
self.right_panel.active = self.right_panel.tabdict["images"][1]
self.right_panel.make_active()
elif self.use_default_images:
cur.execute(select_person_gender_by_id, (current_person,))
gender = cur.fetchone()[0]
if gender == "male":
img_stg = self.male_image
elif gender == "female":
img_stg = self.female_image
elif gender == "unknown":
img_stg = self.unisex_image
new_stg = '{}treebard_gps/data/settings/images/{}'.format(
current_drive, img_stg)
elif self.use_default_images is False:
self.right_panel.active = self.right_panel.tabdict["do list"][1]
self.right_panel.make_active()
cur.close()
conn.close()
self.top_pic_button.config(image="")
self.top_pic_button.image = ""
return
top = Image.open(new_stg)
top = self.resize_top_pic(top, new_stg)
img1 = ImageTk.PhotoImage(top, master=self.master)
self.top_pic_button.config(image=img1)
self.top_pic_button.image = img1
self.tabbook_x = top.width
self.tabbook_y = top.height
cur.close()
conn.close()
def show_top_pic_nested_place(self, tree, current_dir, current_nested_place):
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_links_links_main_image_nested_place, (current_nested_place,))
top_pic_place = cur.fetchone()
if top_pic_place:
img_stg = ''.join(top_pic_place)
new_stg = '{}treebard_gps/data/{}/images/{}'.format(
current_drive, current_dir, img_stg)
elif self.use_default_images:
img_stg = self.nested_place_image
new_stg = '{}treebard_gps/data/settings/images/{}'.format(
current_drive, img_stg)
elif self.use_default_images is False:
cur.close()
conn.close()
self.top_pic_place_button.config(image="")
self.top_pic_place_button.image = ""
return
top = Image.open(new_stg)
top = self.resize_top_pic(top, new_stg)
img1 = ImageTk.PhotoImage(top, master=self.master)
self.top_pic_place_button.config(image=img1)
self.top_pic_place_button.image = img1
cur.close()
conn.close()
def show_top_pic_source(self, tree, current_dir, current_source):
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_links_links_main_image_source, (current_source,))
top_pic_source = cur.fetchone()
if top_pic_source:
img_stg = ''.join(top_pic_source)
new_stg = '{}treebard_gps/data/{}/images/{}'.format(
current_drive, current_dir, img_stg)
elif self.use_default_images:
img_stg = self.source_image
new_stg = '{}treebard_gps/data/settings/images/{}'.format(
current_drive, img_stg)
elif self.use_default_images is False:
cur.close()
conn.close()
self.top_pic_source_button.config(image="")
self.top_pic_source_button.image = ""
return
top = Image.open(new_stg)
top = self.resize_top_pic(top, new_stg)
img1 = ImageTk.PhotoImage(top, master=self.master)
self.top_pic_source_button.config(image=img1)
self.top_pic_source_button.image = img1
cur.close()
conn.close()
# def get_current_person_names(self, conn, cur):
# cur.execute(select_name_all_current, (self.current_person,))
# all_names = cur.fetchall()
# return all_names
def update_name(self):
tree, current_dir = get_current_file()
conn = sqlite3.connect(global_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute('ATTACH ? as tree', (tree,))
for k,v in self.namevars.items():
if v[0].get() == 1:
ent, cbo, sortent, chk, lab = v[1:]
for widg in (ent, cbo.entry, sortent):
if len(widg.get()) == 0:
return
name = ent.get()
name_id = k
name_type = cbo.entry.get()
sorter = sortent.get()
cur.execute(select_name_type_id_by_string, (name_type,))
name_type_id = cur.fetchone()[0]
cur.execute(update_name_type_sorter, (name, name_type_id, sorter, name_id))
conn.commit()
ent.delete(0, "end")
cbo.entry.delete(0, "end")
sortent.delete(0, "end")
chk.deselect()
lab.config(text="Change name '{}' (name type '{}') to:".format(
name, name_type))
new_name = self.new_name_input.get()
name_type = self.new_name_type_cbo.entry.get()
EntryAutoPerson.person_autofill_values = EntryAutoPerson.update_person_autofill_values()
self.current_person_name = get_name_from_id(self.current_person)[0]["name"]
redraw_current_person_area(self.current_person, self, self.current_person_name, tree, current_dir)
if len(new_name) == 0 or len(name_type) == 0:
cur.execute('DETACH tree')
cur.close()
conn.close()
return
cur.execute(select_name_type_id_by_string, (name_type,))
name_type_id = cur.fetchone()[0]
cur.execute(
insert_name_and_type, (
new_name, name_type_id,
self.current_person,
self.new_name_sort_order.get()))
conn.commit()
self.new_name_input.delete(0, 'end')
self.new_name_type_cbo.entry.delete(0, 'end')
self.new_name_sort_order.delete(0, "end")
cur.execute('DETACH tree')
cur.close()
conn.close()
self.make_names_table(self.names_tab)
def make_default_sort_order(self, ent, sortent):
new_name = ent.get().split()
last = new_name.pop()
last = "{},".format(last)
new_name.insert(0, last)
new_name = " ".join(new_name)
sortent.delete(0, "end")
sortent.insert(0, new_name)
def save_new_event_type(self):
couple = self.couple_rad.get()
marital = self.marital_rad.get()
after_death = self.after_death_rad.get()
new_event_type = self.new_event_type_input.get()
conn = sqlite3.connect(global_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(insert_event_type, (new_event_type, couple, marital, after_death))
conn.commit()
cur.close()
conn.close()