gallery.py
Nov 25, 2022 3:11:34 GMT -8
Post by Uncle Buddy on Nov 25, 2022 3:11:34 GMT -8
<drive>:\treebard\gallery.py Last Changed 2024-07-25
# gallery.py
import tkinter as tk
from PIL import Image, ImageTk
import sqlite3
from base import tree_path, image_path, Query
from base import resize_scrolled_content
from widgets import (
ScrolledDialog, configall, make_formats_dict, get_color_scheme_id,
create_tooltip, run_statusbar_tooltips, MessageCopiable, Scrollbar,
RightClickMenu, make_rc_menus)
from messages_context_help import gallery_help_msg, gallery_thumbnail_help_msg
from unigeds_queries import (
select_source_by_id, select_nest_ids, select_place_name_from_id,
select_all_images_nested_place, select_all_images_source,
select_all_images_person)
import dev_tools as dt
from dev_tools import look, seeline
update_media_links_unmain_image_person = '''
DELETE FROM main_tbd
WHERE media_links_id = (
SELECT media_links_id FROM media_links
WHERE person_id = ? AND media_links_id IN (
SELECT media_links_id FROM main_tbd
WHERE media_links_id IS NOT null))
'''
update_media_links_unmain_image_source = '''
DELETE FROM main_tbd
WHERE media_links_id = (
SELECT media_links_id FROM media_links
WHERE source_id = ? AND media_links_id IN (
SELECT media_links_id FROM main_tbd
WHERE media_links_id IS NOT null))
'''
update_media_links_unmain_image_nested_place = '''
DELETE FROM main_tbd
WHERE media_links_id = (
SELECT media_links_id FROM media_links
WHERE nested_place_id = ? AND media_links_id IN (
SELECT media_links_id FROM main_tbd
WHERE media_links_id IS NOT null))
'''
update_media_links_main_image_nested_place = '''
INSERT INTO main_tbd (media_links_id)
SELECT media_links.media_links_id
FROM media_links
JOIN media ON media.media_id = media_links.media_id
WHERE file_name = ? AND media_type_id = (
SELECT media_type_id FROM media_type
WHERE media_type_text = 'image') AND nested_place_id = (
SELECT current_tbd.nested_place_id
FROM current_tbd WHERE current_tbd_id = 1)
'''
update_media_links_main_image_person = '''
INSERT INTO main_tbd (media_links_id)
SELECT media_links.media_links_id
FROM media_links
JOIN media ON media.media_id = media_links.media_id
WHERE file_name = ? AND media_type_id = (
SELECT media_type_id FROM media_type
WHERE media_type_text = 'image') AND person_id = (
SELECT current_tbd.person_id
FROM current_tbd WHERE current_tbd_id = 1)
'''
update_media_links_main_image_source = '''
INSERT INTO main_tbd (media_links_id)
SELECT media_links.media_links_id
FROM media_links
JOIN media ON media.media_id = media_links.media_id
WHERE file_name = ? AND media_type_id = (
SELECT media_type_id FROM media_type
WHERE media_type_text = 'image') AND source_id = (
SELECT current_tbd.source_id
FROM current_tbd WHERE current_tbd_id = 1)
'''
class Gallery(ScrolledDialog):
def __init__(
self, master, main, tabbook, graphics_tab, SCREEN_SIZE,
tab=None, current_person_name=None, mode=None, *args, **kwargs):
ScrolledDialog.__init__(self, master, *args, **kwargs)
self.tree = master
self.main = main
self.tabbook = tabbook
self.graphics_tab = graphics_tab
self.SCREEN_WIDTH, self.SCREEN_HEIGHT = SCREEN_SIZE
self.tab = tab
self.mode = mode
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.current_image = None
self.source = ""
self.place = ""
self.img_path = None
self.default_image = False
self.rc_menu = RightClickMenu(self, self.treebard)
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
self.image_path_base = f"{tree_path}/{self.tree.tree_id}/images/"
current_entity_name = None
self.main_pic = None
self.caption_text = ""
if current_person_name:
current_entity_name = current_person_name
elif self.tree.placid and self.mode == "place":
place_name = []
cur.execute(select_nest_ids, (self.tree.placid,))
nested_place_ids = cur.fetchone()
for num in nested_place_ids:
if num != 1:
cur.execute(select_place_name_from_id, (num,))
place_name.append(cur.fetchone()[0])
current_entity_name = ", ".join(place_name)
elif self.tree.sorcid and self.mode == "source":
cur.execute(select_source_by_id, (self.tree.sorcid,))
current_entity_name = cur.fetchone()[0]
else:
print("line", look(seeline()).lineno, "case_not_handled:")
self.counter = 0
self.thumb_labels = []
self.maxwidth = 0
self.maxheight = 0
image_data = self.get_current_pix_data(cur=cur)
if len(image_data) == 0:
self.default_image = True
self.filter_pix_data(image_data)
self.geometry('+100+20')
self.title(f"Current {self.mode.title()} Image Gallery")
self.make_gallery()
self.make_inputs(current_entity_name)
cur.close()
conn.close()
ScrolledDialog.bind_canvas_to_mousewheel(self.canvas)
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)
configall(self, self.formats)
self.resize_scrolled_content(self, self.canvas, add_y=36)
def cancel(self):
self.grab_release()
self.destroy()
def make_gallery(self):
self.thumb_frame = tk.Frame(self.window)
self.thumb_canvas = tk.Canvas(
self.thumb_frame, bg=self.formats['bg'], bd=0, highlightthickness=0)
self.thumb_sbh = Scrollbar(
self.thumb_frame,
self.formats,
orient='horizontal',
command=self.thumb_canvas.xview,
hideable=True)
self.thumb_canvas.config(xscrollcommand=self.thumb_sbh.set)
self.viewer = tk.Frame(self.window, bd=2, relief='sunken')
self.thumbstrip = tk.Frame(self.thumb_canvas)
self.buttonbox = tk.Frame(self.window)
self.prevbutt = tk.Button(self.buttonbox, text='<<', width=7)
self.pic_canvas = tk.Canvas(
self.viewer, highlightthickness=3, bd=3)
self.pic_canvas.bind('<Button-1>', self.focus_clicked)
self.pic_canvas.bind('<Button-1>', self.scroll_start, add='+')
self.pic_canvas.bind('<B1-Motion>', self.scroll_move)
if self.default_image is False:
self.get_image_data()
def get_image_data(self):
self.img_path = f"{self.image_path_base}{self.main_pic}"
img_big = Image.open(self.img_path)
self.tk_img = ImageTk.PhotoImage(img_big)
self.pic_canvas.image = self.tk_img
self.current_pictures = sorted(self.pictures)
for idx, img in enumerate(self.current_pictures):
pic_col = tk.Frame(self.thumbstrip)
pic_col.grid(column=idx, row=0)
pic_file = img
self.img_path = f"{self.image_path_base}{pic_file}"
bare = pic_file.split(".")[1]
thumbsy = Image.open(self.img_path)
if thumbsy.width > self.maxwidth:
self.maxwidth = thumbsy.width
if thumbsy.height > self.maxheight:
self.maxheight = thumbsy.height
thumbsy.thumbnail((185,85))
thumb_path = f'{image_path}/{bare}_tmb.png'
# overwrites file by same name if it exists
thumbsy.save(thumb_path)
small = ImageTk.PhotoImage(file=thumb_path, master=self.thumbstrip)
thumb = tk.Label(pic_col, image=small)
thumb.grid(column=0, row=0)
thumb.image = small
self.rc_menu.loop_made[thumb] = gallery_thumbnail_help_msg
self.thumb_labels.append(thumb)
rad = tk.Radiobutton(
pic_col,
takefocus=0,
value=pic_file,
command=lambda pic_file=pic_file: self.set_main_pic(
pic_file))
rad.grid(column=0, row=1)
if rad['value'] == self.main_pic:
rad.select()
create_tooltip(pic_col, pic_file, self.formats)
self.pil_img = img_big
self.fit_canvas_to_pic()
self.thumb_dict = dict(zip(self.thumb_labels, self.current_pictures))
def make_inputs(self, current_entity_name):
self.nextbutt = tk.Button(self.buttonbox, text='>>', width=7)
self.editbutt = tk.Button(
self.buttonbox, text="EDIT", width=7,
command=lambda graphics=self.graphics_tab, img_path=self.img_path:
self.go_to_graphics(graphics, img_path))
self.closebutt = tk.Button(
self.buttonbox, text="CLOSE",
width=7, command=self.cancel)
spacer = tk.Frame(self.buttonbox)
panel = tk.Frame(self.buttonbox)
subject = tk.Label(panel)
if current_entity_name:
subject.config(text=current_entity_name)
self.caption_lab = MessageCopiable(panel, self.formats)
self.picfile_lab = MessageCopiable(panel, self.formats)
self.picsize_lab = MessageCopiable(panel, self.formats)
self.prevbutt.config(command=self.back)
self.nextbutt.config(command=self.forward)
if self.default_image is False:
self.caption_lab.insert(1.0, self.caption_text)
self.picfile_lab.insert(1.0, self.img_path)
self.picsize_lab.insert(
1.0, 'width: {}, height: {}'.format(
self.pil_img.width, self.pil_img.height))
self.caption_lab.set_height()
self.picfile_lab.set_height()
self.picsize_lab.set_height()
# children of self
self.statusbar.grid(column=1, row=3, sticky='ew')
# children of self.window (see ScrolledDialog)
self.thumb_frame.grid(column=0, row=0, columnspan=2, sticky='w')
self.viewer.grid(column=0, row=2)
self.buttonbox.grid(column=0, row=3, sticky='ew', padx=12, pady=12)
# children of self.thumb_frame
self.thumb_canvas.grid(column=0, row=0, sticky="news")
self.thumb_sbh.grid(column=0, row=1, sticky="ew")
# children of self.thumb_canvas
self.thumbstrip.grid(column=0, row=0)
# children of self.viewer
self.pic_canvas.grid(column=0, row=0, columnspan=2)
# children of panel
subject.grid(column=0, row=0, sticky='w')
self.caption_lab.grid(column=0, row=1, sticky='w')
self.picfile_lab.grid(column=0, row=2, sticky='w')
self.picsize_lab.grid(column=0, row=3, sticky='w')
# children of self.buttonbox
self.buttonbox.columnconfigure(2, weight=10)
self.prevbutt.grid(column=0, row=0, sticky='e', padx=3, pady=3)
self.nextbutt.grid(column=1, row=0, sticky='e', padx=3, pady=3)
self.editbutt.grid(column=0, row=1, sticky='e', padx=3, pady=3)
self.closebutt.grid(column=1, row=1, sticky='e', padx=3, pady=3)
spacer.grid(column=2, row=0, rowspan=3, sticky='news')
panel.grid(column=3, row=0, rowspan=3, sticky='w')
for thumb in self.thumb_labels:
thumb.bind('<Button-1>', self.show_clicked)
self.pic_canvas.bind('<Key-Left>', lambda evt: self.back())
self.pic_canvas.bind('<Key-Right>', lambda evt: self.forward())
self.thumb_canvas.create_window(0, 0, anchor='nw', window=self.thumbstrip)
self.thumb_canvas.config(
scrollregion=(
0, 0,
self.thumbstrip.winfo_reqwidth(),
self.thumbstrip.winfo_reqheight()))
self.config_labels()
visited = (
(self.thumbstrip,
"Thumbnail Views",
"Click thumbnail to display. Hover to see the file name. "
"Select radio button to make this the main image."),
(self.pic_canvas,
"Image",
"Arrow keys change image when it's in focus."),
(self.prevbutt,
"Left Button",
"Click with mouse or when highlighted click with spacebar."),
(self.nextbutt,
"Right Button",
"Click with mouse or when highlighted click with spacebar."),
(self.editbutt,
"Edit Button",
"Open the current image in the Graphics Tab."))
run_statusbar_tooltips(
visited,
self.statusbar.status_label,
self.statusbar.tooltip_label,
self)
rcm_widgets = (
self.pic_canvas, self.prevbutt, self.nextbutt, self.editbutt)
make_rc_menus(
rcm_widgets,
self.rc_menu,
gallery_help_msg)
self.update_idletasks()
self.thumb_canvas.config(width=self.winfo_reqwidth(), height=130)
self.pic_canvas.focus_set()
def focus_clicked(self, evt):
evt.widget.focus_set()
def show_clicked(self, evt):
select_pic = self.current_pictures.index(self.thumb_dict[evt.widget])
self.chosen_picfile = self.current_pictures[select_pic]
self.img_path = f"{self.image_path_base}{self.chosen_picfile}"
image_data = self.get_current_pix_data()
for lst in image_data:
if lst[0] == self.chosen_picfile:
self.caption_text = lst[1]
self.main.current_image = lst[3]
new = Image.open(self.img_path)
self.tk_img = ImageTk.PhotoImage(new)
self.pil_img = new
self.fit_canvas_to_pic()
self.pic_canvas.image = self.tk_img
self.pic_canvas.config(
scrollregion=(0, 0, self.pil_img.width, self.pil_img.height))
self.config_labels()
self.counter = select_pic
def get_current_pix_data(self, cur=None):
new_connex = False
if cur is None:
conn = sqlite3.connect(self.tree.file)
cur = conn.cursor()
new_connex = True
if self.mode == "person":
cur.execute(select_all_images_person, (self.tree.persid,))
elif self.mode == "place":
cur.execute(select_all_images_nested_place, (self.tree.placid,))
elif self.mode == "source":
cur.execute(select_all_images_source, (self.tree.sorcid,))
results = [list(i) for i in cur.fetchall()]
cur.execute(
"SELECT media_links_id FROM main_tbd WHERE media_links_id IS NOT null")
main_images = [i[0] for i in cur.fetchall()]
for idx,lst in enumerate(list(results)):
media_links_id = lst[3]
if media_links_id in main_images:
results[idx][2] = 1
else:
results[idx][2] = 0
image_data = []
for pic in results:
if pic[0].startswith("0_default_image") is False and pic not in image_data:
image_data.append(pic)
if new_connex:
cur.close()
conn.close()
return image_data
def filter_pix_data(self, image_data):
pix_data = sorted(image_data, key=lambda i: i[1])
for lst in pix_data:
if lst[2] == 1:
self.main_pic = lst[0]
self.caption_text = lst[1]
if self.tab.winfo_name() == 'sourcetab':
self.source = lst[3]
elif self.tab.winfo_name() == 'placetab':
self.place = lst[3]
self.pictures = []
for lst in pix_data:
self.pictures.append(lst[0])
self.caption_path = []
for lst in pix_data:
self.caption_path.append((lst[0], lst[1]))
def scroll_start(self, event):
self.pic_canvas.scan_mark(event.x, event.y)
def scroll_move(self, event):
self.pic_canvas.scan_dragto(event.x, event.y, gain=5)
def set_main_pic(self, val):
""" Radiobutton command. """
if self.mode == "person":
select_query = '''
SELECT person_id FROM current_tbd WHERE current_tbd_id = 1
'''
unmain_query = update_media_links_unmain_image_person
main_query = update_media_links_main_image_person
elif self.mode == "place":
select_query = '''
SELECT nested_place_id FROM current_tbd WHERE current_tbd_id = 1
'''
unmain_query = update_media_links_unmain_image_nested_place
main_query = update_media_links_main_image_nested_place
elif self.mode == "source":
select_query = '''
SELECT source_id FROM current_tbd WHERE current_tbd_id = 1
'''
unmain_query = update_media_links_unmain_image_source
main_query = update_media_links_main_image_source
conn = sqlite3.connect(self.tree.file)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_query)
current_element = cur.fetchone()
cur.execute(unmain_query, current_element)
conn.commit()
cur.execute(main_query, (val,))
conn.commit()
if self.mode == "person":
self.main.show_top_pic(cur)
elif self.mode == "place":
self.main.places_tab_content.show_top_pic_nested_place(cur)
elif self.mode == "source":
self.main.sources_tab_content.show_top_pic_source(cur)
cur.close()
conn.close()
def go_to_graphics(self, graphics, edit_image=None):
""" If a frame with this name already exists it's replaced:
https://stackoverflow.com/questions/59518905/
naming-a-widget-to-auto-destroy-replace-it
"""
picwin = tk.Frame(graphics, name='exists')
picwin.grid()
self.main.to_destroy.append(picwin)
curr_pic = self.picfile_lab.get(1.0, 'end')
curr_pic = curr_pic.strip('\n')
img_path = curr_pic
edit_pic = Image.open(img_path)
edit_img = ImageTk.PhotoImage(edit_pic)
editlab = tk.Label(picwin, None, image=edit_img)
editlab.image = edit_img
self.tabbook.active = self.tabbook.tabdict["graphics"][1]
self.tabbook.make_active()
# scroll to top so controls are seen when tab opens
self.main.canvas.yview_moveto(0.0)
if self:
self.lower(belowThis=self.tabbook)
editlab.grid() # When this grids a big pic, the whole tabbook gets big
# prevent large pics from blowing up size of the whole tabbook
# when placed here by edit button on a gallery
# Will need more attention when ready to make the graphics tab.
editlab.config(width=700, height=700)
self.main.edit_image = curr_pic
def back(self, evt=None):
if self.counter == 0:
self.counter = len(self.caption_path)
self.counter -= 1
self.img_path = f"{self.image_path_base}{self.caption_path[self.counter][0]}"
self.caption_text = self.caption_path[self.counter][1]
new = Image.open(self.img_path)
self.tk_img = ImageTk.PhotoImage(new)
self.pil_img = new
self.fit_canvas_to_pic()
self.pic_canvas.image = self.tk_img
self.pic_canvas.config(
scrollregion=(0, 0, self.pil_img.width, self.pil_img.height))
self.config_labels()
def forward(self, evt=None):
self.counter += 1
if self.counter == len(self.caption_path):
self.counter = 0
self.img_path = f"{self.image_path_base}{self.caption_path[self.counter][0]}"
self.caption_text = self.caption_path[self.counter][1]
new = Image.open(self.img_path)
self.tk_img = ImageTk.PhotoImage(new)
self.pil_img = new
self.fit_canvas_to_pic()
self.pic_canvas.image = self.tk_img
self.pic_canvas.config(
scrollregion=(0, 0, self.pil_img.width, self.pil_img.height))
self.config_labels()
def fit_canvas_to_pic(self):
# don't show overly large images at their full size
FULL = 0.50
if self.maxwidth <= self.SCREEN_WIDTH * FULL:
gallery_wd = self.maxwidth
else:
gallery_wd = self.SCREEN_WIDTH * FULL
if self.maxheight <= self.SCREEN_HEIGHT * FULL:
gallery_ht = self.maxheight
else:
gallery_ht = self.SCREEN_HEIGHT * FULL
self.pic_canvas.config(
scrollregion=(0, 0, self.pil_img.width, self.pil_img.height),
width=gallery_wd,
height=gallery_ht)
# position image in canvas depending on image size
if (self.pil_img.width >= gallery_wd and
self.pil_img.height >= gallery_ht):
image = self.pic_canvas.create_image(
0, 0, anchor='nw', image=self.tk_img)
elif (self.pil_img.width <= gallery_wd and
self.pil_img.height >= gallery_ht):
image = self.pic_canvas.create_image(
self.pic_canvas.winfo_reqwidth()/2, 0,
anchor='n', image=self.tk_img)
elif (self.pil_img.width >= gallery_wd and
self.pil_img.height <= gallery_ht):
image = self.pic_canvas.create_image(
0, self.pic_canvas.winfo_reqheight()/2,
anchor='w', image=self.tk_img)
elif (self.pil_img.width <= gallery_wd and
self.pil_img.height <= gallery_ht):
image = self.pic_canvas.create_image(
self.pic_canvas.winfo_reqwidth()/2,
self.pic_canvas.winfo_reqheight()/2,
anchor='center',
image=self.tk_img)
def config_labels(self):
for widg in (self.caption_lab, self.picfile_lab, self.picsize_lab):
widg.config(state='normal')
widg.delete(1.0, 'end')
if self.default_image is False:
self.caption_lab.insert('1.0', self.caption_text)
self.picfile_lab.insert('1.0', self.img_path)
self.picsize_lab.insert(
'1.0', 'width: {}, height: {}'.format(
self.pil_img.width, self.pil_img.height))
for widg in (self.caption_lab, self.picfile_lab, self.picsize_lab):
widg.config(state='disabled')