Skip to content

Commit

Permalink
Add search function for Config Editor (#424)
Browse files Browse the repository at this point in the history
## Description

We add search button on the user interface to enhance the usability.

![image](https://github.com/user-attachments/assets/fe3d464a-7683-4242-95ea-e680169b892b)
It would be helpful when the number of knobs is large.

For details on how to complete these options and their meaning refer to
[CONTRIBUTING.md](https://github.com/microsoft/mu/blob/HEAD/CONTRIBUTING.md).

- [ ] Impacts functionality?
- [ ] Impacts security?
- [ ] Breaking change?
- [ ] Includes tests?
- [ ] Includes documentation?

## How This Was Tested

Open a config xml file and then search for the key words you want

## Integration Instructions

None
  • Loading branch information
ray007330 authored Dec 13, 2024
1 parent 6fc0a9c commit ad2d487
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 2 deletions.
171 changes: 170 additions & 1 deletion SetupDataPkg/Tools/ConfigEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,35 @@ def __init__(self, master=None):

root.geometry("1200x800")

# Add a frame for the top search bar
top_frame = tkinter.Frame(root)
top_frame.pack(side=tkinter.TOP, fill=tkinter.X, padx=10, pady=5)

# Add search label and entry
search_label = tkinter.Label(top_frame, text="Search:")
search_label.pack(side=tkinter.LEFT)

self.search_active = False
self.search_var = tkinter.StringVar()
search_entry = tkinter.Entry(top_frame, textvariable=self.search_var)
search_entry.pack(side=tkinter.LEFT, fill=tkinter.X, expand=True)
search_entry.bind('<Return>', lambda event: self.search_widget_label())

search_button = tkinter.Button(top_frame, text="Search", command=self.search_widget_label)
search_button.pack(side=tkinter.LEFT, padx=5)

self.match_label = tkinter.Label(top_frame, text="")
self.match_label.pack(side=tkinter.LEFT, padx=5)

up_button = tkinter.Button(top_frame, text="↑", command=self.search_previous)
up_button.pack(side=tkinter.LEFT)

down_button = tkinter.Button(top_frame, text="↓", command=self.search_next)
down_button.pack(side=tkinter.LEFT, padx=(0, 5))

clear_button = tkinter.Button(top_frame, text="Clear", command=self.clear_search)
clear_button.pack(side=tkinter.LEFT, padx=5)

paned = ttk.Panedwindow(root, orient=tkinter.HORIZONTAL)
paned.pack(fill=tkinter.BOTH, expand=True, padx=(4, 4))

Expand Down Expand Up @@ -616,8 +645,146 @@ def __init__(self, master=None):
idx += 1
self.load_cfg_file(sub_path, idx, False)

def search_widget_label(self):
self.current_matches = []
self.current_match_index = -1

def expand_tree_to_page(page_id_list):
leaf_list = []
for item in self.left.get_children():
for child in self.left.get_children(item):
for leaf in self.left.get_children(child):
leaf_list.append(leaf)
sorted_page_id_list = sorted(page_id_list, key=lambda x: leaf_list.index(x))
first_page_id = sorted_page_id_list[0]
self.left.selection_set(first_page_id)
self.left.see(first_page_id)

def find_matching_knob_name(data, struct_name):
return next((knob.name for knob in data.schema.knobs if knob.type == struct_name), False)

def highlight_left_tree_label(page_id_list):
self.left.tag_configure("highlight", background="yellow")
for item in self.left.get_children():
for child in self.left.get_children(item):
for leaf in self.left.get_children(child):
if leaf in page_id_list:
self.left.item(leaf, tags="highlight")
self.left.see(leaf)

def search_in_config_data(search_term):
search_term = search_term.lower()
page_id_list = set()
for cfg_data in self.cfg_data_list.values():
data = cfg_data.cfg_data_obj
for struct in data.schema.structs:
for member in struct.members:
name = (member.pretty_name or member.name).lower()
if search_term in name:
knob_name = find_matching_knob_name(data, struct.name)
if knob_name:
page_id = f"{data.schema.knobs[0].namespace}.{knob_name}"
page_id_list.add(page_id)
return list(page_id_list)

search_term = self.search_var.get().lower()
if not search_term:
print("'Search term' is empty.")
return

self.clear_search(clear_search_bar=False)
search_result = search_in_config_data(search_term)
print(f"search result: {search_result}")
if search_result:
self.remove_highlight_in_left_tree()
self.search_active = True
expand_tree_to_page(search_result)
highlight_left_tree_label(search_result)
self.build_config_data_page(search_result[0])
print(f"'{search_term}' found.")
else:
print(f"'{search_term}' not found.")
self.output_current_status(f"search term '{search_term}' not found.")

def remove_highlight_in_left_tree(self):
for item in self.left.get_children():
for child in self.left.get_children(item):
for leaf in self.left.get_children(child):
self.left.item(leaf, tags=())

def clear_search(self, clear_search_bar=True):
if clear_search_bar:
self.search_var.set('')
self.remove_highlight_in_left_tree()
self.reset_widget_backgrounds()
self.master.focus_set()
self.search_active = False
self.current_matches = []
self.current_match_index = -1
self.match_label.config(text="")

def reset_widget_backgrounds(self):
for widget in self.right_grid.winfo_children()[2::2]:
if isinstance(widget, tkinter.Entry) or isinstance(widget, tkinter.Label):
widget.configure(background=self.cget("background"))

def highlight_label(self):
search_term = self.search_var.get().lower()
self.current_matches = [
widget for widget in self.right_grid.winfo_children()[2::2]
if search_term in widget.cget('text').lower()
]

for widget in self.current_matches:
widget.configure(background="yellow")

if self.current_matches:
self.current_match_index = 0
self.highlight_current_entry()
else:
self.match_label.config(text="")

def highlight_current_entry(self):
if not self.current_matches:
return

widget_list = self.right_grid.winfo_children()
for widget in widget_list[3::2]:
if hasattr(widget, 'configure'):
widget.configure(background=self.cget("background"))

current_widget = self.current_matches[self.current_match_index]
label_index = widget_list.index(current_widget)
entry_widget = widget_list[label_index + 1]

if hasattr(entry_widget, 'selection_range'):
entry_value = entry_widget.get()
entry_widget.selection_range(0, len(entry_value))
entry_widget.focus_set()

self.conf_canvas.update_idletasks()
widget_y = current_widget.winfo_y()
new_scroll = max(0, min(1, widget_y / self.right_grid.winfo_height()))
self.conf_canvas.update()
self.conf_canvas.yview_moveto(new_scroll)

self.match_label.config(text=f"{self.current_match_index + 1}/{len(self.current_matches)}")

def search_previous(self):
if not self.search_active or not self.current_matches:
return

self.current_match_index = (self.current_match_index - 1) % len(self.current_matches)
self.highlight_current_entry()

def search_next(self):
if not self.search_active or not self.current_matches:
return

self.current_match_index = (self.current_match_index + 1) % len(self.current_matches)
self.highlight_current_entry()

def set_object_name(self, widget, name, file_id):
# associate the name of the widget with the file it came from, in case of name conflicts
self.conf_list[id(widget)] = (name, file_id)

def get_object_name(self, widget):
Expand Down Expand Up @@ -783,6 +950,8 @@ def build_config_data_page(self, page_id):
for item in disp_list:
self.add_config_item(item, row, self.page_cfg_map[page_id])
row += 2
if self.search_active:
self.highlight_label()

def load_config_data(self, file_name):
if file_name.endswith('.xml'):
Expand Down
2 changes: 1 addition & 1 deletion SetupDataPkg/Tools/VariableList.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ def __init__(self, schema, xml_node, namespace):
if data_type == "":
raise ParseError(
"Knob '{}' does not have a type".format(self.name))

self.type = data_type
# Look up the format using the schema
# The format may be a built-in format (e.g. 'uint32_t') or a
# user defined enum or struct
Expand Down

0 comments on commit ad2d487

Please sign in to comment.