Skip to content

Commit

Permalink
Merge pull request #60 from shaunhwq/v1.2.0
Browse files Browse the repository at this point in the history
V1.2.0
  • Loading branch information
shaunhwq authored Aug 12, 2024
2 parents 6e5c323 + 83bf4f1 commit 05bf6eb
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 6 deletions.
Binary file added visual_comparison/assets/icons/search_grid_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions visual_comparison/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ theme = dark-blue
interpolation_type = cv2.INTER_LINEAR
fast_loading_threshold_ms = 100
ctk_corner_radius = 3
search_grid_preview_row_height = 75

[Zoom]
interpolation_type = cv2.INTER_NEAREST
Expand Down
1 change: 1 addition & 0 deletions visual_comparison/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
interpolation_type=dict(obj="options", type=eval, values=DISPLAY_INTERPOLATION_TYPES, default="cv2.INTER_LINEAR"),
fast_loading_threshold_ms=dict(obj="entry", type=int, default=100),
ctk_corner_radius=dict(obj="entry", type=int, default=3),
search_grid_preview_row_height=dict(obj="options", type=int, values=["75", "100", "125"], default="75")
),
Zoom=dict(
interpolation_type=dict(obj="options", type=eval, values=ZOOM_INTERPOLATION_TYPES, default="cv2.INTER_NEAREST"),
Expand Down
1 change: 1 addition & 0 deletions visual_comparison/managers/icon_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def __init__(self, icon_assets_path):
self.settings_icon = self.load_ctk_image("settings_icon.png")
self.filter_icon = self.load_ctk_image("filter_icon.png")
self.search_icon = self.load_ctk_image("search_icon.png")
self.search_grid_icon = self.load_ctk_image("search_grid_icon.png")

def load_ctk_image(self, image_name: str) -> customtkinter.CTkImage:
image_path = os.path.join(self.icon_assets_path, image_name)
Expand Down
11 changes: 11 additions & 0 deletions visual_comparison/visual_comparison_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def __init__(self, root=None, preview_folder=None, config_path="visual_compariso
cb_callbacks = dict(
on_prev_file=self.on_prev_file,
on_search=self.on_specify_index,
on_search_grid=self.on_search_grid,
on_next_file=self.on_next_file,
on_filter_files=self.on_filter_files,
on_prev_method=self.on_prev_method,
Expand Down Expand Up @@ -147,6 +148,16 @@ def load_content(self):
self.preview_folder = preview_folder
return True

def on_search_grid(self):
widgets.SearchGridPopup(
images=self.content_handler.thumbnails,
width=1000,
height=720,
callback=self.on_specify_index,
default_value=self.configurations["Display"]["search_grid_preview_row_height"],
)
self.update_idletasks()

def on_change_playback_rate(self, new_rate):
if new_rate == "Max":
self.app_status.VIDEO_PLAYBACK_RATE = self.configurations["Functionality"]["max_fps"]
Expand Down
1 change: 1 addition & 0 deletions visual_comparison/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .widget_display import *
from .widget_pop_ups import *
from .widget_preview import *
from .widget_search_grid import *
from .widget_settings import *
from .widget_settings import *
from .widget_video_controls import *
13 changes: 8 additions & 5 deletions visual_comparison/widgets/widget_control_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@ def __init__(self, icon_manager: IconManager, callbacks, ctk_corner_radius, *arg
button_search = customtkinter.CTkButton(master=frame_00, width=25, height=25, command=callbacks["on_search"], text="", image=icon_manager.search_icon, corner_radius=ctk_corner_radius)
utils.create_tool_tip(button_search, "Search")
button_search.grid(row=0, column=1, padx=(0, 2))
button_search_grid = customtkinter.CTkButton(master=frame_00, width=25, height=25, command=callbacks["on_search_grid"], text="", image=icon_manager.search_grid_icon, corner_radius=ctk_corner_radius)
utils.create_tool_tip(button_search_grid, "Search Grid")
button_search_grid.grid(row=0, column=2, padx=(0, 2))
button_next = customtkinter.CTkButton(master=frame_00, text=">", command=callbacks["on_next_file"], width=30, height=29, corner_radius=ctk_corner_radius)
button_next.grid(row=0, column=2, padx=(0, 5))
button_next.grid(row=0, column=3, padx=(0, 5))

button_filter = customtkinter.CTkButton(master=frame_00, width=25, height=25, command=callbacks["on_filter_files"], text="", image=icon_manager.filter_icon, corner_radius=ctk_corner_radius)
utils.create_tool_tip(button_filter, "Filter")
button_filter.grid(row=0, column=3, padx=5)
button_filter.grid(row=0, column=4, padx=5)

button_prev_method = customtkinter.CTkButton(master=frame_00, text="<", command=callbacks["on_prev_method"], width=30, height=29, corner_radius=ctk_corner_radius)
button_prev_method.grid(row=0, column=4, padx=(5, 2))
button_prev_method.grid(row=0, column=5, padx=(5, 2))
button_method = customtkinter.CTkButton(master=frame_00, text="Method:", command=callbacks["on_select_methods"], width=50, height=29, corner_radius=ctk_corner_radius)
button_method.grid(row=0, column=5, padx=(0, 2))
button_method.grid(row=0, column=6, padx=(0, 2))
button_next_method = customtkinter.CTkButton(master=frame_00, text=">", command=callbacks["on_next_method"], width=30, height=29, corner_radius=ctk_corner_radius)
button_next_method.grid(row=0, column=6, padx=(0, 5))
button_next_method.grid(row=0, column=7, padx=(0, 5))

frame_00.grid(row=0, column=0, padx=10)

Expand Down
32 changes: 31 additions & 1 deletion visual_comparison/widgets/widget_pop_ups.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def __init__(self, all_options, current_options, ctk_corner_radius):
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def _on_checkbox_checked(self):
new_list = [checkbox for checkbox in self.checkboxes if checkbox.get()]
remaining = [checkbox for checkbox in self.checkboxes if not checkbox.get()]
Expand Down Expand Up @@ -148,6 +151,9 @@ def __init__(self, title, values, ctk_corner_radius):
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_ok_pressed(self, tab):
if tab == "Range":
l_ret, lower_val = validate_number_str(self.lower_text_box.get(), desired_type=float)
Expand Down Expand Up @@ -206,6 +212,9 @@ def __init__(self, display_text, strings_to_filter, ctk_corner_radius, *args, **
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_confirm(self):
condition = self.condition_combo_box.get()
text = self.entry_box.get()
Expand Down Expand Up @@ -262,6 +271,9 @@ def __init__(self, data: List[List], column_titles: List[str], ctk_corner_radius
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def filter_options(self, column):
column_index = self.column_titles.index(column)
data_type = self.data_types[column_index]
Expand Down Expand Up @@ -342,7 +354,7 @@ def __init__(self, data: List[List], column_titles: List[str], ctk_corner_radius
search_tab = tabview.tab("Prefix")
search_label = customtkinter.CTkLabel(search_tab, text="Prefix: ")
search_label.grid(row=0, column=0, pady=5, padx=20)
self.search_entry_field = customtkinter.CTkEntry(search_tab, textvariable=entry_var, corner_radius=ctk_corner_radius, width=text_width, placeholder_text="File Path")
self.search_entry_field = customtkinter.CTkEntry(search_tab, textvariable=entry_var, corner_radius=ctk_corner_radius, width=text_width, placeholder_text="File Path (Press tab to complete word)")
self.search_entry_field.bind("<Tab>", self.on_tab_completion)
self.search_entry_field.grid(row=0, column=1, pady=5, padx=(0, 20))
jump_to_idx_button = customtkinter.CTkButton(search_tab, width=75, height=25, command=self.on_search_button, corner_radius=ctk_corner_radius, text="Show Item")
Expand All @@ -355,6 +367,9 @@ def __init__(self, data: List[List], column_titles: List[str], ctk_corner_radius
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def _unbind_entry_tab_pressed(self):
def custom_tab(event):
event.widget.tk_focusNext().focus()
Expand Down Expand Up @@ -436,6 +451,9 @@ def __init__(self, message, ctk_corner_radius, title="Warning", display_time_ms=
self.update_idletasks()
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def _on_lose_focus(self, event):
self.destroy()

Expand Down Expand Up @@ -478,6 +496,9 @@ def __init__(self, text, title, desired_type, ctk_corner_radius, lower_bound=flo
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_confirm(self):
user_input = self.entry.get()
ret, value = validate_number_str(user_input, desired_type=self.desired_type)
Expand Down Expand Up @@ -545,6 +566,9 @@ def __init__(self, root=None, selected_folder=None, ctk_corner_radius=3, *args,
if root is not None:
self.on_select_clicked(root, selected_folder)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_select_clicked(self, desired_dir=None, selected_folder=None):
# Get desired directory
if desired_dir is None:
Expand Down Expand Up @@ -669,6 +693,9 @@ def __init__(self, file_name, img_width, img_height, ctk_corner_radius, video_fp
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_export_options_changed(self, value):
if value == "Custom":
self.checkbox_options_frame.grid(row=5, column=1, padx=10)
Expand Down Expand Up @@ -733,6 +760,9 @@ def __init__(self, ctk_corner_radius, *args, **kwargs):
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_confirm(self):
self.cancelled = False
self.return_value = self.options.get()
Expand Down
142 changes: 142 additions & 0 deletions visual_comparison/widgets/widget_search_grid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from typing import List, Union

from PIL import Image
from PIL.ImageTk import PhotoImage
import tkinter
import customtkinter
import cv2

from ..utils import shift_widget_to_root_center


__all__ = [
"SearchGridPopup",
]


class SearchGridPopup(customtkinter.CTkToplevel):
def __init__(self, images, width: int, height: int, callback, default_value, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry(f"{width}x{height}")

# Internal variables
self.images = images
self.width = width
self.previous_width = width
self.rows = []
def destroy_callback(index: int):
# Basically want to destroy the popup when we click.
self._unbound_to_mousewheel()
self.destroy()
callback(index)
self.callback = destroy_callback

# Create widget objects
self.title("Grid Preview")
self.width_dropdown = customtkinter.CTkComboBox(self, values=["75", "100", "125"])
self.width_dropdown.set(str(default_value))
self.width_dropdown.configure(command=lambda value: self.populate_preview_window(row_height=value))
self.width_dropdown.pack()
self.canvas_viewport = tkinter.Canvas(self, height=height, width=width)
self.canvas_viewport.pack(fill=customtkinter.BOTH, expand=True)
self.scrollable_frame = customtkinter.CTkFrame(self.canvas_viewport)
self.scrollable_frame.bind("<Configure>", lambda e: self.canvas_viewport.configure(scrollregion=self.canvas_viewport.bbox("all")))
self.canvas_viewport.create_window((0, 0), window=self.scrollable_frame, anchor="nw")

# Misc stuff for all popups
self.update_idletasks()
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Various binds for quality of life
self.bind("<Escape>", lambda _: self.destroy())
self.bind("<Configure>", self.on_resize)
self._bound_to_mousewheel()

# Create buttons and wait for user to click
self.populate_preview_window(row_height=self.width_dropdown.get())
self.master.wait_window(self)

def on_resize(self, event):
self.width = event.width

def _bound_to_mousewheel(self):
if self.tk.call("tk", "windowingsystem") == "x11":
self.canvas_viewport.bind_all("<Button-4>", self._on_mousewheel)
self.canvas_viewport.bind_all("<Button-5>", self._on_mousewheel)
else:
self.canvas_viewport.bind_all("<MouseWheel>", self._on_mousewheel)

def _unbound_to_mousewheel(self):
self.canvas_viewport.unbind_all("<MouseWheel>")
if self.tk.call("tk", "windowingsystem") == "x11":
self.canvas_viewport.unbind_all("<Button-4>")
self.canvas_viewport.unbind_all("<Button-5>")
else:
self.canvas_viewport.unbind_all("<MouseWheel>")

def _on_mousewheel(self, *args):
if isinstance(args[0], tkinter.Event):
event = args[0]

if event.num == 4:
event.delta = 1
if event.num == 5:
event.delta = -1

scroll_amount = int(-1 * event.delta / abs(event.delta))
self.canvas_viewport.yview_scroll(scroll_amount, "units")

def populate_preview_window(
self,
border: int = 2,
row_height: Union[str, int] = 75,
) -> None:
"""
Load content
:param images: List of images to place in button
:param callback: Callback for button when clicked
:param row_height: Height of each row in the image
"""
# Destroy previous buttons
row_height = int(row_height)
for row in self.rows:
row.destroy()
self.update_idletasks()
self.rows = []

# Recreate buttons
# Each row are frames, buttons stored in frame. If button size exceeds width of popup, then create another row.
first_row = customtkinter.CTkFrame(master=self.scrollable_frame)
first_row.grid(row=0)
self.rows = [first_row]
row_count, row_width = 0, 0

for i, image in enumerate(self.images):
# Resize image to new size if not there yet
h, w, c = image.shape
if h != row_height:
scale = row_height / h
button_image = cv2.resize(image, (int(w * scale), int(h * scale)))
else:
button_image = image

# Check to ensure row does not overspill
h, w, c = button_image.shape
if row_width + w > self.width:
row = customtkinter.CTkFrame(master=self.scrollable_frame)
row.grid(row=len(self.rows))
self.rows.append(row)
row_count, row_width = 0, 0
continue

# Create button under the row
image_pil = Image.fromarray(button_image)
photo_img = PhotoImage(image_pil)
button = tkinter.Button(master=self.rows[-1], image=photo_img, command=lambda i=i: self.callback(i), borderwidth=0)
button.image = photo_img
button.grid(row=0, column=row_count, padx=0, pady=0)

# Update row variables
row_count += 1
row_width += w + border * 2
3 changes: 3 additions & 0 deletions visual_comparison/widgets/widget_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def __init__(self, configuration_path, configuration_info, icon_manager: IconMan
self.grab_set() # make other windows not clickable
shift_widget_to_root_center(parent_widget=self.master, child_widget=self)

# Escape key to close popup
self.bind("<Escape>", lambda _: self.destroy())

def on_restore_all_to_defaults(self):
for section in self.configuration_info.keys():
for key in self.configuration_info[section].keys():
Expand Down

0 comments on commit 05bf6eb

Please sign in to comment.