import tkinter as tk class SquareWindow: def __init__(self, config, path_info=None, path_mapping=None): # Configuration: [(parent_number, count), ...] # (None, 11) means draw 11 squares inside square #0 (the main square) # (2, 7) means draw 7 squares inside square #2 # path_info: Optional dict mapping square numbers to file/folder names # path_mapping: Optional dict mapping square numbers to file paths self.config = config self.path_info = path_info if path_info is not None else {} self.number_to_path = path_mapping if path_mapping is not None else {} # Set window size to 1000x1000 pixels (needed for calculate_squares) self.window_size = 1000 # Calculate squares based on config self.squares = self.calculate_squares() # Create the main window self.root = tk.Tk() self.root.title("Square Window") self.root.geometry(f"{self.window_size}x{self.window_size}") # Create a canvas to draw on self.canvas = tk.Canvas(self.root, width=self.window_size, height=self.window_size, bg="white") self.canvas.pack(fill=tk.BOTH, expand=True) # Property to store square information: {number: (x, y, size, parent_number)} self.square_positions = {} # Store original colors for flashing effect self.original_colors = {} # Draw all squares based on the calculated squares dictionary for number, (x, y, size, parent_number) in self.squares.items(): self.draw_square_at_location(x, y, size, number, parent_number) # Print the square positions dictionary output_content = f"Square positions: {self.square_positions}" print(output_content) # Save the output to print.log file with open("print.log", "w", encoding="utf-8") as f: f.write(output_content) # Initialize clipboard monitoring for file content search self.highlighted_squares = set() self.all_highlighted_squares = set() # Track all previously highlighted squares self.check_clipboard_content() def calculate_squares(self): """ Calculate all squares based on the config. Config format: [(parent_number, count), ...] (None, count) means draw 'count' squares inside the main square (number 0) (number, count) means draw 'count' squares inside square with 'number' """ import math # Initialize the main square (number 0) main_square_size = int(self.window_size * 0.9) # 90% of 1000 = 900 margin = (self.window_size - main_square_size) // 2 # (1000 - 900) / 2 = 50 # Dictionary to store all squares: {number: (x, y, size, parent_number)} squares = {} # Add the main square (number 0) squares[0] = (margin, margin, main_square_size, None) # Track the next available number for new squares next_number = 1 # Process each configuration entry for parent_number, count in self.config: if parent_number is None: # Draw 'count' squares inside the main square (number 0) parent_num = 0 else: parent_num = parent_number # Get the parent square's info if parent_num in squares: parent_x, parent_y, parent_size, _ = squares[parent_num] # Handle case where count is 0 if count <= 0: continue # Skip if there are no items to draw # Calculate grid size for the child squares n = math.ceil(math.sqrt(count)) # Number of rows/columns needed child_size = (parent_size // n) - 2 # Size of each child square minus padding # Calculate spacing to distribute squares evenly with gaps total_used_space = n * child_size # Total space occupied by squares remaining_space = parent_size - total_used_space # Remaining space for gaps gap = remaining_space // (n + 1) # Gap between squares and margins # Draw 'count' child squares inside the parent square squares_drawn = 0 for i in range(n): # Rows for j in range(n): # Columns if squares_drawn >= count: break # Calculate position for each child square with proper spacing child_x = parent_x + gap + j * (child_size + gap) child_y = parent_y + gap + i * (child_size + gap) # Add the child square to the dictionary squares[next_number] = (child_x, child_y, child_size, parent_num) next_number += 1 squares_drawn += 1 if squares_drawn >= count: break return squares def draw_square_at_location(self, x, y, size, number, parent_number): """ Draw a square at a specific location with a number in the center. Args: x: X coordinate of the top-left corner y: Y coordinate of the top-left corner size: Size of the square (width and height) number: Number to display in the center parent_number: The number of the parent square (None if no parent) """ # Get the name associated with this square number if available name = self.path_info.get(number, str(number)) # Calculate the level of the square (how deep it is in the hierarchy) level = self.get_square_level(number) # Define light colors for different levels (with transparency effect) colors = [ "#E0E0E0", # Level 0 (light gray, main square) "#FFCCCC", # Level 1 (light red) "#CCE5FF", # Level 2 (light blue) "#CCFFCC", # Level 3 (light green) "#E5CCFF", # Level 4 (light purple) "#FFE5CC", # Level 5 (light orange) "#D9CCAA", # Level 6 (light brown) "#FFD9E5", # Level 7 (light pink) "#FFFFCC", # Level 8 (light yellow) "#CCFFFF" # Level 9+ (light cyan) ] # Select color based on level color = colors[level] if level < len(colors) else "gray" # Draw the square with light colored fill (simulating transparency) and darker outline outline_color = color.replace("#", "") # Get hex color without # # Darken the outline color by reducing RGB values if len(outline_color) == 6: # Convert hex to RGB, then darken r = max(0, int(outline_color[0:2], 16) - 60) g = max(0, int(outline_color[2:4], 16) - 60) b = max(0, int(outline_color[4:6], 16) - 60) dark_outline = f"#{r:02x}{g:02x}{b:02x}" else: dark_outline = "black" # fallback # Store the original color for flashing self.original_colors[number] = color # Create a semi-transparent effect using stipple pattern # This creates a checkerboard-like pattern that simulates transparency rect_id = self.canvas.create_rectangle(x, y, x + size, y + size, outline=dark_outline, fill=color, width=1, stipple='gray50') # Tag the rectangle with the number for easy identification self.canvas.addtag_withtag(f"square_{number}", rect_id) # Draw a line segment at the bottom edge of the square (inside the square) only if size >= 50 if size >= 50: line_y = y + size - 20 # Just above the bottom edge of the square self.canvas.create_line(x, line_y, x + size, line_y, fill="black", width=1) # Draw the name between the square and the line name_y = y + size - 10 # Position the name between square and line self.canvas.create_text(x + size // 2, name_y, text=name, fill="black", font=("Arial", 8)) # Record the square's position information self.square_positions[number] = (x, y, size, parent_number) def get_square_level(self, number): """ Calculate the level of a square in the hierarchy. Level 0: Main square (number 0) Level 1: Direct children of main square Level 2: Children of level 1 squares etc. """ if number == 0: return 0 level = 1 # Start at level 1 since we're past the main square current_parent = self.squares[number][3] # Get parent number from (x, y, size, parent_number) while current_parent is not None: level += 1 if current_parent == 0: break current_parent = self.squares[current_parent][3] # Get parent's parent return level def check_clipboard_content(self): """Check clipboard content and highlight matching files""" try: # Get clipboard content clipboard_content = self.root.clipboard_get() # Find all files that contain the clipboard content matching_squares = set() if clipboard_content and len(clipboard_content.strip()) > 0: matching_squares = self.search_files_for_content(clipboard_content) # Update highlighted squares old_highlighted = self.highlighted_squares.copy() self.highlighted_squares = matching_squares # If there are changes, update the highlighted squares if old_highlighted != self.highlighted_squares: # Update the colors of highlighted squares self.update_highlighted_squares() except tk.TclError: # Clipboard is empty or contains non-text data old_highlighted = self.highlighted_squares.copy() self.highlighted_squares = set() # Restore colors of previously highlighted squares for square_num in old_highlighted: self.restore_square_color(square_num) # Schedule next check self.root.after(1000, self.check_clipboard_content) # Check every second def restore_square_color(self, square_num): """Restore the original color of a square""" if square_num in self.squares: x, y, size, parent_number = self.squares[square_num] # Find the rectangle item for this square tags = f"square_{square_num}" items = self.canvas.find_withtag(tags) # Restore original color original_color = self.original_colors.get(square_num, "#FFFFFF") for item in items: if "rectangle" in str(self.canvas.type(item)): self.canvas.itemconfig(item, fill=original_color) def stop_flashing(self): """Stop the highlighting (placeholder function)""" pass def search_files_for_content(self, search_content): """Search all text files for the given content and return matching square numbers""" import os matching_squares = set() # Only search files (not directories) that contain the search content if hasattr(self, 'number_to_path'): for number, path in self.number_to_path.items(): # Double-check that this is a file, not a directory # Using both isfile() and not isdir() for extra safety # Double-check that this is a file, not a directory # Using both isfile() and not isdir() for extra safety if os.path.isfile(path) and not os.path.isdir(path): try: # Try to read the file as text with open(path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() if search_content.lower() in content.lower(): matching_squares.add(number) except Exception: # If we can't read the file, skip it continue return matching_squares def update_highlighted_squares(self): """Update the color of highlighted squares to red""" # First, restore all previously highlighted squares to their original color for square_num in self.all_highlighted_squares: if square_num in self.squares: x, y, size, parent_number = self.squares[square_num] # Find the rectangle item for this square tags = f"square_{square_num}" items = self.canvas.find_withtag(tags) # Restore original color original_color = self.original_colors.get(square_num, "#FFFFFF") for item in items: if "rectangle" in str(self.canvas.type(item)): self.canvas.itemconfig(item, fill=original_color) # Now highlight the new matching squares in red for square_num in self.highlighted_squares: if square_num in self.squares: x, y, size, parent_number = self.squares[square_num] # Find the rectangle item for this square tags = f"square_{square_num}" items = self.canvas.find_withtag(tags) # Change color to red for item in items: if "rectangle" in str(self.canvas.type(item)): self.canvas.itemconfig(item, fill="red") # Update the set of all highlighted squares self.all_highlighted_squares = self.highlighted_squares.copy() def find_square_items_by_position(self, x, y, size): """Find canvas item IDs that correspond to a square at the given position""" # Find items by coordinates items = [] overlapping = self.canvas.find_overlapping(x, y, x + size, y + size) for item in overlapping: # Check if this item is part of a square we drew tags = self.canvas.gettags(item) if any('square' in tag.lower() for tag in tags) or len(tags) == 0: # Assume squares have no special tags or 'square' in tag items.append(item) return items def run(self): self.root.mainloop() def folder_to_config(folder_path, skip_folder=[]): """ 根据文件夹路径生成配置列表,表示每个文件夹下的子项数量。 Args: folder_path: 要分析的根文件夹路径 skip_folder: 不需要遍历的文件夹名称列表 Returns: tuple: (config_list, path_info_dict, path_mapping_dict) config_list: 包含(父级编号, 子项数量)元组的列表 (None, n) 表示根目录下有n个子项 path_info_dict: 映射编号到文件/文件夹名称的字典 path_mapping_dict: 映射编号到完整文件路径的字典 """ import os # 存储路径到编号的映射 path_to_number = {} number_to_name = {} number_to_path = {} # 新增:编号到路径的映射 # 首先为根目录分配编号 path_to_number[folder_path] = 0 number_to_name[0] = os.path.basename(folder_path) number_to_path[0] = folder_path # 添加路径映射 # 使用栈进行迭代式深度优先遍历,确保正确处理父子关系 stack = [folder_path] while stack: current_path = stack.pop() try: items = os.listdir(current_path) # 获取当前目录的直接子项(过滤掉需要跳过的目录) direct_children = [] for item in items: item_path = os.path.join(current_path, item) # 检查是否是需要跳过的目录 if os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder: continue direct_children.append(item_path) # 为所有直接子项分配编号 for item_path in direct_children: if item_path not in path_to_number: number = len(path_to_number) path_to_number[item_path] = number number_to_name[number] = os.path.basename(item_path) number_to_path[number] = item_path # 添加路径映射 # 如果是目录且不在跳过列表中,加入栈中待处理 if os.path.isdir(item_path): stack.append(item_path) except PermissionError: # 如果没有权限访问某个文件夹,则跳过 continue # 构建结果 - 计算每个目录的直接子项数量 result = [] # 添加根目录信息 root_items = [] try: items = os.listdir(folder_path) for item in items: item_path = os.path.join(folder_path, item) if not (os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder): root_items.append(item_path) except PermissionError: pass result.append((None, len(root_items))) # 根目录用None表示 # 为每个目录添加信息(除了根目录) for path in path_to_number: if path != folder_path and os.path.isdir(path): # 排除根目录,只处理子目录 try: items = os.listdir(path) # 计算当前目录的直接子项数量(过滤掉需要跳过的目录) child_count = 0 for item in items: item_path = os.path.join(path, item) if not (os.path.isdir(item_path) and os.path.basename(item_path) in skip_folder): child_count += 1 parent_number = path_to_number[path] result.append((parent_number, child_count)) except PermissionError: # 如果无法访问目录,子项数量为0 parent_number = path_to_number[path] result.append((parent_number, 0)) return result, number_to_name, number_to_path if __name__ == "__main__": config, path_info, path_mapping = folder_to_config(r"E:\agricultural_research_platform", skip_folder=["automatedDeployment", "node_modules", ".vscode", ".git", "images"]) print(config) app = SquareWindow(config, path_info, path_mapping) app.run()