main.py
Nov 25, 2022 3:51:09 GMT -8
Post by Uncle Buddy on Nov 25, 2022 3:51:09 GMT -8
<drive>:\treebard\main.py Last Changed 2024-07-25
# main.py
import tkinter as tk
from tkinter import filedialog
import sqlite3
from PIL import Image, ImageTk
from base import Query, app_path, tree_path, image_path, get_image_dir
from redraw import Redraw, get_current_person_gender, get_name_from_id
from widgets import (
configall, make_formats_dict, ButtonBigPic, LabelH3, run_statusbar_tooltips,
RightClickMenu, make_rc_menus, TabBook, Combobox,
get_color_scheme_id)
from do_list import DoList
from dates import DatePreferences
from media import MediaTab
from element_types import TypesTab
from families import NuclearFamiliesTable
from events_table import EventsTable
from places import ValidatePlace, PlacesTab, EntryAutoPlace
from sources import SourcesTab
from gallery import Gallery
from user_formats import Colorizer, FontPicker
from messages_context_help import main_help_msg
from search import PersonSearch
from persons import open_new_person_dialog, EntryAutoPerson, NamesTab
from graphics import CropTool, BorderTextTool, ResizeTool, CaptionTool
from unigeds_queries import (
update_person_gender, select_person_gender_by_id,
delete_notes_links_nesting, delete_nested_place,
update_event_nested_place_unknown, select_nested_place_id_not_this,
insert_event_type, insert_kin_type, insert_media_type,
insert_name_type, insert_place_type, insert_role_type,
insert_source_type, insert_transcription_type)
import dev_tools as dt
from dev_tools import look, seeline
# Other TabBook lists are in assertions.py and the root module.
MAIN_TABS = (
("person", "P"), ("names", "N"), ("places", "L"), ("sources", "S"),
("media", "M"), ("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"),
("where?", "W"))
NUKEFAM_HEADS = ("NAME OF CHILD", "GENDER", "DATE OF BIRTH", "DATE OF DEATH")
ABOUT_TREEBARD = ("Treebard is free, portable, open-source, public domain "
"genealogy 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. Treebard is a working model anyone can use to "
"write their own genealogy software. It can be translated from Python to "
"the programming language of your choice.")
ABOUT_THE_AUTHOR = ("Created 2015-2024 by Scott Robertson\nEmail: "
"stumpednomore-at-gmail.com\nforum/blog/code repository: "
"https://treebard.proboards.com\nwebsite: https://treebard.com")
def get_current_elements(tree_id):
conn = sqlite3.connect(f"{tree_path}/{tree_id}/{tree_id}.tbd")
cur = conn.cursor()
cur.execute(
''' SELECT person_id, nested_place_id, source_id
FROM current_tbd WHERE current_tbd_id = 1
''')
current_elements = cur.fetchone()
cur.close()
conn.close()
return current_elements
class Main(tk.Frame):
def __init__(self, master, formats, treebard, tree, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.canvas = master
self.formats = formats
self.treebard = treebard
self.tree = tree
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
(self.tree.persid, self.tree.placid,
self.tree.sorcid) = get_current_elements(self.tree.tree_id)
self.current_person_name = ""
self.current_gender = get_current_person_gender(cur, self.tree.persid)
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.tree.place_data = self.tree.get_place_values()
self.tree.create_source_lists(cur=cur)
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.tree, self.treebard)
query = Query()
self.use_default_images = self.get_image_preferences(query)
# # *************************UNCOMMENT DO NOT DELETE THIS SECTION
# # Cover the flashing of white widgets being created then colorized.
# # To see the difference, comment this section.
# # See also `change_current_person` in main.py, families.py,
# # and search.py.
screen = tk.Toplevel(treebard)
screen.attributes('-fullscreen', True)
treebard.update_idletasks()
screen.config(bg=self.formats["bg"])
screen.title("Your family tree is loading")
treebard.after(500, lambda screen=screen: self.show_tree(screen))
screen.lift()
# # *************************
self.make_widgets()
self.make_inputs(cur, query)
self.grid_widgets()
self.get_current_values()
self.nukefam_table.make_nukefam_inputs()
self.make_help_tools(tree)
cur.close()
conn.close()
def show_tree(self, screen):
screen.destroy()
def get_image_preferences(self, query):
use_default_images = query.select("use_default_images")
if use_default_images == '0':
use_default_images = False
elif use_default_images == '1':
use_default_images = True
return use_default_images
def make_widgets(self):
self.current_person_area = tk.Frame(self)
self.main_tabs = TabBook(
self, self.formats, dialog=self.tree, tabs=MAIN_TABS,
miny=0.5, minx=0.33, takefocus=0)
self.persons_tab = tk.Frame(self.main_tabs.store["person"])
self.places_tab = tk.Frame(
self.main_tabs.store["places"], name="placetab")
self.sources_tab = tk.Frame(
self.main_tabs.store["sources"], name="sourcetab")
self.names_tab = tk.Frame(self.main_tabs.store["names"])
self.graphics_tab = tk.Frame(self.main_tabs.store["graphics"])
self.prefs_tab = tk.Frame(self.main_tabs.store["preferences"])
self.types_tab = tk.Frame(self.main_tabs.store["types"])
def make_inputs(self, cur, query):
self.make_current_person_area()
self.make_right_panel(cur, query)
self.make_person_tables()
self.make_places_tab(cur)
self.make_sources_tab(cur)
self.make_names_tab()
self.make_graphics_tab()
self.make_preferences_tab()
self.make_types_tab(cur)
self.make_general_tab()
self.make_colors_tab()
self.make_fonts_tab()
self.make_media_tab()
self.make_where_tab()
def make_current_person_area(self):
self.current_person_label = LabelH3(
self.current_person_area, self.formats)
self.change_person_label = LabelH3(
self.current_person_area, self.formats,
text="Change current person to:")
self.current_person_input = EntryAutoPerson(
self.current_person_area, self.tree, autofill=True)
self.tree.person_autofills.append(self.current_person_input)
self.person_changer = tk.Button(
self.current_person_area, text="OK", width=6,
command=self.change_current_person)
self.person_search = tk.Button(
self.current_person_area,
text="WHO?", width=6,
command=self.open_person_search)
def make_right_panel(self, cur, query):
self.get_default_images(query)
minx = self.tabbook_x/self.SCREEN_SIZE[0]
miny = self.tabbook_y/self.SCREEN_SIZE[1]
self.top_area = tk.Frame(self.persons_tab)
self.right_panel = TabBook(
self.top_area, self.formats, dialog=self.tree,
tabs=RIGHT_PANEL_TABS, side="se", name="rightpanel",
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"], self.formats, self.right_panel)
if len(self.tree.tree_id) != 0:
self.show_top_pic(cur)
self.update_idletasks()
def make_person_tables(self):
self.events_table = EventsTable(
self.persons_tab, self.formats, self.tree, self.treebard, self)
self.nukefam_table = NuclearFamiliesTable(
self.top_area, self.formats, self.tree, self.treebard,
self.events_table, self.right_panel, main=self)
self.septor = tk.Frame(self.top_area, height=2)
def make_places_tab(self, cur):
self.places_tab_content = PlacesTab(
self.places_tab, self.tree, self.treebard, self, self.formats, cur)
def make_sources_tab(self, cur):
self.sources_tab_content = SourcesTab(
self.sources_tab, self.tree, self.treebard, self, self.formats, cur)
def make_names_tab(self):
self.names_tab_content = NamesTab(
self.names_tab, self.treebard, self.formats, self, self.tree)
self.names_tab_content.grid(column=0, row=0)
def make_media_tab(self):
self.media_tab = MediaTab(
self.main_tabs.store['media'], self.formats, self.treebard,
self.tree, self)
def make_types_tab(self, cur):
self.types_tab_content = TypesTab(
self.types_tab, self.tree, self.treebard, self, self.formats, cur)
def make_graphics_tab(self):
self.graphics_buttons = tk.Frame(self.graphics_tab)
self.cropper = tk.Button(
self.graphics_buttons, text="CROP", width=18,
command=lambda tool="cropper": self.open_graphics_tool(tool))
self.texter = tk.Button(
self.graphics_buttons, text="ADD BORDER & TEXT", width=18,
command=lambda tool="texter": self.open_graphics_tool(tool))
self.resizer = tk.Button(
self.graphics_buttons, text="COPY/RESIZE", width=18,
command=lambda tool="resizer": self.open_graphics_tool(tool))
self.captioner = tk.Button(
self.graphics_buttons, text="EDIT CAPTION", width=18,
command=lambda tool="captioner": self.open_graphics_tool(tool))
self.clearbutt = tk.Button(
self.graphics_buttons, text="CLEAR THIS TAB",
command=self.clear_image_frame, width=18)
def make_preferences_tab(self):
self.options_tabs = TabBook(
self.prefs_tab, self.formats, dialog=self.tree,
tabs=PREFS_TABS, side="se",
miny=0.5, minx=0.66)
def make_general_tab(self):
self.general = tk.Frame(self.options_tabs.store['general'])
self.general.columnconfigure(0, weight=1)
self.general.rowconfigure(0, weight=1)
self.splash = tk.Label(self.general)
splash_file = f"{image_path}/splash.png"
pil_img = Image.open(splash_file)
width = pil_img.width
tk_img = ImageTk.PhotoImage(pil_img, master=self.splash)
self.splash.config(image=tk_img)
self.splash.image = tk_img
self.quotes = tk.Label(self.general, bd=1, relief='raised',
text=(
f"I would never stoop so low as to be fashionable, that's the "
f"easiest thing in the world to do.\n --Dolly Parton\n\nI'm "
f"my own grampa.\n --Moe Jaffe, Dwight Latham, 1947\n\nTime "
f"has existed since before time began.\n --Philomena Cunk"),
justify="left", wraplength=480)
self.about = tk.Label(
self.general,
text=f"{ABOUT_TREEBARD}\n\n{ABOUT_THE_AUTHOR}",
justify='left', wraplength=1200)
def make_colors_tab(self):
self.colorizer = Colorizer(
self.options_tabs.store['colors'], self.formats, self.tree,
self.treebard, self, tabbook=self.right_panel)
def make_fonts_tab(self):
self.fontpicker = FontPicker(
self.options_tabs.store['fonts'], self.formats, self.treebard,
self.tree, self)
self.date_options = DatePreferences(
self.options_tabs.store['dates'], self.formats, self.tree)
def make_where_tab(self):
backups_plan = ("Treebard is a portable application. The backups/trash "
"folder takes the place of Windows Recycle Bin in case you want to "
"restore a tree after it has been deleted. This folder is located at "
"`{current_drive}/users/{windows_user}/documents/treebard/backups/trash`.")
self.default_directories = tk.Frame(
self.options_tabs.store['where?'])
self.dirlab = tk.Label(
self.default_directories,
text="Default tree_id for image source:\n"
"(will open in file explorer)", bd=1, relief="raised")
self.dirent = tk.Entry(self.default_directories, width=60)
imagedir = get_image_dir()
self.dirent.insert(0, imagedir)
self.browse = tk.Button(
self.default_directories,
text=" ... ", command=self.change_image_dir)
self.browse.focus_set()
self.backups_lab = tk.Label(
self.default_directories, text=backups_plan, bd=1, relief="raised",
wraplength=400, justify="left")
def grid_widgets(self):
# children of self
self.rowconfigure(1, weight=1)
self.current_person_area.grid(
column=0, row=0, sticky='ew', pady=(0,12), columnspan=4)
self.main_tabs.grid(column=0, row=1, sticky='ew', padx=(0,12))
# children of main tabs
self.persons_tab.grid(column=0, row=0, sticky='news')
self.places_tab.grid(column=0, row=0, sticky='news')
self.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')
self.prefs_tab.grid(column=0, row=0, sticky='news')
self.types_tab.grid(column=0, row=0, sticky='news')
# children of self.persons_tab
self.top_area.grid(
column=0, row=0, pady=12, sticky="ew")#sticky="news", padx=12,
self.events_table.grid(
column=0, row=1, padx=12, pady=12, sticky="w")
# children of self.top_area
self.top_area.columnconfigure(1, weight=1)
self.nukefam_table.grid(column=0, row=0)
self.right_panel.grid(column=1, row=0, sticky='e', padx=12, pady=12)
self.septor.grid(column=0, row=1, sticky="we", padx=(12,0), columnspan=2)
# children of tabs (content frames inside tab frames)
self.places_tab_content.grid(
column=0, row=0, sticky="news", padx=12, pady=12, columnspan=3)
self.sources_tab_content.grid(
column=0, row=0, sticky="news", padx=12, pady=12, columnspan=3)
self.types_tab_content.grid(
column=0, row=0, sticky="news", padx=12, pady=12, columnspan=3)
self.media_tab.columnconfigure(0, weight=1)
self.media_tab.rowconfigure(0, weight=1)
self.media_tab.grid(
column=0, row=0, sticky="news", padx=12, pady=12)
# children of self.graphics_tab
self.graphics_buttons.grid(
column=0, row=0, padx=12, pady=12, sticky="ew")
# children of self.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))
self.clearbutt.grid(column=4, row=0, padx=(0,6))
# self.grid_types_tab()
self.grid_preferences_tabbook()
def grid_preferences_tabbook(self):
# children of preferences tab
self.options_tabs.grid(column=0, row=0, sticky="news", padx=12, pady=12)
# children of preferences > general tab
self.general.grid(column=0, row=0, sticky='news', padx=18, pady=13)
# children of self.general
self.splash.grid(column=0, row=0, sticky="w")
self.quotes.grid(
column=1, row=0, sticky="ew", ipadx=12, ipady=12, padx=18)
self.about.grid(
column=0, row=1, sticky='news', columnspan=2, pady=(18,0), padx=18)
# children of preferences > colors tab
self.colorizer.grid(column=0, row=0)
# children of preferences > fonts tab
self.fontpicker.grid(column=0, row=0)
# children of preferences > dates tab
self.date_options.grid(column=0, row=0)
# children of preferences > where tab
self.default_directories.columnconfigure(0, weight=1)
self.default_directories.rowconfigure(0, weight=1)
self.default_directories.grid(
column=0, row=0, sticky='news', ipadx=6, ipady=6)
# children of self.current_person_area
self.current_person_area.columnconfigure(1, weight=1)
self.current_person_label.grid(
column=0, row=0, padx=(0,12), sticky="w", columnspan=4)
self.change_person_label.grid(column=0, row=1, sticky="w", padx=(0,12))
self.current_person_input.grid(column=1, row=1, sticky="ew", padx=(0,12))
self.person_changer.grid(column=2, row=1, sticky="e", padx=(0,12))
self.person_search.grid(column=3, row=1, sticky="e", padx=(0,12))
# 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)
# 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")
# children of self.default_directories
self.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)
self.browse.grid(column=2, row=0, sticky="w", padx=12, pady=12)
self.backups_lab.grid(
column=0, row=1, sticky="ew", padx=12, pady=12, ipadx=6, ipady=6)
def make_help_tools(self, tree):
self.make_statusbar_tooltips()
self.make_right_click_menu()
def make_statusbar_tooltips(self):
visited = (
(self.current_person_input,
"New Current Person Entry",
"Name of a change-to-current person will auto-fill when you "
"start typing."),
(self.person_changer,
"New Current Person OK Button",
"Press OK to change current person as per input to the left."),
(self.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.do_list.do_list_frame,
"",
"The current family tree's to do list."),
(self.do_list.item_input,
"To Do List Item Input",
"Input new items for the current tree's to do list."),
(self.nukefam_table.parents_table,
"",
"Add parents here or add adoptive/foster parents or guardians by "
"creating an adoption/fosterage/guardianship event."),
(self.nukefam_table.progeny_table,
"",
"Add or edit partners and children of the current person and "
"the selected partner."),
(self.nukefam_table.pa_input,
"Biological Father Input",
"Add a new or existing person as the biological father of the "
"current person."),
(self.nukefam_table.ma_input,
"Biological Mother Input",
"Add a new or existing person as the biological mother of the "
"current person."),
(self.nukefam_table.new_partner_input,
"New Partner Input",
"Add a new or existing person as a partner of the current person."),
(self.nukefam_table.new_child_input,
"New Child Input",
"Add a new or existing person as a child of the current person "
"and the selected partner."),
(self.nukefam_table.partner_linker,
"Link Partner to Event Button",
"To link the selected partner to an existing couple event, click "
"the big button to highlight eligible events, then click the "
"event, then click OK."),
(self.events_table.new_event_input,
"New Event or Attribute Input",
"Input for new conclusions including new event types."),
(self.events_table.add_event_button,
"New Event Input or Attribute Button",
"Press to submit new event or attribute 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,
"Font Family Select",
"Select the font family for output text."),
(self.colorizer.current_display,
"",
"Click ID to highlight currently applied swatch."),
(self.colorizer.swatch_window,
"Color Scheme Samples",
"To try a color scheme, click a swatch or navigate with the arrow keys."),
(self.colorizer.copy_button,
"Copy Color Scheme Button",
"Press to fill inputs with selected color scheme; "
"change one or more copied color."),
(self.colorizer.apply_button,
"Apply Color Scheme Button",
"Apply selected color scheme to everything."),
(self.colorizer.add_button,
"New Color Scheme Button",
"Save new color scheme using colors filled into the "
"four inputs."),
(self.colorizer.bg1,
"Background Color 1 Input",
"Type hex color string or double-click to open color chooser."),
(self.colorizer.bg2,
"Background Color 2 Input",
"Type hex color string or double-click to open color chooser."),
(self.colorizer.bg3,
"Background Color 3 Input",
"Type hex color string or double-click to open color chooser."),
(self.colorizer.fg1,
"Font Color Input",
"Type hex color string or double-click to open color chooser."),
(self.events_table.headers[0],
"",
"Press delete key to delete this conclusion row."),
(self.events_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.events_table.headers[2],
"",
"Existing places will auto-fill when you start typing, "
"starting with places used most recently."),
(self.events_table.headers[3],
"",
"Use for short notes. Press button in Notes column to input "
"longer notes."),
(self.events_table.headers[4],
"",
"Age at time of event, in any format."),
(self.events_table.headers[5],
"",
"Create, add and edit roles adjunct to this event."),
(self.events_table.headers[6],
"",
"Create, add and edit notes regarding this conclusion."),
(self.events_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."),
(self.media_tab.add_input,
"Add Image",
"Select an image to add to the tree's files."),
(self.media_tab.add_caption,
"Add Caption",
"Describe/explain/annotate the image."),
(self.media_tab.ok_new_image,
"OK New Image",
"Add the image to the tree."),
(self.media_tab.image_type,
"Image Type",
"Will the image be linked to a person, a place, or a source?"),
(self.media_tab.link_input,
"Link or Unlink Image",
"Select an image from the tree's files to link or unlink to an "
"element."),
(self.media_tab.radframe,
"Select Action",
"What will be done with the selected image?"),
(self.media_tab.ok_link_pic,
"OK Link/Unlink",
"Link or unlink the image from the current person, place, or "
"source."),
(self.media_tab.no_image,
"Use Default Images?",
"Use default images for elements with no linked image?"),
(self.media_tab.user_input,
"Add/Change User-defined Default Images",
"Empty field OK for 'no user-defined default image'."),
(self.media_tab.ok_default_image,
"OK Default Image",
"Add, change or delete a user-defined default image."),
(self.media_tab.reset,
"Reset Default Images",
"Delete all user-defined default images."))
run_statusbar_tooltips(
visited,
self.tree.statusbar.status_label,
self.tree.statusbar.tooltip_label,
self.tree)
def make_right_click_menu(self):
rcm_widgets = (
self.current_person_input,
self.person_changer,
self.person_search,
self.top_pic_button,
self.do_list.do_list_frame,
self.do_list.item_input,
self.events_table.new_event_input,
self.nukefam_table.parents_table,
self.nukefam_table.progeny_table,
self.nukefam_table.pa_input,
self.nukefam_table.ma_input,
self.nukefam_table.new_partner_input,
self.nukefam_table.new_child_input,
self.nukefam_table.partner_linker,
self.events_table.headers[0],
self.events_table.headers[1],
self.events_table.headers[2],
self.events_table.headers[3],
self.events_table.headers[4],
self.events_table.headers[5],
self.events_table.headers[6],
self.events_table.headers[7],
self.fontpicker.output_sample,
self.fontpicker.label_sample,
self.fontpicker.preview_button,
self.fontpicker.font_sizer,
self.fontpicker.cbo,
self.colorizer.header,
self.colorizer.current_display,
self.colorizer.copy_button,
self.colorizer.apply_button,
self.colorizer.add_button,
self.colorizer.bg1,
self.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'],
self.date_options.prefcombos['Estimated'],
self.date_options.prefcombos['Approximate'],
self.date_options.prefcombos['Calculated'],
self.date_options.prefcombos['Before/After'],
self.date_options.prefcombos['Epoch'],
self.date_options.prefcombos['Julian/Gregorian'],
self.date_options.prefcombos['From...To...'],
self.date_options.prefcombos['Between...And...'],
self.date_options.submit, self.date_options.revert,
self.media_tab.add_input,
self.media_tab.add_caption,
self.media_tab.image_type,
self.media_tab.link_input,
self.media_tab.radframe_children[0],
self.media_tab.radframe_children[1],
self.media_tab.radframe_children[2],
self.media_tab.radframe_children[3],
self.media_tab.no_image,
self.media_tab.user_input,
self.media_tab.reset)
make_rc_menus(rcm_widgets, self.rc_menu, main_help_msg)
def open_graphics_tool(self, tool=None, img_id=None):
def do_it(img):
if tool == "cropper":
dlg = CropTool(self.tree, self, img)
elif tool == "texter":
dlg = BorderTextTool(
self.tree, self, img, in_path=self.edit_image)
elif tool == "resizer":
dlg = ResizeTool(
self.tree, self, img, in_path=self.edit_image)
elif tool == "captioner":
dlg = CaptionTool(
self.tree, self, img, img_id=self.current_image,
in_path=self.edit_image)
if self.edit_image:
img = Image.open(self.edit_image)
else:
query = Query()
openpic_dir = query.select("look_first_dir_for_images")
path = filedialog.askopenfilename(
initialdir=openpic_dir,
title="Select Image to Edit",
defaultextension=".jpg",
filetypes=(
('', '*.jpg'),
('', '*.png'),
('', '*.gif'),
('', '*.bmp'),
('all files', '*.*')))
# Prevent Attribute Error in case user closes the file dialog with
# the X button on the 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):
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
self.current_person_name = get_name_from_id(self.tree.persid, cur)
self.current_person_label.config(
text=f"Current Person (ID): {self.current_person_name} ({self.tree.persid})")
self.current_person_label.bind("<Enter>", self.show_gender)
self.current_person_label.bind("<Leave>", self.show_id)
self.current_person_label.bind("<Double-Button-1>", self.change_gender)
self.current_person_input.values = self.tree.person_autofill_values
self.current_person_input.focus_force()
cur.close()
conn.close()
def change_gender(self, evt):
def ok_bend():
gender = bendervar.get()
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(update_person_gender, (gender, self.tree.persid))
conn.commit()
cur.close()
conn.close()
rdw = Redraw(tree=self.tree, main=self)
rdw.redraw_current_person_area()
gender_changer.destroy()
def cancel_bend():
gender_changer.destroy()
bendervar = tk.StringVar(None, "unknown")
gender_changer = tk.Toplevel(self.tree)
gender_changer.title("Edit Gender")
frm = tk.Frame(gender_changer)
for idx,text in enumerate(("male", "female", "unknown", "other")):
rad = tk.Radiobutton(
frm, text=text, variable=bendervar, value=text, anchor="w")
rad.grid(column=0, row=idx, sticky="w", padx=24)
if idx == 0:
rad.grid_configure(pady=(12,0))
buttons = tk.Frame(frm)
ok_bender = tk.Button(buttons, text="OK", width=7, command=ok_bend)
cancel_bender = tk.Button(
buttons, text="CANCEL", width=7, command=cancel_bend)
frm.grid(column=0, row=0, sticky="news")
buttons.grid(column=0, row=idx+1, sticky="e", padx=12, pady=12)
ok_bender.grid(column=0, row=0, sticky="e")
cancel_bender.grid(column=1, row=0, sticky="e", padx=(6,0))
configall(gender_changer, self.formats)
def show_gender(self, evt):
if self.current_gender in ("U", "O"):
return
chars = len(str(self.tree.persid)) - 1
chars1 = (chars // 2) * " "
chars2 = (chars - len(chars1)) * " "
gender = f"{chars1}{self.current_gender}{chars2}"
self.current_person_label.config(
text=f"Current Person (ID): {self.current_person_name} ({gender})")
def show_id(self, evt):
self.current_person_label.config(
text="Current Person (ID): {} ({})".format(
self.current_person_name, self.tree.persid))
def open_person_search(self):
person_search_dialog = PersonSearch(
self.tree, self.treebard, self, self.current_person_input,
self.events_table, self.formats)
def get_default_images(self, query):
default_images = []
for field in (
'image_male', 'image_female', 'image_unisex',
'image_nested_place', 'image_source', 'default_image_male',
'default_image_female', 'default_image_unisex',
'default_image_nested_place', 'default_image_source'):
result = query.select(field)
default_images.append(result)
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_current_person(self, key="changer"):
color_scheme_id = get_color_scheme_id(self.tree.tree_id)
self.formats = make_formats_dict(
self.tree.tree_id, color_scheme_id=color_scheme_id)
width, height = self.tree.geometry().split("+", 1)[0].split("x")
screen = tk.Frame(
self.tree, bg=self.formats["bg"], width=width, height=height)
screen.grid()
got = self.current_person_input.get()
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
if key == "changer":
self.tree.forward_stack = []
# Append old value of persid to stack before updating
# persid to its new value.
self.tree.backward_stack.append(self.tree.persid)
name_data = self.current_person_input.check_name(cur)
if name_data is None:
redo = f"{got}+"
name_data = self.current_person_input.check_name(
cur, redo=redo, screen=screen)
if name_data == "add_new_person":
old_current_person = self.tree.persid
self.tree.persid = open_new_person_dialog(
self.tree, self.treebard, inwidg=self.current_person_input,
got=got)
if self.tree.persid is None:
self.tree.persid = old_current_person
else:
self.tree.person_autofill_values = self.tree.update_person_autofill_values()
self.current_person_name = get_name_from_id(
self.tree.persid, cur)
elif name_data is None:
# Avoid error if user inputs #2546, but #2546 doesn't exist.
return
else:
if got.startswith("#"):
self.tree.persid = int(got.replace("#", ""))
self.current_person_name = name_data
else:
self.current_person_name = name_data[0]["name"]
self.tree.persid = name_data[1]
elif key == "backward":
if len(self.tree.backward_stack) == 0:
screen.destroy()
return
else:
self.tree.forward_stack.append(self.tree.persid)
self.tree.persid = self.tree.backward_stack.pop()
elif key == "forward":
if len(self.tree.forward_stack) == 0:
screen.destroy()
return
else:
self.tree.backward_stack.append(self.tree.persid)
self.tree.persid = self.tree.forward_stack.pop()
elif key == "tree":
self.tree.forward_stack = []
self.tree.backward_stack.append(self.tree.persid)
self.tree.persid = 1
# This is the essence of changing the current person:
rdw = Redraw(main=self, tree=self.tree)
rdw.redraw_person_tab()
self.names_tab.name_change_table = rdw.redraw_names_tab()
TabBook.resize_scrolled_dialog_with_tabbook(self.tree, self.canvas, self)
cur.close()
conn.close()
# *************************************
screen.destroy()
def change_image_dir(self):
imagedir = filedialog.askdirectory()
self.dirent.delete(0, "end")
self.dirent.insert(0, imagedir)
query = Query()
query.update("look_first_dir_for_images", imagedir)
def open_person_gallery(self):
person_gallery = Gallery(
self.tree, self, self.main_tabs, self.main_tabs.store['graphics'],
self.SCREEN_SIZE, tab=self.persons_tab, mode="person",
current_person_name=self.current_person_name)
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]
thumb_path = f'{app_path}images/thumbnails/{bare}_resized_top_pic.png'
# overwrites file by same name if it exists
top.save(thumb_path)
return top
def show_top_pic(self, cur):
cur.execute(
''' SELECT file_name
FROM media
JOIN media_links
ON media.media_id = media_links.media_id
JOIN main_tbd
ON main_tbd.media_links_id = media_links.media_links_id
JOIN person
ON person.person_id = media_links.person_id
WHERE media_links.person_id = ?
''',
(self.tree.persid,))
top_pic = cur.fetchone()
if top_pic:
img_stg = ''.join(top_pic)
new_stg = f"{tree_path}/{self.tree.tree_id}/images/{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, (self.tree.persid,))
gender = cur.fetchone()[0]
if gender == "male":
img_stg = self.male_image
elif gender == "female":
img_stg = self.female_image
elif gender in ("unknown", "other"):
img_stg = self.unisex_image
new_stg = f"{tree_path}/settings/images/{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()
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.canvas)
self.top_pic_button.config(image=img1)
self.top_pic_button.image = img1
self.tabbook_x = top.width
self.tabbook_y = top.height
self.update_idletasks()