From f095fe89c8e9ea16aed9d1f747e6fbac09b525b3 Mon Sep 17 00:00:00 2001 From: Giteadmin Date: Sun, 10 Sep 2023 20:48:18 +0200 Subject: [PATCH] Added dropdown menu for dsn --- pyDbSearch.py | 756 +++++++++++++++++++++++++------------------------- 1 file changed, 379 insertions(+), 377 deletions(-) diff --git a/pyDbSearch.py b/pyDbSearch.py index 6f96e7e..49695b2 100644 --- a/pyDbSearch.py +++ b/pyDbSearch.py @@ -1,377 +1,379 @@ -import pyodbc -import tkinter as tk -from tkinter import ttk -from tkinter import messagebox -#import ttkbootstrap -import threading - -continue_search = True - -datatype_mappings = { - "Textual": ['char', 'varchar', 'text', 'nchar', 'nvarchar', 'ntext', 'uniqueidentifierstr'], - "Numeric": ['tinyint', 'smallint', 'int', 'bigint', 'float', 'real', 'decimal', 'numeric'], - "Date/Time": ['datetime', 'timestamp'], - "Monetary": ['money', 'smallmoney'] -} - - -def connect_and_search(dsn_entry, uid_entry, password_entry, search_entry): - global continue_search, condition - DSN = dsn_entry.get() - UID = uid_entry.get() - pwd = password_entry.get() - search_string = search_entry.get() - - # Check if any of the required fields are empty - if not (DSN and UID and pwd and search_string): - messagebox.showwarning("Warning", - "Please fill in all fields (DSN, UID, Password, and Search String) before searching.") - return - - search_type = search_type_var.get() - conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' - - results_listbox.delete(0, tk.END) # Clear the listbox before adding new results - - try: - conn = pyodbc.connect(conn_str) - cursor = conn.cursor() - - # Get domain ids based on selected datatype - datatype_category = datatype_var.get() - datatypes_for_category = datatype_mappings.get(datatype_category, []) - - if not datatypes_for_category: - messagebox.showwarning("Warning", f"No datatypes found for category: {datatype_category}") - return - - cursor.execute("SELECT DISTINCT domain_id FROM SYS.SYSDOMAIN WHERE domain_name IN ({})".format( - ', '.join(['?'] * len(datatypes_for_category))), datatypes_for_category) - domain_ids = cursor.fetchall() - - # Insert domain ids at the top of the results listbox - ids_str = ", ".join([str(id[0]) for id in domain_ids]) - results_listbox.insert(tk.END, f"Searching domain_id: {ids_str}") - results_listbox.insert(tk.END, "") # Blank line - - cursor.execute( - "SELECT user_name(creator), table_name FROM SYS.SYSTABLE WHERE table_type = 'BASE' and creator>100") - tables = cursor.fetchall() - - for owner, table_name in tables: - if not continue_search: - return # Exit the function if continue_search is False - cursor.execute( - "SELECT c.column_name, c.domain_id FROM SYS.SYSCOLUMN c JOIN SYS.SYSTABLE t ON c.table_id = t.table_id WHERE t.table_name = ? AND t.creator = user_id(?)", - table_name, owner) - columns = cursor.fetchall() - - for column in columns: - column_name, domain_id = column - - selected_datatype = datatype_var.get() - if not is_datatype(cursor, domain_id, selected_datatype): - continue - - if search_type == "Exact": - condition = f"\"{column_name}\" = ?" - elif search_type == "Like": - condition = f"\"{column_name}\" LIKE ?" - elif search_type == "Regex": - condition = f"\"{column_name}\" REGEXP ?" - search_param = search_string if search_type == "Exact" else f"%{search_string}%" - - query = f"SELECT '{owner}.{table_name}.{column_name}' AS Location, \"{column_name}\" AS Value FROM \"{owner}\".\"{table_name}\" WHERE {condition}" - - try: - cursor.execute(query, search_param) - except pyodbc.DataError: - # This can capture data errors such as trying to search a string in a numeric column. - continue - - results = cursor.fetchall() - - for result in results: - results_listbox.insert(tk.END, str(result)) - - conn.close() - except Exception as e: - messagebox.showerror("Error", str(e)) - - -class TextSpinner: - def __init__(self, master): - self.master = master - self.spinner_states = ['-', '\\', '|', '/'] - self.state = 0 - self.label = tk.Label(self.master, text=self.spinner_states[self.state], font=('Courier', 24)) - self.label.pack(pady=20) - self.update_spinner() - - def update_spinner(self): - # Update the displayed spinner state - self.state = (self.state + 1) % len(self.spinner_states) - self.label.config(text=self.spinner_states[self.state]) - # Schedule the next update - self.master.after(200, self.update_spinner) - - -def is_datatype(cursor, domain_id, datatype_category): - datatypes_for_category = datatype_mappings.get(datatype_category, []) - cursor.execute("SELECT domain_name FROM SYS.SYSDOMAIN WHERE domain_id = ?", (domain_id,)) - domain_name = cursor.fetchone()[0] - return domain_name.lower() in [datatype.lower() for datatype in datatypes_for_category] - - -def connect_and_search_thread(): - global continue_search - continue_search = True # Reset the flag each time you start a new search - - # Show the loading popup - loading_popup = tk.Toplevel(root) - loading_popup.title("Loading") - - spinner = TextSpinner(loading_popup) - - loading_label = tk.Label(loading_popup, text="Searching... Please wait.") - loading_label.pack(padx=50, pady=20) - - # Define stop_search function - def stop_search(): - global continue_search - continue_search = False - results_listbox.insert(tk.END, "Search aborted...") - - loading_popup.protocol("WM_DELETE_WINDOW", stop_search) - - loading_popup.update() - - # Run the actual search function - connect_and_search(dsn_entry, uid_entry, password_entry, search_string_entry) - - # Close the loading popup once done - loading_popup.destroy() - - -def contains_only_digits_and_symbols(search_string): - allowed_symbols = "-.:" # List of allowed symbols - for char in search_string: - if not (char.isdigit() or char in allowed_symbols): - return False - return True - - -def is_valid_input_for_datatype(search_string, datatype): - if datatype == "Textual": - return isinstance(search_string, str) - elif datatype == "Numeric": - return search_string.isdigit() - elif datatype == "Date/Time": - return contains_only_digits_and_symbols(search_string) - return True - - -def start_search(): - search_string = search_string_entry.get() - selected_datatype = datatype_var.get() - if not is_valid_input_for_datatype(search_string, selected_datatype): - messagebox.showerror("Error", - f"Entered value '{search_string}' is not valid for datatype '{selected_datatype}'") - return - - # Start the threaded search - search_thread = threading.Thread(target=connect_and_search_thread) - search_thread.start() - - -def display_company_info(): - DSN = dsn_entry.get() - UID = uid_entry.get() - pwd = password_entry.get() - conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' - - results_listbox.delete(0, tk.END) # Clear the listbox before adding new results - - try: - conn = pyodbc.connect(conn_str) - cursor = conn.cursor() - - cursor.execute("SELECT * FROM company") - results = cursor.fetchall() - - for result in results: - results_listbox.insert(tk.END, str(result)) - - cursor.close() - conn.close() - - except Exception as e: - messagebox.showerror("Error", str(e)) - - -def display_domain_ids(): - DSN = dsn_entry.get() - UID = uid_entry.get() - pwd = password_entry.get() - conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' - - results_listbox.delete(0, tk.END) # Clear the listbox before adding new results - - try: - conn = pyodbc.connect(conn_str) - cursor = conn.cursor() - - cursor.execute("SELECT * FROM SYSDOMAIN") - results = cursor.fetchall() - - for result in results: - results_listbox.insert(tk.END, str(result)) - - cursor.close() - conn.close() - - except Exception as e: - messagebox.showerror("Error", str(e)) - - -def copy_to_clipboard(): - try: - selected_line = results_listbox.get(results_listbox.curselection()) - root.clipboard_clear() - root.clipboard_append(selected_line) - root.update() # To ensure clipboard contents persist - except: - messagebox.showinfo("Info", "Please select a line to copy.") - - -def reset_results(): - results_listbox.delete(0, tk.END) - - -root = tk.Tk() -#style = ttkbootstrap.Style(theme="darkly") -root.geometry("800x600") -root.title('Search Interface') - -# Variables for entry widgets -dsn_var = tk.StringVar() -uid_var = tk.StringVar() -password_var = tk.StringVar() -search_string_var = tk.StringVar() - -# for debugging -# dsn_var.set("DBName") -# uid_var.set("DBUser") -# search_string_var.set("SearchString") - - - -def update_button_states(*args): - # Check if DSN, UID, and password have values entered - entries_filled = bool(dsn_var.get()) and bool(uid_var.get()) and bool(password_var.get()) - - # Enable/Disable "Company Info" and "Domain Id's" buttons based on the entries - company_info_button['state'] = 'normal' if entries_filled else 'disabled' - domain_ids_button['state'] = 'normal' if entries_filled else 'disabled' - - # For the "Search" button, additionally check if a search string is entered - search_button['state'] = 'normal' if entries_filled and bool(search_string_var.get()) else 'disabled' - - -# Labels and Entry widgets for DSN, UID, and password -ttk.Label(root, text="DSN:").grid(row=0, column=0, padx=20, pady=10, sticky='e') -dsn_entry = ttk.Entry(root, textvariable=dsn_var) -dsn_entry.grid(row=0, column=1, padx=20, pady=10, sticky='ew') - -ttk.Label(root, text="UID:").grid(row=1, column=0, padx=20, pady=10, sticky='e') -uid_entry = ttk.Entry(root, textvariable=uid_var) -uid_entry.grid(row=1, column=1, padx=20, pady=10, sticky='ew') - -ttk.Label(root, text="Password:").grid(row=2, column=0, padx=20, pady=10, sticky='e') -password_entry = ttk.Entry(root, show="*", textvariable=password_var) -password_entry.grid(row=2, column=1, padx=20, pady=10, sticky='ew') - -ttk.Label(root, text="Search String:").grid(row=3, column=0, padx=20, pady=10, sticky='e') -search_string_entry = ttk.Entry(root, textvariable=search_string_var) -search_string_entry.grid(row=3, column=1, padx=20, pady=10, sticky='ew') - -# Create a frame for the buttons -button_frame = ttk.Frame(root) -button_frame.grid(row=4, column=0, columnspan=3, pady=20) # Spanning 3 columns to cover the width - -# Create buttons directly within the button_frame -search_button = ttk.Button(button_frame, text="Search", command=start_search) -company_info_button = ttk.Button(button_frame, text="Company Info") -domain_ids_button = ttk.Button(button_frame, text="Domain Id's") - -# Place the buttons inside the button_frame -search_button.grid(row=0, column=0, padx=10, sticky='e') -company_info_button.grid(row=0, column=1, padx=10, sticky='w') -domain_ids_button.grid(row=0, column=2, padx=10, sticky='w') - -# Use a ttk frame to give the listbox a more refined boundary -listbox_frame = ttk.Frame(root, padding="10") -listbox_frame.grid(row=7, column=0, columnspan=3, padx=20, pady=10, sticky='nsew') - -# Stylish Listbox (you can adjust the bg, fg, and font as you like) -results_listbox = tk.Listbox(listbox_frame, bg="white", fg="black", font=("Arial", 12)) -scrollbar = ttk.Scrollbar(listbox_frame, orient="vertical", command=results_listbox.yview) -results_listbox.config(yscrollcommand=scrollbar.set) - -results_listbox.grid(row=0, column=0, sticky='nsew') -scrollbar.grid(row=0, column=1, sticky='ns') - -# Adjusting weights for dynamic resizing -listbox_frame.grid_rowconfigure(0, weight=1) -listbox_frame.grid_columnconfigure(0, weight=1) - -# Adding search type and data type selectors -search_type_var = tk.StringVar() -search_type_var.set("Exact") # Default value -search_type_options = ["Exact", "Like", "Regex"] -search_type_label = ttk.Label(root, text="Search Type:") -search_type_label.grid(row=5, column=0, padx=20, pady=10, sticky='e') -search_type_dropdown = ttk.Combobox(root, textvariable=search_type_var, values=search_type_options, state="readonly") -search_type_dropdown.grid(row=5, column=1, padx=20, pady=10, sticky='ew') - -datatype_var = tk.StringVar() -datatype_var.set("Textual") # Default value -datatype_options = ["Textual", "Numeric", "Date/Time", "Monetary"] -datatype_label = ttk.Label(root, text="Data Type:") -datatype_label.grid(row=6, column=0, padx=20, pady=10, sticky='e') -datatype_dropdown = ttk.Combobox(root, textvariable=datatype_var, values=datatype_options, state="readonly") -datatype_dropdown.grid(row=6, column=1, padx=20, pady=10, sticky='ew') - -# Weight configuration for rows and columns that should expand -root.grid_rowconfigure(7, weight=1) # This means row 7 (Listbox) will expand vertically -root.grid_columnconfigure(0, weight=1) # This means column 0 will expand horizontally -root.grid_columnconfigure(1, weight=1) # This means column 1 will expand horizontally - -# Create a frame for the bottom buttons -bottom_button_frame = ttk.Frame(root) -bottom_button_frame.grid(row=9, column=0, columnspan=3) # Assuming 3 columns span the main window - -# Buttons below the listbox -copy_button = ttk.Button(bottom_button_frame, text="Copy to Clipboard", command=copy_to_clipboard, - padding=(10, 5, 10, 5)) -reset_button = ttk.Button(bottom_button_frame, text="Reset Results", command=reset_results, padding=(10, 5, 10, 5)) - -# Place the buttons inside the bottom_button_frame -copy_button.grid(row=0, column=0, padx=10, pady=10, sticky='e') -reset_button.grid(row=0, column=1, padx=10, pady=10, sticky='w') - -# Initially disable the buttons -company_info_button['state'] = 'disabled' -domain_ids_button['state'] = 'disabled' -search_button['state'] = 'disabled' - -# Bind entry changes to the button state update function -dsn_var.trace_add('write', update_button_states) -uid_var.trace_add('write', update_button_states) -password_var.trace_add('write', update_button_states) -search_string_var.trace_add('write', update_button_states) - -company_info_button.config(command=display_company_info) -domain_ids_button.config(command=display_domain_ids) - -root.mainloop() +import pyodbc +import tkinter as tk +from tkinter import ttk +from tkinter import messagebox +#import ttkbootstrap +import threading + +continue_search = True + +datatype_mappings = { + "Textual": ['char', 'varchar', 'text', 'nchar', 'nvarchar', 'ntext', 'uniqueidentifierstr'], + "Numeric": ['tinyint', 'smallint', 'int', 'bigint', 'float', 'real', 'decimal', 'numeric'], + "Date/Time": ['datetime', 'timestamp'], + "Monetary": ['money', 'smallmoney'] +} + +def get_available_dsns(): + return list(pyodbc.dataSources().keys()) +def connect_and_search(dsn_entry, uid_entry, password_entry, search_entry): + global continue_search, condition + DSN = dsn_entry.get() + UID = uid_entry.get() + pwd = password_entry.get() + search_string = search_entry.get() + + # Check if any of the required fields are empty + if not (DSN and UID and pwd and search_string): + messagebox.showwarning("Warning", + "Please fill in all fields (DSN, UID, Password, and Search String) before searching.") + return + + search_type = search_type_var.get() + conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' + + results_listbox.delete(0, tk.END) # Clear the listbox before adding new results + + try: + conn = pyodbc.connect(conn_str) + cursor = conn.cursor() + + # Get domain ids based on selected datatype + datatype_category = datatype_var.get() + datatypes_for_category = datatype_mappings.get(datatype_category, []) + + if not datatypes_for_category: + messagebox.showwarning("Warning", f"No datatypes found for category: {datatype_category}") + return + + cursor.execute("SELECT DISTINCT domain_id FROM SYS.SYSDOMAIN WHERE domain_name IN ({})".format( + ', '.join(['?'] * len(datatypes_for_category))), datatypes_for_category) + domain_ids = cursor.fetchall() + + # Insert domain ids at the top of the results listbox + ids_str = ", ".join([str(id[0]) for id in domain_ids]) + results_listbox.insert(tk.END, f"Searching domain_id: {ids_str}") + results_listbox.insert(tk.END, "") # Blank line + + cursor.execute( + "SELECT user_name(creator), table_name FROM SYS.SYSTABLE WHERE table_type = 'BASE' and creator>100") + tables = cursor.fetchall() + + for owner, table_name in tables: + if not continue_search: + return # Exit the function if continue_search is False + cursor.execute( + "SELECT c.column_name, c.domain_id FROM SYS.SYSCOLUMN c JOIN SYS.SYSTABLE t ON c.table_id = t.table_id WHERE t.table_name = ? AND t.creator = user_id(?)", + table_name, owner) + columns = cursor.fetchall() + + for column in columns: + column_name, domain_id = column + + selected_datatype = datatype_var.get() + if not is_datatype(cursor, domain_id, selected_datatype): + continue + + if search_type == "Exact": + condition = f"\"{column_name}\" = ?" + elif search_type == "Like": + condition = f"\"{column_name}\" LIKE ?" + elif search_type == "Regex": + condition = f"\"{column_name}\" REGEXP ?" + search_param = search_string if search_type == "Exact" else f"%{search_string}%" + + query = f"SELECT '{owner}.{table_name}.{column_name}' AS Location, \"{column_name}\" AS Value FROM \"{owner}\".\"{table_name}\" WHERE {condition}" + + try: + cursor.execute(query, search_param) + except pyodbc.DataError: + # This can capture data errors such as trying to search a string in a numeric column. + continue + + results = cursor.fetchall() + + for result in results: + results_listbox.insert(tk.END, str(result)) + + conn.close() + except Exception as e: + messagebox.showerror("Error", str(e)) + + +class TextSpinner: + def __init__(self, master): + self.master = master + self.spinner_states = ['-', '\\', '|', '/'] + self.state = 0 + self.label = tk.Label(self.master, text=self.spinner_states[self.state], font=('Courier', 24)) + self.label.pack(pady=20) + self.update_spinner() + + def update_spinner(self): + # Update the displayed spinner state + self.state = (self.state + 1) % len(self.spinner_states) + self.label.config(text=self.spinner_states[self.state]) + # Schedule the next update + self.master.after(200, self.update_spinner) + + +def is_datatype(cursor, domain_id, datatype_category): + datatypes_for_category = datatype_mappings.get(datatype_category, []) + cursor.execute("SELECT domain_name FROM SYS.SYSDOMAIN WHERE domain_id = ?", (domain_id,)) + domain_name = cursor.fetchone()[0] + return domain_name.lower() in [datatype.lower() for datatype in datatypes_for_category] + + +def connect_and_search_thread(): + global continue_search + continue_search = True # Reset the flag each time you start a new search + + # Show the loading popup + loading_popup = tk.Toplevel(root) + loading_popup.title("Loading") + + spinner = TextSpinner(loading_popup) + + loading_label = tk.Label(loading_popup, text="Searching... Please wait.") + loading_label.pack(padx=50, pady=20) + + # Define stop_search function + def stop_search(): + global continue_search + continue_search = False + results_listbox.insert(tk.END, "Search aborted...") + + loading_popup.protocol("WM_DELETE_WINDOW", stop_search) + + loading_popup.update() + + # Run the actual search function + connect_and_search(dsn_entry, uid_entry, password_entry, search_string_entry) + + # Close the loading popup once done + loading_popup.destroy() + + +def contains_only_digits_and_symbols(search_string): + allowed_symbols = "-.:" # List of allowed symbols + for char in search_string: + if not (char.isdigit() or char in allowed_symbols): + return False + return True + + +def is_valid_input_for_datatype(search_string, datatype): + if datatype == "Textual": + return isinstance(search_string, str) + elif datatype == "Numeric": + return search_string.isdigit() + elif datatype == "Date/Time": + return contains_only_digits_and_symbols(search_string) + return True + + +def start_search(): + search_string = search_string_entry.get() + selected_datatype = datatype_var.get() + if not is_valid_input_for_datatype(search_string, selected_datatype): + messagebox.showerror("Error", + f"Entered value '{search_string}' is not valid for datatype '{selected_datatype}'") + return + + # Start the threaded search + search_thread = threading.Thread(target=connect_and_search_thread) + search_thread.start() + + +def display_company_info(): + DSN = dsn_entry.get() + UID = uid_entry.get() + pwd = password_entry.get() + conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' + + results_listbox.delete(0, tk.END) # Clear the listbox before adding new results + + try: + conn = pyodbc.connect(conn_str) + cursor = conn.cursor() + + cursor.execute("SELECT * FROM company") + results = cursor.fetchall() + + for result in results: + results_listbox.insert(tk.END, str(result)) + + cursor.close() + conn.close() + + except Exception as e: + messagebox.showerror("Error", str(e)) + + +def display_domain_ids(): + DSN = dsn_entry.get() + UID = uid_entry.get() + pwd = password_entry.get() + conn_str = f'DSN={DSN};UID={UID};pwd={pwd}' + + results_listbox.delete(0, tk.END) # Clear the listbox before adding new results + + try: + conn = pyodbc.connect(conn_str) + cursor = conn.cursor() + + cursor.execute("SELECT * FROM SYSDOMAIN") + results = cursor.fetchall() + + for result in results: + results_listbox.insert(tk.END, str(result)) + + cursor.close() + conn.close() + + except Exception as e: + messagebox.showerror("Error", str(e)) + + +def copy_to_clipboard(): + try: + selected_line = results_listbox.get(results_listbox.curselection()) + root.clipboard_clear() + root.clipboard_append(selected_line) + root.update() # To ensure clipboard contents persist + except: + messagebox.showinfo("Info", "Please select a line to copy.") + + +def reset_results(): + results_listbox.delete(0, tk.END) + + +root = tk.Tk() +#style = ttkbootstrap.Style(theme="darkly") +root.geometry("800x600") +root.title('Search Interface') + +# Variables for entry widgets +dsn_var = tk.StringVar() +available_dsns = get_available_dsns() +uid_var = tk.StringVar() +password_var = tk.StringVar() +search_string_var = tk.StringVar() + +# for debugging +# dsn_var.set("DBName") +# uid_var.set("DBUser") +# search_string_var.set("SearchString") + + + +def update_button_states(*args): + # Check if DSN, UID, and password have values entered + entries_filled = bool(dsn_var.get()) and bool(uid_var.get()) and bool(password_var.get()) + + # Enable/Disable "Company Info" and "Domain Id's" buttons based on the entries + company_info_button['state'] = 'normal' if entries_filled else 'disabled' + domain_ids_button['state'] = 'normal' if entries_filled else 'disabled' + + # For the "Search" button, additionally check if a search string is entered + search_button['state'] = 'normal' if entries_filled and bool(search_string_var.get()) else 'disabled' + + +# Labels and Entry widgets for DSN, UID, and password +ttk.Label(root, text="DSN:").grid(row=0, column=0, padx=20, pady=10, sticky='e') +dsn_entry = ttk.Combobox(root, textvariable=dsn_var, values=available_dsns, state="readonly") +dsn_entry.grid(row=0, column=1, padx=20, pady=10, sticky='ew') + +ttk.Label(root, text="UID:").grid(row=1, column=0, padx=20, pady=10, sticky='e') +uid_entry = ttk.Entry(root, textvariable=uid_var) +uid_entry.grid(row=1, column=1, padx=20, pady=10, sticky='ew') + +ttk.Label(root, text="Password:").grid(row=2, column=0, padx=20, pady=10, sticky='e') +password_entry = ttk.Entry(root, show="*", textvariable=password_var) +password_entry.grid(row=2, column=1, padx=20, pady=10, sticky='ew') + +ttk.Label(root, text="Search String:").grid(row=3, column=0, padx=20, pady=10, sticky='e') +search_string_entry = ttk.Entry(root, textvariable=search_string_var) +search_string_entry.grid(row=3, column=1, padx=20, pady=10, sticky='ew') + +# Create a frame for the buttons +button_frame = ttk.Frame(root) +button_frame.grid(row=4, column=0, columnspan=3, pady=20) # Spanning 3 columns to cover the width + +# Create buttons directly within the button_frame +search_button = ttk.Button(button_frame, text="Search", command=start_search) +company_info_button = ttk.Button(button_frame, text="Company Info") +domain_ids_button = ttk.Button(button_frame, text="Domain Id's") + +# Place the buttons inside the button_frame +search_button.grid(row=0, column=0, padx=10, sticky='e') +company_info_button.grid(row=0, column=1, padx=10, sticky='w') +domain_ids_button.grid(row=0, column=2, padx=10, sticky='w') + +# Use a ttk frame to give the listbox a more refined boundary +listbox_frame = ttk.Frame(root, padding="10") +listbox_frame.grid(row=7, column=0, columnspan=3, padx=20, pady=10, sticky='nsew') + +# Stylish Listbox (you can adjust the bg, fg, and font as you like) +results_listbox = tk.Listbox(listbox_frame, bg="white", fg="black", font=("Arial", 12)) +scrollbar = ttk.Scrollbar(listbox_frame, orient="vertical", command=results_listbox.yview) +results_listbox.config(yscrollcommand=scrollbar.set) + +results_listbox.grid(row=0, column=0, sticky='nsew') +scrollbar.grid(row=0, column=1, sticky='ns') + +# Adjusting weights for dynamic resizing +listbox_frame.grid_rowconfigure(0, weight=1) +listbox_frame.grid_columnconfigure(0, weight=1) + +# Adding search type and data type selectors +search_type_var = tk.StringVar() +search_type_var.set("Exact") # Default value +search_type_options = ["Exact", "Like", "Regex"] +search_type_label = ttk.Label(root, text="Search Type:") +search_type_label.grid(row=5, column=0, padx=20, pady=10, sticky='e') +search_type_dropdown = ttk.Combobox(root, textvariable=search_type_var, values=search_type_options, state="readonly") +search_type_dropdown.grid(row=5, column=1, padx=20, pady=10, sticky='ew') + +datatype_var = tk.StringVar() +datatype_var.set("Textual") # Default value +datatype_options = ["Textual", "Numeric", "Date/Time", "Monetary"] +datatype_label = ttk.Label(root, text="Data Type:") +datatype_label.grid(row=6, column=0, padx=20, pady=10, sticky='e') +datatype_dropdown = ttk.Combobox(root, textvariable=datatype_var, values=datatype_options, state="readonly") +datatype_dropdown.grid(row=6, column=1, padx=20, pady=10, sticky='ew') + +# Weight configuration for rows and columns that should expand +root.grid_rowconfigure(7, weight=1) # This means row 7 (Listbox) will expand vertically +root.grid_columnconfigure(0, weight=1) # This means column 0 will expand horizontally +root.grid_columnconfigure(1, weight=1) # This means column 1 will expand horizontally + +# Create a frame for the bottom buttons +bottom_button_frame = ttk.Frame(root) +bottom_button_frame.grid(row=9, column=0, columnspan=3) # Assuming 3 columns span the main window + +# Buttons below the listbox +copy_button = ttk.Button(bottom_button_frame, text="Copy to Clipboard", command=copy_to_clipboard, + padding=(10, 5, 10, 5)) +reset_button = ttk.Button(bottom_button_frame, text="Reset Results", command=reset_results, padding=(10, 5, 10, 5)) + +# Place the buttons inside the bottom_button_frame +copy_button.grid(row=0, column=0, padx=10, pady=10, sticky='e') +reset_button.grid(row=0, column=1, padx=10, pady=10, sticky='w') + +# Initially disable the buttons +company_info_button['state'] = 'disabled' +domain_ids_button['state'] = 'disabled' +search_button['state'] = 'disabled' + +# Bind entry changes to the button state update function +dsn_var.trace_add('write', update_button_states) +uid_var.trace_add('write', update_button_states) +password_var.trace_add('write', update_button_states) +search_string_var.trace_add('write', update_button_states) + +company_info_button.config(command=display_company_info) +domain_ids_button.config(command=display_domain_ids) + +root.mainloop()