media.py
Dec 25, 2023 23:58:18 GMT -8
Post by Uncle Buddy on Dec 25, 2023 23:58:18 GMT -8
<drive>:\treebard\media.py Last Changed 2024-07-25
# media.py
import tkinter as tk
import sqlite3
from os import remove, path
from PIL import Image, ImageTk
from base import Query, tree_path, image_path
from redraw import Redraw
from widgets import open_message, Combobox
from messages import images_msg
from unigeds_queries import (
select_media_id,
select_media_links_count_image_person, insert_media_links_image_source,
select_media_links_count_image_nested_place, insert_media_links_image_person,
select_media_links_count_image_source,
select_media_links_person,
delete_media_links_image_person,
select_media_links_nested_place,
delete_media_links_image_nested_place, insert_image_new, delete_media,
select_media_links_source,
delete_media_links_image_source,
delete_media_links_image,
insert_media_links_image_nested_place)
import dev_tools as dt
from dev_tools import look, seeline
KINDS = ("male", "female", "unisex", "place", "source")
class MediaTab(tk.Frame):
def __init__(
self, master, formats, treebard, tree, main, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.formats = formats
self.treebard = treebard
self.tree = tree
self.main = main
self.usepicvar = tk.IntVar()
if self.main.use_default_images is False:
self.usepicvar.set(0)
elif self.main.use_default_images:
self.usepicvar.set(1)
self.radframe_children = []
self.imagegot = ""
self.make_inputs()
self.get_user_prefs()
def combobox_selected(self):
pictype = self.tree.comboboxes[self.image_type].get()
for k,v in self.user_pics.items():
if k == pictype:
self.user_input.delete(0, "end")
self.user_input.insert(0, v)
break
def make_inputs(self):
instrux = tk.Label(
self,
text=(
f"Any .jpg, .png, .gif, or .bmp file on your computer can be "
f"linked to any number of person, place, or source elements in "
f"your tree. First add the file to the tree, then to link it "
f"to an element, tell treebard what type of element it is, and "
f"select the file from the project images folder. Images, main "
f"images, and default images are explained in the right-click "
f"context help menus."),
wraplength=750, justify="left", bd=1, relief="raised")
labframe1 = tk.LabelFrame(
self, text="ADD AN IMAGE TO THE TREE")
addlab = tk.Label(
labframe1, text="Select an image:")
self.add_input = tk.Entry(labframe1, width=64)
browse1 = tk.Button(labframe1, text=" ... ")
browse1.config(
command=lambda browse1=browse1, entry=self.add_input:
self.open_image_explorer(browse1, entry))
caption_lab = tk.Label(labframe1, text="Caption:")
self.add_caption = tk.Entry(labframe1, width=64)
buttons1 = tk.Frame(labframe1)
self.ok_new_image = tk.Button(
buttons1, text="OK", width=6, command=self.ok_add_img)
frame = tk.Frame(self)
typelab = tk.Label(
frame, text="Changes below will relate to what element type?")
self.image_type = Combobox(
frame, self.formats, tree=self.tree, values=KINDS)
labframe2 = tk.LabelFrame(
self, text="LINK OR UNLINK CURRENT ELEMENT AND EXISTING IMAGE")
linklab = tk.Label(
labframe2,
text="Select an image that's already in the tree:")
self.link_input = tk.Entry(labframe2, width=64)
self.browse2 = tk.Button(labframe2, text=" ... ")
self.browse2.config(
command=lambda browse2=self.browse2, entry=self.link_input:
self.open_image_explorer(browse2, entry))
self.linkvar = tk.IntVar(None, 99)
rads = (
"Link to Current Element", "Unlink from Current Element",
"Unlink from All Elements", "Delete from Tree")
self.radframe = tk.Frame(labframe2)
for idx, text in enumerate(rads):
rad = tk.Radiobutton(
self.radframe, text=text,
variable=self.linkvar, value=idx, anchor="w")
rad.grid(column=idx, row=0)
self.radframe_children.append(rad)
buttons2 = tk.Frame(labframe2)
self.ok_link_pic = tk.Button(
buttons2, text="OK", width=6, command=self.ok_link_image)
labframe3 = tk.LabelFrame(self, text="ADD OR DELETE DEFAULT IMAGES")
radframe1 = tk.Frame(labframe3)
self.use_image = tk.Radiobutton(
radframe1, text="Use default images.", variable=self.usepicvar,
value=1)
self.no_image = tk.Radiobutton(
radframe1, text="Don't use default images.", variable=self.usepicvar,
value=0)
use_or_not = tk.Button(
radframe1, text="OK", command=self.ok_use_default_images)
userlab = tk.Label(
labframe3,
text="Add, delete or replace user-defined default image:")
self.user_input = tk.Entry(labframe3, width=64)
browse3 = tk.Button(
labframe3, text=" ... ")
browse3.config(
command=lambda browse1=browse3, entry=self.user_input:
self.open_image_explorer(browse3, entry))
buttons3 = tk.Frame(labframe3)
self.ok_default_image = tk.Button(
buttons3, text="OK", command=self.ok_set_default_image, width=6)
self.reset = tk.Button(
buttons3,
text="REVERT TO BUILT-IN DEFAULT IMAGES", command=self.revert)
# children of self
instrux.grid(column=0, row=0, ipadx=9, ipady=6, columnspan=2, pady=(0,6))
labframe1.grid(column=0, row=1, columnspan=3, sticky="ew", pady=(6,0))
frame.grid(column=0, row=2, sticky="w", pady=(12,6))
labframe2.grid(column=0, row=3, columnspan=3, sticky="ew", pady=(6,0))
labframe3.grid(column=0, row=4, columnspan=3, sticky="ew", pady=(6,0))
# children of labframe1
labframe1.columnconfigure(1, weight=1)
addlab.grid(column=0, row=2, sticky="w", padx=(6,0))
self.add_input.grid(column=1, row=2, sticky="ew", padx=(6,0))
browse1.grid(column=2, row=2, sticky="w", padx=6)
caption_lab.grid(column=0, row=3, sticky="w", padx=(6,0))
self.add_caption.grid(column=1, row=3, sticky="ew", padx=(6,0))
buttons1.grid(
column=1, row=4, sticky="e", columnspan=2, pady=6, padx=(0,6))
# children of buttons1
self.ok_new_image.grid(column=0, row=0, sticky="e")
# children of frame
typelab.grid(column=0, row=0, sticky="w")
self.image_type.grid(column=1, row=0, sticky="w", padx=6)
# children of labframe2
labframe2.columnconfigure(1, weight=1)
linklab.grid(column=0, row=0, sticky="w", padx=(6,0))
self.link_input.grid(column=1, row=0, sticky="ew", padx=(6,0))
self.browse2.grid(column=2, row=0, sticky="w", padx=6)
self.radframe.grid(column=0, row=1, sticky="w", columnspan=2)
buttons2.grid(
column=1, row=2, sticky="e", columnspan=2, pady=6, padx=(0,6))
# children of buttons2
self.ok_link_pic.grid(column=0, row=0, sticky="e")
# children of labframe3
labframe3.columnconfigure(1, weight=1)
radframe1.grid(column=0, row=0, sticky="w", pady=(6,0))
userlab.grid(column=0, row=1, sticky="w", padx=(6,0))
self.user_input.grid(column=1, row=1, sticky="ew", padx=(6,0))
browse3.grid(column=2, row=1, sticky="w", padx=6)
buttons3.grid(
column=1, row=2, sticky="e", pady=6, columnspan=2, padx=(0,6))
# children of radframe1
self.use_image.grid(column=0, row=0, sticky="w")
self.no_image.grid(column=1, row=0, sticky="w")
use_or_not.grid(column=2, row=0, sticky="w", padx=(12,0))
# children of buttons3
self.ok_default_image.grid(column=0, row=0, padx=6)
self.reset.grid(column=1, row=0, sticky="e")
self.add_input.focus_set()
def revert(self):
""" This works but app has to be reloaded to see the result. Also
e.g. if the user selects a custom default picture for males, and
the current person is a female, the current person's default image
will temporarily show the image chosen for males till the current
person's page is reloaded.
"""
query = Query()
for option in (
'image_male', 'image_female', 'image_unisex',
'image_nested_place', 'image_source'):
query.update(option, "")
def get_user_prefs(self):
query = Query()
results = []
for option in (
'image_male', 'image_female', 'image_unisex',
'image_nested_place', 'image_source'):
pref = query.select(option)
results.append(pref)
self.user_pics = dict(zip(KINDS, results))
def open_image_explorer(self, evt_widget, entry):
if evt_widget == self.browse2: # link/unlink existing image to current element
# e.g. D:/treebard/data/sample_tree/images
picdir = f"{tree_path}/{self.tree.tree_id}/images"
title = "Select Image to Link or Unlink from an Element"
else:
query = Query()
picdir = query.select("look_first_dir_for_images")
title = "Select Image to Import to the Project"
self.imagegot = tk.filedialog.askopenfilename(
initialdir=picdir,
title=title,
defaultextension=".jpg",
filetypes=(
('', '*.jpg'),
('', '*.png'),
('', '*.gif'),
('', '*.bmp'),
('all files', '*.*')))
entry.delete(0, "end")
entry.insert(0, self.imagegot)
def ok_use_default_images(self):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
use_default_images = self.usepicvar.get()
if use_default_images == 0:
self.main.use_default_images = False
self.main.top_pic_button.config(image="")
self.main.top_pic_button.image = ""
self.main.places_tab_content.top_pic_place_button.config(image="")
self.main.places_tab_content.top_pic_place_button.image = ""
self.main.sources_tab_content.top_pic_source_button.config(image="")
self.main.sources_tab_content.top_pic_source_button.image = ""
elif use_default_images == 1:
self.main.use_default_images = True
self.main.show_top_pic(cur)
self.main.places_tab_content.show_top_pic_nested_place(cur)
self.main.sources_tab_content.show_top_pic_source(cur)
query = Query()
query.update("use_default_images", str(use_default_images))
cur.close()
conn.close()
def ok_set_default_image(self):
kind = self.image_type.entry.get()
if kind not in KINDS:
self.error_no_type()
return
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
contents = self.user_input.get()
file = self.imagegot.split("/")[-1]
if len(contents) != 0:
saved = Image.open(self.imagegot)
default_images_path = f"{tree_path}/settings/images/{file}"
# overwrites file by same name if it exists
saved.save(default_images_path)
else:
file = ""
query = Query()
if kind == "male":
query.update("image_male", file)
elif kind == "female":
query.update("image_female", file)
elif kind == "unisex":
query.update("image_unisex", file)
elif kind == "place":
query.update("image_nested_place", file)
elif kind == "source":
query.update("image_source", file)
self.get_user_prefs()
self.reset_gui(kind, cur)
if len(file) != 0:
self.redraw_default_image(kind, file)
else:
self.redraw_default_image(kind, file, cur=cur, conn=conn)
self.main.get_default_images(query)
conn.commit()
cur.close()
conn.close()
def ok_add_img(self):
file = self.add_input.get().strip().split("/")[-1]
caption = self.add_caption.get().strip()
if len(file) == 0:
self.error_no_input(self.add_input)
return
elif len(caption) == 0:
self.error_no_input(self.add_caption)
return
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(insert_image_new, (file, caption))
conn.commit()
if len(file) != 0:
saved = Image.open(self.imagegot)
tree_images_path = f"{tree_path}/{self.tree.tree_id}/images/{file}"
# overwrites file by same name if it exists
saved.save(tree_images_path)
cur.close()
conn.close()
self.add_input.delete(0, "end")
self.add_caption.delete(0, "end")
def ok_link_image(self):
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(
''' SELECT person_id, nested_place_id, source_id
FROM current_tbd
WHERE current_tbd_id = 1
''')
current_ids = (persid, placid, sorcid) = cur.fetchone()
path_string = self.link_input.get().strip()
name = path_string.split("/")[-1]
if len(name) == 0:
self.error_no_input(self.link_input)
cur.execute(select_media_id, (name,))
result = cur.fetchone()
if result:
image_id = result[0]
select = self.linkvar.get()
image_count_before_insert = None
kind = self.image_type.entry.get()
if kind not in KINDS:
self.error_no_type()
cur.close()
conn.close()
return
if kind in KINDS[0:3]:
link_query = insert_media_links_image_person
if select == 0:
cur.execute(
select_media_links_count_image_person, (persid,))
image_count_before_insert = cur.fetchone()[0]
element = persid
elif kind == "place":
link_query = insert_media_links_image_nested_place
if select == 0:
cur.execute(
select_media_links_count_image_nested_place,
(placid,))
image_count_before_insert = cur.fetchone()[0]
element = placid
elif kind == "source":
link_query = insert_media_links_image_source
if select == 0:
cur.execute(
select_media_links_count_image_source, (sorcid,))
image_count_before_insert = cur.fetchone()[0]
element = sorcid
else:
print("error message radiobutton not selected")
cur.close()
conn.close()
return
if select > 3:
self.error_no_radio()
cur.close()
conn.close()
return
elif select == 0:
self.link_current_element(
kind, image_id, link_query, element, image_count_before_insert,
conn, cur)
elif select == 1:
returned = self.unlink_current_element(
kind, image_id, current_ids, conn, cur)
if returned == "no kind":
cur.close()
conn.close()
return
elif select == 2:
self.unlink_all_elements(image_id, current_ids, conn, cur)
elif select == 3:
returned = self.delete_from_tree(
kind, image_id, current_ids, name, path_string, conn, cur)
if returned == "no kind":
cur.close()
conn.close()
return
cur.close()
conn.close()
def link_current_element(
self, kind, image_id, link_query, element,
image_count_before_insert, conn, cur):
cur.execute(link_query, (image_id, element))
conn.commit()
new_media_links_id = cur.lastrowid
if image_count_before_insert == 0:
cur.execute(
"INSERT INTO main_tbd (media_links_id) VALUES (?)",
(new_media_links_id,))
conn.commit()
self.reset_gui(kind, cur)
def unlink_current_element(self, kind, image_id, current_ids, conn, cur):
""" There's a bug in here that causes an error which seems to be self-
correcting in some way, i.e. if I keep linking and unlinking images
to a current person, place, or source, it seems to fix itself. It
might have something to do with the setting of a main image in the
Gallery class using the Radiobuttons. Possibly there's interaction
of an instance variable value among instances of person gallery,
place gallery, and source gallery.
"""
persid, placid, sorcid = current_ids
if kind not in KINDS:
self.error_no_type()
return "no kind"
elif kind in (KINDS[0:3]):
cur.execute(select_media_links_person, (persid,))
linked_images = cur.fetchall()
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(delete_media_links_image_person, (persid, image_id))
conn.commit()
elif kind == "place":
cur.execute(
select_media_links_nested_place,
(placid,))
linked_images = [list(i) for i in cur.fetchall()]
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(
delete_media_links_image_nested_place,
(placid, image_id))
conn.commit()
elif kind == "source":
cur.execute(select_media_links_source, (sorcid,))
linked_images = [list(i) for i in cur.fetchall()]
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(delete_media_links_image_source, (sorcid, image_id))
conn.commit()
self.reset_gui(kind, cur)
def unlink_all_elements(self, image_id, current_ids, conn, cur):
for kind in KINDS:
self.unlink_current_element(kind, image_id, current_ids, conn, cur)
self.reset_gui(kind, cur)
cur.execute(delete_media_links_image, (image_id,))
conn.commit()
def delete_from_tree(
self, kind, image_id, current_ids, name, path_string, conn, cur):
persid, placid, sorcid = current_ids
if kind not in KINDS:
self.error_no_type()
return "no kind"
elif kind in (KINDS[0:3]):
cur.execute(select_media_links_person, (persid,))
linked_images = [list(i) for i in cur.fetchall()]
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(delete_media_links_image, (image_id,))
conn.commit()
cur.execute(delete_media, (image_id,))
conn.commit()
file = name.rsplit(".", 1)[0]
bare_path = path_string.rsplit("/", 1)[0]
resized = "{}/thumbnails/{}_resized_top_pic.png".format(bare_path, file)
if path.isfile(path_string):
remove(path_string)
if path.isfile(resized):
remove(resized)
elif kind == "place":
cur.execute(select_media_links_nested_place, (placid,))
linked_images = [list(i) for i in cur.fetchall()]
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(delete_media_links_image, (image_id,))
conn.commit()
cur.execute(delete_media, (image_id,))
conn.commit()
file = name.rsplit(".", 1)[0]
bare_path = path_string.rsplit("/", 1)[0]
resized = f"{bare_path}/thumbnails/{file}_resized_top_pic.png"
if path.isfile(path_string):
remove(path_string)
if path.isfile(resized):
remove(resized)
elif kind == "source":
cur.execute(select_media_links_source, (sorcid,))
linked_images = [list(i) for i in cur.fetchall()]
self.replace_main_image(linked_images, image_id, conn, cur)
cur.execute(delete_media_links_image, (image_id,))
conn.commit()
cur.execute(delete_media, (image_id,))
conn.commit()
file = name.rsplit(".", 1)[0]
bare_path = path_string.rsplit("/", 1)[0]
resized = f"{bare_path}/thumbnails/{file}_resized_top_pic.png"
if path.isfile(path_string):
remove(path_string)
if path.isfile(resized):
remove(resized)
self.reset_gui(kind, cur)
def replace_main_image(self, linked_images, image_id, conn, cur):
""" Find out whether the image being unlinked or deleted was the main
image for the current element. If so, remove its reference from
`main_tbd`. Replace it with the next image found if it wasn't the
only image linked to this element.
"""
image_count = len(linked_images)
if image_id not in [i[1] for i in linked_images]:
return
elif image_count == 0:
return
elif image_count == 1:
media_links_id = linked_images[0][0]
cur.execute(
''' DELETE FROM main_tbd
WHERE media_links_id = ?
''',
(media_links_id,))
conn.commit()
return
for tup in linked_images:
media_links_id, media_id = tup
if media_id != image_id:
continue
elif media_id == image_id:
cur.execute(
''' SELECT main_tbd.media_links_id
FROM media_links
JOIN main_tbd
ON media_links.media_links_id = main_tbd.media_links_id
WHERE media_id = ?
''',
(image_id,))
result = cur.fetchone()
if result is None:
return
else:
old_media_links_id = result[0]
break
for tup in linked_images:
media_links_id, media_id = tup
if media_id != image_id:
new_media_links_id = media_links_id
break
cur.execute(
''' UPDATE main_tbd
SET media_links_id = ?
WHERE media_links_id = ?
''',
(new_media_links_id, old_media_links_id))
conn.commit()
def error_no_type(self):
def err_done1(combo, msg):
msg1[0].grab_release()
msg1[0].destroy()
combo.entry.focus_set()
msg1 = open_message(self, images_msg[0], "Image Type Missing",
"OK", formats=self.formats)
msg1[0].grab_set()
msg1[2].config(
command=lambda combo=self.image_type, msg=msg1: err_done1(
combo, msg))
def error_no_radio(self):
def err_done2(rad1, msg):
msg2[0].grab_release()
msg2[0].destroy()
rad1.focus_set()
rad1 = self.radframe_children[0]
msg2 = open_message(self, images_msg[1], "Action Type Missing",
"OK", formats=self.formats)
msg2[0].grab_set()
msg2[2].config(
command=lambda rad1=rad1, msg=msg2: err_done2(
rad1, msg))
def error_no_input(self, entry):
def err_done3(entry, msg):
msg3[0].grab_release()
msg3[0].destroy()
entry.focus_set()
msg3 = open_message(self, images_msg[2], "Input Missing",
"OK", formats=self.formats)
msg3[0].grab_set()
msg3[2].config(
command=lambda entry=entry, msg=msg3: err_done3(
entry, msg))
def reset_gui(self, kind, cur):
self.link_input.delete(0, "end")
self.image_type.entry.delete(0, "end")
self.linkvar.set(99)
self.user_input.delete(0, "end")
if kind in KINDS[0:3]:
self.main.show_top_pic(cur)
elif kind == KINDS[3]:
self.main.places_tab_content.show_top_pic_nested_place(cur)
elif kind == KINDS[4]:
self.main.sources_tab_content.show_top_pic_source(cur)
def redraw_default_image(self, kind, file):
if len(file) == 0:
options = (
'male_image', 'female_image', 'unisex_image',
'nested_place_image', 'source_image')
query = Query()
results = []
for option in options:
result = query.select(option)
results.append(result)
(male_image, female_image, unisex_image, nested_place_image,
source_image) = results
else:
img_stg = file
if kind in KINDS[0:3]:
button = self.main.top_pic_button
if len(file) == 0:
if kind == "male":
img_stg = male_image
elif kind == "female":
img_stg = female_image
elif kind == "unisex":
img_stg = unisex_image
elif kind == "place":
button = self.main.places_tab_content.top_pic_place_button
if len(file) == 0:
img_stg = nested_place_image
elif kind == "source":
button = self.main.sources_tab_content.top_pic_source_button
if len(file) == 0:
img_stg = source_image
new_stg = f'{tree_path}/settings/images/{img_stg}'
top = Image.open(new_stg)
top = self.main.resize_top_pic(top, new_stg)
img1 = ImageTk.PhotoImage(top, master=self.master)
button.config(image=img1)
button.image = img1