Skip to content

Commit fc9c723

Browse files
authored
Merge pull request #49 from shaunhwq/feature_search
Feature search
2 parents 0a8894a + 64204db commit fc9c723

File tree

9 files changed

+321
-83
lines changed

9 files changed

+321
-83
lines changed

visual_comparison/configurations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"cv2.INTER_NEAREST",
2020
"cv2.INTER_CUBIC",
2121
]
22+
DISPLAY_INTERPOLATION_TYPES = CV2_INTERPOLATION_TYPES + ["None"]
23+
ZOOM_INTERPOLATION_TYPES = CV2_INTERPOLATION_TYPES
2224

2325
# Contains information on config.ini file, e.g. how to parse, default values for each. Possible options.
2426
config_info = dict(
@@ -27,12 +29,12 @@
2729
theme=dict(obj="options", type=str, values=["blue", "green", "dark-blue"], default="dark-blue"),
2830
),
2931
Display=dict(
30-
interpolation_type=dict(obj="options", type=eval, values=CV2_INTERPOLATION_TYPES, default="cv2.INTER_LINEAR"),
32+
interpolation_type=dict(obj="options", type=eval, values=DISPLAY_INTERPOLATION_TYPES, default="cv2.INTER_LINEAR"),
3133
fast_loading_threshold_ms=dict(obj="entry", type=int, default=100),
3234
ctk_corner_radius=dict(obj="entry", type=int, default=3),
3335
),
3436
Zoom=dict(
35-
interpolation_type=dict(obj="options", type=eval, values=CV2_INTERPOLATION_TYPES, default="cv2.INTER_NEAREST"),
37+
interpolation_type=dict(obj="options", type=eval, values=ZOOM_INTERPOLATION_TYPES, default="cv2.INTER_NEAREST"),
3638
),
3739
Functionality=dict(
3840
max_fps=dict(obj="entry", type=int, default=60),
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .image_utils import *
22
from .file_utils import *
3+
from .trie import *
34
from .utils import *
4-
from .widgets import *
5+
from .widgets import *

visual_comparison/utils/trie.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from typing import List
2+
3+
4+
__all__ = ["SearchTrie"]
5+
6+
7+
class TrieNode:
8+
def __init__(self):
9+
self.children = {}
10+
self.indices = []
11+
12+
13+
class SearchTrie:
14+
def __init__(self, strings: List[str]):
15+
self.trie = TrieNode()
16+
self.strings = []
17+
18+
for string in strings:
19+
self.add(string)
20+
21+
def reset(self):
22+
self.trie = TrieNode()
23+
24+
def add(self, string):
25+
str_idx = len(self.strings)
26+
self.strings.append(string)
27+
28+
current_node = self.trie
29+
for character in string:
30+
node = current_node.children.get(character, TrieNode())
31+
node.indices.append(str_idx)
32+
current_node.children[character] = node
33+
current_node = node
34+
35+
def search(self, prefix):
36+
current_node = self.trie
37+
for character in prefix:
38+
node = current_node.children.get(character, TrieNode())
39+
if node is None:
40+
return node
41+
current_node = node
42+
return current_node
43+
44+
def tab_completion(self, prefix):
45+
"""
46+
Get common string for children nodes starting with prefix
47+
48+
e.g. abc_123_def, abc_123_ijk, abc_123_lmn
49+
prefix = 'abc', returns '_123_'
50+
51+
:param prefix: Prefix to look for
52+
:return: Common string for child nodes with prefix
53+
"""
54+
current_node = self.search(prefix)
55+
56+
output_string = ""
57+
while len(current_node.children) == 1:
58+
character, node = list(current_node.children.items())[0]
59+
output_string += character
60+
current_node = node
61+
return output_string

visual_comparison/utils/widgets.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import customtkinter
22
import tkinter
3+
import tkinter.ttk as ttk
34

45

5-
__all__ = ["shift_widget_to_root_center", "set_appearance_mode_and_theme", "create_tool_tip", "is_window_in_background"]
6+
__all__ = [
7+
"shift_widget_to_root_center",
8+
"set_appearance_mode_and_theme",
9+
"create_tool_tip",
10+
"is_window_in_background",
11+
"set_tkinter_widgets_appearance_mode",
12+
]
613

714

815
def shift_widget_to_root_center(parent_widget, child_widget):
@@ -73,3 +80,24 @@ def is_window_in_background(window):
7380
return window.focus_displayof() is None
7481
except KeyError as e:
7582
return True
83+
84+
85+
def set_tkinter_widgets_appearance_mode(ctk_root) -> None:
86+
"""
87+
Sets appearance mode of tkinter objects to match the color scheme of customtkinter objects
88+
:param ctk_root: customtkinter.CTk() object
89+
"""
90+
bg_color = ctk_root._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"])
91+
text_color = ctk_root._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
92+
selected_color = ctk_root._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["fg_color"])
93+
94+
# bg color is light and selected color is either blue or dark-blue
95+
if bg_color in ["gray86", "gray90"] and selected_color in ["#3a7ebf", "#3B8ED0"]:
96+
selected_color = "royal blue"
97+
98+
tree_style = ttk.Style()
99+
tree_style.theme_use('default')
100+
tree_style.configure("Treeview", background=bg_color, foreground=text_color, fieldbackground=bg_color, borderwidth=0.5, font=('Calibri', 10, 'bold'))
101+
tree_style.map('Treeview', background=[('selected', bg_color)], foreground=[('selected', selected_color)])
102+
tree_style.configure("Treeview.Heading", background=bg_color, foreground=text_color, fieldbackground=bg_color, borderwidth=0.5)
103+
tree_style.map('Treeview.Heading', background=[('selected', bg_color)], foreground=[('selected', selected_color)])

visual_comparison/visual_comparison_app.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .widgets import DisplayWidget, ControlButtonsWidget, PreviewWidget, VideoControlsWidget
1717
from .widgets import MultiSelectPopUpWidget, DataSelectionPopup, MessageBoxPopup, GetNumberBetweenRangePopup, RootSelectionPopup, ExportVideoPopup, ExportSelectionPopup, ProgressBarPopup, SettingsPopupWidget
1818
from .enums import VCModes, VCState
19-
from .utils import image_utils, set_appearance_mode_and_theme, file_reader, is_window_in_background
19+
from .utils import image_utils, set_appearance_mode_and_theme, file_reader, is_window_in_background, set_tkinter_widgets_appearance_mode
2020
from .configurations import read_config, parse_config, config_info
2121

2222

@@ -43,6 +43,7 @@ def __init__(self, root=None, preview_folder=None, config_path="visual_compariso
4343
self.configurations = parse_config(read_config(config_path))
4444

4545
set_appearance_mode_and_theme(self.configurations["Appearance"]["mode"], self.configurations["Appearance"]["theme"])
46+
set_tkinter_widgets_appearance_mode(self)
4647

4748
self.root = root
4849
self.preview_folder = preview_folder
@@ -163,6 +164,7 @@ def on_change_settings(self):
163164
self.configurations = new_config
164165
self.bind_keys_to_buttons(prev_config)
165166
set_appearance_mode_and_theme(new_config["Appearance"]["mode"], new_config["Appearance"]["theme"])
167+
set_tkinter_widgets_appearance_mode(self)
166168

167169
def on_change_dir(self):
168170
self.on_pause(paused=True)
@@ -276,11 +278,30 @@ def on_filter_files(self):
276278

277279
# Get data from popup
278280
popup = DataSelectionPopup(self.content_handler.data, column_titles=titles, text_width=text_width, ctk_corner_radius=self.configurations["Display"]["ctk_corner_radius"])
279-
is_cancelled, rows = popup.get_input()
281+
is_cancelled, (action, data) = popup.get_input()
280282

281283
if is_cancelled:
282284
return
283285

286+
if action not in ["search", "filter"]:
287+
raise NotImplementedError(f"Unknown action: {action}")
288+
289+
if action == "search":
290+
search_name = data
291+
292+
# Index returned by search index is for displayed items, so we need to find which items are displayed
293+
try:
294+
selected_index = self.content_handler.current_files.index(search_name)
295+
except ValueError:
296+
msg_popup = MessageBoxPopup(f"Unable to change to '{search_name}', item not in current view", self.configurations["Display"]["ctk_corner_radius"])
297+
msg_popup.wait()
298+
return
299+
300+
self.on_specify_index(selected_index)
301+
return
302+
303+
# Filter action
304+
rows = data
284305
if len(rows) == 0:
285306
msg_popup = MessageBoxPopup("No items selected. Ignoring selection", self.configurations["Display"]["ctk_corner_radius"])
286307
msg_popup.wait()
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from .widget_control_buttons import *
12
from .widget_display import *
23
from .widget_pop_ups import *
3-
from .widget_control_buttons import *
44
from .widget_preview import *
5-
from .widget_video_controls import *
65
from .widget_settings import *
6+
from .widget_settings import *
7+
from .widget_video_controls import *

visual_comparison/widgets/widget_display.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import platform
2+
from typing import Optional
23

34
import cv2
45
import numpy as np
@@ -21,7 +22,7 @@ def __init__(self, *args, **kwargs):
2122
self.image_label.bind("<Motion>", self.on_mouse_move)
2223
self.scale = 1
2324

24-
def update_image(self, image: np.array, interpolation: int):
25+
def update_image(self, image: np.array, interpolation: Optional[int]):
2526
"""
2627
Update display widget with new image
2728
:param image: Image to display
@@ -30,15 +31,20 @@ def update_image(self, image: np.array, interpolation: int):
3031
"""
3132
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
3233
h, w, _ = image.shape
34+
3335
# Different systems have different borders which use part of the screen for their display (dock etc)
34-
h_multiplier = 0.75 if platform.system() == "Darwin" else 0.8
35-
screen_h, screen_w = self.master.winfo_screenheight() * h_multiplier, self.master.winfo_screenwidth()
36-
if w > screen_w or h > screen_h:
37-
self.scale = 1 / max(w / screen_w, h / screen_h)
38-
image = image_utils.resize_scale(image, scale=self.scale, interpolation=interpolation)
39-
h, w, _ = image.shape
40-
else:
36+
if interpolation is None:
4137
self.scale = 1
38+
else:
39+
h_multiplier = 0.75 if platform.system() == "Darwin" else 0.8
40+
screen_h, screen_w = self.master.winfo_screenheight() * h_multiplier, self.master.winfo_screenwidth()
41+
if w > screen_w or h > screen_h:
42+
self.scale = 1 / max(w / screen_w, h / screen_h)
43+
image = image_utils.resize_scale(image, scale=self.scale, interpolation=interpolation)
44+
h, w, _ = image.shape
45+
else:
46+
self.scale = 1
47+
4248
pil_image = Image.fromarray(image)
4349
ctk_image = customtkinter.CTkImage(light_image=pil_image, size=(w, h))
4450
self.image_label.configure(image=ctk_image, width=w, height=h)

0 commit comments

Comments
 (0)